KTextEditor

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

KDE's Doxygen guidelines are available online.