KTextEditor

katetextbuffer.cpp
1 /* SPDX-License-Identifier: LGPL-2.0-or-later
2 
3  Copyright (C) 2010 Christoph Cullmann <[email protected]>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "config.h"
22 #include "kateglobal.h"
23 
24 #include "katesecuretextbuffer_p.h"
25 #include "katetextbuffer.h"
26 #include "katetextloader.h"
27 
28 // this is unfortunate, but needed for performance
29 #include "katedocument.h"
30 #include "katepartdebug.h"
31 #include "kateview.h"
32 
33 #ifndef Q_OS_WIN
34 #include <errno.h>
35 #include <unistd.h>
36 // sadly there seems to be no possibility in Qt to determine detailed error
37 // codes about e.g. file open errors, so we need to resort to evaluating
38 // errno directly on platforms that support this
39 #define CAN_USE_ERRNO
40 #endif
41 
42 #include <QBuffer>
43 #include <QCryptographicHash>
44 #include <QFile>
45 #include <QFileInfo>
46 #include <QTemporaryFile>
47 
48 #if 0
49 #define BUFFER_DEBUG qCDebug(LOG_KTE)
50 #else
51 #define BUFFER_DEBUG \
52  if (0) \
53  qCDebug(LOG_KTE)
54 #endif
55 
56 namespace Kate
57 {
58 TextBuffer::TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize, bool alwaysUseKAuth)
59  : QObject(parent)
60  , m_document(parent)
61  , m_history(*this)
62  , m_blockSize(blockSize)
63  , m_lines(0)
64  , m_lastUsedBlock(0)
65  , m_revision(0)
66  , m_editingTransactions(0)
67  , m_editingLastRevision(0)
68  , m_editingLastLines(0)
69  , m_editingMinimalLineChanged(-1)
70  , m_editingMaximalLineChanged(-1)
71  , m_encodingProberType(KEncodingProber::Universal)
72  , m_fallbackTextCodec(nullptr)
73  , m_textCodec(nullptr)
74  , m_generateByteOrderMark(false)
75  , m_endOfLineMode(eolUnix)
76  , m_newLineAtEof(false)
77  , m_lineLengthLimit(4096)
78  , m_alwaysUseKAuthForSave(alwaysUseKAuth)
79 {
80  // minimal block size must be > 0
81  Q_ASSERT(m_blockSize > 0);
82 
83  // create initial state
84  clear();
85 }
86 
88 {
89  // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect
90  m_document = nullptr;
91 
92  // not allowed during editing
93  Q_ASSERT(m_editingTransactions == 0);
94 
95  // kill all ranges, work on copy, they will remove themself from the hash
96  QSet<TextRange *> copyRanges = m_ranges;
97  qDeleteAll(copyRanges);
98  Q_ASSERT(m_ranges.empty());
99 
100  // clean out all cursors and lines, only cursors belonging to range will survive
101  for (TextBlock *block : qAsConst(m_blocks)) {
102  block->deleteBlockContent();
103  }
104 
105  // delete all blocks, now that all cursors are really deleted
106  // else asserts in destructor of blocks will fail!
107  qDeleteAll(m_blocks);
108  m_blocks.clear();
109 
110  // kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks
111  QSet<TextCursor *> copyCursors = m_invalidCursors;
112  qDeleteAll(copyCursors);
113  Q_ASSERT(m_invalidCursors.empty());
114 }
115 
117 {
118  // invalidate all ranges, work on copy, they might delete themself...
119  const QSet<TextRange *> copyRanges = m_ranges;
120  for (TextRange *range : copyRanges) {
122  }
123 }
124 
126 {
127  // not allowed during editing
128  Q_ASSERT(m_editingTransactions == 0);
129 
131 
132  // new block for empty buffer
133  TextBlock *newBlock = new TextBlock(this, 0);
134  newBlock->appendLine(QString());
135 
136  // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range
137  for (TextBlock *block : qAsConst(m_blocks)) {
138  block->clearBlockContent(newBlock);
139  }
140 
141  // kill all buffer blocks
142  qDeleteAll(m_blocks);
143  m_blocks.clear();
144 
145  // insert one block with one empty line
146  m_blocks.append(newBlock);
147 
148  // reset lines and last used block
149  m_lines = 1;
150  m_lastUsedBlock = 0;
151 
152  // reset revision
153  m_revision = 0;
154 
155  // reset bom detection
156  m_generateByteOrderMark = false;
157 
158  // reset the filter device
159  m_mimeTypeForFilterDev = QStringLiteral("text/plain");
160 
161  // clear edit history
162  m_history.clear();
163 
164  // we got cleared
165  emit cleared();
166 }
167 
169 {
170  // get block, this will assert on invalid line
171  int blockIndex = blockForLine(line);
172 
173  // get line
174  return m_blocks.at(blockIndex)->line(line);
175 }
176 
178 {
179  QString text;
180 
181  // combine all blocks
182  for (TextBlock *block : qAsConst(m_blocks)) {
183  block->text(text);
184  }
185 
186  // return generated string
187  return text;
188 }
189 
191 {
192  // increment transaction counter
193  ++m_editingTransactions;
194 
195  // if not first running transaction, do nothing
196  if (m_editingTransactions > 1) {
197  return false;
198  }
199 
200  // reset information about edit...
201  m_editingLastRevision = m_revision;
202  m_editingLastLines = m_lines;
203  m_editingMinimalLineChanged = -1;
204  m_editingMaximalLineChanged = -1;
205 
206  // transaction has started
207  emit editingStarted();
208  if (m_document)
209  emit m_document->KTextEditor::Document::editingStarted(m_document);
210 
211  // first transaction started
212  return true;
213 }
214 
216 {
217  // only allowed if still transactions running
218  Q_ASSERT(m_editingTransactions > 0);
219 
220  // decrement counter
221  --m_editingTransactions;
222 
223  // if not last running transaction, do nothing
224  if (m_editingTransactions > 0) {
225  return false;
226  }
227 
228  // assert that if buffer changed, the line ranges are set and valid!
229  Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
230  Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
231  Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
232  Q_ASSERT(!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
233 
234  // transaction has finished
235  emit editingFinished();
236  if (m_document)
237  emit m_document->KTextEditor::Document::editingFinished(m_document);
238 
239  // last transaction finished
240  return true;
241 }
242 
244 {
245  // debug output for REAL low-level debugging
246  BUFFER_DEBUG << "wrapLine" << position;
247 
248  // only allowed if editing transaction running
249  Q_ASSERT(m_editingTransactions > 0);
250 
251  // get block, this will assert on invalid line
252  int blockIndex = blockForLine(position.line());
253 
260  ++m_lines; // first alter the line counter, as functions called will need the valid one
261  m_blocks.at(blockIndex)->wrapLine(position, blockIndex);
262 
263  // remember changes
264  ++m_revision;
265 
266  // update changed line interval
267  if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
268  m_editingMinimalLineChanged = position.line();
269  }
270 
271  if (position.line() <= m_editingMaximalLineChanged) {
272  ++m_editingMaximalLineChanged;
273  } else {
274  m_editingMaximalLineChanged = position.line() + 1;
275  }
276 
277  // balance the changed block if needed
278  balanceBlock(blockIndex);
279 
280  // emit signal about done change
281  emit lineWrapped(position);
282  if (m_document)
283  emit m_document->KTextEditor::Document::lineWrapped(m_document, position);
284 }
285 
287 {
288  // debug output for REAL low-level debugging
289  BUFFER_DEBUG << "unwrapLine" << line;
290 
291  // only allowed if editing transaction running
292  Q_ASSERT(m_editingTransactions > 0);
293 
294  // line 0 can't be unwrapped
295  Q_ASSERT(line > 0);
296 
297  // get block, this will assert on invalid line
298  int blockIndex = blockForLine(line);
299 
300  // is this the first line in the block?
301  bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine());
302 
309  m_blocks.at(blockIndex)->unwrapLine(line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) : nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex);
310  --m_lines;
311 
312  // decrement index for later fixup, if we modified the block in front of the found one
313  if (firstLineInBlock) {
314  --blockIndex;
315  }
316 
317  // remember changes
318  ++m_revision;
319 
320  // update changed line interval
321  if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
322  m_editingMinimalLineChanged = line - 1;
323  }
324 
325  if (line <= m_editingMaximalLineChanged) {
326  --m_editingMaximalLineChanged;
327  } else {
328  m_editingMaximalLineChanged = line - 1;
329  }
330 
331  // balance the changed block if needed
332  balanceBlock(blockIndex);
333 
334  // emit signal about done change
335  emit lineUnwrapped(line);
336  if (m_document)
337  emit m_document->KTextEditor::Document::lineUnwrapped(m_document, line);
338 }
339 
341 {
342  // debug output for REAL low-level debugging
343  BUFFER_DEBUG << "insertText" << position << text;
344 
345  // only allowed if editing transaction running
346  Q_ASSERT(m_editingTransactions > 0);
347 
348  // skip work, if no text to insert
349  if (text.isEmpty()) {
350  return;
351  }
352 
353  // get block, this will assert on invalid line
354  int blockIndex = blockForLine(position.line());
355 
356  // let the block handle the insertText
357  m_blocks.at(blockIndex)->insertText(position, text);
358 
359  // remember changes
360  ++m_revision;
361 
362  // update changed line interval
363  if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
364  m_editingMinimalLineChanged = position.line();
365  }
366 
367  if (position.line() > m_editingMaximalLineChanged) {
368  m_editingMaximalLineChanged = position.line();
369  }
370 
371  // emit signal about done change
372  emit textInserted(position, text);
373  if (m_document)
374  emit m_document->KTextEditor::Document::textInserted(m_document, position, text);
375 }
376 
378 {
379  // debug output for REAL low-level debugging
380  BUFFER_DEBUG << "removeText" << range;
381 
382  // only allowed if editing transaction running
383  Q_ASSERT(m_editingTransactions > 0);
384 
385  // only ranges on one line are supported
386  Q_ASSERT(range.start().line() == range.end().line());
387 
388  // start column <= end column and >= 0
389  Q_ASSERT(range.start().column() <= range.end().column());
390  Q_ASSERT(range.start().column() >= 0);
391 
392  // skip work, if no text to remove
393  if (range.isEmpty()) {
394  return;
395  }
396 
397  // get block, this will assert on invalid line
398  int blockIndex = blockForLine(range.start().line());
399 
400  // let the block handle the removeText, retrieve removed text
401  QString text;
402  m_blocks.at(blockIndex)->removeText(range, text);
403 
404  // remember changes
405  ++m_revision;
406 
407  // update changed line interval
408  if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) {
409  m_editingMinimalLineChanged = range.start().line();
410  }
411 
412  if (range.start().line() > m_editingMaximalLineChanged) {
413  m_editingMaximalLineChanged = range.start().line();
414  }
415 
416  // emit signal about done change
417  emit textRemoved(range, text);
418  if (m_document)
419  emit m_document->KTextEditor::Document::textRemoved(m_document, range, text);
420 }
421 
422 int TextBuffer::blockForLine(int line) const
423 {
424  // only allow valid lines
425  if ((line < 0) || (line >= lines())) {
426  qFatal("out of range line requested in text buffer (%d out of [0, %d[)", line, lines());
427  }
428 
429  // we need blocks and last used block should not be negative
430  Q_ASSERT(!m_blocks.isEmpty());
431  Q_ASSERT(m_lastUsedBlock >= 0);
432 
436  if (m_lastUsedBlock < m_blocks.size()) {
441  TextBlock *block = m_blocks[m_lastUsedBlock];
442  const int start = block->startLine();
443  const int lines = block->lines();
444  if (start <= line && line < (start + lines)) {
445  return m_lastUsedBlock;
446  }
447  }
448 
454  int blockStart = 0;
455  int blockEnd = m_blocks.size() - 1;
456  while (blockEnd >= blockStart) {
457  // get middle and ensure it is OK
458  int middle = blockStart + ((blockEnd - blockStart) / 2);
459  Q_ASSERT(middle >= 0);
460  Q_ASSERT(middle < m_blocks.size());
461 
462  // facts bout this block
463  TextBlock *block = m_blocks[middle];
464  const int start = block->startLine();
465  const int lines = block->lines();
466 
467  // right block found, remember it and return it
468  if (start <= line && line < (start + lines)) {
469  m_lastUsedBlock = middle;
470  return middle;
471  }
472 
473  // half our stuff ;)
474  if (line < start) {
475  blockEnd = middle - 1;
476  } else {
477  blockStart = middle + 1;
478  }
479  }
480 
481  // we should always find a block
482  qFatal("line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
483  return -1;
484 }
485 
486 void TextBuffer::fixStartLines(int startBlock)
487 {
488  // only allow valid start block
489  Q_ASSERT(startBlock >= 0);
490  Q_ASSERT(startBlock < m_blocks.size());
491 
492  // new start line for next block
493  TextBlock *block = m_blocks.at(startBlock);
494  int newStartLine = block->startLine() + block->lines();
495 
496  // fixup block
497  for (int index = startBlock + 1; index < m_blocks.size(); ++index) {
498  // set new start line
499  block = m_blocks.at(index);
500  block->setStartLine(newStartLine);
501 
502  // calculate next start line
503  newStartLine += block->lines();
504  }
505 }
506 
507 void TextBuffer::balanceBlock(int index)
508 {
512  TextBlock *blockToBalance = m_blocks.at(index);
513 
514  // first case, too big one, split it
515  if (blockToBalance->lines() >= 2 * m_blockSize) {
516  // half the block
517  int halfSize = blockToBalance->lines() / 2;
518 
519  // create and insert new block behind current one, already set right start line
520  TextBlock *newBlock = blockToBalance->splitBlock(halfSize);
521  Q_ASSERT(newBlock);
522  m_blocks.insert(m_blocks.begin() + index + 1, newBlock);
523 
524  // split is done
525  return;
526  }
527 
528  // second case: possibly too small block
529 
530  // if only one block, no chance to unite
531  // same if this is first block, we always append to previous one
532  if (index == 0) {
533  return;
534  }
535 
536  // block still large enough, do nothing
537  if (2 * blockToBalance->lines() > m_blockSize) {
538  return;
539  }
540 
541  // unite small block with predecessor
542  TextBlock *targetBlock = m_blocks.at(index - 1);
543 
544  // merge block
545  blockToBalance->mergeBlock(targetBlock);
546 
547  // delete old block
548  delete blockToBalance;
549  m_blocks.erase(m_blocks.begin() + index);
550 }
551 
552 void TextBuffer::debugPrint(const QString &title) const
553 {
554  // print header with title
555  printf("%s (lines: %d bs: %d)\n", qPrintable(title), m_lines, m_blockSize);
556 
557  // print all blocks
558  for (int i = 0; i < m_blocks.size(); ++i) {
559  m_blocks.at(i)->debugPrint(i);
560  }
561 }
562 
563 bool TextBuffer::load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec)
564 {
565  // fallback codec must exist
566  Q_ASSERT(m_fallbackTextCodec);
567 
568  // codec must be set!
569  Q_ASSERT(m_textCodec);
570 
574  clear();
575 
579  Kate::TextLoader file(filename, m_encodingProberType);
580 
588  for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
592  for (int 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 
602  m_blocks.last()->clearLines();
603  m_lines = 0;
604 
611  QTextCodec *codec = m_textCodec;
612  if (i == 1) {
613  codec = nullptr;
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.last()->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, length = 0;
630  bool currentError = !file.readLine(offset, length);
631  encodingErrors = encodingErrors || currentError;
632 
633  // bail out on encoding error, if not last round!
634  if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
635  BUFFER_DEBUG << "Failed try to load file" << filename << "with codec" << (file.textCodec() ? file.textCodec()->name() : "(null)");
636  break;
637  }
638 
639  // get Unicode data for this line
640  const QChar *unicodeData = file.unicode() + offset;
641 
642  if (longestLineLoaded < length)
643  longestLineLoaded = length;
644 
648  do {
652  int lineLength = length;
653  if ((m_lineLengthLimit > 0) && (lineLength > m_lineLengthLimit)) {
657  int spacePosition = m_lineLengthLimit - 1;
658  for (int testPosition = m_lineLengthLimit - 1; (testPosition >= 0) && (testPosition >= (m_lineLengthLimit - (m_lineLengthLimit / 10))); --testPosition) {
662  if (unicodeData[testPosition].isSpace() || unicodeData[testPosition].isPunct()) {
663  spacePosition = testPosition;
664  break;
665  }
666  }
667 
671  lineLength = spacePosition + 1;
672  length -= lineLength;
673  tooLongLinesWrapped = true;
674  } else {
678  length = 0;
679  }
680 
685  QString textLine(unicodeData, lineLength);
686  unicodeData += lineLength;
687 
691  if (m_blocks.last()->lines() >= m_blockSize) {
692  m_blocks.append(new TextBlock(this, m_blocks.last()->startLine() + m_blocks.last()->lines()));
693  }
694 
698  m_blocks.last()->appendLine(textLine);
699  ++m_lines;
700  } while (length > 0);
701  }
702 
703  // if no encoding error, break out of reading loop
704  if (!encodingErrors) {
705  // remember used codec, might change bom setting
706  setTextCodec(file.textCodec());
707  break;
708  }
709  }
710 
711  // save checksum of file on disk
712  setDigest(file.digest());
713 
714  // remember if BOM was found
715  if (file.byteOrderMarkFound()) {
717  }
718 
719  // remember eol mode, if any found in file
720  if (file.eol() != eolUnknown) {
721  setEndOfLineMode(file.eol());
722  }
723 
724  // remember mime type for filter device
725  m_mimeTypeForFilterDev = file.mimeTypeForFilterDev();
726 
727  // assert that one line is there!
728  Q_ASSERT(m_lines > 0);
729 
730  // report CODEC + ERRORS
731  BUFFER_DEBUG << "Loaded file " << filename << "with codec" << m_textCodec->name() << (encodingErrors ? "with" : "without") << "encoding errors";
732 
733  // report BOM
734  BUFFER_DEBUG << (file.byteOrderMarkFound() ? "Found" : "Didn't find") << "byte order mark";
735 
736  // report filter device mime-type
737  BUFFER_DEBUG << "used filter device for mime-type" << m_mimeTypeForFilterDev;
738 
739  // emit success
740  emit loaded(filename, encodingErrors);
741 
742  // file loading worked, modulo encoding problems
743  return true;
744 }
745 
747 {
748  return m_digest;
749 }
750 
751 void TextBuffer::setDigest(const QByteArray &checksum)
752 {
753  m_digest = checksum;
754 }
755 
757 {
758  m_textCodec = codec;
759 
760  // enforce bom for some encodings
761  int mib = m_textCodec->mibEnum();
762  if (mib == 1013 || mib == 1014 || mib == 1015) { // utf16
764  }
765  if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32
767  }
768 }
769 
770 bool TextBuffer::save(const QString &filename)
771 {
775  Q_ASSERT(m_textCodec);
776 
777  SaveResult saveRes = saveBufferUnprivileged(filename);
778 
779  if (saveRes == SaveResult::Failed) {
780  return false;
781  } else if (saveRes == SaveResult::MissingPermissions) {
786  if (!saveBufferEscalated(filename)) {
787  return false;
788  }
789  }
790 
791  // remember this revision as last saved
792  m_history.setLastSavedRevision();
793 
794  // inform that we have saved the state
795  markModifiedLinesAsSaved();
796 
797  // emit that file was saved and be done
798  emit saved(filename);
799  return true;
800 }
801 
802 bool TextBuffer::saveBuffer(const QString &filename, KCompressionDevice &saveFile)
803 {
807  QTextStream stream(&saveFile);
808  stream.setCodec(QTextCodec::codecForName("UTF-16"));
809 
810  // set the correct codec
811  stream.setCodec(m_textCodec);
812 
813  // generate byte order mark?
815 
816  // our loved eol string ;)
817  QString eol = QStringLiteral("\n");
818  if (endOfLineMode() == eolDos) {
819  eol = QStringLiteral("\r\n");
820  } else if (endOfLineMode() == eolMac) {
821  eol = QStringLiteral("\r");
822  }
823 
824  // just dump the lines out ;)
825  for (int i = 0; i < m_lines; ++i) {
826  // dump current line
827  stream << line(i)->text();
828 
829  // append correct end of line string
830  if ((i + 1) < m_lines) {
831  stream << eol;
832  }
833 
834  // early out on stream errors
835  if (stream.status() != QTextStream::Ok) {
836  return false;
837  }
838  }
839 
840  // do we need to add a trailing newline char?
841  if (m_newLineAtEof) {
842  Q_ASSERT(m_lines > 0); // see .h file
843  const Kate::TextLine lastLine = line(m_lines - 1);
844  const int firstChar = lastLine->firstChar();
845  if (firstChar > -1 || lastLine->length() > 0) {
846  stream << eol;
847  }
848  }
849 
850  // flush stream
851  stream.flush();
852 
853  // only finalize if stream status == OK
854  if (stream.status() != QTextStream::Ok) {
855  return false;
856  }
857 
858  // close the file, we might want to read from underlying buffer below
859  saveFile.close();
860 
861  // did save work?
862  if (saveFile.error() != QFileDevice::NoError) {
863  BUFFER_DEBUG << "Saving file " << filename << "failed with error" << saveFile.errorString();
864  return false;
865  }
866 
867  return true;
868 }
869 
870 TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(const QString &filename)
871 {
872  if (m_alwaysUseKAuthForSave) {
873  // unit-testing mode, simulate we need privileges
874  return SaveResult::MissingPermissions;
875  }
876 
881  const KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(m_mimeTypeForFilterDev);
882  QScopedPointer<KCompressionDevice> saveFile(new KCompressionDevice(filename, type));
883 
884  if (!saveFile->open(QIODevice::WriteOnly)) {
885 #ifdef CAN_USE_ERRNO
886  if (errno != EACCES) {
887  return SaveResult::Failed;
888  }
889 #endif
890  return SaveResult::MissingPermissions;
891  }
892 
893  if (!saveBuffer(filename, *saveFile)) {
894  return SaveResult::Failed;
895  }
896 
897  return SaveResult::Success;
898 }
899 
900 bool TextBuffer::saveBufferEscalated(const QString &filename)
901 {
906  const KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(m_mimeTypeForFilterDev);
907  QScopedPointer<KCompressionDevice> saveFile(new KCompressionDevice(filename, type));
908  uint ownerId = -2;
909  uint groupId = -2;
910  QScopedPointer<QIODevice> temporaryBuffer;
911 
915  const QFileInfo fileInfo(filename);
916  if (fileInfo.exists()) {
917  ownerId = fileInfo.ownerId();
918  groupId = fileInfo.groupId();
919  }
920 
921  // if that fails we need more privileges to save this file
922  // -> we write to a temporary file and then send its path to KAuth action for privileged save
923  temporaryBuffer.reset(new QBuffer());
924 
925  // open buffer for write and read (read is used for checksum computing and writing to temporary file)
926  if (!temporaryBuffer->open(QIODevice::ReadWrite)) {
927  return false;
928  }
929 
930  // we are now saving to a temporary buffer with potential compression proxy
931  saveFile.reset(new KCompressionDevice(temporaryBuffer.data(), false, type));
932  if (!saveFile->open(QIODevice::WriteOnly)) {
933  return false;
934  }
935 
936  if (!saveBuffer(filename, *saveFile)) {
937  return false;
938  }
939 
940  // temporary buffer was used to save the file
941  // -> computing checksum
942  // -> saving to temporary file
943  // -> copying the temporary file to the original file location with KAuth action
944  QTemporaryFile tempFile;
945  if (!tempFile.open()) {
946  return false;
947  }
948 
949  // go to QBuffer start
950  temporaryBuffer->seek(0);
951 
952  // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile
953  char buffer[bufferLength];
954  qint64 read = -1;
955  QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm);
956  while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) {
957  cryptographicHash.addData(buffer, read);
958  if (tempFile.write(buffer, read) == -1) {
959  return false;
960  }
961  }
962  if (!tempFile.flush()) {
963  return false;
964  }
965 
966  // prepare data for KAuth action
967  QVariantMap kAuthActionArgs;
968  kAuthActionArgs.insert(QStringLiteral("sourceFile"), tempFile.fileName());
969  kAuthActionArgs.insert(QStringLiteral("targetFile"), filename);
970  kAuthActionArgs.insert(QStringLiteral("checksum"), cryptographicHash.result());
971  kAuthActionArgs.insert(QStringLiteral("ownerId"), ownerId);
972  kAuthActionArgs.insert(QStringLiteral("groupId"), groupId);
973 
974  // call save with elevated privileges
976  // unit testing purposes only
977  if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) {
978  return false;
979  }
980  } else {
981  KAuth::Action kAuthSaveAction(QStringLiteral("org.kde.ktexteditor.katetextbuffer.savefile"));
982  kAuthSaveAction.setHelperId(QStringLiteral("org.kde.ktexteditor.katetextbuffer"));
983  kAuthSaveAction.setArguments(kAuthActionArgs);
984  KAuth::ExecuteJob *job = kAuthSaveAction.execute();
985  if (!job->exec()) {
986  return false;
987  }
988  }
989 
990  return true;
991 }
992 
993 void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute)
994 {
998  if (!m_document) {
999  return;
1000  }
1001 
1006  const QList<KTextEditor::View *> views = m_document->views();
1007  for (KTextEditor::View *curView : views) {
1008  // filter wrong views
1009  if (view && view != curView) {
1010  continue;
1011  }
1012 
1013  // notify view, it is really a kate view
1014  static_cast<KTextEditor::ViewPrivate *>(curView)->notifyAboutRangeChange(startLine, endLine, rangeWithAttribute);
1015  }
1016 }
1017 
1018 void TextBuffer::markModifiedLinesAsSaved()
1019 {
1020  for (TextBlock *block : qAsConst(m_blocks)) {
1021  block->markModifiedLinesAsSaved();
1022  }
1023 }
1024 
1025 QList<TextRange *> TextBuffer::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
1026 {
1027  // get block, this will assert on invalid line
1028  const int blockIndex = blockForLine(line);
1029 
1030  // get the ranges of the right block
1031  QList<TextRange *> rightRanges;
1032  const auto blockRanges = m_blocks.at(blockIndex)->rangesForLine(line);
1033  for (const QSet<TextRange *> &ranges : blockRanges) {
1034  for (TextRange *const range : ranges) {
1038  if (rangesWithAttributeOnly && !range->hasAttribute()) {
1039  continue;
1040  }
1041 
1045  if (!view && range->attributeOnlyForViews()) {
1046  continue;
1047  }
1048 
1052  if (range->view() && range->view() != view) {
1053  continue;
1054  }
1055 
1059  if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
1060  rightRanges.append(range);
1061  }
1062  }
1063  }
1064 
1065  // return right ranges
1066  return rightRanges;
1067 }
1068 
1069 }
TextLine line(int line) const
Retrieve a text line.
void setCodec(QTextCodec *codec)
void setGenerateByteOrderMark(bool generateByteOrderMark)
Generate byte order mark on save.
bool editingChangedBuffer() const
Query information from the last editing transaction: was the content of the buffer changed...
EndOfLineMode endOfLineMode() const
Get end of line mode.
static bool unitTestMode()
Returns true, if the unit test mode was enabled through a call of enableUnitTestMode(), otherwise false.
Definition: kateglobal.cpp:75
void setGenerateByteOrderMark(bool generate)
void setDigest(const QByteArray &checksum)
Set the checksum of this buffer.
TextBuffer::EndOfLineMode eol() const
Detected end of line mode for this file.
virtual bool load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec)
Load the given file.
virtual QByteArray name() const const =0
bool flush()
QString errorString() const const
void setHelperId(const QString &id)
bool eof() const
end of file reached?
void debugPrint(const QString &title) const
Debug output, print whole buffer content with line numbers and line length.
TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize=64, bool alwaysUseKAuth=false)
Construct an empty text buffer.
int lines() const
Number of lines in this block.
Definition: katetextblock.h:96
void lineUnwrapped(int line)
A line got unwrapped.
const QByteArray & digest() const
Checksum of the document on disk, set either through file loading in openFile() or in KTextEditor::Do...
QTextCodec * textCodec() const
Get codec for this loader.
virtual bool finishEditing()
Finish an editing transaction.
bool exec()
QTextStream::Status status() const const
void lineWrapped(const KTextEditor::Cursor &position)
A line got wrapped.
void editingStarted()
Editing transaction has started.
void saved(const QString &filename)
Buffer saved successfully a file.
The Cursor represents a position in a Document.
Definition: cursor.h:85
int startLine() const
Start line of this block.
Definition: katetextblock.h:63
virtual void wrapLine(const KTextEditor::Cursor &position)
Wrap line at given cursor position.
void textInserted(const KTextEditor::Cursor &position, const QString &text)
Text got inserted.
static constexpr Cursor invalid() Q_DECL_NOEXCEPT
Returns an invalid cursor.
Definition: cursor.h:123
virtual bool startEditing()
Start an editing transaction, the wrapLine/unwrapLine/insertText and removeText functions are only al...
virtual void insertText(const KTextEditor::Cursor &position, const QString &text)
Insert text at given cursor position.
void reset(T *other)
void setArguments(const QVariantMap &arguments)
void editingFinished()
Editing transaction has finished.
static CompressionType compressionTypeForMimeType(const QString &mimetype)
QFileDevice::FileError error() const
virtual ~TextBuffer()
Destruct the text buffer Virtual, we allow inheritance.
bool byteOrderMarkFound() const
BOM found?
void append(const T &value)
void cleared()
Buffer got cleared.
void setTextCodec(QTextCodec *codec)
Set codec for this buffer to use for load/save.
bool open(QTextCodec *codec)
open file with given codec
void invalidateRanges()
Invalidate all ranges in this buffer.
void addData(const char *data, int length)
bool isPunct() const const
uint groupId() const const
An object representing a section of text, from one Cursor to another.
T * data() const const
void appendLine(const QString &textOfLine)
Append a new line with given text.
bool exists() const const
void textRemoved(const KTextEditor::Range &range, const QString &text)
Text got removed.
virtual void removeText(const KTextEditor::Range &range)
Remove text at given range.
virtual int mibEnum() const const =0
virtual QString fileName() const const override
virtual bool save(const QString &filename)
Save the current buffer content to the given file.
void close() override
virtual void unwrapLine(int line)
Unwrap given line.
void clearLines()
Clear the lines.
QList< TextRange * > rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
Return the ranges which affect the given line.
int lines() const
Lines currently stored in this buffer.
void setEndOfLineMode(EndOfLineMode endOfLineMode)
Set end of line mode for this buffer, not allowed to be set to unknown.
void mergeBlock(TextBlock *targetBlock)
Merge this block with given one, the given one must be a direct predecessor.
void flush()
bool readLine(int &offset, int &length)
read a line, return length + offset in Unicode data
ExecuteJob * execute(ExecutionMode mode=ExecuteMode)
const QString & mimeTypeForFilterDev() const
mime type used to create filter dev
QTextCodec * codecForName(const QByteArray &name)
uint ownerId() const const
bool generateByteOrderMark() const
Generate byte order mark on save?
constexpr int line() const Q_DECL_NOEXCEPT
Retrieve the line on which this cursor is situated.
Definition: cursor.h:199
QString text() const
Retrieve text of complete buffer.
QByteArray result() const const
qint64 write(const char *data, qint64 maxSize)
File Loader, will handle reading of files + detecting encoding.
A text widget with KXMLGUIClient that represents a Document.
Definition: view.h:155
const QChar * unicode() const
internal Unicode data array
Class representing a &#39;clever&#39; text range.
Definition: katetextrange.h:46
void loaded(const QString &filename, bool encodingErrors)
Buffer loaded successfully a file.
virtual void clear()
Clears the buffer, reverts to initial empty state.
Class representing a text block.
Definition: katetextblock.h:44
TextBlock * splitBlock(int fromLine)
Split given block.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Jul 9 2020 22:56:55 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.