KTextEditor

katetextbuffer.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6#include "config.h"
7#include "kateglobal.h"
8
9#include "katetextbuffer.h"
10#include "katetextloader.h"
11
12#include "katedocument.h"
13
14// this is unfortunate, but needed for performance
15#include "katepartdebug.h"
16#include "kateview.h"
17
18#ifndef Q_OS_WIN
19#include <cerrno>
20#include <unistd.h>
21// sadly there seems to be no possibility in Qt to determine detailed error
22// codes about e.g. file open errors, so we need to resort to evaluating
23// errno directly on platforms that support this
24#define CAN_USE_ERRNO
25#endif
26
27#include <QBuffer>
28#include <QCryptographicHash>
29#include <QFile>
30#include <QFileInfo>
31#include <QStringEncoder>
32#include <QTemporaryFile>
33
34#if HAVE_KAUTH
35#include "katesecuretextbuffer_p.h"
36#include <KAuth/Action>
37#include <KAuth/ExecuteJob>
38#endif
39
40#if 0
41#define BUFFER_DEBUG qCDebug(LOG_KTE)
42#else
43#define BUFFER_DEBUG \
44 if (0) \
45 qCDebug(LOG_KTE)
46#endif
47
48namespace Kate
49{
50TextBuffer::TextBuffer(KTextEditor::DocumentPrivate *parent, bool alwaysUseKAuth)
51 : QObject(parent)
52 , m_document(parent)
53 , m_history(*this)
54 , m_lines(0)
55 , m_revision(0)
56 , m_editingTransactions(0)
57 , m_editingLastRevision(0)
58 , m_editingLastLines(0)
59 , m_editingMinimalLineChanged(-1)
60 , m_editingMaximalLineChanged(-1)
61 , m_encodingProberType(KEncodingProber::Universal)
62 , m_generateByteOrderMark(false)
63 , m_endOfLineMode(eolUnix)
64 , m_lineLengthLimit(4096)
65 , m_alwaysUseKAuthForSave(alwaysUseKAuth)
66{
67 // create initial state
68 clear();
69}
70
72{
73 // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect
74 m_document = nullptr;
75
76 // not allowed during editing
77 Q_ASSERT(m_editingTransactions == 0);
78
79 // kill all ranges, work on copy, they will remove themself from the hash
80 QSet<TextRange *> copyRanges = m_ranges;
81 qDeleteAll(copyRanges);
82 Q_ASSERT(m_ranges.empty());
83
84 // clean out all cursors and lines, only cursors belonging to range will survive
85 for (TextBlock *block : std::as_const(m_blocks)) {
86 block->deleteBlockContent();
87 }
88
89 // delete all blocks, now that all cursors are really deleted
90 // else asserts in destructor of blocks will fail!
91 qDeleteAll(m_blocks);
92 m_blocks.clear();
93
94 // kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks
95 QSet<TextCursor *> copyCursors = m_invalidCursors;
96 qDeleteAll(copyCursors);
97 Q_ASSERT(m_invalidCursors.empty());
98}
99
101{
102 // invalidate all ranges, work on copy, they might delete themself...
103 const QSet<TextRange *> copyRanges = m_ranges;
104 for (TextRange *range : copyRanges) {
106 }
107}
108
110{
111 // not allowed during editing
112 Q_ASSERT(m_editingTransactions == 0);
113
115
116 // new block for empty buffer
117 TextBlock *newBlock = new TextBlock(this, 0);
118 newBlock->appendLine(QString());
119
120 // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range
121 for (TextBlock *block : std::as_const(m_blocks)) {
122 block->clearBlockContent(newBlock);
123 }
124
125 // kill all buffer blocks
126 qDeleteAll(m_blocks);
127 m_blocks.clear();
128
129 // insert one block with one empty line
130 m_blocks.push_back(newBlock);
131
132 // reset lines and last used block
133 m_lines = 1;
134
135 // reset revision
136 m_revision = 0;
137
138 // reset bom detection
139 m_generateByteOrderMark = false;
140
141 // reset the filter device
142 m_mimeTypeForFilterDev = QStringLiteral("text/plain");
143
144 // clear edit history
145 m_history.clear();
146
147 // we got cleared
148 Q_EMIT cleared();
149}
150
152{
153 // get block, this will assert on invalid line
154 int blockIndex = blockForLine(line);
155
156 // get line
157 return m_blocks.at(blockIndex)->line(line);
158}
159
160void TextBuffer::setLineMetaData(int line, const TextLine &textLine)
161{
162 // get block, this will assert on invalid line
163 int blockIndex = blockForLine(line);
164
165 // get line
166 return m_blocks.at(blockIndex)->setLineMetaData(line, textLine);
167}
168
170{
171 if (!c.isValid() || c > document()->documentEnd()) {
172 return -1;
173 }
174
175 int off = 0;
176 int line = 0;
177 for (auto block : m_blocks) {
178 if (block->startLine() + block->lines() < c.line()) {
179 off += block->blockSize();
180 line += block->lines();
181 } else {
182 const int lines = block->lines();
183 for (int i = 0; i < lines; ++i) {
184 if (line >= c.line()) {
185 off += qMin(c.column(), block->lineLength(line));
186 return off;
187 }
188 off += block->lineLength(line) + 1;
189 line++;
190 }
191 }
192 }
193
194 Q_ASSERT(false);
195 return -1;
196}
197
199{
200 if (offset >= 0) {
201 int off = 0;
202 for (auto block : m_blocks) {
203 if (off + block->blockSize() < offset) {
204 off += block->blockSize();
205 } else {
206 const int lines = block->lines();
207 int start = block->startLine();
208 int end = start + lines;
209 for (int line = start; line < end; ++line) {
210 const int len = block->lineLength(line);
211 if (off + len >= offset) {
212 return KTextEditor::Cursor(line, offset - off);
213 }
214 off += len + 1;
215 }
216 }
217 }
218 }
220}
221
223{
225 qsizetype size = 0;
226 for (auto b : m_blocks) {
227 size += b->blockSize();
228 }
229 size -= 1; // remove -1, last newline
230 text.reserve(size);
231
232 // combine all blocks
233 for (TextBlock *block : std::as_const(m_blocks)) {
234 block->text(text);
235 }
236
237 Q_ASSERT(size == text.size());
238 return text;
239}
240
242{
243 // increment transaction counter
244 ++m_editingTransactions;
245
246 // if not first running transaction, do nothing
247 if (m_editingTransactions > 1) {
248 return false;
249 }
250
251 // reset information about edit...
252 m_editingLastRevision = m_revision;
253 m_editingLastLines = m_lines;
254 m_editingMinimalLineChanged = -1;
255 m_editingMaximalLineChanged = -1;
256
257 // transaction has started
258 Q_EMIT m_document->KTextEditor::Document::editingStarted(m_document);
259
260 // first transaction started
261 return true;
262}
263
265{
266 // only allowed if still transactions running
267 Q_ASSERT(m_editingTransactions > 0);
268
269 // decrement counter
270 --m_editingTransactions;
271
272 // if not last running transaction, do nothing
273 if (m_editingTransactions > 0) {
274 return false;
275 }
276
277 // assert that if buffer changed, the line ranges are set and valid!
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));
282
283 // transaction has finished
284 Q_EMIT m_document->KTextEditor::Document::editingFinished(m_document);
285
286 // last transaction finished
287 return true;
288}
289
291{
292 // debug output for REAL low-level debugging
293 BUFFER_DEBUG << "wrapLine" << position;
294
295 // only allowed if editing transaction running
296 Q_ASSERT(m_editingTransactions > 0);
297
298 // get block, this will assert on invalid line
299 int blockIndex = blockForLine(position.line());
300
301 // let the block handle the wrapLine
302 // this can only lead to one more line in this block
303 // no other blocks will change
304 // this call will trigger fixStartLines
305 ++m_lines; // first alter the line counter, as functions called will need the valid one
306 m_blocks.at(blockIndex)->wrapLine(position, blockIndex);
307
308 // remember changes
309 ++m_revision;
310
311 // update changed line interval
312 if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
313 m_editingMinimalLineChanged = position.line();
314 }
315
316 if (position.line() <= m_editingMaximalLineChanged) {
317 ++m_editingMaximalLineChanged;
318 } else {
319 m_editingMaximalLineChanged = position.line() + 1;
320 }
321
322 // balance the changed block if needed
323 balanceBlock(blockIndex);
324
325 // emit signal about done change
326 Q_EMIT m_document->KTextEditor::Document::lineWrapped(m_document, position);
327}
328
330{
331 // debug output for REAL low-level debugging
332 BUFFER_DEBUG << "unwrapLine" << line;
333
334 // only allowed if editing transaction running
335 Q_ASSERT(m_editingTransactions > 0);
336
337 // line 0 can't be unwrapped
338 Q_ASSERT(line > 0);
339
340 // get block, this will assert on invalid line
341 int blockIndex = blockForLine(line);
342
343 // is this the first line in the block?
344 bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine());
345
346 // let the block handle the unwrapLine
347 // this can either lead to one line less in this block or the previous one
348 // the previous one could even end up with zero lines
349 // this call will trigger fixStartLines
350 m_blocks.at(blockIndex)->unwrapLine(line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) : nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex);
351 --m_lines;
352
353 // decrement index for later fixup, if we modified the block in front of the found one
354 if (firstLineInBlock) {
355 --blockIndex;
356 }
357
358 // remember changes
359 ++m_revision;
360
361 // update changed line interval
362 if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
363 m_editingMinimalLineChanged = line - 1;
364 }
365
366 if (line <= m_editingMaximalLineChanged) {
367 --m_editingMaximalLineChanged;
368 } else {
369 m_editingMaximalLineChanged = line - 1;
370 }
371
372 // balance the changed block if needed
373 balanceBlock(blockIndex);
374
375 // emit signal about done change
376 Q_EMIT m_document->KTextEditor::Document::lineUnwrapped(m_document, line);
377}
378
379void TextBuffer::insertText(const KTextEditor::Cursor position, const QString &text)
380{
381 // debug output for REAL low-level debugging
382 BUFFER_DEBUG << "insertText" << position << text;
383
384 // only allowed if editing transaction running
385 Q_ASSERT(m_editingTransactions > 0);
386
387 // skip work, if no text to insert
388 if (text.isEmpty()) {
389 return;
390 }
391
392 // get block, this will assert on invalid line
393 int blockIndex = blockForLine(position.line());
394
395 // let the block handle the insertText
396 m_blocks.at(blockIndex)->insertText(position, text);
397
398 // remember changes
399 ++m_revision;
400
401 // update changed line interval
402 if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
403 m_editingMinimalLineChanged = position.line();
404 }
405
406 if (position.line() > m_editingMaximalLineChanged) {
407 m_editingMaximalLineChanged = position.line();
408 }
409
410 // emit signal about done change
411 Q_EMIT m_document->KTextEditor::Document::textInserted(m_document, position, text);
412}
413
415{
416 // debug output for REAL low-level debugging
417 BUFFER_DEBUG << "removeText" << range;
418
419 // only allowed if editing transaction running
420 Q_ASSERT(m_editingTransactions > 0);
421
422 // only ranges on one line are supported
423 Q_ASSERT(range.start().line() == range.end().line());
424
425 // start column <= end column and >= 0
426 Q_ASSERT(range.start().column() <= range.end().column());
427 Q_ASSERT(range.start().column() >= 0);
428
429 // skip work, if no text to remove
430 if (range.isEmpty()) {
431 return;
432 }
433
434 // get block, this will assert on invalid line
435 int blockIndex = blockForLine(range.start().line());
436
437 // let the block handle the removeText, retrieve removed text
439 m_blocks.at(blockIndex)->removeText(range, text);
440
441 // remember changes
442 ++m_revision;
443
444 // update changed line interval
445 if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
446 m_editingMinimalLineChanged = range.start().line();
447 }
448
449 if (range.start().line() > m_editingMaximalLineChanged) {
450 m_editingMaximalLineChanged = range.start().line();
451 }
452
453 // emit signal about done change
454 Q_EMIT m_document->KTextEditor::Document::textRemoved(m_document, range, text);
455}
456
457int TextBuffer::blockForLine(int line) const
458{
459 // only allow valid lines
460 if ((line < 0) || (line >= lines())) {
461 qFatal("out of range line requested in text buffer (%d out of [0, %d])", line, lines());
462 }
463
464 size_t b = line / BufferBlockSize;
465 if (b >= m_blocks.size()) {
466 b = m_blocks.size() - 1;
467 }
468
469 auto block = m_blocks[b];
470 if (block->startLine() <= line && line < block->startLine() + block->lines()) {
471 return b;
472 }
473
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()) {
478 return i;
479 }
480 }
481 }
482
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()) {
487 return i;
488 }
489 }
490 }
491
492 qFatal("line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
493 return -1;
494}
495
496void TextBuffer::fixStartLines(int startBlock)
497{
498 // only allow valid start block
499 Q_ASSERT(startBlock >= 0);
500 Q_ASSERT(startBlock < (int)m_blocks.size());
501
502 // new start line for next block
503 TextBlock *block = m_blocks.at(startBlock);
504 int newStartLine = block->startLine() + block->lines();
505
506 // fixup block
507 for (size_t index = startBlock + 1; index < m_blocks.size(); ++index) {
508 // set new start line
509 block = m_blocks.at(index);
510 block->setStartLine(newStartLine);
511
512 // calculate next start line
513 newStartLine += block->lines();
514 }
515}
516
517void TextBuffer::balanceBlock(int index)
518{
519 // two cases, too big or too small block
520 TextBlock *blockToBalance = m_blocks.at(index);
521
522 // first case, too big one, split it
523 if (blockToBalance->lines() >= 2 * BufferBlockSize) {
524 // half the block
525 int halfSize = blockToBalance->lines() / 2;
526
527 // create and insert new block behind current one, already set right start line
528 TextBlock *newBlock = blockToBalance->splitBlock(halfSize);
529 Q_ASSERT(newBlock);
530 m_blocks.insert(m_blocks.begin() + index + 1, newBlock);
531
532 // split is done
533 return;
534 }
535
536 // second case: possibly too small block
537
538 // if only one block, no chance to unite
539 // same if this is first block, we always append to previous one
540 if (index == 0) {
541 return;
542 }
543
544 // block still large enough, do nothing
545 if (2 * blockToBalance->lines() > BufferBlockSize) {
546 return;
547 }
548
549 // unite small block with predecessor
550 TextBlock *targetBlock = m_blocks.at(index - 1);
551
552 // merge block
553 blockToBalance->mergeBlock(targetBlock);
554
555 // delete old block
556 delete blockToBalance;
557 m_blocks.erase(m_blocks.begin() + index);
558}
559
560void TextBuffer::debugPrint(const QString &title) const
561{
562 // print header with title
563 printf("%s (lines: %d)\n", qPrintable(title), m_lines);
564
565 // print all blocks
566 for (size_t i = 0; i < m_blocks.size(); ++i) {
567 m_blocks.at(i)->debugPrint(i);
568 }
569}
570
571bool TextBuffer::load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec)
572{
573 // fallback codec must exist
574 Q_ASSERT(!m_fallbackTextCodec.isEmpty());
575
576 // codec must be set!
577 Q_ASSERT(!m_textCodec.isEmpty());
578
579 // first: clear buffer in any case!
580 clear();
581
582 // construct the file loader for the given file, with correct prober type
583 Kate::TextLoader file(filename, m_encodingProberType, m_lineLengthLimit);
584
585 // triple play, maximal three loading rounds
586 // 0) use the given encoding, be done, if no encoding errors happen
587 // 1) use BOM to decided if Unicode or if that fails, use encoding prober, if no encoding errors happen, be done
588 // 2) use fallback encoding, be done, if no encoding errors happen
589 // 3) use again given encoding, be done in any case
590 for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
591 // kill all blocks beside first one
592 for (size_t b = 1; b < m_blocks.size(); ++b) {
593 TextBlock *block = m_blocks.at(b);
594 block->clearLines();
595 delete block;
596 }
597 m_blocks.resize(1);
598
599 // remove lines in first block
600 m_blocks.back()->clearLines();
601 m_lines = 0;
602
603 // reset error flags
604 tooLongLinesWrapped = false;
605 longestLineLoaded = 0;
606
607 // try to open file, with given encoding
608 // in round 0 + 3 use the given encoding from user
609 // in round 1 use 0, to trigger detection
610 // in round 2 use fallback
611 QString codec = m_textCodec;
612 if (i == 1) {
613 codec.clear();
614 } else if (i == 2) {
615 codec = m_fallbackTextCodec;
616 }
617
618 if (!file.open(codec)) {
619 // create one dummy textline, in any case
620 m_blocks.back()->appendLine(QString());
621 m_lines++;
622 return false;
623 }
624
625 // read in all lines...
626 encodingErrors = false;
627 while (!file.eof()) {
628 // read line
629 int offset = 0;
630 int length = 0;
631 bool currentError = !file.readLine(offset, length, tooLongLinesWrapped, longestLineLoaded);
632 encodingErrors = encodingErrors || currentError;
633
634 // bail out on encoding error, if not last round!
635 if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
636 BUFFER_DEBUG << "Failed try to load file" << filename << "with codec" << file.textCodec();
637 break;
638 }
639
640 // ensure blocks aren't too large
641 if (m_blocks.back()->lines() >= BufferBlockSize) {
642 m_blocks.push_back(new TextBlock(this, m_blocks.back()->startLine() + m_blocks.back()->lines()));
643 }
644
645 // append line to last block
646 m_blocks.back()->appendLine(QString(file.unicode() + offset, length));
647 ++m_lines;
648 }
649
650 // if no encoding error, break out of reading loop
651 if (!encodingErrors) {
652 // remember used codec, might change bom setting
653 setTextCodec(file.textCodec());
654 break;
655 }
656 }
657
658 // save checksum of file on disk
659 setDigest(file.digest());
660
661 // remember if BOM was found
662 if (file.byteOrderMarkFound()) {
664 }
665
666 // remember eol mode, if any found in file
667 if (file.eol() != eolUnknown) {
668 setEndOfLineMode(file.eol());
669 }
670
671 // remember mime type for filter device
672 m_mimeTypeForFilterDev = file.mimeTypeForFilterDev();
673
674 // assert that one line is there!
675 Q_ASSERT(m_lines > 0);
676
677 // report CODEC + ERRORS
678 BUFFER_DEBUG << "Loaded file " << filename << "with codec" << m_textCodec << (encodingErrors ? "with" : "without") << "encoding errors";
679
680 // report BOM
681 BUFFER_DEBUG << (file.byteOrderMarkFound() ? "Found" : "Didn't find") << "byte order mark";
682
683 // report filter device mime-type
684 BUFFER_DEBUG << "used filter device for mime-type" << m_mimeTypeForFilterDev;
685
686 // emit success
687 Q_EMIT loaded(filename, encodingErrors);
688
689 // file loading worked, modulo encoding problems
690 return true;
691}
692
694{
695 return m_digest;
696}
697
699{
700 m_digest = checksum;
701}
702
704{
705 m_textCodec = codec;
706
707 // enforce bom for some encodings
708 if (const auto setEncoding = QStringConverter::encodingForName(m_textCodec.toUtf8().constData())) {
709 for (const auto encoding : {QStringConverter::Utf16,
715 if (setEncoding == encoding) {
717 break;
718 }
719 }
720 }
721}
722
723bool TextBuffer::save(const QString &filename)
724{
725 // codec must be set, else below we fail!
726 Q_ASSERT(!m_textCodec.isEmpty());
727
728 SaveResult saveRes = saveBufferUnprivileged(filename);
729
730 if (saveRes == SaveResult::Failed) {
731 return false;
732 } else if (saveRes == SaveResult::MissingPermissions) {
733 // either unit-test mode or we're missing permissions to write to the
734 // file => use temporary file and try to use authhelper
735 if (!saveBufferEscalated(filename)) {
736 return false;
737 }
738 }
739
740 // remember this revision as last saved
741 m_history.setLastSavedRevision();
742
743 // inform that we have saved the state
744 markModifiedLinesAsSaved();
745
746 // emit that file was saved and be done
747 Q_EMIT saved(filename);
748 return true;
749}
750
751bool TextBuffer::saveBuffer(const QString &filename, KCompressionDevice &saveFile)
752{
753 QStringEncoder encoder(m_textCodec.toUtf8().constData(), generateByteOrderMark() ? QStringConverter::Flag::WriteBom : QStringConverter::Flag::Default);
754
755 // our loved eol string ;)
756 QString eol = QStringLiteral("\n");
757 if (endOfLineMode() == eolDos) {
758 eol = QStringLiteral("\r\n");
759 } else if (endOfLineMode() == eolMac) {
760 eol = QStringLiteral("\r");
761 }
762
763 // just dump the lines out ;)
764 for (int i = 0; i < m_lines; ++i) {
765 // dump current line
766 saveFile.write(encoder.encode(line(i).text()));
767
768 // append correct end of line string
769 if ((i + 1) < m_lines) {
770 saveFile.write(encoder.encode(eol));
771 }
772
773 // early out on stream errors
774 if (saveFile.error() != QFileDevice::NoError) {
775 return false;
776 }
777 }
778
779 // TODO: this only writes bytes when there is text. This is a fine optimization for most cases, but this makes saving
780 // an empty file with the BOM set impossible (results to an empty file with 0 bytes, no BOM)
781
782 // close the file, we might want to read from underlying buffer below
783 saveFile.close();
784
785 // did save work?
786 if (saveFile.error() != QFileDevice::NoError) {
787 BUFFER_DEBUG << "Saving file " << filename << "failed with error" << saveFile.errorString();
788 return false;
789 }
790
791 return true;
792}
793
794TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(const QString &filename)
795{
796 if (m_alwaysUseKAuthForSave) {
797 // unit-testing mode, simulate we need privileges
798 return SaveResult::MissingPermissions;
799 }
800
801 // construct correct filter device
802 // we try to use the same compression as for opening
804 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
805
806 if (!saveFile->open(QIODevice::WriteOnly)) {
807#ifdef CAN_USE_ERRNO
808 if (errno != EACCES) {
809 return SaveResult::Failed;
810 }
811#endif
812 return SaveResult::MissingPermissions;
813 }
814
815 if (!saveBuffer(filename, *saveFile)) {
816 return SaveResult::Failed;
817 }
818
819 return SaveResult::Success;
820}
821
822bool TextBuffer::saveBufferEscalated(const QString &filename)
823{
824#if HAVE_KAUTH
825 // construct correct filter device
826 // we try to use the same compression as for opening
828 auto saveFile = std::make_unique<KCompressionDevice>(filename, type);
829 uint ownerId = -2;
830 uint groupId = -2;
831 std::unique_ptr<QIODevice> temporaryBuffer;
832
833 // Memorize owner and group.
834 const QFileInfo fileInfo(filename);
835 if (fileInfo.exists()) {
836 ownerId = fileInfo.ownerId();
837 groupId = fileInfo.groupId();
838 }
839
840 // if that fails we need more privileges to save this file
841 // -> we write to a temporary file and then send its path to KAuth action for privileged save
842 temporaryBuffer = std::make_unique<QBuffer>();
843
844 // open buffer for write and read (read is used for checksum computing and writing to temporary file)
845 if (!temporaryBuffer->open(QIODevice::ReadWrite)) {
846 return false;
847 }
848
849 // we are now saving to a temporary buffer with potential compression proxy
850 saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(), false, type);
851 if (!saveFile->open(QIODevice::WriteOnly)) {
852 return false;
853 }
854
855 if (!saveBuffer(filename, *saveFile)) {
856 return false;
857 }
858
859 // temporary buffer was used to save the file
860 // -> computing checksum
861 // -> saving to temporary file
862 // -> copying the temporary file to the original file location with KAuth action
863 QTemporaryFile tempFile;
864 if (!tempFile.open()) {
865 return false;
866 }
867
868 // go to QBuffer start
869 temporaryBuffer->seek(0);
870
871 // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile
872 char buffer[bufferLength];
873 qint64 read = -1;
874 QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm);
875 while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
876 cryptographicHash.addData(QByteArrayView(buffer, read));
877 if (tempFile.write(buffer, read) == -1) {
878 return false;
879 }
880 }
881 if (!tempFile.flush()) {
882 return false;
883 }
884
885 // prepare data for KAuth action
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);
892
893 // call save with elevated privileges
895 // unit testing purposes only
896 if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
897 return false;
898 }
899 } else {
900 KAuth::Action kAuthSaveAction(QStringLiteral("org.kde.ktexteditor6.katetextbuffer.savefile"));
901 kAuthSaveAction.setHelperId(QStringLiteral("org.kde.ktexteditor6.katetextbuffer"));
902 kAuthSaveAction.setArguments(kAuthActionArgs);
903 KAuth::ExecuteJob *job = kAuthSaveAction.execute();
904 if (!job->exec()) {
905 return false;
906 }
907 }
908
909 return true;
910#else
911 Q_UNUSED(filename);
912 return false;
913#endif
914}
915
916void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, KTextEditor::LineRange lineRange, bool needsRepaint)
917{
918 // ignore calls if no document is around
919 if (!m_document) {
920 return;
921 }
922
923 // update all views, this IS ugly and could be a signal, but I profiled and a signal is TOO slow, really
924 // just create 20k ranges in a go and you wait seconds on a decent machine
925 const QList<KTextEditor::View *> views = m_document->views();
926 for (KTextEditor::View *curView : views) {
927 // filter wrong views
928 if (view && view != curView) {
929 continue;
930 }
931
932 // notify view, it is really a kate view
933 static_cast<KTextEditor::ViewPrivate *>(curView)->notifyAboutRangeChange(lineRange, needsRepaint);
934 }
935}
936
937void TextBuffer::markModifiedLinesAsSaved()
938{
939 for (TextBlock *block : std::as_const(m_blocks)) {
940 block->markModifiedLinesAsSaved();
941 }
942}
943}
944
945#include "moc_katetextbuffer.cpp"
static CompressionType compressionTypeForMimeType(const QString &mimetype)
QFileDevice::FileError error() const
void close() override
bool open(QIODevice::OpenMode mode) override
bool exec()
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
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.
Definition linerange.h:41
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.
Definition view.h:244
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?
Class representing a 'clever' text range.
Q_SCRIPTABLE Q_NOREPLY void start()
Type type(const QSqlDatabase &db)
QVariant read(const QByteArray &data, int versionOverride=0)
const char * constData() const const
bool flush()
virtual bool seek(qint64 pos) override
QString errorString() const const
qint64 write(const QByteArray &data)
Q_EMITQ_EMIT
void clear()
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
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.