1 | /* |
2 | SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | #ifndef KATE_TEXTBUFFER_H |
7 | #define KATE_TEXTBUFFER_H |
8 | |
9 | #include <QList> |
10 | #include <QObject> |
11 | #include <QSet> |
12 | #include <QString> |
13 | |
14 | #include "katetextblock.h" |
15 | #include "katetexthistory.h" |
16 | #include <ktexteditor_export.h> |
17 | |
18 | // encoding prober |
19 | #include <KEncodingProber> |
20 | |
21 | namespace KTextEditor |
22 | { |
23 | class DocumentPrivate; |
24 | } |
25 | |
26 | class KCompressionDevice; |
27 | |
28 | namespace Kate |
29 | { |
30 | class ; |
31 | class TextCursor; |
32 | class TextBlock; |
33 | |
34 | constexpr int BufferBlockSize = 64; |
35 | |
36 | /** |
37 | * Class representing a text buffer. |
38 | * The interface is line based, internally the text will be stored in blocks of text lines. |
39 | */ |
40 | class KTEXTEDITOR_EXPORT TextBuffer : public QObject |
41 | { |
42 | friend class TextCursor; |
43 | friend class TextRange; |
44 | friend class TextBlock; |
45 | |
46 | Q_OBJECT |
47 | |
48 | public: |
49 | /** |
50 | * End of line mode |
51 | */ |
52 | enum EndOfLineMode { eolUnknown = -1, eolUnix = 0, eolDos = 1, eolMac = 2 }; |
53 | |
54 | /** |
55 | * Construct an empty text buffer. |
56 | * Empty means one empty line in one block. |
57 | * @param parent parent qobject |
58 | */ |
59 | explicit TextBuffer(KTextEditor::DocumentPrivate *parent, bool alwaysUseKAuth = false); |
60 | |
61 | /** |
62 | * Destruct the text buffer |
63 | * Virtual, we allow inheritance |
64 | */ |
65 | ~TextBuffer() override; |
66 | |
67 | /** |
68 | * Clears the buffer, reverts to initial empty state. |
69 | * Empty means one empty line in one block. |
70 | * Virtual, can be overwritten. |
71 | */ |
72 | virtual void clear(); |
73 | |
74 | /** |
75 | * Set encoding prober type for this buffer to use for load. |
76 | * @param proberType prober type to use for encoding |
77 | */ |
78 | void setEncodingProberType(KEncodingProber::ProberType proberType) |
79 | { |
80 | m_encodingProberType = proberType; |
81 | } |
82 | |
83 | /** |
84 | * Get encoding prober type for this buffer |
85 | * @return currently in use prober type of this buffer |
86 | */ |
87 | KEncodingProber::ProberType encodingProberType() const |
88 | { |
89 | return m_encodingProberType; |
90 | } |
91 | |
92 | /** |
93 | * Set fallback codec for this buffer to use for load. |
94 | * @param codec fallback to use for encoding |
95 | */ |
96 | void setFallbackTextCodec(const QString &codec) |
97 | { |
98 | m_fallbackTextCodec = codec; |
99 | } |
100 | |
101 | /** |
102 | * Get fallback codec for this buffer |
103 | * @return currently in use fallback codec of this buffer |
104 | */ |
105 | QString fallbackTextCodec() const |
106 | { |
107 | return m_fallbackTextCodec; |
108 | } |
109 | |
110 | /** |
111 | * Set codec for this buffer to use for load/save. |
112 | * Loading might overwrite this, if it encounters problems and finds a better codec. |
113 | * Might change BOM setting. |
114 | * @param codec to use for encoding |
115 | */ |
116 | void setTextCodec(const QString &codec); |
117 | |
118 | /** |
119 | * Get codec for this buffer |
120 | * @return currently in use codec of this buffer |
121 | */ |
122 | QString textCodec() const |
123 | { |
124 | return m_textCodec; |
125 | } |
126 | |
127 | /** |
128 | * Generate byte order mark on save. |
129 | * Loading might overwrite this setting, if there is a BOM found inside the file. |
130 | * @param generateByteOrderMark should BOM be generated? |
131 | */ |
132 | void setGenerateByteOrderMark(bool generateByteOrderMark) |
133 | { |
134 | m_generateByteOrderMark = generateByteOrderMark; |
135 | } |
136 | |
137 | /** |
138 | * Generate byte order mark on save? |
139 | * @return should BOM be generated? |
140 | */ |
141 | bool generateByteOrderMark() const |
142 | { |
143 | return m_generateByteOrderMark; |
144 | } |
145 | |
146 | /** |
147 | * Set end of line mode for this buffer, not allowed to be set to unknown. |
148 | * Loading might overwrite this setting, if there is a eol found inside the file. |
149 | * @param endOfLineMode new eol mode |
150 | */ |
151 | void setEndOfLineMode(EndOfLineMode endOfLineMode) |
152 | { |
153 | Q_ASSERT(endOfLineMode != eolUnknown); |
154 | m_endOfLineMode = endOfLineMode; |
155 | } |
156 | |
157 | /** |
158 | * Get end of line mode |
159 | * @return end of line mode |
160 | */ |
161 | EndOfLineMode endOfLineMode() const |
162 | { |
163 | return m_endOfLineMode; |
164 | } |
165 | |
166 | /** |
167 | * Set line length limit |
168 | * @param lineLengthLimit new line length limit |
169 | */ |
170 | void setLineLengthLimit(int lineLengthLimit) |
171 | { |
172 | m_lineLengthLimit = lineLengthLimit; |
173 | } |
174 | |
175 | /** |
176 | * Load the given file. This will first clear the buffer and then load the file. |
177 | * Even on error during loading the buffer will still be cleared. |
178 | * Before calling this, setTextCodec must have been used to set codec! |
179 | * @param filename file to open |
180 | * @param encodingErrors were there problems occurred while decoding the file? |
181 | * @param tooLongLinesWrapped were too long lines found and wrapped? |
182 | * @param longestLineLoaded the longest line in the file (before wrapping) |
183 | * @param enforceTextCodec enforce to use only the set text codec |
184 | * @return success, the file got loaded, perhaps with encoding errors |
185 | * Virtual, can be overwritten. |
186 | */ |
187 | virtual bool load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec); |
188 | |
189 | /** |
190 | * Save the current buffer content to the given file. |
191 | * Before calling this, setTextCodec and setFallbackTextCodec must have been used to set codec! |
192 | * @param filename file to save |
193 | * @return success |
194 | * Virtual, can be overwritten. |
195 | */ |
196 | virtual bool save(const QString &filename); |
197 | |
198 | /** |
199 | * Lines currently stored in this buffer. |
200 | * This is never 0, even clear will let one empty line remain. |
201 | */ |
202 | int lines() const |
203 | { |
204 | Q_ASSERT(m_lines > 0); |
205 | return m_lines; |
206 | } |
207 | |
208 | /** |
209 | * Revision of this buffer. Is set to 0 on construction, clear() (load will trigger clear()). |
210 | * Is incremented on each change to the buffer. |
211 | * @return current revision |
212 | */ |
213 | qint64 revision() const |
214 | { |
215 | return m_revision; |
216 | } |
217 | |
218 | /** |
219 | * Retrieve a text line. |
220 | * @param line wanted line number |
221 | * @return text line |
222 | */ |
223 | TextLine line(int line) const; |
224 | |
225 | /** |
226 | * Transfer all non text attributes for the given line from the given text line to the one in the buffer. |
227 | * @param line line number to set attributes |
228 | * @param textLine line reference to get attributes from |
229 | */ |
230 | void setLineMetaData(int line, const TextLine &textLine); |
231 | |
232 | /** |
233 | * Retrieve length for @p line |
234 | * @param line wanted line number |
235 | * @return length of the line |
236 | */ |
237 | int lineLength(int line) const |
238 | { |
239 | // get block, this will assert on invalid line |
240 | int blockIndex = blockForLine(line); |
241 | |
242 | // get line length |
243 | return m_blocks.at(n: blockIndex)->lineLength(line); |
244 | } |
245 | |
246 | /** |
247 | * Retrieve offset in text for the given cursor position |
248 | */ |
249 | int cursorToOffset(KTextEditor::Cursor c) const; |
250 | |
251 | /** |
252 | * Retrieve cursor in text for the given offset |
253 | */ |
254 | KTextEditor::Cursor offsetToCursor(int offset) const; |
255 | |
256 | /** |
257 | * Retrieve text of complete buffer. |
258 | * @return text for this buffer, lines separated by '\n' |
259 | */ |
260 | QString text() const; |
261 | |
262 | /** |
263 | * Start an editing transaction, the wrapLine/unwrapLine/insertText and removeText functions |
264 | * are only allowed to be called inside a editing transaction. |
265 | * Editing transactions can stack. The number of startEdit and endEdit calls must match. |
266 | * @return returns true, if no transaction was already running |
267 | * Virtual, can be overwritten. |
268 | */ |
269 | virtual bool startEditing(); |
270 | |
271 | /** |
272 | * Finish an editing transaction. Only allowed to be called if editing transaction is started. |
273 | * @return returns true, if this finished last running transaction |
274 | * Virtual, can be overwritten. |
275 | */ |
276 | virtual bool finishEditing(); |
277 | |
278 | /** |
279 | * Query the number of editing transactions running atm. |
280 | * @return number of running transactions |
281 | */ |
282 | int editingTransactions() const |
283 | { |
284 | return m_editingTransactions; |
285 | } |
286 | |
287 | /** |
288 | * Query the revision of this buffer before the ongoing editing transactions. |
289 | * @return revision of buffer before current editing transaction altered it |
290 | */ |
291 | qint64 editingLastRevision() const |
292 | { |
293 | return m_editingLastRevision; |
294 | } |
295 | |
296 | /** |
297 | * Query the number of lines of this buffer before the ongoing editing transactions. |
298 | * @return number of lines of buffer before current editing transaction altered it |
299 | */ |
300 | int editingLastLines() const |
301 | { |
302 | return m_editingLastLines; |
303 | } |
304 | |
305 | /** |
306 | * Query information from the last editing transaction: was the content of the buffer changed? |
307 | * This is checked by comparing the editingLastRevision() with the current revision(). |
308 | * @return content of buffer was changed in last transaction? |
309 | */ |
310 | bool editingChangedBuffer() const |
311 | { |
312 | return editingLastRevision() != revision(); |
313 | } |
314 | |
315 | /** |
316 | * Query information from the last editing transaction: was the number of lines of the buffer changed? |
317 | * This is checked by comparing the editingLastLines() with the current lines(). |
318 | * @return content of buffer was changed in last transaction? |
319 | */ |
320 | bool editingChangedNumberOfLines() const |
321 | { |
322 | return editingLastLines() != lines(); |
323 | } |
324 | |
325 | /** |
326 | * Get minimal line number changed by last editing transaction |
327 | * @return maximal line number changed by last editing transaction, or -1, if none changed |
328 | */ |
329 | int editingMinimalLineChanged() const |
330 | { |
331 | return m_editingMinimalLineChanged; |
332 | } |
333 | |
334 | /** |
335 | * Get maximal line number changed by last editing transaction |
336 | * @return maximal line number changed by last editing transaction, or -1, if none changed |
337 | */ |
338 | int editingMaximalLineChanged() const |
339 | { |
340 | return m_editingMaximalLineChanged; |
341 | } |
342 | |
343 | /** |
344 | * Wrap line at given cursor position. |
345 | * @param position line/column as cursor where to wrap |
346 | * Virtual, can be overwritten. |
347 | */ |
348 | virtual void wrapLine(const KTextEditor::Cursor position); |
349 | |
350 | /** |
351 | * Unwrap given line. |
352 | * @param line line to unwrap |
353 | * Virtual, can be overwritten. |
354 | */ |
355 | virtual void unwrapLine(int line); |
356 | |
357 | /** |
358 | * Insert text at given cursor position. Does nothing if text is empty, beside some consistency checks. |
359 | * @param position position where to insert text |
360 | * @param text text to insert |
361 | * Virtual, can be overwritten. |
362 | */ |
363 | virtual void insertText(const KTextEditor::Cursor position, const QString &text); |
364 | |
365 | /** |
366 | * Remove text at given range. Does nothing if range is empty, beside some consistency checks. |
367 | * @param range range of text to remove, must be on one line only. |
368 | * Virtual, can be overwritten. |
369 | */ |
370 | virtual void removeText(KTextEditor::Range range); |
371 | |
372 | /** |
373 | * TextHistory of this buffer |
374 | * @return text history for this buffer |
375 | */ |
376 | TextHistory &history() |
377 | { |
378 | return m_history; |
379 | } |
380 | |
381 | Q_SIGNALS: |
382 | /** |
383 | * Buffer got cleared. This is emitted when constructor or load have called clear() internally, |
384 | * or when the user of the buffer has called clear() itself. |
385 | */ |
386 | void cleared(); |
387 | |
388 | /** |
389 | * Buffer loaded successfully a file |
390 | * @param filename file which was loaded |
391 | * @param encodingErrors were there problems occurred while decoding the file? |
392 | */ |
393 | void loaded(const QString &filename, bool encodingErrors); |
394 | |
395 | /** |
396 | * Buffer saved successfully a file |
397 | * @param filename file which was saved |
398 | */ |
399 | void saved(const QString &filename); |
400 | |
401 | private: |
402 | /** |
403 | * Save result which indicates an abstract reason why the operation has |
404 | * failed |
405 | */ |
406 | enum class SaveResult { Failed = 0, MissingPermissions, Success }; |
407 | |
408 | /** |
409 | * Find block containing given line. |
410 | * @param line we want to find block for this line |
411 | * @return index of found block |
412 | */ |
413 | int blockForLine(int line) const; |
414 | // exported for movingrange_test |
415 | |
416 | /** |
417 | * Fix start lines of all blocks after the given one |
418 | * @param startBlock index of block from which we start to fix |
419 | */ |
420 | KTEXTEDITOR_NO_EXPORT |
421 | void fixStartLines(int startBlock); |
422 | |
423 | /** |
424 | * Balance the given block. Look if it is too small or too large. |
425 | * @param index block to balance |
426 | */ |
427 | KTEXTEDITOR_NO_EXPORT |
428 | void balanceBlock(int index); |
429 | |
430 | /** |
431 | * A range changed, notify the views, in case of attributes or feedback. |
432 | * @param view which view is affected? nullptr for all views |
433 | * @param lineRange line range that the change spans |
434 | * @param needsRepaint do we need to trigger repaints? e.g. if ranges with attributes change |
435 | */ |
436 | KTEXTEDITOR_NO_EXPORT |
437 | void notifyAboutRangeChange(KTextEditor::View *view, KTextEditor::LineRange lineRange, bool needsRepaint); |
438 | |
439 | /** |
440 | * Mark all modified lines as lines saved on disk (modified line system). |
441 | */ |
442 | KTEXTEDITOR_NO_EXPORT |
443 | void markModifiedLinesAsSaved(); |
444 | |
445 | /** |
446 | * Save the current buffer content to the given already opened device |
447 | * |
448 | * @param filename path name for display/debugging purposes |
449 | * @param saveFile open device to write the buffer to |
450 | */ |
451 | KTEXTEDITOR_NO_EXPORT |
452 | bool saveBuffer(const QString &filename, KCompressionDevice &saveFile); |
453 | |
454 | /** |
455 | * Attempt to save the buffer content in the given filename location using |
456 | * current privileges. |
457 | */ |
458 | KTEXTEDITOR_NO_EXPORT |
459 | SaveResult saveBufferUnprivileged(const QString &filename); |
460 | |
461 | /** |
462 | * Attempt to save the buffer content in the given filename location using |
463 | * escalated privileges. |
464 | */ |
465 | KTEXTEDITOR_NO_EXPORT |
466 | bool saveBufferEscalated(const QString &filename); |
467 | |
468 | public: |
469 | /** |
470 | * Gets the document to which this buffer is bound. |
471 | * \return a pointer to the document |
472 | */ |
473 | KTextEditor::DocumentPrivate *document() const |
474 | { |
475 | return m_document; |
476 | } |
477 | |
478 | /** |
479 | * Debug output, print whole buffer content with line numbers and line length |
480 | * @param title title for this output |
481 | */ |
482 | void debugPrint(const QString &title) const; |
483 | |
484 | /** |
485 | * Return the ranges which affect the given line. |
486 | * @param line line to look at |
487 | * @param view only return ranges associated with given view |
488 | * @param rangesWithAttributeOnly only return ranges which have a attribute set |
489 | * @return list of ranges affecting this line |
490 | */ |
491 | QList<TextRange *> rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const |
492 | { |
493 | // get block, this will assert on invalid line |
494 | const int blockIndex = blockForLine(line); |
495 | return m_blocks.at(n: blockIndex)->rangesForLine(line, view, rangesWithAttributeOnly); |
496 | } |
497 | |
498 | void (int line, KTextEditor::View *view, bool rangesWithAttributeOnly, QList<TextRange *> &outRanges) const |
499 | { |
500 | // get block, this will assert on invalid line |
501 | const int blockIndex = blockForLine(line); |
502 | return m_blocks.at(n: blockIndex)->rangesForLine(line, view, rangesWithAttributeOnly, outRanges); |
503 | } |
504 | |
505 | /** |
506 | * Check if the given range pointer is still valid. |
507 | * @return range pointer still belongs to range for this buffer |
508 | */ |
509 | bool (TextRange *range) const |
510 | { |
511 | return m_ranges.contains(value: range); |
512 | } |
513 | |
514 | /** |
515 | * Invalidate all ranges in this buffer. |
516 | */ |
517 | void invalidateRanges(); |
518 | |
519 | // |
520 | // checksum handling |
521 | // |
522 | public: |
523 | /** |
524 | * Checksum of the document on disk, set either through file loading |
525 | * in openFile() or in KTextEditor::DocumentPrivate::saveFile() |
526 | * @return git compatible sha1 checksum for this document |
527 | */ |
528 | const QByteArray &digest() const; |
529 | |
530 | /** |
531 | * Set the checksum of this buffer. Make sure this checksum is up-to-date |
532 | * when reading digest(). |
533 | * @param checksum git compatible sha1 digest for the document on disk |
534 | */ |
535 | void setDigest(const QByteArray &checksum); |
536 | |
537 | private: |
538 | QByteArray m_digest; |
539 | |
540 | private: |
541 | /** |
542 | * parent document |
543 | */ |
544 | KTextEditor::DocumentPrivate *m_document; |
545 | |
546 | /** |
547 | * text history |
548 | */ |
549 | TextHistory m_history; |
550 | |
551 | /** |
552 | * List of blocks which contain the lines of this buffer |
553 | */ |
554 | std::vector<TextBlock *> m_blocks; |
555 | |
556 | /** |
557 | * Number of lines in buffer |
558 | */ |
559 | int m_lines; |
560 | |
561 | /** |
562 | * Revision of the buffer. |
563 | */ |
564 | qint64 m_revision; |
565 | |
566 | /** |
567 | * Current number of running edit transactions |
568 | */ |
569 | int m_editingTransactions; |
570 | |
571 | /** |
572 | * Revision remembered at start of current editing transaction |
573 | */ |
574 | qint64 m_editingLastRevision; |
575 | |
576 | /** |
577 | * Number of lines remembered at start of current editing transaction |
578 | */ |
579 | int m_editingLastLines; |
580 | |
581 | /** |
582 | * minimal line number changed by last editing transaction |
583 | */ |
584 | int m_editingMinimalLineChanged; |
585 | |
586 | /** |
587 | * maximal line number changed by last editing transaction |
588 | */ |
589 | int m_editingMaximalLineChanged; |
590 | |
591 | /** |
592 | * Set of invalid cursors for this whole buffer. |
593 | * Valid cursors are inside the block the belong to. |
594 | */ |
595 | QSet<TextCursor *> m_invalidCursors; |
596 | |
597 | /** |
598 | * Set of ranges of this whole buffer. |
599 | */ |
600 | QSet<TextRange *> m_ranges; |
601 | |
602 | /** |
603 | * Encoding prober type to use |
604 | */ |
605 | KEncodingProber::ProberType m_encodingProberType; |
606 | |
607 | /** |
608 | * Fallback text codec to use |
609 | */ |
610 | QString m_fallbackTextCodec; |
611 | |
612 | /** |
613 | * Text codec to use |
614 | */ |
615 | QString m_textCodec; |
616 | |
617 | /** |
618 | * Mime-Type used for transparent compression/decompression support |
619 | * Set by load(), reset by clear() |
620 | */ |
621 | QString m_mimeTypeForFilterDev; |
622 | |
623 | /** |
624 | * Should byte order mark be created? |
625 | */ |
626 | bool m_generateByteOrderMark; |
627 | |
628 | /** |
629 | * End of line mode, default is Unix |
630 | */ |
631 | EndOfLineMode m_endOfLineMode; |
632 | |
633 | /** |
634 | * Limit for line length, longer lines will be wrapped on load |
635 | */ |
636 | int m_lineLengthLimit; |
637 | |
638 | /** |
639 | * For unit-testing purposes only. |
640 | */ |
641 | bool m_alwaysUseKAuthForSave; |
642 | |
643 | /** |
644 | * For copying QBuffer -> QTemporaryFile while saving document in privileged mode |
645 | */ |
646 | static const qint64 bufferLength = 4096; |
647 | }; |
648 | |
649 | } |
650 | |
651 | #endif |
652 | |