8#include "katetextbuffer.h"
9#include "katetextloader.h"
11#include "katedocument.h"
14#include "katepartdebug.h"
27#include <QCryptographicHash>
31#include <QStandardPaths>
32#include <QStringEncoder>
33#include <QTemporaryFile>
36#include "katesecuretextbuffer_p.h"
37#include <KAuth/Action>
38#include <KAuth/ExecuteJob>
42#define BUFFER_DEBUG qCDebug(LOG_KTE)
57 , m_editingTransactions(0)
58 , m_editingLastRevision(0)
59 , m_editingLastLines(0)
60 , m_editingMinimalLineChanged(-1)
61 , m_editingMaximalLineChanged(-1)
63 , m_generateByteOrderMark(false)
64 , m_endOfLineMode(eolUnix)
65 , m_lineLengthLimit(4096)
66 , m_alwaysUseKAuthForSave(alwaysUseKAuth)
78 Q_ASSERT(m_editingTransactions == 0);
81 std::vector<Kate::TextRange *> rangesWithFeedback;
82 for (
auto b : m_blocks) {
83 auto cursors = std::move(b->m_cursors);
84 for (
auto it = cursors.begin(); it != cursors.end(); ++it) {
87 cursor->m_block =
nullptr;
88 cursor->m_line = cursor->m_column = -1;
89 cursor->m_buffer =
nullptr;
90 if (
auto r = cursor->kateRange()) {
91 r->m_buffer =
nullptr;
93 rangesWithFeedback.push_back(r);
100 std::sort(rangesWithFeedback.begin(), rangesWithFeedback.end());
101 auto it = std::unique(rangesWithFeedback.begin(), rangesWithFeedback.end());
102 std::for_each(rangesWithFeedback.begin(), it, [](
Kate::TextRange *range) {
103 range->feedback()->rangeInvalid(range);
107 for (TextBlock *block : m_blocks) {
113 qDeleteAll(m_blocks);
119 std::vector<Kate::TextRange *> ranges;
120 ranges.reserve(m_blocks.size());
121 for (TextBlock *block : m_blocks) {
122 for (
auto cursor : block->m_cursors) {
123 if (cursor->kateRange()) {
124 ranges.push_back(cursor->kateRange());
129 std::sort(ranges.begin(), ranges.end());
130 auto it = std::unique(ranges.begin(), ranges.end());
132 range->setRange({KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid()});
139 Q_ASSERT(m_editingTransactions == 0);
141 m_multilineRanges.clear();
145 TextBlock *newBlock =
new TextBlock(
this, 0);
149 for (TextBlock *block : std::as_const(m_blocks)) {
150 auto cursors = std::move(block->m_cursors);
151 for (
auto it = cursors.begin(); it != cursors.end(); ++it) {
153 if (!cursor->kateRange()) {
155 cursor->m_block = newBlock;
157 cursor->m_line = cursor->m_column = 0;
158 newBlock->m_cursors.push_back(cursor);
165 std::sort(newBlock->m_cursors.begin(), newBlock->m_cursors.end());
168 qDeleteAll(m_blocks);
170 m_blocks = {newBlock};
181 m_generateByteOrderMark =
false;
184 m_mimeTypeForFilterDev = QStringLiteral(
"text/plain");
196 int blockIndex = blockForLine(
line);
199 return m_blocks.at(blockIndex)->line(
line - m_startLines[blockIndex]);
205 int blockIndex = blockForLine(
line);
208 return m_blocks.at(blockIndex)->setLineMetaData(
line - m_startLines[blockIndex], textLine);
218 const int blockIndex = blockForLine(c.
line());
219 for (
auto it = m_blockSizes.begin(), end = m_blockSizes.begin() + blockIndex; it != end; ++it) {
223 auto block = m_blocks[blockIndex];
224 int start = block->startLine();
225 int end =
start + block->lines();
228 off += qMin(c.
column(), block->lineLength(
line));
231 off += block->lineLength(
line) + 1;
243 for (
int blockSize : m_blockSizes) {
244 if (off + blockSize < offset) {
247 auto block = m_blocks[blockIdx];
248 const int lines = block->lines();
249 int start = block->startLine();
252 const int len = block->lineLength(
line);
253 if (off + len >= offset) {
269 for (
int blockSize : m_blockSizes) {
276 for (TextBlock *block : m_blocks) {
281 Q_ASSERT(size ==
text.size());
288 ++m_editingTransactions;
291 if (m_editingTransactions > 1) {
296 m_editingLastRevision = m_revision;
297 m_editingLastLines = m_lines;
298 m_editingMinimalLineChanged = -1;
299 m_editingMaximalLineChanged = -1;
302 Q_EMIT m_document->KTextEditor::Document::editingStarted(m_document);
311 Q_ASSERT(m_editingTransactions > 0);
314 --m_editingTransactions;
317 if (m_editingTransactions > 0) {
322 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
323 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
324 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
325 Q_ASSERT(!
editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
328 Q_EMIT m_document->KTextEditor::Document::editingFinished(m_document);
337 BUFFER_DEBUG <<
"wrapLine" << position;
340 Q_ASSERT(m_editingTransactions > 0);
343 int blockIndex = blockForLine(position.
line());
350 m_blocks.at(blockIndex)->wrapLine(position, blockIndex);
351 m_blockSizes[blockIndex] += 1;
357 if (position.
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
358 m_editingMinimalLineChanged = position.
line();
361 if (position.
line() <= m_editingMaximalLineChanged) {
362 ++m_editingMaximalLineChanged;
364 m_editingMaximalLineChanged = position.
line() + 1;
368 balanceBlock(blockIndex);
371 Q_EMIT m_document->KTextEditor::Document::lineWrapped(m_document, position);
377 BUFFER_DEBUG <<
"unwrapLine" <<
line;
380 Q_ASSERT(m_editingTransactions > 0);
386 int blockIndex = blockForLine(
line);
389 const int blockStartLine = m_startLines[blockIndex];
390 const bool firstLineInBlock =
line == blockStartLine;
397 m_blocks.at(blockIndex)
398 ->unwrapLine(
line - blockStartLine, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) :
nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex);
402 if (firstLineInBlock) {
410 if ((
line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
411 m_editingMinimalLineChanged =
line - 1;
414 if (
line <= m_editingMaximalLineChanged) {
415 --m_editingMaximalLineChanged;
417 m_editingMaximalLineChanged =
line - 1;
421 balanceBlock(blockIndex);
424 Q_EMIT m_document->KTextEditor::Document::lineUnwrapped(m_document,
line);
430 BUFFER_DEBUG <<
"insertText" << position <<
text;
433 Q_ASSERT(m_editingTransactions > 0);
436 if (
text.isEmpty()) {
441 int blockIndex = blockForLine(position.
line());
444 m_blocks.at(blockIndex)->insertText(position,
text);
445 m_blockSizes[blockIndex] +=
text.size();
451 if (position.
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
452 m_editingMinimalLineChanged = position.
line();
455 if (position.
line() > m_editingMaximalLineChanged) {
456 m_editingMaximalLineChanged = position.
line();
460 Q_EMIT m_document->KTextEditor::Document::textInserted(m_document, position,
text);
466 BUFFER_DEBUG <<
"removeText" << range;
469 Q_ASSERT(m_editingTransactions > 0);
484 int blockIndex = blockForLine(range.
start().
line());
488 m_blocks.at(blockIndex)->removeText(range,
text);
489 m_blockSizes[blockIndex] -=
text.size();
495 if (range.
start().
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
496 m_editingMinimalLineChanged = range.
start().
line();
499 if (range.
start().
line() > m_editingMaximalLineChanged) {
500 m_editingMaximalLineChanged = range.
start().
line();
504 Q_EMIT m_document->KTextEditor::Document::textRemoved(m_document, range,
text);
507int TextBuffer::blockForLine(
int line)
const
510 if ((line < 0) || (line >= lines())) {
511 qFatal(
"out of range line requested in text buffer (%d out of [0, %d])", line, lines());
514 size_t b = line / BufferBlockSize;
515 if (b >= m_blocks.size()) {
516 b = m_blocks.size() - 1;
519 if (m_startLines[b] <= line && line < m_startLines[b] + m_blocks[b]->lines()) {
523 if (m_startLines[b] > line) {
524 for (
int i = b - 1; i >= 0; --i) {
525 if (m_startLines[i] <= line && line < m_startLines[i] + m_blocks[i]->lines()) {
531 if (m_startLines[b] < line || (m_blocks[b]->lines() == 0)) {
532 for (
size_t i = b + 1; i < m_blocks.size(); ++i) {
533 if (m_startLines[i] <= line && line < m_startLines[i] + m_blocks[i]->lines()) {
539 qFatal(
"line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
543void TextBuffer::fixStartLines(
int startBlock,
int value)
546 Q_ASSERT(startBlock >= 0);
547 Q_ASSERT(startBlock <= (
int)m_startLines.size());
549 for (
auto it = m_startLines.begin() + startBlock, end = m_startLines.end(); it != end; ++it) {
555void TextBuffer::balanceBlock(
int index)
557 auto check = qScopeGuard([
this] {
558 if (!(m_blocks.size() == m_startLines.size() && m_blocks.size() == m_blockSizes.size())) {
559 qFatal(
"blocks/startlines/blocksizes are not equal in size!");
564 TextBlock *blockToBalance = m_blocks.at(index);
567 if (blockToBalance->lines() >= 2 * BufferBlockSize) {
569 int halfSize = blockToBalance->lines() / 2;
572 const int newBlockStartLine = m_startLines[index] + halfSize;
573 TextBlock *newBlock =
new TextBlock(
this, index + 1);
574 m_blocks.insert(m_blocks.begin() + index + 1, newBlock);
575 m_startLines.insert(m_startLines.begin() + index + 1, newBlockStartLine);
576 m_blockSizes.insert(m_blockSizes.begin() + index + 1, 0);
579 for (
auto it = m_blocks.begin() + index, end = m_blocks.end(); it != end; ++it) {
580 (*it)->setBlockIndex(index++);
583 blockToBalance->splitBlock(halfSize, newBlock);
595 if (blockToBalance->lines() == 0) {
596 m_blocks.erase(m_blocks.begin());
597 m_startLines.erase(m_startLines.begin());
598 m_blockSizes.erase(m_blockSizes.begin());
599 Q_ASSERT(m_startLines[0] == 0);
600 for (
auto it = m_blocks.begin(), end = m_blocks.end(); it != end; ++it) {
601 (*it)->setBlockIndex(index++);
608 if (2 * blockToBalance->lines() > BufferBlockSize) {
613 TextBlock *targetBlock = m_blocks.at(index - 1);
616 blockToBalance->mergeBlock(targetBlock);
617 m_blockSizes[index - 1] += m_blockSizes[index];
620 delete blockToBalance;
621 m_blocks.erase(m_blocks.begin() + index);
622 m_startLines.erase(m_startLines.begin() + index);
623 m_blockSizes.erase(m_blockSizes.begin() + index);
625 for (
auto it = m_blocks.begin() + index, end = m_blocks.end(); it != end; ++it) {
626 (*it)->setBlockIndex(index++);
629 Q_ASSERT(index == (
int)m_blocks.size());
635 printf(
"%s (lines: %d)\n", qPrintable(title), m_lines);
638 for (
size_t i = 0; i < m_blocks.size(); ++i) {
639 m_blocks.at(i)->debugPrint(i);
643bool TextBuffer::load(
const QString &filename,
bool &encodingErrors,
bool &tooLongLinesWrapped,
int &longestLineLoaded,
bool enforceTextCodec)
646 Q_ASSERT(!m_fallbackTextCodec.isEmpty());
649 Q_ASSERT(!m_textCodec.isEmpty());
662 for (
int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
664 for (
size_t b = 1; b < m_blocks.size(); ++b) {
665 TextBlock *block = m_blocks.at(b);
670 m_startLines.resize(1);
671 m_blockSizes.resize(1);
674 m_blocks.back()->clearLines();
675 m_startLines.back() = 0;
676 m_blockSizes.back() = 0;
680 tooLongLinesWrapped =
false;
681 longestLineLoaded = 0;
691 codec = m_fallbackTextCodec;
694 if (!file.
open(codec)) {
696 m_blocks.back()->appendLine(
QString());
703 encodingErrors =
false;
704 while (!file.
eof()) {
708 bool currentError = !file.
readLine(offset, length, tooLongLinesWrapped, longestLineLoaded);
709 encodingErrors = encodingErrors || currentError;
712 if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
713 BUFFER_DEBUG <<
"Failed try to load file" << filename <<
"with codec" << file.
textCodec();
718 if (m_blocks.back()->lines() >= BufferBlockSize) {
719 int index = (int)m_blocks.size();
720 int startLine = m_blocks.back()->startLine() + m_blocks.back()->lines();
721 m_blocks.push_back(
new TextBlock(
this, index));
722 m_startLines.push_back(startLine);
723 m_blockSizes.push_back(0);
727 m_blocks.back()->appendLine(
QString(file.
unicode() + offset, length));
728 m_blockSizes.back() += length + 1;
733 if (!encodingErrors) {
749 if (file.
eol() != eolUnknown) {
757 Q_ASSERT(m_lines > 0);
760 BUFFER_DEBUG <<
"Loaded file " << filename <<
"with codec" << m_textCodec << (encodingErrors ?
"with" :
"without") <<
"encoding errors";
763 BUFFER_DEBUG << (file.
byteOrderMarkFound() ?
"Found" :
"Didn't find") <<
"byte order mark";
766 BUFFER_DEBUG <<
"used filter device for mime-type" << m_mimeTypeForFilterDev;
797 if (setEncoding == encoding) {
808 Q_ASSERT(!m_textCodec.isEmpty());
811 auto realFile = filename;
812 if (
const auto realFileResolved =
QFileInfo(realFile).canonicalFilePath(); !realFileResolved.isEmpty()) {
813 realFile = realFileResolved;
816 const auto saveRes = saveBufferUnprivileged(realFile);
817 if (saveRes == SaveResult::Failed) {
820 if (saveRes == SaveResult::MissingPermissions) {
823 if (!saveBufferEscalated(realFile)) {
829 m_history.setLastSavedRevision();
832 markModifiedLinesAsSaved();
841 QStringEncoder encoder(m_textCodec.toUtf8().constData(), generateByteOrderMark() ? QStringConverter::Flag::WriteBom : QStringConverter::Flag::Default);
844 QString eol = QStringLiteral(
"\n");
845 if (endOfLineMode() == eolDos) {
846 eol = QStringLiteral(
"\r\n");
847 }
else if (endOfLineMode() == eolMac) {
848 eol = QStringLiteral(
"\r");
852 for (
int i = 0; i < m_lines; ++i) {
854 saveFile.
write(encoder.encode(line(i).text()));
857 if ((i + 1) < m_lines) {
858 saveFile.
write(encoder.encode(eol));
875 BUFFER_DEBUG <<
"Saving file " << filename <<
"failed with error" << saveFile.
errorString();
882TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(
const QString &filename)
884 if (m_alwaysUseKAuthForSave) {
886 return SaveResult::MissingPermissions;
892 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
896 if (errno != EACCES) {
897 return SaveResult::Failed;
900 return SaveResult::MissingPermissions;
903 if (!saveBuffer(filename, *saveFile)) {
904 return SaveResult::Failed;
907 return SaveResult::Success;
910bool TextBuffer::saveBufferEscalated(
const QString &filename)
916 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
919 std::unique_ptr<QIODevice> temporaryBuffer;
923 if (fileInfo.exists()) {
924 ownerId = fileInfo.ownerId();
925 groupId = fileInfo.groupId();
930 temporaryBuffer = std::make_unique<QBuffer>();
938 saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(),
false, type);
943 if (!saveBuffer(filename, *saveFile)) {
952 if (!tempFile.
open()) {
957 temporaryBuffer->
seek(0);
960 char buffer[bufferLength];
963 while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
965 if (tempFile.
write(buffer, read) == -1) {
969 if (!tempFile.
flush()) {
974 QVariantMap kAuthActionArgs;
975 kAuthActionArgs.insert(QStringLiteral(
"sourceFile"), tempFile.
fileName());
976 kAuthActionArgs.insert(QStringLiteral(
"targetFile"), filename);
977 kAuthActionArgs.insert(QStringLiteral(
"checksum"), cryptographicHash.result());
978 kAuthActionArgs.insert(QStringLiteral(
"ownerId"), ownerId);
979 kAuthActionArgs.insert(QStringLiteral(
"groupId"), groupId);
982 if (QStandardPaths::isTestModeEnabled()) {
984 if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
988 KAuth::Action kAuthSaveAction(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer.savefile"));
989 kAuthSaveAction.setHelperId(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer"));
990 kAuthSaveAction.setArguments(kAuthActionArgs);
1016 if (view && view != curView && !deleteRange) {
1021 static_cast<KTextEditor::ViewPrivate *
>(curView)->notifyAboutRangeChange(lineRange, needsRepaint, deleteRange);
1025void TextBuffer::markModifiedLinesAsSaved()
1027 for (TextBlock *block : std::as_const(m_blocks)) {
1028 block->markModifiedLinesAsSaved();
1034 auto it = std::find(m_multilineRanges.begin(), m_multilineRanges.end(), range);
1035 if (it == m_multilineRanges.end()) {
1036 m_multilineRanges.push_back(range);
1041void TextBuffer::removeMultilineRange(
TextRange *range)
1043 m_multilineRanges.erase(std::remove(m_multilineRanges.begin(), m_multilineRanges.end(), range), m_multilineRanges.end());
1048 return std::find(m_multilineRanges.begin(), m_multilineRanges.end(), range) != m_multilineRanges.end();
1055 const int blockIndex = blockForLine(line);
1056 m_blocks.at(blockIndex)->rangesForLine(line, view, rangesWithAttributeOnly, outRanges);
1058 for (TextRange *range : std::as_const(m_multilineRanges)) {
1059 if (rangesWithAttributeOnly && !range->hasAttribute()) {
1069 if (range->
view() && range->
view() != view) {
1074 if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
1078 std::sort(outRanges.
begin(), outRanges.
end());
1079 outRanges.
erase(std::unique(outRanges.
begin(), outRanges.
end()), outRanges.
end());
1083#include "moc_katetextbuffer.cpp"
static CompressionType compressionTypeForMimeType(const QString &mimetype)
QFileDevice::FileError error() const
bool open(QIODevice::OpenMode mode) override
The Cursor represents a position in a Document.
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Backend of KTextEditor::Document related public KTextEditor interfaces.
An object representing lines from a start line to an end line.
A range that is bound to a specific Document, and maintains its position.
virtual bool attributeOnlyForViews() const =0
Is this range's attribute only visible in views, not for example prints?
virtual View * view() const =0
Gets the active view for this range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
A text widget with KXMLGUIClient that represents a Document.
void appendLine(const QString &textOfLine)
Append a new line with given text.
void clearLines()
Clear the lines.
virtual void removeText(KTextEditor::Range range)
Remove text at given range.
int cursorToOffset(KTextEditor::Cursor c) const
Retrieve offset in text for the given cursor position.
TextBuffer(KTextEditor::DocumentPrivate *parent, bool alwaysUseKAuth=false)
Construct an empty text buffer.
virtual bool save(const QString &filename)
Save the current buffer content to the given file.
TextLine line(int line) const
Retrieve a text line.
void cleared()
Buffer got cleared.
virtual void wrapLine(const KTextEditor::Cursor position)
Wrap line at given cursor position.
virtual void unwrapLine(int line)
Unwrap given line.
void setDigest(const QByteArray &checksum)
Set the checksum of this buffer.
bool editingChangedBuffer() const
Query information from the last editing transaction: was the content of the buffer changed?
~TextBuffer() override
Destruct the text buffer Virtual, we allow inheritance.
int lines() const
Lines currently stored in this buffer.
KTextEditor::Cursor offsetToCursor(int offset) const
Retrieve cursor in text for the given offset.
virtual void insertText(const KTextEditor::Cursor position, const QString &text)
Insert text at given cursor position.
void setLineMetaData(int line, const TextLine &textLine)
Transfer all non text attributes for the given line from the given text line to the one in the buffer...
void invalidateRanges()
Invalidate all ranges in this buffer.
void setEndOfLineMode(EndOfLineMode endOfLineMode)
Set end of line mode for this buffer, not allowed to be set to unknown.
void loaded(const QString &filename, bool encodingErrors)
Buffer loaded successfully a file.
void saved(const QString &filename)
Buffer saved successfully a file.
void setTextCodec(const QString &codec)
Set codec for this buffer to use for load/save.
virtual bool load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec)
Load the given file.
virtual bool startEditing()
Start an editing transaction, the wrapLine/unwrapLine/insertText and removeText functions are only al...
virtual void clear()
Clears the buffer, reverts to initial empty state.
virtual bool finishEditing()
Finish an editing transaction.
void setGenerateByteOrderMark(bool generateByteOrderMark)
Generate byte order mark on save.
KTEXTEDITOR_NO_EXPORT void addMultilineRange(TextRange *range)
Add/Remove a multiline range that spans multiple blocks.
void debugPrint(const QString &title) const
Debug output, print whole buffer content with line numbers and line length.
const QByteArray & digest() const
Checksum of the document on disk, set either through file loading in openFile() or in KTextEditor::Do...
QString text() const
Retrieve text of complete buffer.
Class representing a single text line.
File Loader, will handle reading of files + detecting encoding.
const QChar * unicode() const
internal Unicode data array
QString textCodec() const
Get codec for this loader.
bool eof() const
end of file reached?
const QString & mimeTypeForFilterDev() const
mime type used to create filter dev
bool readLine(int &offset, int &length, bool &tooLongLinesWrapped, int &longestLineLoaded)
read a line, return length + offset in Unicode data
bool open(const QString &codec)
open file with given codec
TextBuffer::EndOfLineMode eol() const
Detected end of line mode for this file.
bool byteOrderMarkFound() const
BOM found?
Q_SCRIPTABLE QString start(QString train="")
QVariant read(const QByteArray &data, int versionOverride=0)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
virtual bool seek(qint64 pos) override
QString errorString() const const
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
iterator erase(const_iterator begin, const_iterator end)
QObject * parent() const const
std::optional< Encoding > encodingForName(const char *name)
virtual QString fileName() const const override