kate Library API Documentation

katebuffer.cpp

00001 /* This file is part of the KDE libraries 00002 Copyright (c) 2000 Waldo Bastian <bastian@kde.org> 00003 Copyright (C) 2002-2004 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 version 2 as published by the Free Software Foundation. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00017 Boston, MA 02111-1307, USA. 00018 */ 00019 00020 #include <sys/types.h> 00021 #include <sys/stat.h> 00022 #include <unistd.h> 00023 00024 #include "katebuffer.h" 00025 #include "katebuffer.moc" 00026 00027 #include "katedocument.h" 00028 #include "katehighlight.h" 00029 #include "kateconfig.h" 00030 #include "katefactory.h" 00031 #include "kateautoindent.h" 00032 00033 #include <kdebug.h> 00034 #include <kglobal.h> 00035 #include <kcharsets.h> 00036 00037 #include <qpopupmenu.h> 00038 #include <qfile.h> 00039 #include <qtextstream.h> 00040 #include <qtimer.h> 00041 #include <qtextcodec.h> 00042 #include <qcstring.h> 00043 #include <qdatetime.h> 00044 00049 static const Q_ULONG KATE_FILE_LOADER_BS = 256 * 1024; 00050 00057 static const Q_ULONG KATE_AVG_BLOCK_SIZE = 2048 * 80; 00058 static const Q_ULONG KATE_MAX_BLOCK_LINES = 2048; 00059 00065 static const uint KATE_HL_LOOKAHEAD = 64; 00066 00072 uint KateBuffer::m_maxLoadedBlocks = 16; 00073 00077 static const uint KATE_MAX_DYNAMIC_CONTEXTS = 512; 00078 00079 void KateBuffer::setMaxLoadedBlocks (uint count) 00080 { 00081 m_maxLoadedBlocks = KMAX ((uint)4, count); 00082 } 00083 00084 class KateFileLoader 00085 { 00086 public: 00087 KateFileLoader (const QString &filename, QTextCodec *codec) 00088 : m_file (filename) 00089 , m_buffer (KMIN (m_file.size(), KATE_FILE_LOADER_BS)) 00090 , m_codec (codec) 00091 , m_decoder (m_codec->makeDecoder()) 00092 , m_position (0) 00093 , m_lastLineStart (0) 00094 , m_eof (false) // default to not eof 00095 , lastWasEndOfLine (true) // at start of file, we had a virtual newline 00096 , lastWasR (false) // we have not found a \r as last char 00097 , m_eol (-1) // no eol type detected atm 00098 , m_twoByteEncoding (QString(codec->name()) == "ISO-10646-UCS-2") 00099 , m_binary (false) 00100 { 00101 } 00102 00103 ~KateFileLoader () 00104 { 00105 delete m_decoder; 00106 } 00107 00111 bool open () 00112 { 00113 if (m_file.open (IO_ReadOnly)) 00114 { 00115 int c = m_file.readBlock (m_buffer.data(), m_buffer.size()); 00116 00117 if (c > 0) 00118 { 00119 // fix utf16 LE, stolen from khtml ;) 00120 if ((c >= 2) && (m_codec->mibEnum() == 1000) && (m_buffer[1] == 0x00)) 00121 { 00122 // utf16LE, we need to put the decoder in LE mode 00123 char reverseUtf16[3] = {0xFF, 0xFE, 0x00}; 00124 m_decoder->toUnicode(reverseUtf16, 2); 00125 } 00126 00127 processNull (c); 00128 m_text = m_decoder->toUnicode (m_buffer, c); 00129 } 00130 00131 m_eof = (c == -1) || (c == 0) || (m_text.length() == 0) || m_file.atEnd(); 00132 00133 for (uint i=0; i < m_text.length(); i++) 00134 { 00135 if (m_text[i] == '\n') 00136 { 00137 m_eol = KateDocumentConfig::eolUnix; 00138 break; 00139 } 00140 else if ((m_text[i] == '\r')) 00141 { 00142 if (((i+1) < m_text.length()) && (m_text[i+1] == '\n')) 00143 { 00144 m_eol = KateDocumentConfig::eolDos; 00145 break; 00146 } 00147 else 00148 { 00149 m_eol = KateDocumentConfig::eolMac; 00150 break; 00151 } 00152 } 00153 } 00154 00155 return true; 00156 } 00157 00158 return false; 00159 } 00160 00161 // no new lines around ? 00162 inline bool eof () const { return m_eof && !lastWasEndOfLine && (m_lastLineStart == m_text.length()); } 00163 00164 // eol mode ? autodetected on open(), -1 for no eol found in the first block! 00165 inline int eol () const { return m_eol; } 00166 00167 // binary ? 00168 inline bool binary () const { return m_binary; } 00169 00170 // internal unicode data array 00171 inline const QChar *unicode () const { return m_text.unicode(); } 00172 00173 // read a line, return length + offset in unicode data 00174 void readLine (uint &offset, uint &length) 00175 { 00176 length = 0; 00177 offset = 0; 00178 00179 while (m_position <= m_text.length()) 00180 { 00181 if (m_position == m_text.length()) 00182 { 00183 // try to load more text if something is around 00184 if (!m_eof) 00185 { 00186 int c = m_file.readBlock (m_buffer.data(), m_buffer.size()); 00187 00188 uint readString = 0; 00189 if (c > 0) 00190 { 00191 processNull (c); 00192 00193 QString str (m_decoder->toUnicode (m_buffer, c)); 00194 readString = str.length(); 00195 00196 m_text = m_text.mid (m_lastLineStart, m_position-m_lastLineStart) 00197 + str; 00198 } 00199 else 00200 m_text = m_text.mid (m_lastLineStart, m_position-m_lastLineStart); 00201 00202 // is file completly read ? 00203 m_eof = (c == -1) || (c == 0) || (readString == 0) || m_file.atEnd(); 00204 00205 // recalc current pos and last pos 00206 m_position -= m_lastLineStart; 00207 m_lastLineStart = 0; 00208 } 00209 00210 // oh oh, end of file, escape ! 00211 if (m_eof && (m_position == m_text.length())) 00212 { 00213 lastWasEndOfLine = false; 00214 00215 // line data 00216 offset = m_lastLineStart; 00217 length = m_position-m_lastLineStart; 00218 00219 m_lastLineStart = m_position; 00220 00221 return; 00222 } 00223 } 00224 00225 if (m_text[m_position] == '\n') 00226 { 00227 lastWasEndOfLine = true; 00228 00229 if (lastWasR) 00230 { 00231 m_lastLineStart++; 00232 lastWasR = false; 00233 } 00234 else 00235 { 00236 // line data 00237 offset = m_lastLineStart; 00238 length = m_position-m_lastLineStart; 00239 00240 m_lastLineStart = m_position+1; 00241 m_position++; 00242 00243 return; 00244 } 00245 } 00246 else if (m_text[m_position] == '\r') 00247 { 00248 lastWasEndOfLine = true; 00249 lastWasR = true; 00250 00251 // line data 00252 offset = m_lastLineStart; 00253 length = m_position-m_lastLineStart; 00254 00255 m_lastLineStart = m_position+1; 00256 m_position++; 00257 00258 return; 00259 } 00260 else 00261 { 00262 lastWasEndOfLine = false; 00263 lastWasR = false; 00264 } 00265 00266 m_position++; 00267 } 00268 } 00269 00270 // this nice methode will kill all 0 bytes (or double bytes) 00271 // and remember if this was a binary or not ;) 00272 void processNull (uint length) 00273 { 00274 if (m_twoByteEncoding) 00275 { 00276 for (uint i=1; i < length; i+=2) 00277 { 00278 if ((m_buffer[i] == 0) && (m_buffer[i-1] == 0)) 00279 { 00280 m_binary = true; 00281 m_buffer[i] = ' '; 00282 } 00283 } 00284 } 00285 else 00286 { 00287 for (uint i=0; i < length; i++) 00288 { 00289 if (m_buffer[i] == 0) 00290 { 00291 m_binary = true; 00292 m_buffer[i] = ' '; 00293 } 00294 } 00295 } 00296 } 00297 00298 private: 00299 QFile m_file; 00300 QByteArray m_buffer; 00301 QTextCodec *m_codec; 00302 QTextDecoder *m_decoder; 00303 QString m_text; 00304 uint m_position; 00305 uint m_lastLineStart; 00306 bool m_eof; 00307 bool lastWasEndOfLine; 00308 bool lastWasR; 00309 int m_eol; 00310 bool m_twoByteEncoding; 00311 bool m_binary; 00312 }; 00313 00317 KateBuffer::KateBuffer(KateDocument *doc) 00318 : QObject (doc), 00319 editSessionNumber (0), 00320 editIsRunning (false), 00321 editTagLineStart (0xffffffff), 00322 editTagLineEnd (0), 00323 m_doc (doc), 00324 m_lines (0), 00325 m_lastInSyncBlock (0), 00326 m_lastFoundBlock (0), 00327 m_cacheReadError(false), 00328 m_cacheWriteError(false), 00329 m_loadingBorked (false), 00330 m_binary (false), 00331 m_highlight (0), 00332 m_regionTree (this), 00333 m_tabWidth (8), 00334 m_lineHighlightedMax (0), 00335 m_lineHighlighted (0), 00336 m_maxDynamicContexts (KATE_MAX_DYNAMIC_CONTEXTS) 00337 { 00338 clear(); 00339 } 00340 00344 KateBuffer::~KateBuffer() 00345 { 00346 // DELETE ALL BLOCKS, will free mem 00347 for (uint i=0; i < m_blocks.size(); i++) 00348 delete m_blocks[i]; 00349 00350 // release HL 00351 if (m_highlight) 00352 m_highlight->release(); 00353 } 00354 00355 void KateBuffer::editStart () 00356 { 00357 editSessionNumber++; 00358 00359 if (editSessionNumber > 1) 00360 return; 00361 00362 editIsRunning = true; 00363 00364 editTagLineStart = 0xffffffff; 00365 editTagLineEnd = 0; 00366 } 00367 00368 void KateBuffer::editEnd () 00369 { 00370 if (editSessionNumber == 0) 00371 return; 00372 00373 editSessionNumber--; 00374 00375 if (editSessionNumber > 0) 00376 return; 00377 00378 // hl update !!! 00379 if ( m_highlight && !m_highlight->noHighlighting() 00380 && (editTagLineStart <= editTagLineEnd) 00381 && (editTagLineEnd <= m_lineHighlighted)) 00382 { 00383 // look one line too far, needed for linecontinue stuff 00384 editTagLineEnd++; 00385 00386 // look one line before, needed nearly 100% only for indentation based folding ! 00387 if (editTagLineStart > 0) 00388 editTagLineStart--; 00389 00390 KateBufBlock *buf2 = 0; 00391 bool needContinue = false; 00392 while ((buf2 = findBlock(editTagLineStart))) 00393 { 00394 needContinue = doHighlight (buf2, 00395 (editTagLineStart > buf2->startLine()) ? editTagLineStart : buf2->startLine(), 00396 (editTagLineEnd > buf2->endLine()) ? buf2->endLine() : editTagLineEnd, 00397 true); 00398 00399 editTagLineStart = (editTagLineEnd > buf2->endLine()) ? buf2->endLine() : editTagLineEnd; 00400 00401 if ((editTagLineStart >= m_lines) || (editTagLineStart >= editTagLineEnd)) 00402 break; 00403 } 00404 00405 if (needContinue) 00406 m_lineHighlighted = editTagLineStart; 00407 00408 if (editTagLineStart > m_lineHighlightedMax) 00409 m_lineHighlightedMax = editTagLineStart; 00410 } 00411 else if (editTagLineStart < m_lineHighlightedMax) 00412 m_lineHighlightedMax = editTagLineStart; 00413 00414 editIsRunning = false; 00415 } 00416 00417 void KateBuffer::editTagLine (uint line) 00418 { 00419 if (line < editTagLineStart) 00420 editTagLineStart = line; 00421 00422 if (line > editTagLineEnd) 00423 editTagLineEnd = line; 00424 } 00425 00426 void KateBuffer::editInsertTagLine (uint line) 00427 { 00428 if (line < editTagLineStart) 00429 editTagLineStart = line; 00430 00431 if (line <= editTagLineEnd) 00432 editTagLineEnd++; 00433 00434 if (line > editTagLineEnd) 00435 editTagLineEnd = line; 00436 } 00437 00438 void KateBuffer::editRemoveTagLine (uint line) 00439 { 00440 if (line < editTagLineStart) 00441 editTagLineStart = line; 00442 00443 if (line < editTagLineEnd) 00444 editTagLineEnd--; 00445 00446 if (line > editTagLineEnd) 00447 editTagLineEnd = line; 00448 } 00449 00450 void KateBuffer::clear() 00451 { 00452 m_regionTree.clear(); 00453 00454 // cleanup the blocks 00455 for (uint i=0; i < m_blocks.size(); i++) 00456 delete m_blocks[i]; 00457 00458 m_blocks.clear (); 00459 00460 // create a bufblock with one line, we need that, only in openFile we won't have that 00461 KateBufBlock *block = new KateBufBlock(this, 0, 0); 00462 m_blocks.append (block); 00463 00464 // reset the state 00465 m_lines = block->lines(); 00466 m_lastInSyncBlock = 0; 00467 m_lastFoundBlock = 0; 00468 m_cacheWriteError = false; 00469 m_cacheReadError = false; 00470 m_loadingBorked = false; 00471 m_binary = false; 00472 00473 m_lineHighlightedMax = 0; 00474 m_lineHighlighted = 0; 00475 } 00476 00477 bool KateBuffer::openFile (const QString &m_file) 00478 { 00479 KateFileLoader file (m_file, m_doc->config()->codec()); 00480 00481 bool ok = false; 00482 struct stat sbuf; 00483 if (stat(QFile::encodeName(m_file), &sbuf) == 0) 00484 { 00485 if (S_ISREG(sbuf.st_mode) && file.open()) 00486 ok = true; 00487 } 00488 00489 if (!ok) 00490 { 00491 clear(); 00492 return false; // Error 00493 } 00494 00495 // set eol mode, if a eol char was found in the first 256kb block! 00496 if (file.eol() != -1) 00497 m_doc->config()->setEol (file.eol()); 00498 00499 // flush current content 00500 clear (); 00501 00502 // cleanup the blocks 00503 for (uint i=0; i < m_blocks.size(); i++) 00504 delete m_blocks[i]; 00505 00506 m_blocks.clear (); 00507 00508 // do the real work 00509 KateBufBlock *block = 0; 00510 m_lines = 0; 00511 while (!file.eof() && !m_cacheWriteError) 00512 { 00513 block = new KateBufBlock (this, block, 0, &file); 00514 00515 m_lines = block->endLine (); 00516 00517 if (m_cacheWriteError || (block->lines() == 0)) 00518 { 00519 delete block; 00520 break; 00521 } 00522 else 00523 m_blocks.append (block); 00524 } 00525 00526 // we had a cache write error, this load is really borked ! 00527 if (m_cacheWriteError) 00528 m_loadingBorked = true; 00529 00530 if (m_blocks.isEmpty() || (m_lines == 0)) 00531 { 00532 // file was really empty, clean the buffers + emit the line changed 00533 // loadingBorked will be false for such files, not matter what happened 00534 // before 00535 clear (); 00536 } 00537 else 00538 { 00539 // fix region tree 00540 m_regionTree.fixRoot (m_lines); 00541 } 00542 00543 // if we have no hl or the "None" hl activated, whole file is correct highlighted 00544 // after loading, which wonder ;) 00545 if (!m_highlight || m_highlight->noHighlighting()) 00546 { 00547 m_lineHighlighted = m_lines; 00548 m_lineHighlightedMax = m_lines; 00549 } 00550 00551 // binary? 00552 m_binary = file.binary (); 00553 00554 kdDebug (13020) << "LOADING DONE" << endl; 00555 00556 return !m_loadingBorked; 00557 } 00558 00559 bool KateBuffer::canEncode () 00560 { 00561 QTextCodec *codec = m_doc->config()->codec(); 00562 00563 kdDebug(13020) << "ENC NAME: " << codec->name() << endl; 00564 00565 // hardcode some unicode encodings which can encode all chars 00566 if ((QString(codec->name()) == "UTF-8") || (QString(codec->name()) == "ISO-10646-UCS-2")) 00567 return true; 00568 00569 for (uint i=0; i < m_lines; i++) 00570 { 00571 if (!codec->canEncode (plainLine(i)->string())) 00572 { 00573 kdDebug(13020) << "STRING LINE: " << plainLine(i)->string() << endl; 00574 kdDebug(13020) << "ENC WORKING: FALSE" << endl; 00575 00576 return false; 00577 } 00578 } 00579 00580 return true; 00581 } 00582 00583 bool KateBuffer::saveFile (const QString &m_file) 00584 { 00585 QFile file (m_file); 00586 QTextStream stream (&file); 00587 00588 if ( !file.open( IO_WriteOnly ) ) 00589 { 00590 return false; // Error 00591 } 00592 00593 QTextCodec *codec = m_doc->config()->codec(); 00594 00595 // disable Unicode headers 00596 stream.setEncoding(QTextStream::RawUnicode); 00597 00598 // this line sets the mapper to the correct codec 00599 stream.setCodec(codec); 00600 00601 QString eol = m_doc->config()->eolString (); 00602 00603 // for tab replacement, initialize only once 00604 uint pos, found, ml, l; 00605 QChar onespace(' '); 00606 QString onetab("\t"); 00607 uint tw = m_doc->config()->tabWidth(); 00608 00609 // Use the document methods 00610 if ( m_doc->configFlags() & KateDocument::cfReplaceTabs || 00611 m_doc->configFlags() & KateDocument::cfRemoveSpaces ) 00612 m_doc->editStart(); 00613 00614 for (uint i=0; i < m_lines; i++) 00615 { 00616 KateTextLine::Ptr textLine = plainLine(i); 00617 00618 if (textLine) 00619 { 00620 // replace tabs if required 00621 if ( m_doc->configFlags() & KateDocument::cfReplaceTabs ) 00622 { 00623 pos = 0; 00624 while ( textLine->searchText( pos, onetab, &found, &ml ) ) 00625 { 00626 l = tw - ( found%tw ); 00627 if ( l ) 00628 { 00629 QString t; 00630 m_doc->editRemoveText( i, found, 1 ); 00631 m_doc->editInsertText( i, found, t.fill(onespace, l) ); // ### anything more efficient? 00632 pos += l-1; 00633 } 00634 } 00635 } 00636 00637 // remove trailing spaces if required 00638 if ( (m_doc->configFlags() & KateDocument::cfRemoveSpaces) && textLine->length() ) 00639 { 00640 pos = textLine->length() - 1; 00641 uint lns = textLine->lastChar(); 00642 if ( lns != pos ) 00643 m_doc->editRemoveText( i, lns + 1, pos - lns ); 00644 } 00645 00646 stream << textLine->string(); 00647 00648 if ((i+1) < m_lines) 00649 stream << eol; 00650 } 00651 } 00652 00653 if ( m_doc->configFlags() & KateDocument::cfReplaceTabs || 00654 m_doc->configFlags() & KateDocument::cfRemoveSpaces ) 00655 m_doc->editEnd(); 00656 00657 file.close (); 00658 00659 m_loadingBorked = false; 00660 00661 return (file.status() == IO_Ok); 00662 } 00663 00664 KateTextLine::Ptr KateBuffer::line_internal (KateBufBlock *buf, uint i) 00665 { 00666 // update hl until this line + max KATE_HL_LOOKAHEAD 00667 KateBufBlock *buf2 = 0; 00668 while ((i >= m_lineHighlighted) && (buf2 = findBlock(m_lineHighlighted))) 00669 { 00670 uint end = kMin(i + KATE_HL_LOOKAHEAD, buf2->endLine()); 00671 00672 doHighlight ( buf2, 00673 kMax(m_lineHighlighted, buf2->startLine()), 00674 end, 00675 false ); 00676 00677 m_lineHighlighted = end; 00678 } 00679 00680 // update hl max 00681 if (m_lineHighlighted > m_lineHighlightedMax) 00682 m_lineHighlightedMax = m_lineHighlighted; 00683 00684 return buf->line (i - buf->startLine()); 00685 } 00686 00687 KateBufBlock *KateBuffer::findBlock_internal (uint i, uint *index) 00688 { 00689 uint lastLine = m_blocks[m_lastInSyncBlock]->endLine (); 00690 00691 if (lastLine > i) // we are in a allready known area ! 00692 { 00693 while (true) 00694 { 00695 KateBufBlock *buf = m_blocks[m_lastFoundBlock]; 00696 00697 if ( (buf->startLine() <= i) 00698 && (buf->endLine() > i) ) 00699 { 00700 if (index) 00701 (*index) = m_lastFoundBlock; 00702 00703 return m_blocks[m_lastFoundBlock]; 00704 } 00705 00706 if (i < buf->startLine()) 00707 m_lastFoundBlock--; 00708 else 00709 m_lastFoundBlock++; 00710 } 00711 } 00712 else // we need first to resync the startLines ! 00713 { 00714 if ((m_lastInSyncBlock+1) < m_blocks.size()) 00715 m_lastInSyncBlock++; 00716 else 00717 return 0; 00718 00719 for (; m_lastInSyncBlock < m_blocks.size(); m_lastInSyncBlock++) 00720 { 00721 // get next block 00722 KateBufBlock *buf = m_blocks[m_lastInSyncBlock]; 00723 00724 // sync startLine ! 00725 buf->setStartLine (lastLine); 00726 00727 // is it allready the searched block ? 00728 if ((i >= lastLine) && (i < buf->endLine())) 00729 { 00730 // remember this block as last found ! 00731 m_lastFoundBlock = m_lastInSyncBlock; 00732 00733 if (index) 00734 (*index) = m_lastFoundBlock; 00735 00736 return buf; 00737 } 00738 00739 // increase lastLine with blocklinecount 00740 lastLine += buf->lines (); 00741 } 00742 } 00743 00744 // no block found ! 00745 // index will not be set to any useful value in this case ! 00746 return 0; 00747 } 00748 00749 void KateBuffer::changeLine(uint i) 00750 { 00751 KateBufBlock *buf = findBlock(i); 00752 00753 editTagLine (i); 00754 00755 if (buf) 00756 buf->markDirty (); 00757 } 00758 00759 void KateBuffer::insertLine(uint i, KateTextLine::Ptr line) 00760 { 00761 uint index = 0; 00762 KateBufBlock *buf; 00763 if (i == m_lines) 00764 buf = findBlock(i-1, &index); 00765 else 00766 buf = findBlock(i, &index); 00767 00768 if (!buf) 00769 return; 00770 00771 buf->insertLine(i - buf->startLine(), line); 00772 00773 if (m_lineHighlightedMax > i) 00774 m_lineHighlightedMax++; 00775 00776 if (m_lineHighlighted > i) 00777 m_lineHighlighted++; 00778 00779 m_lines++; 00780 00781 // last sync block adjust 00782 if (m_lastInSyncBlock > index) 00783 m_lastInSyncBlock = index; 00784 00785 // last found 00786 if (m_lastInSyncBlock < m_lastFoundBlock) 00787 m_lastFoundBlock = m_lastInSyncBlock; 00788 00789 editInsertTagLine (i); 00790 00791 m_regionTree.lineHasBeenInserted (i); 00792 } 00793 00794 void KateBuffer::removeLine(uint i) 00795 { 00796 uint index = 0; 00797 KateBufBlock *buf = findBlock(i, &index); 00798 00799 if (!buf) 00800 return; 00801 00802 buf->removeLine(i - buf->startLine()); 00803 00804 if (m_lineHighlightedMax > i) 00805 m_lineHighlightedMax--; 00806 00807 if (m_lineHighlighted > i) 00808 m_lineHighlighted--; 00809 00810 m_lines--; 00811 00812 // trash away a empty block 00813 if (buf->lines() == 0) 00814 { 00815 // we need to change which block is last in sync 00816 if (m_lastInSyncBlock >= index) 00817 { 00818 m_lastInSyncBlock = index; 00819 00820 if (buf->next()) 00821 { 00822 if (buf->prev()) 00823 buf->next()->setStartLine (buf->prev()->endLine()); 00824 else 00825 buf->next()->setStartLine (0); 00826 } 00827 } 00828 00829 // cu block ! 00830 delete buf; 00831 m_blocks.erase (m_blocks.begin()+index); 00832 } 00833 else 00834 { 00835 // last sync block adjust 00836 if (m_lastInSyncBlock > index) 00837 m_lastInSyncBlock = index; 00838 } 00839 00840 // last found 00841 if (m_lastInSyncBlock < m_lastFoundBlock) 00842 m_lastFoundBlock = m_lastInSyncBlock; 00843 00844 editRemoveTagLine (i); 00845 00846 m_regionTree.lineHasBeenRemoved (i); 00847 } 00848 00849 void KateBuffer::setTabWidth (uint w) 00850 { 00851 if ((m_tabWidth != w) && (m_tabWidth > 0)) 00852 { 00853 m_tabWidth = w; 00854 00855 if (m_highlight && m_highlight->foldingIndentationSensitive()) 00856 invalidateHighlighting(); 00857 } 00858 } 00859 00860 void KateBuffer::setHighlight(uint hlMode) 00861 { 00862 KateHighlighting *h = KateHlManager::self()->getHl(hlMode); 00863 00864 // aha, hl will change 00865 if (h != m_highlight) 00866 { 00867 bool invalidate = !h->noHighlighting(); 00868 00869 if (m_highlight) 00870 { 00871 m_highlight->release(); 00872 invalidate = true; 00873 } 00874 00875 h->use(); 00876 00877 // try to set indentation 00878 if (!h->indentation().isEmpty()) 00879 m_doc->config()->setIndentationMode (KateAutoIndent::modeNumber(h->indentation())); 00880 00881 m_highlight = h; 00882 00883 if (invalidate) 00884 invalidateHighlighting(); 00885 00886 // inform the document that the hl was really changed 00887 // needed to update attributes and more ;) 00888 m_doc->bufferHlChanged (); 00889 } 00890 } 00891 00892 void KateBuffer::invalidateHighlighting() 00893 { 00894 m_lineHighlightedMax = 0; 00895 m_lineHighlighted = 0; 00896 } 00897 00898 bool KateBuffer::doHighlight (KateBufBlock *buf, uint startLine, uint endLine, bool invalidate) 00899 { 00900 // no hl around, no stuff to do 00901 if (!m_highlight) 00902 return false; 00903 00904 // we tried to start in a line behind this buf block ! 00905 if (startLine >= (buf->startLine()+buf->lines())) 00906 return false; 00907 00908 QTime t; 00909 t.start(); 00910 kdDebug (13020) << "HIGHLIGHTED START --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine << endl; 00911 kdDebug (13020) << "HL UNTIL LINE: " << m_lineHighlighted << " MAX: " << m_lineHighlightedMax << endl; 00912 kdDebug (13020) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts << endl; 00913 00914 // see if there are too many dynamic contexts; if yes, invalidate HL of all documents 00915 if (KateHlManager::self()->countDynamicCtxs() >= m_maxDynamicContexts) 00916 { 00917 { 00918 if (KateHlManager::self()->resetDynamicCtxs()) 00919 { 00920 kdDebug (13020) << "HL invalidated - too many dynamic contexts ( >= " << m_maxDynamicContexts << ")" << endl; 00921 00922 // avoid recursive invalidation 00923 KateHlManager::self()->setForceNoDCReset(true); 00924 00925 for (KateDocument *doc = KateFactory::self()->documents()->first(); doc; doc = KateFactory::self()->documents()->next()) 00926 doc->makeAttribs(); 00927 00928 // doHighlight *shall* do his work. After invalidation, some highlight has 00929 // been recalculated, but *maybe not* until endLine ! So we shall force it manually... 00930 KateBufBlock *buf = 0; 00931 while ((endLine > m_lineHighlighted) && (buf = findBlock(m_lineHighlighted))) 00932 { 00933 uint end = kMin(endLine, buf->endLine()); 00934 00935 doHighlight ( buf, 00936 kMax(m_lineHighlighted, buf->startLine()), 00937 end, 00938 false ); 00939 00940 m_lineHighlighted = end; 00941 } 00942 00943 KateHlManager::self()->setForceNoDCReset(false); 00944 00945 return false; 00946 } 00947 else 00948 { 00949 m_maxDynamicContexts *= 2; 00950 kdDebug (13020) << "New dynamic contexts limit: " << m_maxDynamicContexts << endl; 00951 } 00952 } 00953 } 00954 00955 // get the previous line, if we start at the beginning of this block 00956 // take the last line of the previous block 00957 KateTextLine::Ptr prevLine = 0; 00958 00959 if ((startLine == buf->startLine()) && buf->prev() && (buf->prev()->lines() > 0)) 00960 prevLine = buf->prev()->line (buf->prev()->lines() - 1); 00961 else if ((startLine > buf->startLine()) && (startLine <= buf->endLine())) 00962 prevLine = buf->line(startLine - buf->startLine() - 1); 00963 else 00964 prevLine = new KateTextLine (); 00965 00966 // does we need to emit a signal for the folding changes ? 00967 bool codeFoldingUpdate = false; 00968 00969 // here we are atm, start at start line in the block 00970 uint current_line = startLine - buf->startLine(); 00971 00972 // does we need to continue 00973 bool stillcontinue=false; 00974 00975 // loop over the lines of the block, from startline to endline or end of block 00976 // if stillcontinue forces us to do so 00977 while ( (current_line < buf->lines()) 00978 && (stillcontinue || ((current_line + buf->startLine()) <= endLine)) ) 00979 { 00980 // current line 00981 KateTextLine::Ptr textLine = buf->line(current_line); 00982 00983 QMemArray<uint> foldingList; 00984 bool ctxChanged = false; 00985 00986 m_highlight->doHighlight (prevLine, textLine, &foldingList, &ctxChanged); 00987 00988 // 00989 // indentation sensitive folding 00990 // 00991 bool indentChanged = false; 00992 if (m_highlight->foldingIndentationSensitive()) 00993 { 00994 // get the indentation array of the previous line to start with ! 00995 QMemArray<unsigned short> indentDepth; 00996 indentDepth.duplicate (prevLine->indentationDepthArray()); 00997 00998 // current indentation of this line 00999 uint iDepth = textLine->indentDepth(m_tabWidth); 01000 01001 // this line is empty, beside spaces, use indentation depth of the previous line ! 01002 if (textLine->firstChar() == -1) 01003 { 01004 // do this to get skipped empty lines indent right, which was given in the indenation array 01005 if (!prevLine->indentationDepthArray().isEmpty()) 01006 iDepth = (prevLine->indentationDepthArray())[prevLine->indentationDepthArray().size()-1]; 01007 else 01008 iDepth = prevLine->indentDepth(m_tabWidth); 01009 } 01010 01011 // query the next line indentation, if we are at the end of the block 01012 // use the first line of the next buf block 01013 uint nextLineIndentation = 0; 01014 01015 if ((current_line+1) < buf->lines()) 01016 { 01017 if (buf->line(current_line+1)->firstChar() == -1) 01018 nextLineIndentation = iDepth; 01019 else 01020 nextLineIndentation = buf->line(current_line+1)->indentDepth(m_tabWidth); 01021 } 01022 else 01023 { 01024 KateBufBlock *blk = buf->next(); 01025 01026 if (blk && (blk->lines() > 0)) 01027 { 01028 if (blk->line (0)->firstChar() == -1) 01029 nextLineIndentation = iDepth; 01030 else 01031 nextLineIndentation = blk->line (0)->indentDepth(m_tabWidth); 01032 } 01033 } 01034 01035 // recalculate the indentation array for this line, query if we have to add 01036 // a new folding start, this means newIn == true ! 01037 bool newIn = false; 01038 if ((iDepth > 0) && (indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1] < iDepth))) 01039 { 01040 indentDepth.resize (indentDepth.size()+1, QGArray::SpeedOptim); 01041 indentDepth[indentDepth.size()-1] = iDepth; 01042 newIn = true; 01043 } 01044 else 01045 { 01046 for (int z=indentDepth.size()-1; z > -1; z--) 01047 { 01048 if (indentDepth[z] > iDepth) 01049 indentDepth.resize (z, QGArray::SpeedOptim); 01050 else if (indentDepth[z] == iDepth) 01051 break; 01052 else if (indentDepth[z] < iDepth) 01053 { 01054 indentDepth.resize (indentDepth.size()+1, QGArray::SpeedOptim); 01055 indentDepth[indentDepth.size()-1] = iDepth; 01056 newIn = true; 01057 break; 01058 } 01059 } 01060 } 01061 01062 // just for debugging always true to start with ! 01063 indentChanged = !(indentDepth == textLine->indentationDepthArray()); 01064 01065 // assign the new array to the textline ! 01066 if (indentChanged) 01067 textLine->setIndentationDepth (indentDepth); 01068 01069 // add folding start to the list ! 01070 if (newIn) 01071 { 01072 foldingList.resize (foldingList.size() + 2, QGArray::SpeedOptim); 01073 foldingList[foldingList.size()-2] = 1; 01074 foldingList[foldingList.size()-1] = 0; 01075 } 01076 01077 // calculate how much end folding symbols must be added to the list ! 01078 // remIn gives you the count of them 01079 uint remIn = 0; 01080 01081 for (int z=indentDepth.size()-1; z > -1; z--) 01082 { 01083 if (indentDepth[z] > nextLineIndentation) 01084 remIn++; 01085 else 01086 break; 01087 } 01088 01089 if (remIn > 0) 01090 { 01091 foldingList.resize (foldingList.size() + (remIn*2), QGArray::SpeedOptim); 01092 01093 for (uint z= foldingList.size()-(remIn*2); z < foldingList.size(); z=z+2) 01094 { 01095 foldingList[z] = -1; 01096 foldingList[z+1] = 0; 01097 } 01098 } 01099 } 01100 bool foldingColChanged=false; 01101 bool foldingChanged = false; 01102 if (foldingList.size()!=textLine->foldingListArray().size()) { 01103 foldingChanged=true; 01104 } else { 01105 QMemArray<uint>::ConstIterator it=foldingList.begin(); 01106 QMemArray<uint>::ConstIterator it1=textLine->foldingListArray(); 01107 bool markerType=true; 01108 for(;it!=foldingList.end();++it,++it1) { 01109 if (markerType) { 01110 if ( ((*it)!=(*it1))) { 01111 foldingChanged=true; 01112 foldingColChanged=false; 01113 break; 01114 } 01115 } else { 01116 if ((*it)!=(*it1)) { 01117 foldingColChanged=true; 01118 } 01119 } 01120 markerType=!markerType; 01121 } 01122 } 01123 01124 if (foldingChanged || foldingColChanged) { 01125 textLine->setFoldingList(foldingList); 01126 if (foldingChanged==false){ 01127 textLine->setFoldingColumnsOutdated(textLine->foldingColumnsOutdated() | foldingColChanged); 01128 } else textLine->setFoldingColumnsOutdated(false); 01129 } 01130 bool retVal_folding = false; 01131 //perhaps make en enums out of the change flags 01132 m_regionTree.updateLine (current_line + buf->startLine(), &foldingList, &retVal_folding, foldingChanged,foldingColChanged); 01133 01134 codeFoldingUpdate = codeFoldingUpdate | retVal_folding; 01135 01136 // need we to continue ? 01137 stillcontinue = ctxChanged || indentChanged; 01138 01139 // move around the lines 01140 prevLine = textLine; 01141 01142 // increment line 01143 current_line++; 01144 } 01145 01146 buf->markDirty (); 01147 01148 // tag the changed lines ! 01149 if (invalidate) 01150 emit tagLines (startLine, current_line + buf->startLine()); 01151 01152 // emit that we have changed the folding 01153 if (codeFoldingUpdate) 01154 emit codeFoldingUpdated(); 01155 01156 kdDebug (13020) << "HIGHLIGHTED END --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine << endl; 01157 kdDebug (13020) << "HL UNTIL LINE: " << m_lineHighlighted << " MAX: " << m_lineHighlightedMax << endl; 01158 kdDebug (13020) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts << endl; 01159 kdDebug (13020) << "TIME TAKEN: " << t.elapsed() << endl; 01160 01161 // if we are at the last line of the block + we still need to continue 01162 // return the need of that ! 01163 return stillcontinue && ((current_line+1) == buf->lines()); 01164 } 01165 01166 void KateBuffer::codeFoldingColumnUpdate(unsigned int lineNr) { 01167 KateTextLine::Ptr line=plainLine(lineNr); 01168 if (!line) return; 01169 if (line->foldingColumnsOutdated()) { 01170 line->setFoldingColumnsOutdated(false); 01171 bool tmp; 01172 QMemArray<uint> folding=line->foldingListArray(); 01173 m_regionTree.updateLine(lineNr,&folding,&tmp,true,false); 01174 } 01175 } 01176 01177 //BEGIN KateBufBlock 01178 01179 KateBufBlock::KateBufBlock ( KateBuffer *parent, KateBufBlock *prev, KateBufBlock *next, 01180 KateFileLoader *stream ) 01181 : m_state (KateBufBlock::stateDirty), 01182 m_startLine (0), 01183 m_lines (0), 01184 m_vmblock (0), 01185 m_vmblockSize (0), 01186 m_parent (parent), 01187 m_prev (prev), 01188 m_next (next), 01189 list (0), 01190 listPrev (0), 01191 listNext (0) 01192 { 01193 // init startline + the next pointers of the neighbour blocks 01194 if (m_prev) 01195 { 01196 m_startLine = m_prev->endLine (); 01197 m_prev->m_next = this; 01198 } 01199 01200 if (m_next) 01201 m_next->m_prev = this; 01202 01203 // we have a stream, use it to fill the block ! 01204 // this can lead to 0 line blocks which are invalid ! 01205 if (stream) 01206 { 01207 // this we lead to either dirty or swapped state 01208 fillBlock (stream); 01209 } 01210 else // init the block if no stream given ! 01211 { 01212 // fill in one empty line ! 01213 KateTextLine::Ptr textLine = new KateTextLine (); 01214 m_stringList.push_back (textLine); 01215 m_lines++; 01216 01217 // if we have allready enough blocks around, swap one 01218 if (m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks()) 01219 m_parent->m_loadedBlocks.first()->swapOut(); 01220 01221 // we are a new nearly empty dirty block 01222 m_state = KateBufBlock::stateDirty; 01223 m_parent->m_loadedBlocks.append (this); 01224 } 01225 } 01226 01227 KateBufBlock::~KateBufBlock () 01228 { 01229 // sync prev/next pointers 01230 if (m_prev) 01231 m_prev->m_next = m_next; 01232 01233 if (m_next) 01234 m_next->m_prev = m_prev; 01235 01236 // if we have some swapped data allocated, free it now or never 01237 if (m_vmblock) 01238 KateFactory::self()->vm()->free(m_vmblock); 01239 01240 // remove me from the list I belong 01241 KateBufBlockList::remove (this); 01242 } 01243 01244 void KateBufBlock::fillBlock (KateFileLoader *stream) 01245 { 01246 // is allready too much stuff around in mem ? 01247 bool swap = m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks(); 01248 01249 QByteArray rawData; 01250 01251 // calcs the approx size for KATE_AVG_BLOCK_SIZE chars ! 01252 if (swap) 01253 rawData.resize ((KATE_AVG_BLOCK_SIZE * sizeof(QChar)) + ((KATE_AVG_BLOCK_SIZE/80) * 8)); 01254 01255 char *buf = rawData.data (); 01256 uint size = 0; 01257 uint blockSize = 0; 01258 while (!stream->eof() && (blockSize < KATE_AVG_BLOCK_SIZE) && (m_lines < KATE_MAX_BLOCK_LINES)) 01259 { 01260 uint offset = 0, length = 0; 01261 stream->readLine(offset, length); 01262 const QChar *unicodeData = stream->unicode () + offset; 01263 01264 blockSize += length; 01265 01266 if (swap) 01267 { 01268 // create the swapped data on the fly, no need to waste time 01269 // via going over the textline classes and dump them ! 01270 char attr = KateTextLine::flagNoOtherData; 01271 uint pos = size; 01272 01273 // calc new size 01274 size = size + 1 + sizeof(uint) + (sizeof(QChar)*length); 01275 01276 if (size > rawData.size ()) 01277 { 01278 rawData.resize (size); 01279 buf = rawData.data (); 01280 } 01281 01282 memcpy(buf+pos, (char *) &attr, 1); 01283 pos += 1; 01284 01285 memcpy(buf+pos, (char *) &length, sizeof(uint)); 01286 pos += sizeof(uint); 01287 01288 memcpy(buf+pos, (char *) unicodeData, sizeof(QChar)*length); 01289 pos += sizeof(QChar)*length; 01290 } 01291 else 01292 { 01293 KateTextLine::Ptr textLine = new KateTextLine (); 01294 textLine->insertText (0, length, unicodeData); 01295 m_stringList.push_back (textLine); 01296 } 01297 01298 m_lines++; 01299 } 01300 01301 if (swap) 01302 { 01303 m_vmblock = KateFactory::self()->vm()->allocate(size); 01304 m_vmblockSize = size; 01305 01306 if (!rawData.isEmpty()) 01307 { 01308 if (!KateFactory::self()->vm()->copyBlock(m_vmblock, rawData.data(), 0, size)) 01309 { 01310 if (m_vmblock) 01311 KateFactory::self()->vm()->free(m_vmblock); 01312 01313 m_vmblock = 0; 01314 m_vmblockSize = 0; 01315 01316 m_parent->m_cacheWriteError = true; 01317 } 01318 } 01319 01320 // fine, we are swapped ! 01321 m_state = KateBufBlock::stateSwapped; 01322 } 01323 else 01324 { 01325 // we are a new dirty block without any swap data 01326 m_state = KateBufBlock::stateDirty; 01327 m_parent->m_loadedBlocks.append (this); 01328 } 01329 01330 kdDebug (13020) << "A BLOCK LOADED WITH LINES: " << m_lines << endl; 01331 } 01332 01333 KateTextLine::Ptr KateBufBlock::line(uint i) 01334 { 01335 // take care that the string list is around !!! 01336 if (m_state == KateBufBlock::stateSwapped) 01337 swapIn (); 01338 01339 // LRU 01340 if (!m_parent->m_loadedBlocks.isLast(this)) 01341 m_parent->m_loadedBlocks.append (this); 01342 01343 return m_stringList[i]; 01344 } 01345 01346 void KateBufBlock::insertLine(uint i, KateTextLine::Ptr line) 01347 { 01348 // take care that the string list is around !!! 01349 if (m_state == KateBufBlock::stateSwapped) 01350 swapIn (); 01351 01352 m_stringList.insert (m_stringList.begin()+i, line); 01353 m_lines++; 01354 01355 markDirty (); 01356 } 01357 01358 void KateBufBlock::removeLine(uint i) 01359 { 01360 // take care that the string list is around !!! 01361 if (m_state == KateBufBlock::stateSwapped) 01362 swapIn (); 01363 01364 m_stringList.erase (m_stringList.begin()+i); 01365 m_lines--; 01366 01367 markDirty (); 01368 } 01369 01370 void KateBufBlock::markDirty () 01371 { 01372 if (m_state != KateBufBlock::stateSwapped) 01373 { 01374 // LRU 01375 if (!m_parent->m_loadedBlocks.isLast(this)) 01376 m_parent->m_loadedBlocks.append (this); 01377 01378 if (m_state == KateBufBlock::stateClean) 01379 { 01380 // if we have some swapped data allocated which is dirty, free it now 01381 if (m_vmblock) 01382 KateFactory::self()->vm()->free(m_vmblock); 01383 01384 m_vmblock = 0; 01385 m_vmblockSize = 0; 01386 01387 // we are dirty 01388 m_state = KateBufBlock::stateDirty; 01389 } 01390 } 01391 } 01392 01393 void KateBufBlock::swapIn () 01394 { 01395 if (m_state != KateBufBlock::stateSwapped) 01396 return; 01397 01398 QByteArray rawData (m_vmblockSize); 01399 01400 // what to do if that fails ? 01401 if (!KateFactory::self()->vm()->copyBlock(rawData.data(), m_vmblock, 0, rawData.size())) 01402 m_parent->m_cacheReadError = true; 01403 01404 // reserve mem, keep realloc away on push_back 01405 m_stringList.reserve (m_lines); 01406 01407 char *buf = rawData.data(); 01408 for (uint i=0; i < m_lines; i++) 01409 { 01410 KateTextLine::Ptr textLine = new KateTextLine (); 01411 buf = textLine->restore (buf); 01412 m_stringList.push_back (textLine); 01413 } 01414 01415 // if we have allready enough blocks around, swap one 01416 if (m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks()) 01417 m_parent->m_loadedBlocks.first()->swapOut(); 01418 01419 // fine, we are now clean again, save state + append to clean list 01420 m_state = KateBufBlock::stateClean; 01421 m_parent->m_loadedBlocks.append (this); 01422 } 01423 01424 void KateBufBlock::swapOut () 01425 { 01426 if (m_state == KateBufBlock::stateSwapped) 01427 return; 01428 01429 if (m_state == KateBufBlock::stateDirty) 01430 { 01431 bool haveHl = m_parent->m_highlight && !m_parent->m_highlight->noHighlighting(); 01432 01433 // Calculate size. 01434 uint size = 0; 01435 for (uint i=0; i < m_lines; i++) 01436 size += m_stringList[i]->dumpSize (haveHl); 01437 01438 QByteArray rawData (size); 01439 char *buf = rawData.data(); 01440 01441 // Dump textlines 01442 for (uint i=0; i < m_lines; i++) 01443 buf = m_stringList[i]->dump (buf, haveHl); 01444 01445 m_vmblock = KateFactory::self()->vm()->allocate(rawData.size()); 01446 m_vmblockSize = rawData.size(); 01447 01448 if (!rawData.isEmpty()) 01449 { 01450 if (!KateFactory::self()->vm()->copyBlock(m_vmblock, rawData.data(), 0, rawData.size())) 01451 { 01452 if (m_vmblock) 01453 KateFactory::self()->vm()->free(m_vmblock); 01454 01455 m_vmblock = 0; 01456 m_vmblockSize = 0; 01457 01458 m_parent->m_cacheWriteError = true; 01459 01460 return; 01461 } 01462 } 01463 } 01464 01465 m_stringList.clear(); 01466 01467 // we are now swapped out, set state + remove us out of the lists ! 01468 m_state = KateBufBlock::stateSwapped; 01469 KateBufBlockList::remove (this); 01470 } 01471 01472 //END KateBufBlock 01473 01474 //BEGIN KateBufBlockList 01475 01476 KateBufBlockList::KateBufBlockList () 01477 : m_count (0), 01478 m_first (0), 01479 m_last (0) 01480 { 01481 } 01482 01483 void KateBufBlockList::append (KateBufBlock *buf) 01484 { 01485 if (buf->list) 01486 buf->list->removeInternal (buf); 01487 01488 m_count++; 01489 01490 // append a element 01491 if (m_last) 01492 { 01493 m_last->listNext = buf; 01494 01495 buf->listPrev = m_last; 01496 buf->listNext = 0; 01497 01498 m_last = buf; 01499 01500 buf->list = this; 01501 01502 return; 01503 } 01504 01505 // insert the first element 01506 m_last = buf; 01507 m_first = buf; 01508 01509 buf->listPrev = 0; 01510 buf->listNext = 0; 01511 01512 buf->list = this; 01513 } 01514 01515 void KateBufBlockList::removeInternal (KateBufBlock *buf) 01516 { 01517 if (buf->list != this) 01518 return; 01519 01520 m_count--; 01521 01522 if ((buf == m_first) && (buf == m_last)) 01523 { 01524 // last element removed ! 01525 m_first = 0; 01526 m_last = 0; 01527 } 01528 else if (buf == m_first) 01529 { 01530 // first element removed 01531 m_first = buf->listNext; 01532 m_first->listPrev = 0; 01533 } 01534 else if (buf == m_last) 01535 { 01536 // last element removed 01537 m_last = buf->listPrev; 01538 m_last->listNext = 0; 01539 } 01540 else 01541 { 01542 buf->listPrev->listNext = buf->listNext; 01543 buf->listNext->listPrev = buf->listPrev; 01544 } 01545 01546 buf->listPrev = 0; 01547 buf->listNext = 0; 01548 01549 buf->list = 0; 01550 } 01551 01552 //END KateBufBlockList 01553 01554 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE Logo
This file is part of the documentation for kate Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Apr 12 23:39:57 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003