9#include "katetextbuffer.h"
10#include "katetextloader.h"
12#include "katedocument.h"
15#include "katepartdebug.h"
28#include <QCryptographicHash>
31#include <QStringEncoder>
32#include <QTemporaryFile>
35#include "katesecuretextbuffer_p.h"
36#include <KAuth/Action>
37#include <KAuth/ExecuteJob>
41#define BUFFER_DEBUG qCDebug(LOG_KTE)
56 , m_editingTransactions(0)
57 , m_editingLastRevision(0)
58 , m_editingLastLines(0)
59 , m_editingMinimalLineChanged(-1)
60 , m_editingMaximalLineChanged(-1)
62 , m_generateByteOrderMark(false)
63 , m_endOfLineMode(eolUnix)
64 , m_lineLengthLimit(4096)
65 , m_alwaysUseKAuthForSave(alwaysUseKAuth)
77 Q_ASSERT(m_editingTransactions == 0);
81 qDeleteAll(copyRanges);
82 Q_ASSERT(m_ranges.empty());
85 for (
TextBlock *block : std::as_const(m_blocks)) {
86 block->deleteBlockContent();
96 qDeleteAll(copyCursors);
97 Q_ASSERT(m_invalidCursors.empty());
112 Q_ASSERT(m_editingTransactions == 0);
121 for (
TextBlock *block : std::as_const(m_blocks)) {
122 block->clearBlockContent(newBlock);
126 qDeleteAll(m_blocks);
130 m_blocks.push_back(newBlock);
139 m_generateByteOrderMark =
false;
142 m_mimeTypeForFilterDev = QStringLiteral(
"text/plain");
154 int blockIndex = blockForLine(
line);
157 return m_blocks.at(blockIndex)->line(
line);
163 int blockIndex = blockForLine(
line);
166 return m_blocks.at(blockIndex)->setLineMetaData(
line, textLine);
177 for (
auto block : m_blocks) {
178 if (block->startLine() + block->lines() < c.
line()) {
179 off += block->blockSize();
180 line += block->lines();
182 const int lines = block->lines();
183 for (
int i = 0; i <
lines; ++i) {
185 off += qMin(c.
column(), block->lineLength(
line));
188 off += block->lineLength(
line) + 1;
202 for (
auto block : m_blocks) {
203 if (off + block->blockSize() < offset) {
204 off += block->blockSize();
206 const int lines = block->lines();
207 int start = block->startLine();
210 const int len = block->lineLength(
line);
211 if (off + len >= offset) {
226 for (
auto b : m_blocks) {
227 size += b->blockSize();
233 for (
TextBlock *block : std::as_const(m_blocks)) {
244 ++m_editingTransactions;
247 if (m_editingTransactions > 1) {
252 m_editingLastRevision = m_revision;
253 m_editingLastLines = m_lines;
254 m_editingMinimalLineChanged = -1;
255 m_editingMaximalLineChanged = -1;
258 Q_EMIT m_document->KTextEditor::Document::editingStarted(m_document);
267 Q_ASSERT(m_editingTransactions > 0);
270 --m_editingTransactions;
273 if (m_editingTransactions > 0) {
278 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
279 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
280 Q_ASSERT(!
editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
281 Q_ASSERT(!
editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
284 Q_EMIT m_document->KTextEditor::Document::editingFinished(m_document);
293 BUFFER_DEBUG <<
"wrapLine" << position;
296 Q_ASSERT(m_editingTransactions > 0);
299 int blockIndex = blockForLine(position.
line());
306 m_blocks.at(blockIndex)->wrapLine(position, blockIndex);
312 if (position.
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
313 m_editingMinimalLineChanged = position.
line();
316 if (position.
line() <= m_editingMaximalLineChanged) {
317 ++m_editingMaximalLineChanged;
319 m_editingMaximalLineChanged = position.
line() + 1;
323 balanceBlock(blockIndex);
326 Q_EMIT m_document->KTextEditor::Document::lineWrapped(m_document, position);
332 BUFFER_DEBUG <<
"unwrapLine" <<
line;
335 Q_ASSERT(m_editingTransactions > 0);
341 int blockIndex = blockForLine(
line);
344 bool firstLineInBlock = (
line == m_blocks.at(blockIndex)->startLine());
350 m_blocks.at(blockIndex)->unwrapLine(
line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) :
nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex);
354 if (firstLineInBlock) {
362 if ((
line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
363 m_editingMinimalLineChanged =
line - 1;
366 if (
line <= m_editingMaximalLineChanged) {
367 --m_editingMaximalLineChanged;
369 m_editingMaximalLineChanged =
line - 1;
373 balanceBlock(blockIndex);
376 Q_EMIT m_document->KTextEditor::Document::lineUnwrapped(m_document,
line);
382 BUFFER_DEBUG <<
"insertText" << position <<
text;
385 Q_ASSERT(m_editingTransactions > 0);
393 int blockIndex = blockForLine(position.
line());
396 m_blocks.at(blockIndex)->insertText(position,
text);
402 if (position.
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
403 m_editingMinimalLineChanged = position.
line();
406 if (position.
line() > m_editingMaximalLineChanged) {
407 m_editingMaximalLineChanged = position.
line();
411 Q_EMIT m_document->KTextEditor::Document::textInserted(m_document, position,
text);
417 BUFFER_DEBUG <<
"removeText" << range;
420 Q_ASSERT(m_editingTransactions > 0);
435 int blockIndex = blockForLine(range.
start().
line());
439 m_blocks.at(blockIndex)->removeText(range,
text);
445 if (range.
start().
line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
446 m_editingMinimalLineChanged = range.
start().
line();
449 if (range.
start().
line() > m_editingMaximalLineChanged) {
450 m_editingMaximalLineChanged = range.
start().
line();
454 Q_EMIT m_document->KTextEditor::Document::textRemoved(m_document, range,
text);
457int TextBuffer::blockForLine(
int line)
const
461 qFatal(
"out of range line requested in text buffer (%d out of [0, %d])",
line,
lines());
464 size_t b =
line / BufferBlockSize;
465 if (b >= m_blocks.size()) {
466 b = m_blocks.size() - 1;
469 auto block = m_blocks[b];
470 if (block->startLine() <=
line && line < block->startLine() + block->lines()) {
474 if (block->startLine() >
line) {
475 for (
int i = b - 1; i >= 0; --i) {
476 auto block = m_blocks[i];
477 if (block->startLine() <=
line && line < block->startLine() + block->lines()) {
483 if (block->startLine() <
line || (block->lines() == 0)) {
484 for (
size_t i = b + 1; i < m_blocks.size(); ++i) {
485 auto block = m_blocks[i];
486 if (block->startLine() <=
line && line < block->startLine() + block->lines()) {
492 qFatal(
"line requested in text buffer (%d out of [0, %d[), no block found",
line,
lines());
496void TextBuffer::fixStartLines(
int startBlock)
499 Q_ASSERT(startBlock >= 0);
500 Q_ASSERT(startBlock < (
int)m_blocks.size());
503 TextBlock *block = m_blocks.at(startBlock);
504 int newStartLine = block->startLine() + block->lines();
507 for (
size_t index = startBlock + 1; index < m_blocks.size(); ++index) {
509 block = m_blocks.at(index);
510 block->setStartLine(newStartLine);
513 newStartLine += block->lines();
517void TextBuffer::balanceBlock(
int index)
520 TextBlock *blockToBalance = m_blocks.at(index);
523 if (blockToBalance->lines() >= 2 * BufferBlockSize) {
525 int halfSize = blockToBalance->lines() / 2;
528 TextBlock *newBlock = blockToBalance->splitBlock(halfSize);
530 m_blocks.insert(m_blocks.begin() + index + 1, newBlock);
545 if (2 * blockToBalance->lines() > BufferBlockSize) {
550 TextBlock *targetBlock = m_blocks.at(index - 1);
553 blockToBalance->mergeBlock(targetBlock);
556 delete blockToBalance;
557 m_blocks.erase(m_blocks.begin() + index);
563 printf(
"%s (lines: %d)\n", qPrintable(title), m_lines);
566 for (
size_t i = 0; i < m_blocks.size(); ++i) {
567 m_blocks.at(i)->debugPrint(i);
571bool TextBuffer::load(
const QString &filename,
bool &encodingErrors,
bool &tooLongLinesWrapped,
int &longestLineLoaded,
bool enforceTextCodec)
574 Q_ASSERT(!m_fallbackTextCodec.
isEmpty());
577 Q_ASSERT(!m_textCodec.
isEmpty());
590 for (
int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
592 for (
size_t b = 1; b < m_blocks.size(); ++b) {
600 m_blocks.back()->clearLines();
604 tooLongLinesWrapped =
false;
605 longestLineLoaded = 0;
615 codec = m_fallbackTextCodec;
618 if (!file.
open(codec)) {
620 m_blocks.back()->appendLine(
QString());
626 encodingErrors =
false;
627 while (!file.
eof()) {
631 bool currentError = !file.
readLine(offset, length, tooLongLinesWrapped, longestLineLoaded);
632 encodingErrors = encodingErrors || currentError;
635 if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
636 BUFFER_DEBUG <<
"Failed try to load file" << filename <<
"with codec" << file.
textCodec();
641 if (m_blocks.back()->lines() >= BufferBlockSize) {
642 m_blocks.push_back(
new TextBlock(
this, m_blocks.back()->startLine() + m_blocks.back()->lines()));
646 m_blocks.back()->appendLine(
QString(file.
unicode() + offset, length));
651 if (!encodingErrors) {
667 if (file.
eol() != eolUnknown) {
675 Q_ASSERT(m_lines > 0);
678 BUFFER_DEBUG <<
"Loaded file " << filename <<
"with codec" << m_textCodec << (encodingErrors ?
"with" :
"without") <<
"encoding errors";
681 BUFFER_DEBUG << (file.
byteOrderMarkFound() ?
"Found" :
"Didn't find") <<
"byte order mark";
684 BUFFER_DEBUG <<
"used filter device for mime-type" << m_mimeTypeForFilterDev;
715 if (setEncoding == encoding) {
726 Q_ASSERT(!m_textCodec.
isEmpty());
728 SaveResult saveRes = saveBufferUnprivileged(filename);
730 if (saveRes == SaveResult::Failed) {
732 }
else if (saveRes == SaveResult::MissingPermissions) {
735 if (!saveBufferEscalated(filename)) {
741 m_history.setLastSavedRevision();
744 markModifiedLinesAsSaved();
756 QString eol = QStringLiteral(
"\n");
758 eol = QStringLiteral(
"\r\n");
760 eol = QStringLiteral(
"\r");
764 for (
int i = 0; i < m_lines; ++i) {
769 if ((i + 1) < m_lines) {
770 saveFile.
write(encoder.encode(eol));
787 BUFFER_DEBUG <<
"Saving file " << filename <<
"failed with error" << saveFile.
errorString();
794TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(
const QString &filename)
796 if (m_alwaysUseKAuthForSave) {
798 return SaveResult::MissingPermissions;
804 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
808 if (errno != EACCES) {
809 return SaveResult::Failed;
812 return SaveResult::MissingPermissions;
815 if (!saveBuffer(filename, *saveFile)) {
816 return SaveResult::Failed;
819 return SaveResult::Success;
822bool TextBuffer::saveBufferEscalated(
const QString &filename)
828 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
831 std::unique_ptr<QIODevice> temporaryBuffer;
835 if (fileInfo.exists()) {
836 ownerId = fileInfo.ownerId();
837 groupId = fileInfo.groupId();
842 temporaryBuffer = std::make_unique<QBuffer>();
850 saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(),
false, type);
855 if (!saveBuffer(filename, *saveFile)) {
864 if (!tempFile.
open()) {
869 temporaryBuffer->
seek(0);
872 char buffer[bufferLength];
875 while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
877 if (tempFile.
write(buffer, read) == -1) {
881 if (!tempFile.
flush()) {
886 QVariantMap kAuthActionArgs;
887 kAuthActionArgs.insert(QStringLiteral(
"sourceFile"), tempFile.
fileName());
888 kAuthActionArgs.insert(QStringLiteral(
"targetFile"), filename);
889 kAuthActionArgs.insert(QStringLiteral(
"checksum"), cryptographicHash.result());
890 kAuthActionArgs.insert(QStringLiteral(
"ownerId"), ownerId);
891 kAuthActionArgs.insert(QStringLiteral(
"groupId"), groupId);
896 if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
900 KAuth::Action kAuthSaveAction(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer.savefile"));
901 kAuthSaveAction.setHelperId(QStringLiteral(
"org.kde.ktexteditor6.katetextbuffer"));
902 kAuthSaveAction.setArguments(kAuthActionArgs);
928 if (view && view != curView) {
933 static_cast<KTextEditor::ViewPrivate *
>(curView)->notifyAboutRangeChange(lineRange, needsRepaint);
937void TextBuffer::markModifiedLinesAsSaved()
939 for (TextBlock *block :
std::as_const(m_blocks)) {
940 block->markModifiedLinesAsSaved();
945#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 bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
static bool unitTestMode()
Returns true, if the unit test mode was enabled through a call of enableUnitTestMode(),...
An object representing lines from a start line to an end line.
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.
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.
EndOfLineMode endOfLineMode() const
Get end of line mode.
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...
bool generateByteOrderMark() const
Generate byte order mark on save?
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.
KTextEditor::DocumentPrivate * document() const
Gets the document to which this buffer is bound.
virtual bool finishEditing()
Finish an editing transaction.
void setGenerateByteOrderMark(bool generateByteOrderMark)
Generate byte order mark on save.
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 Q_NOREPLY void start()
Type type(const QSqlDatabase &db)
QVariant read(const QByteArray &data, int versionOverride=0)
const char * constData() const const
virtual bool seek(qint64 pos) override
QString errorString() const const
qint64 write(const QByteArray &data)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
QByteArray toUtf8() const const
std::optional< Encoding > encodingForName(const char *name)
virtual QString fileName() const const override