Kate
katetextbuffer.cpp
Go to the documentation of this file.
00001 /* This file is part of the Kate project. 00002 * 00003 * Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org> 00004 * 00005 * This library is free software; you can redistribute it and/or 00006 * modify it under the terms of the GNU Library General Public 00007 * License as published by the Free Software Foundation; either 00008 * version 2 of the License, or (at your option) any later version. 00009 * 00010 * This library is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Library General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU Library General Public License 00016 * along with this library; see the file COPYING.LIB. If not, write to 00017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 * Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "config.h" 00022 00023 #include "katetextbuffer.h" 00024 #include "katetextloader.h" 00025 00026 // this is unfortunate, but needed for performance 00027 #include "katedocument.h" 00028 #include "kateview.h" 00029 00030 #include <kde_file.h> 00031 00032 namespace Kate { 00033 00034 TextBuffer::TextBuffer (KTextEditor::Document *parent, int blockSize) 00035 : QObject (parent) 00036 , m_document (parent) 00037 , m_history (*this) 00038 , m_blockSize (blockSize) 00039 , m_lines (0) 00040 , m_lastUsedBlock (0) 00041 , m_revision (0) 00042 , m_editingTransactions (0) 00043 , m_editingLastRevision (0) 00044 , m_editingLastLines (0) 00045 , m_editingMinimalLineChanged (-1) 00046 , m_editingMaximalLineChanged (-1) 00047 , m_encodingProberType (KEncodingProber::Universal) 00048 , m_fallbackTextCodec (0) 00049 , m_textCodec (0) 00050 , m_generateByteOrderMark (false) 00051 , m_endOfLineMode (eolUnix) 00052 , m_removeTrailingSpaces (false) 00053 { 00054 // minimal block size must be > 0 00055 Q_ASSERT (m_blockSize > 0); 00056 00057 // create initial state 00058 clear (); 00059 } 00060 00061 TextBuffer::~TextBuffer () 00062 { 00063 // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect 00064 m_document = 0; 00065 00066 // not allowed during editing 00067 Q_ASSERT (m_editingTransactions == 0); 00068 00069 // kill all ranges, work on copy, they will remove themself from the hash 00070 QSet<TextRange *> copyRanges = m_ranges; 00071 qDeleteAll (copyRanges); 00072 Q_ASSERT (m_ranges.empty()); 00073 00074 // clean out all cursors and lines, only cursors belonging to range will survive 00075 foreach(TextBlock* block, m_blocks) 00076 block->deleteBlockContent (); 00077 00078 // delete all blocks, now that all cursors are really deleted 00079 // else asserts in destructor of blocks will fail! 00080 qDeleteAll (m_blocks); 00081 m_blocks.clear (); 00082 00083 // kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks 00084 QSet<TextCursor *> copyCursors = m_invalidCursors; 00085 qDeleteAll (copyCursors); 00086 Q_ASSERT (m_invalidCursors.empty()); 00087 } 00088 00089 void TextBuffer::invalidateRanges() 00090 { 00091 // invalidate all ranges, work on copy, they might delete themself... 00092 QSet<TextRange *> copyRanges = m_ranges; 00093 foreach (TextRange *range, copyRanges) 00094 range->setRange (KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid()); 00095 } 00096 00097 void TextBuffer::clear () 00098 { 00099 // not allowed during editing 00100 Q_ASSERT (m_editingTransactions == 0); 00101 00102 invalidateRanges(); 00103 00104 // new block for empty buffer 00105 TextBlock *newBlock = new TextBlock (this, 0); 00106 newBlock->appendLine (TextLine (new TextLineData())); 00107 00108 // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range 00109 foreach(TextBlock* block, m_blocks) 00110 block->clearBlockContent (newBlock); 00111 00112 // kill all buffer blocks 00113 qDeleteAll (m_blocks); 00114 m_blocks.clear (); 00115 00116 // insert one block with one empty line 00117 m_blocks.append (newBlock); 00118 00119 // reset lines and last used block 00120 m_lines = 1; 00121 m_lastUsedBlock = 0; 00122 00123 // reset revision 00124 m_revision = 0; 00125 00126 // reset bom detection 00127 m_generateByteOrderMark = false; 00128 00129 // reset the filter device 00130 m_mimeTypeForFilterDev = "text/plain"; 00131 00132 // clear edit history 00133 m_history.clear (); 00134 00135 // we got cleared 00136 emit cleared (); 00137 } 00138 00139 TextLine TextBuffer::line (int line) const 00140 { 00141 // get block, this will assert on invalid line 00142 int blockIndex = blockForLine (line); 00143 00144 // get line 00145 return m_blocks.at(blockIndex)->line (line); 00146 } 00147 00148 QString TextBuffer::text () const 00149 { 00150 QString text; 00151 00152 // combine all blocks 00153 foreach(TextBlock* block, m_blocks) 00154 block->text (text); 00155 00156 // return generated string 00157 return text; 00158 } 00159 00160 bool TextBuffer::startEditing () 00161 { 00162 // increment transaction counter 00163 ++m_editingTransactions; 00164 00165 // if not first running transaction, do nothing 00166 if (m_editingTransactions > 1) 00167 return false; 00168 00169 // reset informations about edit... 00170 m_editingLastRevision = m_revision; 00171 m_editingLastLines = m_lines; 00172 m_editingMinimalLineChanged = -1; 00173 m_editingMaximalLineChanged = -1; 00174 00175 // transaction has started 00176 emit editingStarted (); 00177 00178 // first transaction started 00179 return true; 00180 } 00181 00182 bool TextBuffer::finishEditing () 00183 { 00184 // only allowed if still transactions running 00185 Q_ASSERT (m_editingTransactions > 0); 00186 00187 // decrement counter 00188 --m_editingTransactions; 00189 00190 // if not last running transaction, do nothing 00191 if (m_editingTransactions > 0) 00192 return false; 00193 00194 // assert that if buffer changed, the line ranges are set and valid! 00195 Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1)); 00196 Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged)); 00197 Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines)); 00198 Q_ASSERT (!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines)); 00199 00200 // transaction has finished 00201 emit editingFinished (); 00202 00203 // last transaction finished 00204 return true; 00205 } 00206 00207 void TextBuffer::wrapLine (const KTextEditor::Cursor &position) 00208 { 00209 // only allowed if editing transaction running 00210 Q_ASSERT (m_editingTransactions > 0); 00211 00212 // get block, this will assert on invalid line 00213 int blockIndex = blockForLine (position.line()); 00214 00215 // let the block handle the wrapLine 00216 // this can only lead to one more line in this block 00217 // no other blocks will change 00218 ++m_lines; // first alter the line counter, as functions called will need the valid one 00219 m_blocks.at(blockIndex)->wrapLine (position); 00220 00221 // remember changes 00222 ++m_revision; 00223 00224 // update changed line interval 00225 if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) 00226 m_editingMinimalLineChanged = position.line(); 00227 00228 if (position.line() <= m_editingMaximalLineChanged) 00229 ++m_editingMaximalLineChanged; 00230 else 00231 m_editingMaximalLineChanged = position.line() + 1; 00232 00233 // fixup all following blocks 00234 fixStartLines (blockIndex); 00235 00236 // balance the changed block if needed 00237 balanceBlock (blockIndex); 00238 00239 // emit signal about done change 00240 emit lineWrapped (position); 00241 } 00242 00243 void TextBuffer::unwrapLine (int line) 00244 { 00245 // only allowed if editing transaction running 00246 Q_ASSERT (m_editingTransactions > 0); 00247 00248 // line 0 can't be unwrapped 00249 Q_ASSERT (line > 0); 00250 00251 // get block, this will assert on invalid line 00252 int blockIndex = blockForLine (line); 00253 00254 // is this the first line in the block? 00255 bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine()); 00256 00257 // let the block handle the unwrapLine 00258 // this can either lead to one line less in this block or the previous one 00259 // the previous one could even end up with zero lines 00260 m_blocks.at(blockIndex)->unwrapLine (line, (blockIndex > 0) ? m_blocks.at(blockIndex-1) : 0); 00261 --m_lines; 00262 00263 // decrement index for later fixup, if we modified the block in front of the found one 00264 if (firstLineInBlock) 00265 --blockIndex; 00266 00267 // remember changes 00268 ++m_revision; 00269 00270 // update changed line interval 00271 if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) 00272 m_editingMinimalLineChanged = line - 1; 00273 00274 if (line <= m_editingMaximalLineChanged) 00275 --m_editingMaximalLineChanged; 00276 else 00277 m_editingMaximalLineChanged = line -1; 00278 00279 // fixup all following blocks 00280 fixStartLines (blockIndex); 00281 00282 // balance the changed block if needed 00283 balanceBlock (blockIndex); 00284 00285 // emit signal about done change 00286 emit lineUnwrapped (line); 00287 } 00288 00289 void TextBuffer::insertText (const KTextEditor::Cursor &position, const QString &text) 00290 { 00291 // only allowed if editing transaction running 00292 Q_ASSERT (m_editingTransactions > 0); 00293 00294 // skip work, if no text to insert 00295 if (text.isEmpty()) 00296 return; 00297 00298 // get block, this will assert on invalid line 00299 int blockIndex = blockForLine (position.line()); 00300 00301 // let the block handle the insertText 00302 m_blocks.at(blockIndex)->insertText (position, text); 00303 00304 // remember changes 00305 ++m_revision; 00306 00307 // update changed line interval 00308 if (position.line () < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) 00309 m_editingMinimalLineChanged = position.line (); 00310 00311 if (position.line () > m_editingMaximalLineChanged) 00312 m_editingMaximalLineChanged = position.line (); 00313 00314 // emit signal about done change 00315 emit textInserted (position, text); 00316 } 00317 00318 void TextBuffer::removeText (const KTextEditor::Range &range) 00319 { 00320 // only allowed if editing transaction running 00321 Q_ASSERT (m_editingTransactions > 0); 00322 00323 // only ranges on one line are supported 00324 Q_ASSERT (range.start().line() == range.end().line()); 00325 00326 // start colum <= end column and >= 0 00327 Q_ASSERT (range.start().column() <= range.end().column()); 00328 Q_ASSERT (range.start().column() >= 0); 00329 00330 // skip work, if no text to remove 00331 if (range.isEmpty()) 00332 return; 00333 00334 // get block, this will assert on invalid line 00335 int blockIndex = blockForLine (range.start().line()); 00336 00337 // let the block handle the removeText, retrieve removed text 00338 QString text; 00339 m_blocks.at(blockIndex)->removeText (range, text); 00340 00341 // remember changes 00342 ++m_revision; 00343 00344 // update changed line interval 00345 if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) 00346 m_editingMinimalLineChanged = range.start().line(); 00347 00348 if (range.start().line() > m_editingMaximalLineChanged) 00349 m_editingMaximalLineChanged = range.start().line(); 00350 00351 // emit signal about done change 00352 emit textRemoved (range, text); 00353 } 00354 00355 int TextBuffer::blockForLine (int line) const 00356 { 00357 // only allow valid lines 00358 Q_ASSERT (line >= 0); 00359 Q_ASSERT (line < lines()); 00360 00361 // block to start search with 00362 int index = m_lastUsedBlock; 00363 int blockStart = 0; 00364 int blockEnd = m_blocks.size(); 00365 00366 // check if start is ok 00367 if (index < 0 || index >= m_blocks.size()) 00368 index = 0; 00369 00370 // search for right block 00371 forever { 00372 // facts bout this block 00373 TextBlock* block = m_blocks.at(index); 00374 const int start = block->startLine(); 00375 const int lines = block->lines (); 00376 00377 // right block found, remember it and return it 00378 if (start <= line && line < (start + lines)) { 00379 m_lastUsedBlock = index; 00380 return index; 00381 } 00382 00383 if (line < start) 00384 { 00385 // Search left of index 00386 blockEnd = index; 00387 }else{ 00388 // Search right of index 00389 blockStart = index+1; 00390 } 00391 index = (blockStart + (blockEnd-1)) / 2; 00392 } 00393 00394 // we should always find a block 00395 Q_ASSERT (false); 00396 return -1; 00397 } 00398 00399 void TextBuffer::fixStartLines (int startBlock) 00400 { 00401 // only allow valid start block 00402 Q_ASSERT (startBlock >= 0); 00403 Q_ASSERT (startBlock < m_blocks.size()); 00404 00405 // new start line for next block 00406 TextBlock* block = m_blocks.at(startBlock); 00407 int newStartLine = block->startLine () + block->lines (); 00408 00409 // fixup block 00410 for (int index = startBlock + 1; index < m_blocks.size(); ++index) { 00411 // set new start line 00412 block = m_blocks.at(index); 00413 block->setStartLine (newStartLine); 00414 00415 // calculate next start line 00416 newStartLine += block->lines (); 00417 } 00418 } 00419 00420 void TextBuffer::balanceBlock (int index) 00421 { 00425 TextBlock *blockToBalance = m_blocks.at(index); 00426 00427 // first case, too big one, split it 00428 if (blockToBalance->lines () >= 2 * m_blockSize) { 00429 // half the block 00430 int halfSize = blockToBalance->lines () / 2; 00431 00432 // create and insert new block behind current one, already set right start line 00433 TextBlock *newBlock = blockToBalance->splitBlock (halfSize); 00434 Q_ASSERT (newBlock); 00435 m_blocks.insert (m_blocks.begin() + index + 1, newBlock); 00436 00437 // split is done 00438 return; 00439 } 00440 00441 // second case: possibly too small block 00442 00443 // if only one block, no chance to unite 00444 // same if this is first block, we always append to previous one 00445 if (index == 0) 00446 return; 00447 00448 // block still large enough, do nothing 00449 if (2 * blockToBalance->lines () > m_blockSize) 00450 return; 00451 00452 // unite small block with predecessor 00453 TextBlock *targetBlock = m_blocks.at(index-1); 00454 00455 // merge block 00456 blockToBalance->mergeBlock (targetBlock); 00457 00458 // delete old block 00459 delete blockToBalance; 00460 m_blocks.erase (m_blocks.begin() + index); 00461 } 00462 00463 void TextBuffer::debugPrint (const QString &title) const 00464 { 00465 // print header with title 00466 printf ("%s (lines: %d bs: %d)\n", qPrintable (title), m_lines, m_blockSize); 00467 00468 // print all blocks 00469 for (int i = 0; i < m_blocks.size(); ++i) 00470 m_blocks.at(i)->debugPrint (i); 00471 } 00472 00473 bool TextBuffer::load (const QString &filename, bool &encodingErrors) 00474 { 00475 // fallback codec must exist 00476 Q_ASSERT (m_fallbackTextCodec); 00477 00478 // codec must be set! 00479 Q_ASSERT (m_textCodec); 00480 00484 clear (); 00485 00489 KDE_struct_stat sbuf; 00490 if (KDE::stat(filename, &sbuf) != 0 || !S_ISREG(sbuf.st_mode)) 00491 return false; 00492 00496 Kate::TextLoader file (filename, m_encodingProberType); 00497 00505 for (int i = 0; i < 4; ++i) { 00509 for (int b = 1; b < m_blocks.size(); ++b) { 00510 TextBlock* block = m_blocks.at(b); 00511 block->m_lines.clear (); 00512 delete block; 00513 } 00514 m_blocks.resize (1); 00515 00519 m_blocks.last()->m_lines.clear (); 00520 m_lines = 0; 00521 00528 QTextCodec *codec = m_textCodec; 00529 if (i == 1) 00530 codec = 0; 00531 else if (i == 2) 00532 codec = m_fallbackTextCodec; 00533 00534 if (!file.open (codec)) { 00535 // create one dummy textline, in any case 00536 m_blocks.last()->appendLine (TextLine (new TextLineData())); 00537 m_lines++; 00538 return false; 00539 } 00540 00541 // read in all lines... 00542 encodingErrors = false; 00543 while ( !file.eof() ) 00544 { 00545 // read line 00546 int offset = 0, length = 0; 00547 bool currentError = !file.readLine (offset, length); 00548 encodingErrors = encodingErrors || currentError; 00549 00550 // bail out on encoding error, if not last round! 00551 if (encodingErrors && i < 3) { 00552 kDebug (13020) << "Failed try to load file" << filename << "with codec" << 00553 (file.textCodec() ? file.textCodec()->name() : "(null)"); 00554 break; 00555 } 00556 00557 // get unicode data for this line 00558 const QChar *unicodeData = file.unicode () + offset; 00559 00560 // construct new text line with content from file 00561 TextLine textLine = TextLine (new TextLineData(QString (unicodeData, length))); 00562 00563 // ensure blocks aren't too large 00564 if (m_blocks.last()->lines() >= m_blockSize) 00565 m_blocks.append (new TextBlock (this, m_blocks.last()->startLine() + m_blocks.last()->lines())); 00566 00567 m_blocks.last()->appendLine (textLine); 00568 m_lines++; 00569 } 00570 00571 // if no encoding error, break out of reading loop 00572 if (!encodingErrors) { 00573 // remember used codec 00574 m_textCodec = file.textCodec (); 00575 break; 00576 } 00577 } 00578 00579 // remember if BOM was found 00580 if (file.byteOrderMarkFound ()) 00581 setGenerateByteOrderMark (true); 00582 00583 // remember eol mode, if any found in file 00584 if (file.eol() != eolUnknown) 00585 setEndOfLineMode (file.eol()); 00586 00587 // remember mime type for filter device 00588 m_mimeTypeForFilterDev = file.mimeTypeForFilterDev (); 00589 00590 // assert that one line is there! 00591 Q_ASSERT (m_lines > 0); 00592 00593 // report CODEC + ERRORS 00594 kDebug (13020) << "Loaded file " << filename << "with codec" << m_textCodec->name() 00595 << (encodingErrors ? "with" : "without") << "encoding errors"; 00596 00597 // report BOM 00598 kDebug (13020) << (file.byteOrderMarkFound () ? "Found" : "Didn't find") << "byte order mark"; 00599 00600 // report filter device mime-type 00601 kDebug (13020) << "used filter device for mime-type" << m_mimeTypeForFilterDev; 00602 00603 // emit success 00604 emit loaded (filename, encodingErrors); 00605 00606 // file loading worked, modulo encoding problems 00607 return true; 00608 } 00609 00610 bool TextBuffer::save (const QString &filename) 00611 { 00612 // codec must be set! 00613 Q_ASSERT (m_textCodec); 00614 00618 QIODevice *file = KFilterDev::deviceForFile (filename, m_mimeTypeForFilterDev, false); 00619 if (!file->open (QIODevice::WriteOnly)) { 00620 delete file; 00621 return false; 00622 } 00623 00627 QTextStream stream (file); 00628 stream.setCodec (QTextCodec::codecForName("UTF-16")); 00629 00630 // set the correct codec 00631 stream.setCodec (m_textCodec); 00632 00633 // generate byte order mark? 00634 stream.setGenerateByteOrderMark (generateByteOrderMark()); 00635 00636 // our loved eol string ;) 00637 QString eol = "\n"; //m_doc->config()->eolString (); 00638 if (endOfLineMode() == eolDos) 00639 eol = QString ("\r\n"); 00640 else if (endOfLineMode() == eolMac) 00641 eol = QString ("\r"); 00642 00643 // just dump the lines out ;) 00644 for (int i = 0; i < m_lines; ++i) 00645 { 00646 // get line to save 00647 Kate::TextLine textline = line (i); 00648 00649 // strip trailing spaces 00650 if (m_removeTrailingSpaces) 00651 { 00652 int lastChar = textline->lastChar(); 00653 if (lastChar > -1) 00654 { 00655 stream << textline->text().left (lastChar+1); 00656 } 00657 } 00658 else // simple, dump the line 00659 stream << textline->text(); 00660 00661 // append correct end of line string 00662 if ((i+1) < m_lines) 00663 stream << eol; 00664 } 00665 00666 // flush stream 00667 stream.flush (); 00668 00669 // close and delete file 00670 file->close (); 00671 delete file; 00672 00673 #ifndef Q_OS_WIN 00674 // ensure that the file is written to disk 00675 // we crete new qfile, as the above might be wrapper around compression 00676 QFile syncFile (filename); 00677 syncFile.open (QIODevice::ReadOnly); 00678 00679 #ifdef HAVE_FDATASYNC 00680 fdatasync (syncFile.handle()); 00681 #else 00682 fsync (syncFile.handle()); 00683 #endif 00684 #endif 00685 00686 // did save work? 00687 bool ok = stream.status() == QTextStream::Ok; 00688 00689 // remember this revision as last saved if we had success! 00690 if (ok) 00691 m_history.setLastSavedRevision (); 00692 00693 // report CODEC + ERRORS 00694 kDebug (13020) << "Saved file " << filename << "with codec" << m_textCodec->name() 00695 << (ok ? "without" : "with") << "errors"; 00696 00697 // emit signal on success 00698 if (ok) 00699 emit saved (filename); 00700 00701 // return success or not 00702 return ok; 00703 } 00704 00705 void TextBuffer::notifyAboutRangeChange (KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute) 00706 { 00710 if (!m_document) 00711 return; 00712 00717 const QList<KTextEditor::View *> &views = m_document->views (); 00718 foreach(KTextEditor::View* curView, views) { 00719 // filter wrong views 00720 if (view && view != curView) 00721 continue; 00722 00723 // notify view, it is really a kate view 00724 static_cast<KateView *> (curView)->notifyAboutRangeChange (startLine, endLine, rangeWithAttribute); 00725 } 00726 } 00727 00728 QList<TextRange *> TextBuffer::rangesForLine (int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const 00729 { 00730 // get block, this will assert on invalid line 00731 const int blockIndex = blockForLine (line); 00732 00733 // get the ranges of the right block 00734 QList<TextRange *> rightRanges; 00735 foreach (const QSet<TextRange *> &ranges, m_blocks.at(blockIndex)->rangesForLine (line)) { 00736 foreach (TextRange * const range, ranges) { 00740 if (rangesWithAttributeOnly && !range->hasAttribute()) 00741 continue; 00742 00746 if (!view && range->attributeOnlyForViews()) 00747 continue; 00748 00752 if (range->view() && range->view() != view) 00753 continue; 00754 00758 if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) 00759 rightRanges.append (range); 00760 } 00761 } 00762 00763 // return right ranges 00764 return rightRanges; 00765 } 00766 00767 }
KDE 4.6 API Reference