1/* This file is part of the Kate project.
2 *
3 * Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
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
23#include "katetextbuffer.h"
24#include "katetextloader.h"
25
26// this is unfortunate, but needed for performance
27#include "katedocument.h"
28#include "kateview.h"
29
30// fdatasync
31#include <kde_file.h>
32
33#include <KSaveFile>
34#include <kdeversion.h>
35
36#if 0
37#define EDIT_DEBUG kDebug()
38#else
39#define EDIT_DEBUG if (0) kDebug()
40#endif
41
42namespace Kate {
43
44TextBuffer::TextBuffer (KateDocument *parent, int blockSize)
45 : QObject (parent)
46 , m_document (parent)
47 , m_history (*this)
48 , m_blockSize (blockSize)
49 , m_lines (0)
50 , m_lastUsedBlock (0)
51 , m_revision (0)
52 , m_editingTransactions (0)
53 , m_editingLastRevision (0)
54 , m_editingLastLines (0)
55 , m_editingMinimalLineChanged (-1)
56 , m_editingMaximalLineChanged (-1)
57 , m_encodingProberType (KEncodingProber::Universal)
58 , m_fallbackTextCodec (0)
59 , m_textCodec (0)
60 , m_generateByteOrderMark (false)
61 , m_endOfLineMode (eolUnix)
62 , m_newLineAtEof (false)
63 , m_lineLengthLimit (4096)
64{
65 // minimal block size must be > 0
66 Q_ASSERT (m_blockSize > 0);
67
68 // create initial state
69 clear ();
70}
71
72TextBuffer::~TextBuffer ()
73{
74 // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect
75 m_document = 0;
76
77 // not allowed during editing
78 Q_ASSERT (m_editingTransactions == 0);
79
80 // kill all ranges, work on copy, they will remove themself from the hash
81 QSet<TextRange *> copyRanges = m_ranges;
82 qDeleteAll (copyRanges);
83 Q_ASSERT (m_ranges.empty());
84
85 // clean out all cursors and lines, only cursors belonging to range will survive
86 foreach(TextBlock* block, m_blocks)
87 block->deleteBlockContent ();
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
100void TextBuffer::invalidateRanges()
101{
102 // invalidate all ranges, work on copy, they might delete themself...
103 QSet<TextRange *> copyRanges = m_ranges;
104 foreach (TextRange *range, copyRanges)
105 range->setRange (KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid());
106}
107
108void TextBuffer::clear ()
109{
110 // not allowed during editing
111 Q_ASSERT (m_editingTransactions == 0);
112
113 invalidateRanges();
114
115 // new block for empty buffer
116 TextBlock *newBlock = new TextBlock (this, 0);
117 newBlock->appendLine (QString());
118
119 // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range
120 foreach(TextBlock* block, m_blocks)
121 block->clearBlockContent (newBlock);
122
123 // kill all buffer blocks
124 qDeleteAll (m_blocks);
125 m_blocks.clear ();
126
127 // insert one block with one empty line
128 m_blocks.append (newBlock);
129
130 // reset lines and last used block
131 m_lines = 1;
132 m_lastUsedBlock = 0;
133
134 // reset revision
135 m_revision = 0;
136
137 // reset bom detection
138 m_generateByteOrderMark = false;
139
140 // reset the filter device
141 m_mimeTypeForFilterDev = "text/plain";
142
143 // clear edit history
144 m_history.clear ();
145
146 // we got cleared
147 emit cleared ();
148}
149
150TextLine TextBuffer::line (int line) const
151{
152 // get block, this will assert on invalid line
153 int blockIndex = blockForLine (line);
154
155 // get line
156 return m_blocks.at(blockIndex)->line (line);
157}
158
159QString TextBuffer::text () const
160{
161 QString text;
162
163 // combine all blocks
164 foreach(TextBlock* block, m_blocks)
165 block->text (text);
166
167 // return generated string
168 return text;
169}
170
171bool TextBuffer::startEditing ()
172{
173 // increment transaction counter
174 ++m_editingTransactions;
175
176 // if not first running transaction, do nothing
177 if (m_editingTransactions > 1)
178 return false;
179
180 // reset information about edit...
181 m_editingLastRevision = m_revision;
182 m_editingLastLines = m_lines;
183 m_editingMinimalLineChanged = -1;
184 m_editingMaximalLineChanged = -1;
185
186 // transaction has started
187 emit editingStarted ();
188
189 // first transaction started
190 return true;
191}
192
193bool TextBuffer::finishEditing ()
194{
195 // only allowed if still transactions running
196 Q_ASSERT (m_editingTransactions > 0);
197
198 // decrement counter
199 --m_editingTransactions;
200
201 // if not last running transaction, do nothing
202 if (m_editingTransactions > 0)
203 return false;
204
205 // assert that if buffer changed, the line ranges are set and valid!
206 Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
207 Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
208 Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
209 Q_ASSERT (!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
210
211 // transaction has finished
212 emit editingFinished ();
213
214 // last transaction finished
215 return true;
216}
217
218void TextBuffer::wrapLine (const KTextEditor::Cursor &position)
219{
220 // debug output for REAL low-level debugging
221 EDIT_DEBUG << "wrapLine" << position;
222
223 // only allowed if editing transaction running
224 Q_ASSERT (m_editingTransactions > 0);
225
226 // get block, this will assert on invalid line
227 int blockIndex = blockForLine (position.line());
228
229 /**
230 * let the block handle the wrapLine
231 * this can only lead to one more line in this block
232 * no other blocks will change
233 * this call will trigger fixStartLines
234 */
235 ++m_lines; // first alter the line counter, as functions called will need the valid one
236 m_blocks.at(blockIndex)->wrapLine (position, blockIndex);
237
238 // remember changes
239 ++m_revision;
240
241 // update changed line interval
242 if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
243 m_editingMinimalLineChanged = position.line();
244
245 if (position.line() <= m_editingMaximalLineChanged)
246 ++m_editingMaximalLineChanged;
247 else
248 m_editingMaximalLineChanged = position.line() + 1;
249
250 // balance the changed block if needed
251 balanceBlock (blockIndex);
252
253 // emit signal about done change
254 emit lineWrapped (position);
255}
256
257void TextBuffer::unwrapLine (int line)
258{
259 // debug output for REAL low-level debugging
260 EDIT_DEBUG << "unwrapLine" << line;
261
262 // only allowed if editing transaction running
263 Q_ASSERT (m_editingTransactions > 0);
264
265 // line 0 can't be unwrapped
266 Q_ASSERT (line > 0);
267
268 // get block, this will assert on invalid line
269 int blockIndex = blockForLine (line);
270
271 // is this the first line in the block?
272 bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine());
273
274 /**
275 * let the block handle the unwrapLine
276 * this can either lead to one line less in this block or the previous one
277 * the previous one could even end up with zero lines
278 * this call will trigger fixStartLines
279 */
280 m_blocks.at(blockIndex)->unwrapLine (line, (blockIndex > 0) ? m_blocks.at(blockIndex-1) : 0, firstLineInBlock ? (blockIndex - 1) : blockIndex);
281 --m_lines;
282
283 // decrement index for later fixup, if we modified the block in front of the found one
284 if (firstLineInBlock)
285 --blockIndex;
286
287 // remember changes
288 ++m_revision;
289
290 // update changed line interval
291 if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
292 m_editingMinimalLineChanged = line - 1;
293
294 if (line <= m_editingMaximalLineChanged)
295 --m_editingMaximalLineChanged;
296 else
297 m_editingMaximalLineChanged = line -1;
298
299 // balance the changed block if needed
300 balanceBlock (blockIndex);
301
302 // emit signal about done change
303 emit lineUnwrapped (line);
304}
305
306void TextBuffer::insertText (const KTextEditor::Cursor &position, const QString &text)
307{
308 // debug output for REAL low-level debugging
309 EDIT_DEBUG << "insertText" << position << text;
310
311 // only allowed if editing transaction running
312 Q_ASSERT (m_editingTransactions > 0);
313
314 // skip work, if no text to insert
315 if (text.isEmpty())
316 return;
317
318 // get block, this will assert on invalid line
319 int blockIndex = blockForLine (position.line());
320
321 // let the block handle the insertText
322 m_blocks.at(blockIndex)->insertText (position, text);
323
324 // remember changes
325 ++m_revision;
326
327 // update changed line interval
328 if (position.line () < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
329 m_editingMinimalLineChanged = position.line ();
330
331 if (position.line () > m_editingMaximalLineChanged)
332 m_editingMaximalLineChanged = position.line ();
333
334 // emit signal about done change
335 emit textInserted (position, text);
336}
337
338void TextBuffer::removeText (const KTextEditor::Range &range)
339{
340 // debug output for REAL low-level debugging
341 EDIT_DEBUG << "removeText" << range;
342
343 // only allowed if editing transaction running
344 Q_ASSERT (m_editingTransactions > 0);
345
346 // only ranges on one line are supported
347 Q_ASSERT (range.start().line() == range.end().line());
348
349 // start column <= end column and >= 0
350 Q_ASSERT (range.start().column() <= range.end().column());
351 Q_ASSERT (range.start().column() >= 0);
352
353 // skip work, if no text to remove
354 if (range.isEmpty())
355 return;
356
357 // get block, this will assert on invalid line
358 int blockIndex = blockForLine (range.start().line());
359
360 // let the block handle the removeText, retrieve removed text
361 QString text;
362 m_blocks.at(blockIndex)->removeText (range, text);
363
364 // remember changes
365 ++m_revision;
366
367 // update changed line interval
368 if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
369 m_editingMinimalLineChanged = range.start().line();
370
371 if (range.start().line() > m_editingMaximalLineChanged)
372 m_editingMaximalLineChanged = range.start().line();
373
374 // emit signal about done change
375 emit textRemoved (range, text);
376}
377
378int TextBuffer::blockForLine (int line) const
379{
380 // only allow valid lines
381 if ((line < 0) || (line >= lines()))
382 qFatal ("out of range line requested in text buffer (%d out of [0, %d[)", line, lines());
383
384 // we need blocks and last used block should not be negative
385 Q_ASSERT (!m_blocks.isEmpty());
386 Q_ASSERT (m_lastUsedBlock >= 0);
387
388 /**
389 * shortcut: try last block first
390 */
391 if (m_lastUsedBlock < m_blocks.size()) {
392 /**
393 * check if block matches
394 * if yes, just return again this block
395 */
396 TextBlock* block = m_blocks[m_lastUsedBlock];
397 const int start = block->startLine();
398 const int lines = block->lines ();
399 if (start <= line && line < (start + lines))
400 return m_lastUsedBlock;
401 }
402
403 /**
404 * search for right block
405 * use binary search
406 * if we leave this loop not by returning the found element we have an error
407 */
408 int blockStart = 0;
409 int blockEnd = m_blocks.size() - 1;
410 while (blockEnd >= blockStart) {
411 // get middle and ensure it is OK
412 int middle = blockStart + ((blockEnd - blockStart) / 2);
413 Q_ASSERT (middle >= 0);
414 Q_ASSERT (middle < m_blocks.size());
415
416 // facts bout this block
417 TextBlock* block = m_blocks[middle];
418 const int start = block->startLine();
419 const int lines = block->lines ();
420
421 // right block found, remember it and return it
422 if (start <= line && line < (start + lines)) {
423 m_lastUsedBlock = middle;
424 return middle;
425 }
426
427 // half our stuff ;)
428 if (line < start)
429 blockEnd = middle - 1;
430 else
431 blockStart = middle + 1;
432 }
433
434 // we should always find a block
435 qFatal ("line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
436 return -1;
437}
438
439void TextBuffer::fixStartLines (int startBlock)
440{
441 // only allow valid start block
442 Q_ASSERT (startBlock >= 0);
443 Q_ASSERT (startBlock < m_blocks.size());
444
445 // new start line for next block
446 TextBlock* block = m_blocks.at(startBlock);
447 int newStartLine = block->startLine () + block->lines ();
448
449 // fixup block
450 for (int index = startBlock + 1; index < m_blocks.size(); ++index) {
451 // set new start line
452 block = m_blocks.at(index);
453 block->setStartLine (newStartLine);
454
455 // calculate next start line
456 newStartLine += block->lines ();
457 }
458}
459
460void TextBuffer::balanceBlock (int index)
461{
462 /**
463 * two cases, too big or too small block
464 */
465 TextBlock *blockToBalance = m_blocks.at(index);
466
467 // first case, too big one, split it
468 if (blockToBalance->lines () >= 2 * m_blockSize) {
469 // half the block
470 int halfSize = blockToBalance->lines () / 2;
471
472 // create and insert new block behind current one, already set right start line
473 TextBlock *newBlock = blockToBalance->splitBlock (halfSize);
474 Q_ASSERT (newBlock);
475 m_blocks.insert (m_blocks.begin() + index + 1, newBlock);
476
477 // split is done
478 return;
479 }
480
481 // second case: possibly too small block
482
483 // if only one block, no chance to unite
484 // same if this is first block, we always append to previous one
485 if (index == 0)
486 return;
487
488 // block still large enough, do nothing
489 if (2 * blockToBalance->lines () > m_blockSize)
490 return;
491
492 // unite small block with predecessor
493 TextBlock *targetBlock = m_blocks.at(index-1);
494
495 // merge block
496 blockToBalance->mergeBlock (targetBlock);
497
498 // delete old block
499 delete blockToBalance;
500 m_blocks.erase (m_blocks.begin() + index);
501}
502
503void TextBuffer::debugPrint (const QString &title) const
504{
505 // print header with title
506 printf ("%s (lines: %d bs: %d)\n", qPrintable (title), m_lines, m_blockSize);
507
508 // print all blocks
509 for (int i = 0; i < m_blocks.size(); ++i)
510 m_blocks.at(i)->debugPrint (i);
511}
512
513bool TextBuffer::load (const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, bool enforceTextCodec)
514{
515 // fallback codec must exist
516 Q_ASSERT (m_fallbackTextCodec);
517
518 // codec must be set!
519 Q_ASSERT (m_textCodec);
520
521 /**
522 * first: clear buffer in any case!
523 */
524 clear ();
525
526 /**
527 * construct the file loader for the given file, with correct prober type
528 */
529 Kate::TextLoader file (filename, m_encodingProberType);
530
531 /**
532 * triple play, maximal three loading rounds
533 * 0) use the given encoding, be done, if no encoding errors happen
534 * 1) use BOM to decided if unicode or if that fails, use encoding prober, if no encoding errors happen, be done
535 * 2) use fallback encoding, be done, if no encoding errors happen
536 * 3) use again given encoding, be done in any case
537 */
538 for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
539 /**
540 * kill all blocks beside first one
541 */
542 for (int b = 1; b < m_blocks.size(); ++b) {
543 TextBlock* block = m_blocks.at(b);
544 block->clearLines ();
545 delete block;
546 }
547 m_blocks.resize (1);
548
549 /**
550 * remove lines in first block
551 */
552 m_blocks.last()->clearLines ();
553 m_lines = 0;
554
555 /**
556 * try to open file, with given encoding
557 * in round 0 + 3 use the given encoding from user
558 * in round 1 use 0, to trigger detection
559 * in round 2 use fallback
560 */
561 QTextCodec *codec = m_textCodec;
562 if (i == 1)
563 codec = 0;
564 else if (i == 2)
565 codec = m_fallbackTextCodec;
566
567 if (!file.open (codec)) {
568 // create one dummy textline, in any case
569 m_blocks.last()->appendLine (QString());
570 m_lines++;
571 return false;
572 }
573
574 // read in all lines...
575 encodingErrors = false;
576 while ( !file.eof() )
577 {
578 // read line
579 int offset = 0, length = 0;
580 bool currentError = !file.readLine (offset, length);
581 encodingErrors = encodingErrors || currentError;
582
583 // bail out on encoding error, if not last round!
584 if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
585 kDebug (13020) << "Failed try to load file" << filename << "with codec" <<
586 (file.textCodec() ? file.textCodec()->name() : "(null)");
587 break;
588 }
589
590 // get unicode data for this line
591 const QChar *unicodeData = file.unicode () + offset;
592
593 /**
594 * split lines, if too large
595 */
596 do {
597 /**
598 * calculate line length
599 */
600 int lineLength = length;
601 if ((m_lineLengthLimit > 0) && (lineLength > m_lineLengthLimit)) {
602 /**
603 * search for place to wrap
604 */
605 int spacePosition = m_lineLengthLimit-1;
606 for (int testPosition = m_lineLengthLimit-1; (testPosition >= 0) && (testPosition >= (m_lineLengthLimit - (m_lineLengthLimit/10))); --testPosition) {
607 /**
608 * wrap place found?
609 */
610 if (unicodeData[testPosition].isSpace() || unicodeData[testPosition].isPunct()) {
611 spacePosition = testPosition;
612 break;
613 }
614 }
615
616 /**
617 * wrap the line
618 */
619 lineLength = spacePosition+1;
620 length -= lineLength;
621 tooLongLinesWrapped = true;
622 } else {
623 /**
624 * be done after this round
625 */
626 length = 0;
627 }
628
629 /**
630 * construct new text line with content from file
631 * move data pointer
632 */
633 QString textLine (unicodeData, lineLength);
634 unicodeData += lineLength;
635
636 /**
637 * ensure blocks aren't too large
638 */
639 if (m_blocks.last()->lines() >= m_blockSize)
640 m_blocks.append (new TextBlock (this, m_blocks.last()->startLine() + m_blocks.last()->lines()));
641
642 /**
643 * append line to last block
644 */
645 m_blocks.last()->appendLine (textLine);
646 ++m_lines;
647 } while (length > 0);
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 md5sum of file on disk
659 setDigest (file.digest ());
660
661 // remember if BOM was found
662 if (file.byteOrderMarkFound ())
663 setGenerateByteOrderMark (true);
664
665 // remember eol mode, if any found in file
666 if (file.eol() != eolUnknown)
667 setEndOfLineMode (file.eol());
668
669 // remember mime type for filter device
670 m_mimeTypeForFilterDev = file.mimeTypeForFilterDev ();
671
672 // assert that one line is there!
673 Q_ASSERT (m_lines > 0);
674
675 // report CODEC + ERRORS
676 kDebug (13020) << "Loaded file " << filename << "with codec" << m_textCodec->name()
677 << (encodingErrors ? "with" : "without") << "encoding errors";
678
679 // report BOM
680 kDebug (13020) << (file.byteOrderMarkFound () ? "Found" : "Didn't find") << "byte order mark";
681
682 // report filter device mime-type
683 kDebug (13020) << "used filter device for mime-type" << m_mimeTypeForFilterDev;
684
685 // emit success
686 emit loaded (filename, encodingErrors);
687
688 // file loading worked, modulo encoding problems
689 return true;
690}
691
692const QByteArray &TextBuffer::digest () const
693{
694 return m_digest;
695}
696
697void TextBuffer::setDigest (const QByteArray & md5sum)
698{
699 m_digest = md5sum;
700}
701
702void TextBuffer::setTextCodec (QTextCodec *codec)
703{
704 m_textCodec = codec;
705
706 // enforce bom for some encodings
707 int mib = m_textCodec->mibEnum ();
708 if (mib == 1013 || mib == 1014 || mib == 1015) // utf16
709 setGenerateByteOrderMark (true);
710 if (mib == 1017 || mib == 1018 || mib == 1019) // utf32
711 setGenerateByteOrderMark (true);
712}
713
714bool TextBuffer::save (const QString &filename)
715{
716 // codec must be set!
717 Q_ASSERT (m_textCodec);
718
719 /**
720 * use KSaveFile for save write + rename
721 */
722 KSaveFile saveFile (filename);
723
724#if KDE_IS_VERSION(4,10,3)
725 /**
726 * allow fallback if directory not writable
727 * fixes bug 312415
728 */
729 saveFile.setDirectWriteFallback (true);
730#endif
731
732 /**
733 * try to open or fail
734 */
735 if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
736 return false;
737 }
738
739 /**
740 * construct correct filter device and try to open
741 */
742 QIODevice *file = KFilterDev::device (&saveFile, m_mimeTypeForFilterDev, false);
743 const bool deleteFile = file;
744 if (!file)
745 file = &saveFile;
746
747 /**
748 * try to open, if new file
749 */
750 if (deleteFile) {
751 if (!file->open (QIODevice::WriteOnly | QIODevice::Truncate)) {
752 delete file;
753 return false;
754 }
755 }
756
757 /**
758 * construct stream + disable Unicode headers
759 */
760 QTextStream stream (file);
761 stream.setCodec (QTextCodec::codecForName("UTF-16"));
762
763 // set the correct codec
764 stream.setCodec (m_textCodec);
765
766 // generate byte order mark?
767 stream.setGenerateByteOrderMark (generateByteOrderMark());
768
769 // our loved eol string ;)
770 QString eol = "\n"; //m_doc->config()->eolString ();
771 if (endOfLineMode() == eolDos)
772 eol = QString ("\r\n");
773 else if (endOfLineMode() == eolMac)
774 eol = QString ("\r");
775
776 // just dump the lines out ;)
777 for (int i = 0; i < m_lines; ++i)
778 {
779 // get line to save
780 Kate::TextLine textline = line (i);
781
782 stream << textline->text();
783
784 // append correct end of line string
785 if ((i+1) < m_lines)
786 stream << eol;
787 }
788
789 if (m_newLineAtEof) {
790 Q_ASSERT(m_lines > 0); // see .h file
791 const Kate::TextLine lastLine = line (m_lines - 1);
792 const int firstChar = lastLine->firstChar();
793 if (firstChar > -1 || lastLine->length() > 0) {
794 stream << eol;
795 }
796 }
797
798 // flush stream
799 stream.flush ();
800
801 // close and delete file
802 if (deleteFile) {
803 file->close ();
804 delete file;
805 }
806
807 // flush file
808 if (!saveFile.flush())
809 return false;
810
811#ifndef Q_OS_WIN
812 // ensure that the file is written to disk
813#ifdef HAVE_FDATASYNC
814 fdatasync (saveFile.handle());
815#else
816 fsync (saveFile.handle());
817#endif
818#endif
819
820 // did save work?
821 // only finalize if stream status == OK
822 bool ok = (stream.status() == QTextStream::Ok) && saveFile.finalize();
823
824 // remember this revision as last saved if we had success!
825 if (ok)
826 m_history.setLastSavedRevision ();
827
828 // report CODEC + ERRORS
829 kDebug (13020) << "Saved file " << filename << "with codec" << m_textCodec->name()
830 << (ok ? "without" : "with") << "errors";
831
832 if (ok)
833 markModifiedLinesAsSaved();
834
835 // emit signal on success
836 if (ok)
837 emit saved (filename);
838
839 // return success or not
840 return ok;
841}
842
843void TextBuffer::notifyAboutRangeChange (KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute)
844{
845 /**
846 * ignore calls if no document is around
847 */
848 if (!m_document)
849 return;
850
851 /**
852 * update all views, this IS ugly and could be a signal, but I profiled and a signal is TOO slow, really
853 * just create 20k ranges in a go and you wait seconds on a decent machine
854 */
855 const QList<KTextEditor::View *> &views = m_document->views ();
856 foreach(KTextEditor::View* curView, views) {
857 // filter wrong views
858 if (view && view != curView)
859 continue;
860
861 // notify view, it is really a kate view
862 static_cast<KateView *> (curView)->notifyAboutRangeChange (startLine, endLine, rangeWithAttribute);
863 }
864}
865
866void TextBuffer::markModifiedLinesAsSaved()
867{
868 foreach(TextBlock* block, m_blocks)
869 block->markModifiedLinesAsSaved ();
870}
871
872QList<TextRange *> TextBuffer::rangesForLine (int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
873{
874 // get block, this will assert on invalid line
875 const int blockIndex = blockForLine (line);
876
877 // get the ranges of the right block
878 QList<TextRange *> rightRanges;
879 foreach (const QSet<TextRange *> &ranges, m_blocks.at(blockIndex)->rangesForLine (line)) {
880 foreach (TextRange * const range, ranges) {
881 /**
882 * we want only ranges with attributes, but this one has none
883 */
884 if (rangesWithAttributeOnly && !range->hasAttribute())
885 continue;
886
887 /**
888 * we want ranges for no view, but this one's attribute is only valid for views
889 */
890 if (!view && range->attributeOnlyForViews())
891 continue;
892
893 /**
894 * the range's attribute is not valid for this view
895 */
896 if (range->view() && range->view() != view)
897 continue;
898
899 /**
900 * if line is in the range, ok
901 */
902 if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal())
903 rightRanges.append (range);
904 }
905 }
906
907 // return right ranges
908 return rightRanges;
909}
910
911}
912