Warning, file /frameworks/ktexteditor/src/buffer/katetextbuffer.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "config.h"
0007 #include "kateglobal.h"
0008 
0009 #include "katetextbuffer.h"
0010 #include "katetextloader.h"
0011 
0012 #include "katedocument.h"
0013 
0014 // this is unfortunate, but needed for performance
0015 #include "katepartdebug.h"
0016 #include "kateview.h"
0017 
0018 
0019 #ifndef Q_OS_WIN
0020 #include <cerrno>
0021 #include <unistd.h>
0022 // sadly there seems to be no possibility in Qt to determine detailed error
0023 // codes about e.g. file open errors, so we need to resort to evaluating
0024 // errno directly on platforms that support this
0025 #define CAN_USE_ERRNO
0026 #endif
0027 
0028 #include <QBuffer>
0029 #include <QCryptographicHash>
0030 #include <QFile>
0031 #include <QFileInfo>
0032 #include <QTemporaryFile>
0033 
0034 #if HAVE_KAUTH
0035 #include "katesecuretextbuffer_p.h"
0036 #include <KAuth/Action>
0037 #include <KAuth/ExecuteJob>
0038 #endif
0039 
0040 #if 0
0041 #define BUFFER_DEBUG qCDebug(LOG_KTE)
0042 #else
0043 #define BUFFER_DEBUG                                                                                                                                           \
0044     if (0)                                                                                                                                                     \
0045     qCDebug(LOG_KTE)
0046 #endif
0047 
0048 namespace Kate
0049 {
0050 TextBuffer::TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize, bool alwaysUseKAuth)
0051     : QObject(parent)
0052     , m_document(parent)
0053     , m_history(*this)
0054     , m_blockSize(blockSize)
0055     , m_lines(0)
0056     , m_lastUsedBlock(0)
0057     , m_revision(0)
0058     , m_editingTransactions(0)
0059     , m_editingLastRevision(0)
0060     , m_editingLastLines(0)
0061     , m_editingMinimalLineChanged(-1)
0062     , m_editingMaximalLineChanged(-1)
0063     , m_encodingProberType(KEncodingProber::Universal)
0064     , m_fallbackTextCodec(nullptr)
0065     , m_textCodec(nullptr)
0066     , m_generateByteOrderMark(false)
0067     , m_endOfLineMode(eolUnix)
0068     , m_lineLengthLimit(4096)
0069     , m_alwaysUseKAuthForSave(alwaysUseKAuth)
0070 {
0071     // minimal block size must be > 0
0072     Q_ASSERT(m_blockSize > 0);
0073 
0074     // create initial state
0075     clear();
0076 }
0077 
0078 TextBuffer::~TextBuffer()
0079 {
0080     // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect
0081     m_document = nullptr;
0082 
0083     // not allowed during editing
0084     Q_ASSERT(m_editingTransactions == 0);
0085 
0086     // kill all ranges, work on copy, they will remove themself from the hash
0087     QSet<TextRange *> copyRanges = m_ranges;
0088     qDeleteAll(copyRanges);
0089     Q_ASSERT(m_ranges.empty());
0090 
0091     // clean out all cursors and lines, only cursors belonging to range will survive
0092     for (TextBlock *block : std::as_const(m_blocks)) {
0093         block->deleteBlockContent();
0094     }
0095 
0096     // delete all blocks, now that all cursors are really deleted
0097     // else asserts in destructor of blocks will fail!
0098     qDeleteAll(m_blocks);
0099     m_blocks.clear();
0100 
0101     // kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks
0102     QSet<TextCursor *> copyCursors = m_invalidCursors;
0103     qDeleteAll(copyCursors);
0104     Q_ASSERT(m_invalidCursors.empty());
0105 }
0106 
0107 void TextBuffer::invalidateRanges()
0108 {
0109     // invalidate all ranges, work on copy, they might delete themself...
0110     const QSet<TextRange *> copyRanges = m_ranges;
0111     for (TextRange *range : copyRanges) {
0112         range->setRange(KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid());
0113     }
0114 }
0115 
0116 void TextBuffer::clear()
0117 {
0118     // not allowed during editing
0119     Q_ASSERT(m_editingTransactions == 0);
0120 
0121     invalidateRanges();
0122 
0123     // new block for empty buffer
0124     TextBlock *newBlock = new TextBlock(this, 0);
0125     newBlock->appendLine(QString());
0126 
0127     // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range
0128     for (TextBlock *block : std::as_const(m_blocks)) {
0129         block->clearBlockContent(newBlock);
0130     }
0131 
0132     // kill all buffer blocks
0133     qDeleteAll(m_blocks);
0134     m_blocks.clear();
0135 
0136     // insert one block with one empty line
0137     m_blocks.push_back(newBlock);
0138 
0139     // reset lines and last used block
0140     m_lines = 1;
0141     m_lastUsedBlock = 0;
0142 
0143     // reset revision
0144     m_revision = 0;
0145 
0146     // reset bom detection
0147     m_generateByteOrderMark = false;
0148 
0149     // reset the filter device
0150     m_mimeTypeForFilterDev = QStringLiteral("text/plain");
0151 
0152     // clear edit history
0153     m_history.clear();
0154 
0155     // we got cleared
0156     Q_EMIT cleared();
0157 }
0158 
0159 TextLine TextBuffer::line(int line) const
0160 {
0161     // get block, this will assert on invalid line
0162     int blockIndex = blockForLine(line);
0163 
0164     // get line
0165     return m_blocks.at(blockIndex)->line(line);
0166 }
0167 
0168 QString TextBuffer::text() const
0169 {
0170     QString text;
0171 
0172     // combine all blocks
0173     for (TextBlock *block : std::as_const(m_blocks)) {
0174         block->text(text);
0175     }
0176 
0177     // return generated string
0178     return text;
0179 }
0180 
0181 bool TextBuffer::startEditing()
0182 {
0183     // increment transaction counter
0184     ++m_editingTransactions;
0185 
0186     // if not first running transaction, do nothing
0187     if (m_editingTransactions > 1) {
0188         return false;
0189     }
0190 
0191     // reset information about edit...
0192     m_editingLastRevision = m_revision;
0193     m_editingLastLines = m_lines;
0194     m_editingMinimalLineChanged = -1;
0195     m_editingMaximalLineChanged = -1;
0196 
0197     // transaction has started
0198     Q_EMIT editingStarted();
0199     if (m_document) {
0200         Q_EMIT m_document->KTextEditor::Document::editingStarted(m_document);
0201     }
0202 
0203     // first transaction started
0204     return true;
0205 }
0206 
0207 bool TextBuffer::finishEditing()
0208 {
0209     // only allowed if still transactions running
0210     Q_ASSERT(m_editingTransactions > 0);
0211 
0212     // decrement counter
0213     --m_editingTransactions;
0214 
0215     // if not last running transaction, do nothing
0216     if (m_editingTransactions > 0) {
0217         return false;
0218     }
0219 
0220     // assert that if buffer changed, the line ranges are set and valid!
0221     Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
0222     Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
0223     Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
0224     Q_ASSERT(!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
0225 
0226     // transaction has finished
0227     Q_EMIT editingFinished();
0228     if (m_document) {
0229         Q_EMIT m_document->KTextEditor::Document::editingFinished(m_document);
0230     }
0231 
0232     // last transaction finished
0233     return true;
0234 }
0235 
0236 void TextBuffer::wrapLine(const KTextEditor::Cursor position)
0237 {
0238     // debug output for REAL low-level debugging
0239     BUFFER_DEBUG << "wrapLine" << position;
0240 
0241     // only allowed if editing transaction running
0242     Q_ASSERT(m_editingTransactions > 0);
0243 
0244     // get block, this will assert on invalid line
0245     int blockIndex = blockForLine(position.line());
0246 
0247     // let the block handle the wrapLine
0248     // this can only lead to one more line in this block
0249     // no other blocks will change
0250     // this call will trigger fixStartLines
0251     ++m_lines; // first alter the line counter, as functions called will need the valid one
0252     m_blocks.at(blockIndex)->wrapLine(position, blockIndex);
0253 
0254     // remember changes
0255     ++m_revision;
0256 
0257     // update changed line interval
0258     if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
0259         m_editingMinimalLineChanged = position.line();
0260     }
0261 
0262     if (position.line() <= m_editingMaximalLineChanged) {
0263         ++m_editingMaximalLineChanged;
0264     } else {
0265         m_editingMaximalLineChanged = position.line() + 1;
0266     }
0267 
0268     // balance the changed block if needed
0269     balanceBlock(blockIndex);
0270 
0271     // emit signal about done change
0272     Q_EMIT lineWrapped(position);
0273     if (m_document) {
0274         Q_EMIT m_document->KTextEditor::Document::lineWrapped(m_document, position);
0275     }
0276 }
0277 
0278 void TextBuffer::unwrapLine(int line)
0279 {
0280     // debug output for REAL low-level debugging
0281     BUFFER_DEBUG << "unwrapLine" << line;
0282 
0283     // only allowed if editing transaction running
0284     Q_ASSERT(m_editingTransactions > 0);
0285 
0286     // line 0 can't be unwrapped
0287     Q_ASSERT(line > 0);
0288 
0289     // get block, this will assert on invalid line
0290     int blockIndex = blockForLine(line);
0291 
0292     // is this the first line in the block?
0293     bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine());
0294 
0295     // let the block handle the unwrapLine
0296     // this can either lead to one line less in this block or the previous one
0297     // the previous one could even end up with zero lines
0298     // this call will trigger fixStartLines
0299     m_blocks.at(blockIndex)->unwrapLine(line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) : nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex);
0300     --m_lines;
0301 
0302     // decrement index for later fixup, if we modified the block in front of the found one
0303     if (firstLineInBlock) {
0304         --blockIndex;
0305     }
0306 
0307     // remember changes
0308     ++m_revision;
0309 
0310     // update changed line interval
0311     if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
0312         m_editingMinimalLineChanged = line - 1;
0313     }
0314 
0315     if (line <= m_editingMaximalLineChanged) {
0316         --m_editingMaximalLineChanged;
0317     } else {
0318         m_editingMaximalLineChanged = line - 1;
0319     }
0320 
0321     // balance the changed block if needed
0322     balanceBlock(blockIndex);
0323 
0324     // emit signal about done change
0325     Q_EMIT lineUnwrapped(line);
0326     if (m_document) {
0327         Q_EMIT m_document->KTextEditor::Document::lineUnwrapped(m_document, line);
0328     }
0329 }
0330 
0331 void TextBuffer::insertText(const KTextEditor::Cursor position, const QString &text)
0332 {
0333     // debug output for REAL low-level debugging
0334     BUFFER_DEBUG << "insertText" << position << text;
0335 
0336     // only allowed if editing transaction running
0337     Q_ASSERT(m_editingTransactions > 0);
0338 
0339     // skip work, if no text to insert
0340     if (text.isEmpty()) {
0341         return;
0342     }
0343 
0344     // get block, this will assert on invalid line
0345     int blockIndex = blockForLine(position.line());
0346 
0347     // let the block handle the insertText
0348     m_blocks.at(blockIndex)->insertText(position, text);
0349 
0350     // remember changes
0351     ++m_revision;
0352 
0353     // update changed line interval
0354     if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
0355         m_editingMinimalLineChanged = position.line();
0356     }
0357 
0358     if (position.line() > m_editingMaximalLineChanged) {
0359         m_editingMaximalLineChanged = position.line();
0360     }
0361 
0362     // emit signal about done change
0363     Q_EMIT textInserted(position, text);
0364     if (m_document) {
0365         Q_EMIT m_document->KTextEditor::Document::textInserted(m_document, position, text);
0366     }
0367 }
0368 
0369 void TextBuffer::removeText(KTextEditor::Range range)
0370 {
0371     // debug output for REAL low-level debugging
0372     BUFFER_DEBUG << "removeText" << range;
0373 
0374     // only allowed if editing transaction running
0375     Q_ASSERT(m_editingTransactions > 0);
0376 
0377     // only ranges on one line are supported
0378     Q_ASSERT(range.start().line() == range.end().line());
0379 
0380     // start column <= end column and >= 0
0381     Q_ASSERT(range.start().column() <= range.end().column());
0382     Q_ASSERT(range.start().column() >= 0);
0383 
0384     // skip work, if no text to remove
0385     if (range.isEmpty()) {
0386         return;
0387     }
0388 
0389     // get block, this will assert on invalid line
0390     int blockIndex = blockForLine(range.start().line());
0391 
0392     // let the block handle the removeText, retrieve removed text
0393     QString text;
0394     m_blocks.at(blockIndex)->removeText(range, text);
0395 
0396     // remember changes
0397     ++m_revision;
0398 
0399     // update changed line interval
0400     if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
0401         m_editingMinimalLineChanged = range.start().line();
0402     }
0403 
0404     if (range.start().line() > m_editingMaximalLineChanged) {
0405         m_editingMaximalLineChanged = range.start().line();
0406     }
0407 
0408     // emit signal about done change
0409     Q_EMIT textRemoved(range, text);
0410     if (m_document) {
0411         Q_EMIT m_document->KTextEditor::Document::textRemoved(m_document, range, text);
0412     }
0413 }
0414 
0415 int TextBuffer::blockForLine(int line) const
0416 {
0417     // only allow valid lines
0418     if ((line < 0) || (line >= lines())) {
0419         qFatal("out of range line requested in text buffer (%d out of [0, %d])", line, lines());
0420     }
0421 
0422     // we need blocks and last used block should not be negative
0423     Q_ASSERT(!m_blocks.empty());
0424     Q_ASSERT(m_lastUsedBlock >= 0);
0425 
0426     // shortcut: try last block first
0427     if (m_lastUsedBlock < (int)m_blocks.size()) {
0428         // check if block matches
0429         // if yes, just return again this block
0430         TextBlock *block = m_blocks[m_lastUsedBlock];
0431         const int start = block->startLine();
0432         const int lines = block->lines();
0433         if (start <= line && line < (start + lines)) {
0434             return m_lastUsedBlock;
0435         }
0436     }
0437 
0438     // search for right block
0439     // use binary search
0440     // if we leave this loop not by returning the found element we have an error
0441     int blockStart = 0;
0442     int blockEnd = m_blocks.size() - 1;
0443     while (blockEnd >= blockStart) {
0444         // get middle and ensure it is OK
0445         int middle = blockStart + ((blockEnd - blockStart) / 2);
0446         Q_ASSERT(middle >= 0);
0447         Q_ASSERT(middle < (int)m_blocks.size());
0448 
0449         // facts bout this block
0450         TextBlock *block = m_blocks[middle];
0451         const int start = block->startLine();
0452         const int lines = block->lines();
0453 
0454         // right block found, remember it and return it
0455         if (start <= line && line < (start + lines)) {
0456             m_lastUsedBlock = middle;
0457             return middle;
0458         }
0459 
0460         // half our stuff ;)
0461         if (line < start) {
0462             blockEnd = middle - 1;
0463         } else {
0464             blockStart = middle + 1;
0465         }
0466     }
0467 
0468     // we should always find a block
0469     qFatal("line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
0470     return -1;
0471 }
0472 
0473 void TextBuffer::fixStartLines(int startBlock)
0474 {
0475     // only allow valid start block
0476     Q_ASSERT(startBlock >= 0);
0477     Q_ASSERT(startBlock < (int)m_blocks.size());
0478 
0479     // new start line for next block
0480     TextBlock *block = m_blocks.at(startBlock);
0481     int newStartLine = block->startLine() + block->lines();
0482 
0483     // fixup block
0484     for (size_t index = startBlock + 1; index < m_blocks.size(); ++index) {
0485         // set new start line
0486         block = m_blocks.at(index);
0487         block->setStartLine(newStartLine);
0488 
0489         // calculate next start line
0490         newStartLine += block->lines();
0491     }
0492 }
0493 
0494 void TextBuffer::balanceBlock(int index)
0495 {
0496     // two cases, too big or too small block
0497     TextBlock *blockToBalance = m_blocks.at(index);
0498 
0499     // first case, too big one, split it
0500     if (blockToBalance->lines() >= 2 * m_blockSize) {
0501         // half the block
0502         int halfSize = blockToBalance->lines() / 2;
0503 
0504         // create and insert new block behind current one, already set right start line
0505         TextBlock *newBlock = blockToBalance->splitBlock(halfSize);
0506         Q_ASSERT(newBlock);
0507         m_blocks.insert(m_blocks.begin() + index + 1, newBlock);
0508 
0509         // split is done
0510         return;
0511     }
0512 
0513     // second case: possibly too small block
0514 
0515     // if only one block, no chance to unite
0516     // same if this is first block, we always append to previous one
0517     if (index == 0) {
0518         return;
0519     }
0520 
0521     // block still large enough, do nothing
0522     if (2 * blockToBalance->lines() > m_blockSize) {
0523         return;
0524     }
0525 
0526     // unite small block with predecessor
0527     TextBlock *targetBlock = m_blocks.at(index - 1);
0528 
0529     // merge block
0530     blockToBalance->mergeBlock(targetBlock);
0531 
0532     // delete old block
0533     delete blockToBalance;
0534     m_blocks.erase(m_blocks.begin() + index);
0535 }
0536 
0537 void TextBuffer::debugPrint(const QString &title) const
0538 {
0539     // print header with title
0540     printf("%s (lines: %d bs: %d)\n", qPrintable(title), m_lines, m_blockSize);
0541 
0542     // print all blocks
0543     for (size_t i = 0; i < m_blocks.size(); ++i) {
0544         m_blocks.at(i)->debugPrint(i);
0545     }
0546 }
0547 
0548 bool TextBuffer::load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec)
0549 {
0550     // fallback codec must exist
0551     Q_ASSERT(m_fallbackTextCodec);
0552 
0553     // codec must be set!
0554     Q_ASSERT(m_textCodec);
0555 
0556     // first: clear buffer in any case!
0557     clear();
0558 
0559     // construct the file loader for the given file, with correct prober type
0560     Kate::TextLoader file(filename, m_encodingProberType);
0561 
0562     // triple play, maximal three loading rounds
0563     // 0) use the given encoding, be done, if no encoding errors happen
0564     // 1) use BOM to decided if Unicode or if that fails, use encoding prober, if no encoding errors happen, be done
0565     // 2) use fallback encoding, be done, if no encoding errors happen
0566     // 3) use again given encoding, be done in any case
0567     for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
0568         // kill all blocks beside first one
0569         for (size_t b = 1; b < m_blocks.size(); ++b) {
0570             TextBlock *block = m_blocks.at(b);
0571             block->clearLines();
0572             delete block;
0573         }
0574         m_blocks.resize(1);
0575 
0576         // remove lines in first block
0577         m_blocks.back()->clearLines();
0578         m_lines = 0;
0579 
0580         // try to open file, with given encoding
0581         // in round 0 + 3 use the given encoding from user
0582         // in round 1 use 0, to trigger detection
0583         // in round 2 use fallback
0584         QTextCodec *codec = m_textCodec;
0585         if (i == 1) {
0586             codec = nullptr;
0587         } else if (i == 2) {
0588             codec = m_fallbackTextCodec;
0589         }
0590 
0591         if (!file.open(codec)) {
0592             // create one dummy textline, in any case
0593             m_blocks.back()->appendLine(QString());
0594             m_lines++;
0595             return false;
0596         }
0597 
0598         // read in all lines...
0599         encodingErrors = false;
0600         while (!file.eof()) {
0601             // read line
0602             int offset = 0;
0603             int length = 0;
0604             bool currentError = !file.readLine(offset, length);
0605             encodingErrors = encodingErrors || currentError;
0606 
0607             // bail out on encoding error, if not last round!
0608             if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
0609                 BUFFER_DEBUG << "Failed try to load file" << filename << "with codec" << (file.textCodec() ? file.textCodec()->name() : "(null)");
0610                 break;
0611             }
0612 
0613             // get Unicode data for this line
0614             const QChar *unicodeData = file.unicode() + offset;
0615 
0616             if (longestLineLoaded < length) {
0617                 longestLineLoaded = length;
0618             }
0619 
0620             // split lines, if too large
0621             do {
0622                 // calculate line length
0623                 int lineLength = length;
0624                 if ((m_lineLengthLimit > 0) && (lineLength > m_lineLengthLimit)) {
0625                     // search for place to wrap
0626                     int spacePosition = m_lineLengthLimit - 1;
0627                     for (int testPosition = m_lineLengthLimit - 1; (testPosition >= 0) && (testPosition >= (m_lineLengthLimit - (m_lineLengthLimit / 10)));
0628                          --testPosition) {
0629                         // wrap place found?
0630                         if (unicodeData[testPosition].isSpace() || unicodeData[testPosition].isPunct()) {
0631                             spacePosition = testPosition;
0632                             break;
0633                         }
0634                     }
0635 
0636                     // wrap the line
0637                     lineLength = spacePosition + 1;
0638                     length -= lineLength;
0639                     tooLongLinesWrapped = true;
0640                 } else {
0641                     // be done after this round
0642                     length = 0;
0643                 }
0644 
0645                 // construct new text line with content from file
0646                 // move data pointer
0647                 QString textLine(unicodeData, lineLength);
0648                 unicodeData += lineLength;
0649 
0650                 // ensure blocks aren't too large
0651                 if (m_blocks.back()->lines() >= m_blockSize) {
0652                     m_blocks.push_back(new TextBlock(this, m_blocks.back()->startLine() + m_blocks.back()->lines()));
0653                 }
0654 
0655                 // append line to last block
0656                 m_blocks.back()->appendLine(textLine);
0657                 ++m_lines;
0658             } while (length > 0);
0659         }
0660 
0661         // if no encoding error, break out of reading loop
0662         if (!encodingErrors) {
0663             // remember used codec, might change bom setting
0664             setTextCodec(file.textCodec());
0665             break;
0666         }
0667     }
0668 
0669     // save checksum of file on disk
0670     setDigest(file.digest());
0671 
0672     // remember if BOM was found
0673     if (file.byteOrderMarkFound()) {
0674         setGenerateByteOrderMark(true);
0675     }
0676 
0677     // remember eol mode, if any found in file
0678     if (file.eol() != eolUnknown) {
0679         setEndOfLineMode(file.eol());
0680     }
0681 
0682     // remember mime type for filter device
0683     m_mimeTypeForFilterDev = file.mimeTypeForFilterDev();
0684 
0685     // assert that one line is there!
0686     Q_ASSERT(m_lines > 0);
0687 
0688     // report CODEC + ERRORS
0689     BUFFER_DEBUG << "Loaded file " << filename << "with codec" << m_textCodec->name() << (encodingErrors ? "with" : "without") << "encoding errors";
0690 
0691     // report BOM
0692     BUFFER_DEBUG << (file.byteOrderMarkFound() ? "Found" : "Didn't find") << "byte order mark";
0693 
0694     // report filter device mime-type
0695     BUFFER_DEBUG << "used filter device for mime-type" << m_mimeTypeForFilterDev;
0696 
0697     // emit success
0698     Q_EMIT loaded(filename, encodingErrors);
0699 
0700     // file loading worked, modulo encoding problems
0701     return true;
0702 }
0703 
0704 const QByteArray &TextBuffer::digest() const
0705 {
0706     return m_digest;
0707 }
0708 
0709 void TextBuffer::setDigest(const QByteArray &checksum)
0710 {
0711     m_digest = checksum;
0712 }
0713 
0714 void TextBuffer::setTextCodec(QTextCodec *codec)
0715 {
0716     m_textCodec = codec;
0717 
0718     // enforce bom for some encodings
0719     int mib = m_textCodec->mibEnum();
0720     if (mib == 1013 || mib == 1014 || mib == 1015) { // utf16
0721         setGenerateByteOrderMark(true);
0722     }
0723     if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32
0724         setGenerateByteOrderMark(true);
0725     }
0726 }
0727 
0728 bool TextBuffer::save(const QString &filename)
0729 {
0730     // codec must be set, else below we fail!
0731     Q_ASSERT(m_textCodec);
0732 
0733     SaveResult saveRes = saveBufferUnprivileged(filename);
0734 
0735     if (saveRes == SaveResult::Failed) {
0736         return false;
0737     } else if (saveRes == SaveResult::MissingPermissions) {
0738         // either unit-test mode or we're missing permissions to write to the
0739         // file => use temporary file and try to use authhelper
0740         if (!saveBufferEscalated(filename)) {
0741             return false;
0742         }
0743     }
0744 
0745     // remember this revision as last saved
0746     m_history.setLastSavedRevision();
0747 
0748     // inform that we have saved the state
0749     markModifiedLinesAsSaved();
0750 
0751     // emit that file was saved and be done
0752     Q_EMIT saved(filename);
0753     return true;
0754 }
0755 
0756 bool TextBuffer::saveBuffer(const QString &filename, KCompressionDevice &saveFile)
0757 {
0758     std::unique_ptr<QTextEncoder> encoder(m_textCodec->makeEncoder(generateByteOrderMark() ? QTextCodec::DefaultConversion : QTextCodec::IgnoreHeader));
0759 
0760     // our loved eol string ;)
0761     QString eol = QStringLiteral("\n");
0762     if (endOfLineMode() == eolDos) {
0763         eol = QStringLiteral("\r\n");
0764     } else if (endOfLineMode() == eolMac) {
0765         eol = QStringLiteral("\r");
0766     }
0767 
0768     // just dump the lines out ;)
0769     for (int i = 0; i < m_lines; ++i) {
0770         // dump current line
0771         saveFile.write(encoder->fromUnicode(line(i)->text()));
0772 
0773         // append correct end of line string
0774         if ((i + 1) < m_lines) {
0775             saveFile.write(encoder->fromUnicode(eol));
0776         }
0777 
0778         // early out on stream errors
0779         if (saveFile.error() != QFileDevice::NoError) {
0780             return false;
0781         }
0782     }
0783 
0784     // TODO: this only writes bytes when there is text. This is a fine optimization for most cases, but this makes saving
0785     // an empty file with the BOM set impossible (results to an empty file with 0 bytes, no BOM)
0786 
0787     // close the file, we might want to read from underlying buffer below
0788     saveFile.close();
0789 
0790     // did save work?
0791     if (saveFile.error() != QFileDevice::NoError) {
0792         BUFFER_DEBUG << "Saving file " << filename << "failed with error" << saveFile.errorString();
0793         return false;
0794     }
0795 
0796     return true;
0797 }
0798 
0799 TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(const QString &filename)
0800 {
0801     if (m_alwaysUseKAuthForSave) {
0802         // unit-testing mode, simulate we need privileges
0803         return SaveResult::MissingPermissions;
0804     }
0805 
0806     // construct correct filter device
0807     // we try to use the same compression as for opening
0808     const KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(m_mimeTypeForFilterDev);
0809     auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
0810 
0811     if (!saveFile->open(QIODevice::WriteOnly)) {
0812 #ifdef CAN_USE_ERRNO
0813         if (errno != EACCES) {
0814             return SaveResult::Failed;
0815         }
0816 #endif
0817         return SaveResult::MissingPermissions;
0818     }
0819 
0820     if (!saveBuffer(filename, *saveFile)) {
0821         return SaveResult::Failed;
0822     }
0823 
0824     return SaveResult::Success;
0825 }
0826 
0827 bool TextBuffer::saveBufferEscalated(const QString &filename)
0828 {
0829 #if HAVE_KAUTH
0830     // construct correct filter device
0831     // we try to use the same compression as for opening
0832     const KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(m_mimeTypeForFilterDev);
0833     auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
0834     uint ownerId = -2;
0835     uint groupId = -2;
0836     std::unique_ptr<QIODevice> temporaryBuffer;
0837 
0838     // Memorize owner and group.
0839     const QFileInfo fileInfo(filename);
0840     if (fileInfo.exists()) {
0841         ownerId = fileInfo.ownerId();
0842         groupId = fileInfo.groupId();
0843     }
0844 
0845     // if that fails we need more privileges to save this file
0846     // -> we write to a temporary file and then send its path to KAuth action for privileged save
0847     temporaryBuffer = std::make_unique<QBuffer>();
0848 
0849     // open buffer for write and read (read is used for checksum computing and writing to temporary file)
0850     if (!temporaryBuffer->open(QIODevice::ReadWrite)) {
0851         return false;
0852     }
0853 
0854     // we are now saving to a temporary buffer with potential compression proxy
0855     saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(), false, type);
0856     if (!saveFile->open(QIODevice::WriteOnly)) {
0857         return false;
0858     }
0859 
0860     if (!saveBuffer(filename, *saveFile)) {
0861         return false;
0862     }
0863 
0864     // temporary buffer was used to save the file
0865     // -> computing checksum
0866     // -> saving to temporary file
0867     // -> copying the temporary file to the original file location with KAuth action
0868     QTemporaryFile tempFile;
0869     if (!tempFile.open()) {
0870         return false;
0871     }
0872 
0873     // go to QBuffer start
0874     temporaryBuffer->seek(0);
0875 
0876     // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile
0877     char buffer[bufferLength];
0878     qint64 read = -1;
0879     QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm);
0880     while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
0881         cryptographicHash.addData(buffer, read);
0882         if (tempFile.write(buffer, read) == -1) {
0883             return false;
0884         }
0885     }
0886     if (!tempFile.flush()) {
0887         return false;
0888     }
0889 
0890     // prepare data for KAuth action
0891     QVariantMap kAuthActionArgs;
0892     kAuthActionArgs.insert(QStringLiteral("sourceFile"), tempFile.fileName());
0893     kAuthActionArgs.insert(QStringLiteral("targetFile"), filename);
0894     kAuthActionArgs.insert(QStringLiteral("checksum"), cryptographicHash.result());
0895     kAuthActionArgs.insert(QStringLiteral("ownerId"), ownerId);
0896     kAuthActionArgs.insert(QStringLiteral("groupId"), groupId);
0897 
0898     // call save with elevated privileges
0899     if (KTextEditor::EditorPrivate::unitTestMode()) {
0900         // unit testing purposes only
0901         if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
0902             return false;
0903         }
0904     } else {
0905         KAuth::Action kAuthSaveAction(QStringLiteral("org.kde.ktexteditor.katetextbuffer.savefile"));
0906         kAuthSaveAction.setHelperId(QStringLiteral("org.kde.ktexteditor.katetextbuffer"));
0907         kAuthSaveAction.setArguments(kAuthActionArgs);
0908         KAuth::ExecuteJob *job = kAuthSaveAction.execute();
0909         if (!job->exec()) {
0910             return false;
0911         }
0912     }
0913 
0914     return true;
0915 #else
0916     Q_UNUSED(filename);
0917     return false;
0918 #endif
0919 }
0920 
0921 void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, KTextEditor::LineRange lineRange, bool needsRepaint)
0922 {
0923     // ignore calls if no document is around
0924     if (!m_document) {
0925         return;
0926     }
0927 
0928     // update all views, this IS ugly and could be a signal, but I profiled and a signal is TOO slow, really
0929     // just create 20k ranges in a go and you wait seconds on a decent machine
0930     const QList<KTextEditor::View *> views = m_document->views();
0931     for (KTextEditor::View *curView : views) {
0932         // filter wrong views
0933         if (view && view != curView) {
0934             continue;
0935         }
0936 
0937         // notify view, it is really a kate view
0938         static_cast<KTextEditor::ViewPrivate *>(curView)->notifyAboutRangeChange(lineRange, needsRepaint);
0939     }
0940 }
0941 
0942 void TextBuffer::markModifiedLinesAsSaved()
0943 {
0944     for (TextBlock *block : std::as_const(m_blocks)) {
0945         block->markModifiedLinesAsSaved();
0946     }
0947 }
0948 
0949 }
0950 
0951 #include "moc_katetextbuffer.cpp"