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);
113 qDeleteAll(m_blocks);
119 std::vector<Kate::TextRange *> ranges;
120 ranges.reserve(m_blocks.size());
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()});
136void TextBuffer::clear()
139 Q_ASSERT(m_editingTransactions == 0);
141 m_multilineRanges.clear();
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]);
202void TextBuffer::setLineMetaData(
int line,
const TextLine &textLine)
205 int blockIndex = blockForLine(line);
208 return m_blocks.at(blockIndex)->setLineMetaData(line - m_startLines[blockIndex], textLine);
213 if ((c.
line() < 0) || (c.
line() >= lines())) {
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();
226 for (
int line =
start; line < end; ++line) {
227 if (line >= c.
line()) {
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();
250 int end =
start + lines;
251 for (
int line =
start; line < end; ++line) {
252 const int len = block->lineLength(line);
253 if (off + len >= offset) {
269 for (
int blockSize : m_blockSizes) {
281 Q_ASSERT(size == text.
size());
285bool TextBuffer::startEditing()
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);
308bool TextBuffer::finishEditing()
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);
374void TextBuffer::unwrapLine(
int line)
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);
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());
632void TextBuffer::debugPrint(
const QString &title)
const
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) {
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) {
741 setDigest(file.digest());
745 setGenerateByteOrderMark(
true);
749 if (file.
eol() != eolUnknown) {
750 setEndOfLineMode(file.
eol());
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;
769 Q_EMIT loaded(filename, encodingErrors);
785void TextBuffer::setTextCodec(
const QString &codec)
797 if (setEncoding == encoding) {
798 setGenerateByteOrderMark(
true);
808 Q_ASSERT(!m_textCodec.isEmpty());
810 SaveResult saveRes = saveBufferUnprivileged(filename);
812 if (saveRes == SaveResult::Failed) {
814 }
else if (saveRes == SaveResult::MissingPermissions) {
817 if (!saveBufferEscalated(filename)) {
823 m_history.setLastSavedRevision();
826 markModifiedLinesAsSaved();
829 Q_EMIT saved(filename);
835 QStringEncoder encoder(m_textCodec.toUtf8().constData(), generateByteOrderMark() ? QStringConverter::Flag::WriteBom : QStringConverter::Flag::Default);
838 QString eol = QStringLiteral(
"\n");
839 if (endOfLineMode() == eolDos) {
840 eol = QStringLiteral(
"\r\n");
841 }
else if (endOfLineMode() == eolMac) {
842 eol = QStringLiteral(
"\r");
846 for (
int i = 0; i < m_lines; ++i) {
848 saveFile.
write(encoder.encode(line(i).text()));
851 if ((i + 1) < m_lines) {
852 saveFile.
write(encoder.encode(eol));
869 BUFFER_DEBUG <<
"Saving file " << filename <<
"failed with error" << saveFile.
errorString();
876TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(
const QString &filename)
878 if (m_alwaysUseKAuthForSave) {
880 return SaveResult::MissingPermissions;
886 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
890 if (errno != EACCES) {
891 return SaveResult::Failed;
894 return SaveResult::MissingPermissions;
897 if (!saveBuffer(filename, *saveFile)) {
898 return SaveResult::Failed;
901 return SaveResult::Success;
904bool TextBuffer::saveBufferEscalated(
const QString &filename)
910 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
913 std::unique_ptr<QIODevice> temporaryBuffer;
917 if (fileInfo.exists()) {
918 ownerId = fileInfo.ownerId();
919 groupId = fileInfo.groupId();
924 temporaryBuffer = std::make_unique<QBuffer>();
932 saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(),
false, type);
937 if (!saveBuffer(filename, *saveFile)) {
946 if (!tempFile.
open()) {
951 temporaryBuffer->
seek(0);
954 char buffer[bufferLength];
957 while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
959 if (tempFile.
write(buffer, read) == -1) {
963 if (!tempFile.
flush()) {
968 QVariantMap kAuthActionArgs;
969 kAuthActionArgs.insert(QStringLiteral(
"sourceFile"), tempFile.
fileName());
970 kAuthActionArgs.insert(QStringLiteral(
"targetFile"), filename);
971 kAuthActionArgs.insert(QStringLiteral(
"checksum"), cryptographicHash.result());
972 kAuthActionArgs.insert(QStringLiteral(
"ownerId"), ownerId);
973 kAuthActionArgs.insert(QStringLiteral(
"groupId"), groupId);
976 if (QStandardPaths::isTestModeEnabled()) {
978 if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
982 KAuth::Action kAuthSaveAction(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer.savefile"));
983 kAuthSaveAction.setHelperId(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer"));
984 kAuthSaveAction.setArguments(kAuthActionArgs);
1010 if (view && view != curView && !deleteRange) {
1015 static_cast<KTextEditor::ViewPrivate *
>(curView)->notifyAboutRangeChange(lineRange, needsRepaint, deleteRange);
1019void TextBuffer::markModifiedLinesAsSaved()
1021 for (TextBlock *block : std::as_const(m_blocks)) {
1022 block->markModifiedLinesAsSaved();
1028 auto it = std::find(m_multilineRanges.begin(), m_multilineRanges.end(), range);
1029 if (it == m_multilineRanges.end()) {
1030 m_multilineRanges.push_back(range);
1035void TextBuffer::removeMultilineRange(
TextRange *range)
1037 m_multilineRanges.erase(std::remove(m_multilineRanges.begin(), m_multilineRanges.end(), range), m_multilineRanges.end());
1042 return std::find(m_multilineRanges.begin(), m_multilineRanges.end(), range) != m_multilineRanges.end();
1048 const int blockIndex = blockForLine(line);
1049 m_blocks.at(blockIndex)->rangesForLine(line, view, rangesWithAttributeOnly, outRanges);
1051 for (
auto range : m_multilineRanges) {
1052 if (rangesWithAttributeOnly && !range->hasAttribute()) {
1062 if (range->
view() && range->
view() != view) {
1067 else if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
1074#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? Default is false.
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.
Class representing a text block.
void appendLine(const QString &textOfLine)
Append a new line with given text.
void clearLines()
Clear the lines.
TextBuffer(KTextEditor::DocumentPrivate *parent, bool alwaysUseKAuth=false)
Construct an empty text buffer.
~TextBuffer() override
Destruct the text buffer Virtual, we allow inheritance.
void invalidateRanges()
Invalidate all ranges in this buffer.
virtual void clear()
Clears the buffer, reverts to initial empty state.
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 Q_NOREPLY void start()
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)
const QChar at(qsizetype position) const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
std::optional< Encoding > encodingForName(const char *name)
virtual QString fileName() const const override