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};
182 m_generateByteOrderMark =
false;
185 m_mimeTypeForFilterDev = QStringLiteral(
"text/plain");
197 int blockIndex = blockForLine(
line);
200 return m_blocks.at(blockIndex)->line(
line - m_startLines[blockIndex]);
206 int blockIndex = blockForLine(
line);
209 return m_blocks.at(blockIndex)->setLineMetaData(
line - m_startLines[blockIndex], textLine);
219 const int blockIndex = blockForLine(c.
line());
220 for (
auto it = m_blockSizes.begin(), end = m_blockSizes.begin() + blockIndex; it != end; ++it) {
224 auto block = m_blocks[blockIndex];
225 int start = block->startLine();
226 int end =
start + block->lines();
229 off += qMin(c.
column(), block->lineLength(
line));
232 off += block->lineLength(
line) + 1;
244 for (
int blockSize : m_blockSizes) {
245 if (off + blockSize < offset) {
248 auto block = m_blocks[blockIdx];
249 const int lines = block->lines();
250 int start = block->startLine();
253 const int len = block->lineLength(
line);
254 if (off + len >= offset) {
270 for (
int blockSize : m_blockSizes) {
277 for (TextBlock *block : m_blocks) {
282 Q_ASSERT(size ==
text.size());
289 ++m_editingTransactions;
292 if (m_editingTransactions > 1) {
297 m_editingLastRevision = m_revision;
298 m_editingLastLines = m_lines;
299 m_editingMinimalLineChanged = -1;
300 m_editingMaximalLineChanged = -1;
303 Q_EMIT m_document->KTextEditor::Document::editingStarted(m_document);
312 Q_ASSERT(m_editingTransactions > 0);
315 --m_editingTransactions;
318 if (m_editingTransactions > 0) {
323 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
324 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
325 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
326 Q_ASSERT(!
editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
329 Q_EMIT m_document->KTextEditor::Document::editingFinished(m_document);
338 BUFFER_DEBUG <<
"wrapLine" << position;
341 Q_ASSERT(m_editingTransactions > 0);
344 int blockIndex = blockForLine(position.
line());
351 m_blocks.at(blockIndex)->wrapLine(position, blockIndex);
352 m_blockSizes[blockIndex] += 1;
358 if (position.
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
359 m_editingMinimalLineChanged = position.
line();
362 if (position.
line() <= m_editingMaximalLineChanged) {
363 ++m_editingMaximalLineChanged;
365 m_editingMaximalLineChanged = position.
line() + 1;
369 balanceBlock(blockIndex);
372 Q_EMIT m_document->KTextEditor::Document::lineWrapped(m_document, position);
378 BUFFER_DEBUG <<
"unwrapLine" <<
line;
381 Q_ASSERT(m_editingTransactions > 0);
387 int blockIndex = blockForLine(
line);
390 const int blockStartLine = m_startLines[blockIndex];
391 const bool firstLineInBlock =
line == blockStartLine;
398 m_blocks.at(blockIndex)
399 ->unwrapLine(
line - blockStartLine, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) :
nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex);
403 if (firstLineInBlock) {
411 if ((
line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
412 m_editingMinimalLineChanged =
line - 1;
415 if (
line <= m_editingMaximalLineChanged) {
416 --m_editingMaximalLineChanged;
418 m_editingMaximalLineChanged =
line - 1;
422 balanceBlock(blockIndex);
425 Q_EMIT m_document->KTextEditor::Document::lineUnwrapped(m_document,
line);
431 BUFFER_DEBUG <<
"insertText" << position <<
text;
434 Q_ASSERT(m_editingTransactions > 0);
437 if (
text.isEmpty()) {
442 int blockIndex = blockForLine(position.
line());
445 m_blocks.at(blockIndex)->insertText(position,
text);
446 m_blockSizes[blockIndex] +=
text.size();
452 if (position.
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
453 m_editingMinimalLineChanged = position.
line();
456 if (position.
line() > m_editingMaximalLineChanged) {
457 m_editingMaximalLineChanged = position.
line();
461 Q_EMIT m_document->KTextEditor::Document::textInserted(m_document, position,
text);
467 BUFFER_DEBUG <<
"removeText" << range;
470 Q_ASSERT(m_editingTransactions > 0);
485 int blockIndex = blockForLine(range.
start().
line());
489 m_blocks.at(blockIndex)->removeText(range,
text);
490 m_blockSizes[blockIndex] -=
text.size();
496 if (range.
start().
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
497 m_editingMinimalLineChanged = range.
start().
line();
500 if (range.
start().
line() > m_editingMaximalLineChanged) {
501 m_editingMaximalLineChanged = range.
start().
line();
505 Q_EMIT m_document->KTextEditor::Document::textRemoved(m_document, range,
text);
508int TextBuffer::blockForLine(
int line)
const
511 if ((line < 0) || (line >= lines())) {
512 qFatal(
"out of range line requested in text buffer (%d out of [0, %d])", line, lines());
515 size_t b = line / BufferBlockSize;
516 if (b >= m_blocks.size()) {
517 b = m_blocks.size() - 1;
520 if (m_startLines[b] <= line && line < m_startLines[b] + m_blocks[b]->lines()) {
524 if (m_startLines[b] > line) {
525 for (
int i = b - 1; i >= 0; --i) {
526 if (m_startLines[i] <= line && line < m_startLines[i] + m_blocks[i]->lines()) {
532 if (m_startLines[b] < line || (m_blocks[b]->lines() == 0)) {
533 for (
size_t i = b + 1; i < m_blocks.size(); ++i) {
534 if (m_startLines[i] <= line && line < m_startLines[i] + m_blocks[i]->lines()) {
540 qFatal(
"line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
544void TextBuffer::fixStartLines(
int startBlock,
int value)
547 Q_ASSERT(startBlock >= 0);
548 Q_ASSERT(startBlock <= (
int)m_startLines.size());
550 for (
auto it = m_startLines.begin() + startBlock, end = m_startLines.end(); it != end; ++it) {
556void TextBuffer::balanceBlock(
int index)
558 auto check = qScopeGuard([
this] {
559 if (!(m_blocks.size() == m_startLines.size() && m_blocks.size() == m_blockSizes.size())) {
560 qFatal(
"blocks/startlines/blocksizes are not equal in size!");
565 TextBlock *blockToBalance = m_blocks.at(index);
568 if (blockToBalance->lines() >= 2 * BufferBlockSize) {
570 int halfSize = blockToBalance->lines() / 2;
573 const int newBlockStartLine = m_startLines[index] + halfSize;
574 TextBlock *newBlock =
new TextBlock(
this, index + 1);
575 m_blocks.insert(m_blocks.begin() + index + 1, newBlock);
576 m_startLines.insert(m_startLines.begin() + index + 1, newBlockStartLine);
577 m_blockSizes.insert(m_blockSizes.begin() + index + 1, 0);
580 for (
auto it = m_blocks.begin() + index, end = m_blocks.end(); it != end; ++it) {
581 (*it)->setBlockIndex(index++);
584 blockToBalance->splitBlock(halfSize, newBlock);
596 if (blockToBalance->lines() == 0) {
597 m_blocks.erase(m_blocks.begin());
598 m_startLines.erase(m_startLines.begin());
599 m_blockSizes.erase(m_blockSizes.begin());
600 Q_ASSERT(m_startLines[0] == 0);
601 for (
auto it = m_blocks.begin(), end = m_blocks.end(); it != end; ++it) {
602 (*it)->setBlockIndex(index++);
609 if (2 * blockToBalance->lines() > BufferBlockSize) {
614 TextBlock *targetBlock = m_blocks.at(index - 1);
617 blockToBalance->mergeBlock(targetBlock);
618 m_blockSizes[index - 1] += m_blockSizes[index];
621 delete blockToBalance;
622 m_blocks.erase(m_blocks.begin() + index);
623 m_startLines.erase(m_startLines.begin() + index);
624 m_blockSizes.erase(m_blockSizes.begin() + index);
626 for (
auto it = m_blocks.begin() + index, end = m_blocks.end(); it != end; ++it) {
627 (*it)->setBlockIndex(index++);
630 Q_ASSERT(index == (
int)m_blocks.size());
636 printf(
"%s (lines: %d)\n", qPrintable(title), m_lines);
639 for (
size_t i = 0; i < m_blocks.size(); ++i) {
640 m_blocks.at(i)->debugPrint(i);
644bool TextBuffer::load(
const QString &filename,
bool &encodingErrors,
bool &tooLongLinesWrapped,
int &longestLineLoaded,
bool enforceTextCodec)
647 Q_ASSERT(!m_fallbackTextCodec.isEmpty());
650 Q_ASSERT(!m_textCodec.isEmpty());
663 for (
int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
665 for (
size_t b = 1; b < m_blocks.size(); ++b) {
666 TextBlock *block = m_blocks.at(b);
671 m_startLines.resize(1);
672 m_blockSizes.resize(1);
675 m_blocks.back()->clearLines();
676 m_startLines.back() = 0;
677 m_blockSizes.back() = 0;
681 tooLongLinesWrapped =
false;
682 longestLineLoaded = 0;
692 codec = m_fallbackTextCodec;
695 if (!file.
open(codec)) {
697 m_blocks.back()->appendLine(
QString());
704 encodingErrors =
false;
705 while (!file.
eof()) {
709 bool currentError = !file.
readLine(offset, length, tooLongLinesWrapped, longestLineLoaded);
710 encodingErrors = encodingErrors || currentError;
713 if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
714 BUFFER_DEBUG <<
"Failed try to load file" << filename <<
"with codec" << file.
textCodec();
719 if (m_blocks.back()->lines() >= BufferBlockSize) {
720 int index = (int)m_blocks.size();
721 int startLine = m_blocks.back()->startLine() + m_blocks.back()->lines();
722 m_blocks.push_back(
new TextBlock(
this, index));
723 m_startLines.push_back(startLine);
724 m_blockSizes.push_back(0);
728 m_blocks.back()->appendLine(
QString(file.
unicode() + offset, length));
729 m_blockSizes.back() += length + 1;
734 if (!encodingErrors) {
750 if (file.
eol() != eolUnknown) {
758 Q_ASSERT(m_lines > 0);
761 BUFFER_DEBUG <<
"Loaded file " << filename <<
"with codec" << m_textCodec << (encodingErrors ?
"with" :
"without") <<
"encoding errors";
764 BUFFER_DEBUG << (file.
byteOrderMarkFound() ?
"Found" :
"Didn't find") <<
"byte order mark";
767 BUFFER_DEBUG <<
"used filter device for mime-type" << m_mimeTypeForFilterDev;
798 if (setEncoding == encoding) {
809 Q_ASSERT(!m_textCodec.isEmpty());
812 auto realFile = filename;
813 if (
const auto realFileResolved =
QFileInfo(realFile).canonicalFilePath(); !realFileResolved.isEmpty()) {
814 realFile = realFileResolved;
817 const auto saveRes = saveBufferUnprivileged(realFile);
818 if (saveRes == SaveResult::Failed) {
821 if (saveRes == SaveResult::MissingPermissions) {
824 if (!saveBufferEscalated(realFile)) {
830 m_history.setLastSavedRevision();
833 markModifiedLinesAsSaved();
842 QStringEncoder encoder(m_textCodec.toUtf8().constData(), generateByteOrderMark() ? QStringConverter::Flag::WriteBom : QStringConverter::Flag::Default);
845 QString eol = QStringLiteral(
"\n");
846 if (endOfLineMode() == eolDos) {
847 eol = QStringLiteral(
"\r\n");
848 }
else if (endOfLineMode() == eolMac) {
849 eol = QStringLiteral(
"\r");
853 for (
int i = 0; i < m_lines; ++i) {
855 saveFile.
write(encoder.encode(line(i).text()));
858 if ((i + 1) < m_lines) {
859 saveFile.
write(encoder.encode(eol));
876 BUFFER_DEBUG <<
"Saving file " << filename <<
"failed with error" << saveFile.
errorString();
883TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(
const QString &filename)
885 if (m_alwaysUseKAuthForSave) {
887 return SaveResult::MissingPermissions;
893 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
897 if (errno != EACCES) {
898 return SaveResult::Failed;
901 return SaveResult::MissingPermissions;
904 if (!saveBuffer(filename, *saveFile)) {
905 return SaveResult::Failed;
908 return SaveResult::Success;
911bool TextBuffer::saveBufferEscalated(
const QString &filename)
917 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
920 std::unique_ptr<QIODevice> temporaryBuffer;
924 if (fileInfo.exists()) {
925 ownerId = fileInfo.ownerId();
926 groupId = fileInfo.groupId();
931 temporaryBuffer = std::make_unique<QBuffer>();
939 saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(),
false, type);
944 if (!saveBuffer(filename, *saveFile)) {
953 if (!tempFile.
open()) {
958 temporaryBuffer->
seek(0);
961 char buffer[bufferLength];
964 while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
966 if (tempFile.
write(buffer, read) == -1) {
970 if (!tempFile.
flush()) {
975 QVariantMap kAuthActionArgs;
976 kAuthActionArgs.insert(QStringLiteral(
"sourceFile"), tempFile.
fileName());
977 kAuthActionArgs.insert(QStringLiteral(
"targetFile"), filename);
978 kAuthActionArgs.insert(QStringLiteral(
"checksum"), cryptographicHash.result());
979 kAuthActionArgs.insert(QStringLiteral(
"ownerId"), ownerId);
980 kAuthActionArgs.insert(QStringLiteral(
"groupId"), groupId);
983 if (QStandardPaths::isTestModeEnabled()) {
985 if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
989 KAuth::Action kAuthSaveAction(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer.savefile"));
990 kAuthSaveAction.setHelperId(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer"));
991 kAuthSaveAction.setArguments(kAuthActionArgs);
1017 if (view && view != curView && !deleteRange) {
1022 static_cast<KTextEditor::ViewPrivate *
>(curView)->notifyAboutRangeChange(lineRange, needsRepaint, deleteRange);
1026void TextBuffer::markModifiedLinesAsSaved()
1028 for (TextBlock *block : std::as_const(m_blocks)) {
1029 block->markModifiedLinesAsSaved();
1035 auto it = std::find(m_multilineRanges.begin(), m_multilineRanges.end(), range);
1036 if (it == m_multilineRanges.end()) {
1037 m_multilineRanges.push_back(range);
1042void TextBuffer::removeMultilineRange(
TextRange *range)
1044 m_multilineRanges.erase(std::remove(m_multilineRanges.begin(), m_multilineRanges.end(), range), m_multilineRanges.end());
1049 return std::find(m_multilineRanges.begin(), m_multilineRanges.end(), range) != m_multilineRanges.end();
1056 const int blockIndex = blockForLine(line);
1057 m_blocks.at(blockIndex)->rangesForLine(line, view, rangesWithAttributeOnly, outRanges);
1059 for (TextRange *range : std::as_const(m_multilineRanges)) {
1060 if (rangesWithAttributeOnly && !range->hasAttribute()) {
1070 if (range->
view() && range->
view() != view) {
1075 if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
1079 std::sort(outRanges.
begin(), outRanges.
end());
1080 outRanges.
erase(std::unique(outRanges.
begin(), outRanges.
end()), outRanges.
end());
1084#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