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