kateautoindent.cpp

00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2003 Jesse Yurkovich <yurkjes@iit.edu>
00003    Copyright (C) 2004 >Anders Lund <anders@alweb.dk> (KateVarIndent class)
00004    Copyright (C) 2005 Dominik Haumann <dhdev@gmx.de> (basic support for config page)
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License version 2 as published by the Free Software Foundation.
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 "kateautoindent.h"
00022 #include "kateautoindent.moc"
00023 
00024 #include "kateconfig.h"
00025 #include "katehighlight.h"
00026 #include "katefactory.h"
00027 #include "katejscript.h"
00028 #include "kateview.h"
00029 
00030 #include <klocale.h>
00031 #include <kdebug.h>
00032 #include <kpopupmenu.h>
00033 
00034 #include <cctype>
00035 
00036 //BEGIN KateAutoIndent
00037 
00038 KateAutoIndent *KateAutoIndent::createIndenter (KateDocument *doc, uint mode)
00039 {
00040   if (mode == KateDocumentConfig::imNormal)
00041     return new KateNormalIndent (doc);
00042   else if (mode == KateDocumentConfig::imCStyle)
00043     return new KateCSmartIndent (doc);
00044   else if (mode == KateDocumentConfig::imPythonStyle)
00045     return new KatePythonIndent (doc);
00046   else if (mode == KateDocumentConfig::imXmlStyle)
00047     return new KateXmlIndent (doc);
00048   else if (mode == KateDocumentConfig::imCSAndS)
00049     return new KateCSAndSIndent (doc);
00050   else if ( mode == KateDocumentConfig::imVarIndent )
00051     return new KateVarIndent ( doc );
00052 //  else if ( mode == KateDocumentConfig::imScriptIndent)
00053 //    return new KateScriptIndent ( doc );
00054 
00055   return new KateAutoIndent (doc);
00056 }
00057 
00058 QStringList KateAutoIndent::listModes ()
00059 {
00060   QStringList l;
00061 
00062   l << modeDescription(KateDocumentConfig::imNone);
00063   l << modeDescription(KateDocumentConfig::imNormal);
00064   l << modeDescription(KateDocumentConfig::imCStyle);
00065   l << modeDescription(KateDocumentConfig::imPythonStyle);
00066   l << modeDescription(KateDocumentConfig::imXmlStyle);
00067   l << modeDescription(KateDocumentConfig::imCSAndS);
00068   l << modeDescription(KateDocumentConfig::imVarIndent);
00069 //  l << modeDescription(KateDocumentConfig::imScriptIndent);
00070 
00071   return l;
00072 }
00073 
00074 QString KateAutoIndent::modeName (uint mode)
00075 {
00076   if (mode == KateDocumentConfig::imNormal)
00077     return QString ("normal");
00078   else if (mode == KateDocumentConfig::imCStyle)
00079     return QString ("cstyle");
00080   else if (mode == KateDocumentConfig::imPythonStyle)
00081     return QString ("python");
00082   else if (mode == KateDocumentConfig::imXmlStyle)
00083     return QString ("xml");
00084   else if (mode == KateDocumentConfig::imCSAndS)
00085     return QString ("csands");
00086   else if ( mode  == KateDocumentConfig::imVarIndent )
00087     return QString( "varindent" );
00088 //  else if ( mode  == KateDocumentConfig::imScriptIndent )
00089 //    return QString( "scriptindent" );
00090 
00091   return QString ("none");
00092 }
00093 
00094 QString KateAutoIndent::modeDescription (uint mode)
00095 {
00096   if (mode == KateDocumentConfig::imNormal)
00097     return i18n ("Normal");
00098   else if (mode == KateDocumentConfig::imCStyle)
00099     return i18n ("C Style");
00100   else if (mode == KateDocumentConfig::imPythonStyle)
00101     return i18n ("Python Style");
00102   else if (mode == KateDocumentConfig::imXmlStyle)
00103     return i18n ("XML Style");
00104   else if (mode == KateDocumentConfig::imCSAndS)
00105     return i18n ("S&S C Style");
00106   else if ( mode == KateDocumentConfig::imVarIndent )
00107     return i18n("Variable Based Indenter");
00108 //  else if ( mode == KateDocumentConfig::imScriptIndent )
00109 //    return i18n("JavaScript Indenter");
00110 
00111   return i18n ("None");
00112 }
00113 
00114 uint KateAutoIndent::modeNumber (const QString &name)
00115 {
00116   if (modeName(KateDocumentConfig::imNormal) == name)
00117     return KateDocumentConfig::imNormal;
00118   else if (modeName(KateDocumentConfig::imCStyle) == name)
00119     return KateDocumentConfig::imCStyle;
00120   else if (modeName(KateDocumentConfig::imPythonStyle) == name)
00121     return KateDocumentConfig::imPythonStyle;
00122   else if (modeName(KateDocumentConfig::imXmlStyle) == name)
00123     return KateDocumentConfig::imXmlStyle;
00124   else if (modeName(KateDocumentConfig::imCSAndS) == name)
00125     return KateDocumentConfig::imCSAndS;
00126   else if ( modeName( KateDocumentConfig::imVarIndent ) == name )
00127     return KateDocumentConfig::imVarIndent;
00128 //  else if ( modeName( KateDocumentConfig::imScriptIndent ) == name )
00129 //    return KateDocumentConfig::imScriptIndent;
00130 
00131   return KateDocumentConfig::imNone;
00132 }
00133 
00134 bool KateAutoIndent::hasConfigPage (uint mode)
00135 {
00136 //  if ( mode == KateDocumentConfig::imScriptIndent )
00137 //    return true;
00138 
00139   return false;
00140 }
00141 
00142 IndenterConfigPage* KateAutoIndent::configPage(QWidget *parent, uint mode)
00143 {
00144 //  if ( mode == KateDocumentConfig::imScriptIndent )
00145 //    return new ScriptIndentConfigPage(parent, "script_indent_config_page");
00146 
00147   return 0;
00148 }
00149 
00150 KateAutoIndent::KateAutoIndent (KateDocument *_doc)
00151 : doc(_doc)
00152 {
00153 }
00154 KateAutoIndent::~KateAutoIndent ()
00155 {
00156 }
00157 
00158 //END KateAutoIndent
00159 
00160 //BEGIN KateViewIndentAction
00161 KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject* parent, const char* name)
00162        : KActionMenu (text, parent, name), doc(_doc)
00163 {
00164   connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow()));
00165 }
00166 
00167 void KateViewIndentationAction::slotAboutToShow()
00168 {
00169   QStringList modes = KateAutoIndent::listModes ();
00170 
00171   popupMenu()->clear ();
00172   for (uint z=0; z<modes.size(); ++z)
00173     popupMenu()->insertItem ( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&"), this, SLOT(setMode(int)), 0,  z);
00174 
00175   popupMenu()->setItemChecked (doc->config()->indentationMode(), true);
00176 }
00177 
00178 void KateViewIndentationAction::setMode (int mode)
00179 {
00180   doc->config()->setIndentationMode((uint)mode);
00181 }
00182 //END KateViewIndentationAction
00183 
00184 //BEGIN KateNormalIndent
00185 
00186 KateNormalIndent::KateNormalIndent (KateDocument *_doc)
00187  : KateAutoIndent (_doc)
00188 {
00189 }
00190 KateNormalIndent::~KateNormalIndent ()
00191 {
00192 }
00193 
00194 void KateNormalIndent::updateConfig ()
00195 {
00196   KateDocumentConfig *config = doc->config();
00197 
00198   useSpaces   = config->configFlags() & KateDocument::cfSpaceIndent || config->configFlags() & KateDocumentConfig::cfReplaceTabsDyn;
00199   mixedIndent = useSpaces && config->configFlags() & KateDocumentConfig::cfMixedIndent;
00200   keepProfile = config->configFlags() & KateDocument::cfKeepIndentProfile;
00201   tabWidth    = config->tabWidth();
00202   indentWidth = useSpaces? config->indentationWidth() : tabWidth;
00203 
00204   commentAttrib = 255;
00205   doxyCommentAttrib = 255;
00206   regionAttrib = 255;
00207   symbolAttrib = 255;
00208   alertAttrib = 255;
00209   tagAttrib = 255;
00210   wordAttrib = 255;
00211   keywordAttrib = 255;
00212   normalAttrib = 255;
00213   extensionAttrib = 255;
00214 
00215   KateHlItemDataList items;
00216   doc->highlight()->getKateHlItemDataListCopy (0, items);
00217 
00218   for (uint i=0; i<items.count(); i++)
00219   {
00220     QString name = items.at(i)->name;
00221     if (name.find("Comment") != -1 && commentAttrib == 255)
00222     {
00223       commentAttrib = i;
00224     }
00225     else if (name.find("Region Marker") != -1 && regionAttrib == 255)
00226     {
00227       regionAttrib = i;
00228     }
00229     else if (name.find("Symbol") != -1 && symbolAttrib == 255)
00230     {
00231       symbolAttrib = i;
00232     }
00233     else if (name.find("Alert") != -1)
00234     {
00235       alertAttrib = i;
00236     }
00237     else if (name.find("Comment") != -1 && commentAttrib != 255 && doxyCommentAttrib == 255)
00238     {
00239       doxyCommentAttrib = i;
00240     }
00241     else if (name.find("Tags") != -1 && tagAttrib == 255)
00242     {
00243       tagAttrib = i;
00244     }
00245     else if (name.find("Word") != -1 && wordAttrib == 255)
00246     {
00247       wordAttrib = i;
00248     }
00249     else if (name.find("Keyword") != -1 && keywordAttrib == 255)
00250     {
00251       keywordAttrib = i;
00252     }
00253     else if (name.find("Normal") != -1 && normalAttrib == 255)
00254     {
00255       normalAttrib = i;
00256     }
00257     else if (name.find("Extensions") != -1 && extensionAttrib == 255)
00258     {
00259       extensionAttrib = i;
00260     }
00261   }
00262 }
00263 
00264 bool KateNormalIndent::isBalanced (KateDocCursor &begin, const KateDocCursor &end, QChar open, QChar close, uint &pos) const
00265 {
00266   int parenOpen = 0;
00267   bool atLeastOne = false;
00268   bool getNext = false;
00269 
00270   pos = doc->plainKateTextLine(begin.line())->firstChar();
00271 
00272   // Iterate one-by-one finding opening and closing chars
00273   // Assume that open and close are 'Symbol' characters
00274   while (begin < end)
00275   {
00276     QChar c = begin.currentChar();
00277     if (begin.currentAttrib() == symbolAttrib)
00278     {
00279       if (c == open)
00280       {
00281         if (!atLeastOne)
00282         {
00283           atLeastOne = true;
00284           getNext = true;
00285           pos = measureIndent(begin) + 1;
00286         }
00287         parenOpen++;
00288       }
00289       else if (c == close)
00290       {
00291         parenOpen--;
00292       }
00293     }
00294     else if (getNext && !c.isSpace())
00295     {
00296       getNext = false;
00297       pos = measureIndent(begin);
00298     }
00299 
00300     if (atLeastOne && parenOpen <= 0)
00301       return true;
00302 
00303     begin.moveForward(1);
00304   }
00305 
00306   return (atLeastOne) ? false : true;
00307 }
00308 
00309 bool KateNormalIndent::skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const
00310 {
00311   int curLine = cur.line();
00312   if (newline)
00313     cur.moveForward(1);
00314 
00315   if (cur >= max)
00316     return false;
00317 
00318   do
00319   {
00320     uchar attrib = cur.currentAttrib();
00321     const QString hlFile = doc->highlight()->hlKeyForAttrib( attrib );
00322 
00323     if (attrib != commentAttrib && attrib != regionAttrib && attrib != alertAttrib && !hlFile.endsWith("doxygen.xml"))
00324     {
00325       QChar c = cur.currentChar();
00326       if (!c.isNull() && !c.isSpace())
00327         break;
00328     }
00329 
00330     // Make sure col is 0 if we spill into next line  i.e. count the '\n' as a character
00331     if (!cur.moveForward(1))
00332       break;
00333     if (curLine != cur.line())
00334     {
00335       if (!newline)
00336         break;
00337       curLine = cur.line();
00338       cur.setCol(0);
00339     }
00340   } while (cur < max);
00341 
00342   if (cur > max)
00343     cur = max;
00344   return true;
00345 }
00346 
00347 uint KateNormalIndent::measureIndent (KateDocCursor &cur) const
00348 {
00349   // We cannot short-cut by checking for useSpaces because there may be
00350   // tabs in the line despite this setting.
00351 
00352   return doc->plainKateTextLine(cur.line())->cursorX(cur.col(), tabWidth);
00353 }
00354 
00355 QString KateNormalIndent::tabString(uint pos) const
00356 {
00357   QString s;
00358   pos = kMin (pos, 80U); // sanity check for large values of pos
00359 
00360   if (!useSpaces || mixedIndent)
00361   {
00362     while (pos >= tabWidth)
00363     {
00364       s += '\t';
00365       pos -= tabWidth;
00366     }
00367   }
00368   while (pos > 0)
00369   {
00370     s += ' ';
00371     pos--;
00372   }
00373   return s;
00374 }
00375 
00376 void KateNormalIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/)
00377 {
00378   int line = begin.line() - 1;
00379   int pos = begin.col();
00380 
00381   while ((line > 0) && (pos < 0)) // search a not empty text line
00382     pos = doc->plainKateTextLine(--line)->firstChar();
00383 
00384   if (pos > 0)
00385   {
00386     QString filler = doc->text(line, 0, line, pos);
00387     doc->insertText(begin.line(), 0, filler);
00388     begin.setCol(filler.length());
00389   }
00390   else
00391     begin.setCol(0);
00392 }
00393 
00394 //END
00395 
00396 //BEGIN KateCSmartIndent
00397 
00398 KateCSmartIndent::KateCSmartIndent (KateDocument *doc)
00399 :  KateNormalIndent (doc),
00400     allowSemi (false),
00401     processingBlock (false)
00402 {
00403   kdDebug(13030)<<"CREATING KATECSMART INTDETER"<<endl;
00404 }
00405 
00406 KateCSmartIndent::~KateCSmartIndent ()
00407 {
00408 
00409 }
00410 
00411 void KateCSmartIndent::processLine (KateDocCursor &line)
00412 {
00413   kdDebug(13030)<<"PROCESSING LINE "<<line.line()<<endl;
00414   KateTextLine::Ptr textLine = doc->plainKateTextLine(line.line());
00415 
00416   int firstChar = textLine->firstChar();
00417   // Empty line is worthless ... but only when doing more than 1 line
00418   if (firstChar == -1 && processingBlock)
00419     return;
00420 
00421   uint indent = 0;
00422 
00423   // TODO Here we do not check for beginning and ending comments ...
00424   QChar first = textLine->getChar(firstChar);
00425   QChar last = textLine->getChar(textLine->lastChar());
00426 
00427   if (first == '}')
00428   {
00429     indent = findOpeningBrace(line);
00430   }
00431   else if (first == ')')
00432   {
00433     indent = findOpeningParen(line);
00434   }
00435   else if (first == '{')
00436   {
00437     // If this is the first brace, we keep the indent at 0
00438     KateDocCursor temp(line.line(), firstChar, doc);
00439     if (!firstOpeningBrace(temp))
00440       indent = calcIndent(temp, false);
00441   }
00442   else if (first == ':')
00443   {
00444     // Initialization lists (handle c++ and c#)
00445     int pos = findOpeningBrace(line);
00446     if (pos == 0)
00447       indent = indentWidth;
00448     else
00449       indent = pos + (indentWidth * 2);
00450   }
00451   else if (last == ':')
00452   {
00453     if (textLine->stringAtPos (firstChar, "case") ||
00454         textLine->stringAtPos (firstChar, "default") ||
00455         textLine->stringAtPos (firstChar, "public") ||
00456         textLine->stringAtPos (firstChar, "private") ||
00457         textLine->stringAtPos (firstChar, "protected") ||
00458         textLine->stringAtPos (firstChar, "signals") ||
00459         textLine->stringAtPos (firstChar, "Q_SIGNALS") ||
00460         textLine->stringAtPos (firstChar, "Q_SLOTS") ||
00461         textLine->stringAtPos (firstChar, "slots"))
00462     {
00463       indent = findOpeningBrace(line) + indentWidth;
00464     }
00465   }
00466   else if (first == '*')
00467   {
00468     if (last == '/')
00469     {
00470       int lineEnd = textLine->lastChar();
00471       if (lineEnd > 0 && textLine->getChar(lineEnd - 1) == '*')
00472       {
00473         indent = findOpeningComment(line);
00474         if (textLine->attribute(firstChar) == doxyCommentAttrib)
00475           indent++;
00476       }
00477       else
00478         return;
00479     }
00480     else
00481     {
00482       KateDocCursor temp = line;
00483       if (textLine->attribute(firstChar) == doxyCommentAttrib)
00484         indent = calcIndent(temp, false) + 1;
00485       else
00486         indent = calcIndent(temp, true);
00487     }
00488   }
00489   else if (first == '#')
00490   {
00491     // c# regions
00492     if (textLine->stringAtPos (firstChar, "#region") ||
00493         textLine->stringAtPos (firstChar, "#endregion"))
00494     {
00495       KateDocCursor temp = line;
00496       indent = calcIndent(temp, true);
00497     }
00498   }
00499   else
00500   {
00501     // Everything else ...
00502     if (first == '/' && last != '/')
00503       return;
00504 
00505     KateDocCursor temp = line;
00506     indent = calcIndent(temp, true);
00507     if (indent == 0)
00508     {
00509       KateNormalIndent::processNewline(line, true);
00510       return;
00511     }
00512   }
00513 
00514   // Slightly faster if we don't indent what we don't have to
00515   if (indent != measureIndent(line) || first == '}' || first == '{' || first == '#')
00516   {
00517     doc->removeText(line.line(), 0, line.line(), firstChar);
00518     QString filler = tabString(indent);
00519     if (indent > 0) doc->insertText(line.line(), 0, filler);
00520     if (!processingBlock) line.setCol(filler.length());
00521   }
00522 }
00523 
00524 void KateCSmartIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end)
00525 {
00526   kdDebug(13030)<<"PROCESS SECTION"<<endl;
00527   KateDocCursor cur = begin;
00528   QTime t;
00529   t.start();
00530 
00531   processingBlock = (end.line() - cur.line() > 0) ? true : false;
00532 
00533   while (cur.line() <= end.line())
00534   {
00535     processLine (cur);
00536     if (!cur.gotoNextLine())
00537       break;
00538   }
00539 
00540   processingBlock = false;
00541   kdDebug(13030) << "+++ total: " << t.elapsed() << endl;
00542 }
00543 
00544 bool KateCSmartIndent::handleDoxygen (KateDocCursor &begin)
00545 {
00546   // Factor out the rather involved Doxygen stuff here ...
00547   int line = begin.line();
00548   int first = -1;
00549   while ((line > 0) && (first < 0))
00550     first = doc->plainKateTextLine(--line)->firstChar();
00551 
00552   if (first >= 0)
00553   {
00554     KateTextLine::Ptr textLine = doc->plainKateTextLine(line);
00555     bool insideDoxygen = false;
00556     if (textLine->attribute(first) == doxyCommentAttrib || textLine->attribute(textLine->lastChar()) == doxyCommentAttrib)
00557     {
00558       if (!textLine->stringAtPos(textLine->lastChar()-1, "*/"))
00559         insideDoxygen = true;
00560       while (textLine->attribute(first) != doxyCommentAttrib && first <= textLine->lastChar())
00561         first++;
00562       if (textLine->stringAtPos(first, "//"))
00563         return false;
00564     }
00565 
00566     // Align the *'s and then go ahead and insert one too ...
00567     if (insideDoxygen)
00568     {
00569       textLine = doc->plainKateTextLine(begin.line());
00570       first = textLine->firstChar();
00571       int indent = findOpeningComment(begin);
00572       QString filler = tabString (indent);
00573 
00574       bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping;
00575 
00576       if ( doxygenAutoInsert &&
00577            ((first < 0) || (!textLine->stringAtPos(first, "*/") && !textLine->stringAtPos(first, "*"))))
00578       {
00579         filler = filler + " * ";
00580       }
00581 
00582       doc->removeText (begin.line(), 0, begin.line(), first);
00583       doc->insertText (begin.line(), 0, filler);
00584       begin.setCol(filler.length());
00585 
00586       return true;
00587     }
00588   }
00589 
00590   return false;
00591 }
00592 
00593 void KateCSmartIndent::processNewline (KateDocCursor &begin, bool needContinue)
00594 {
00595   if (!handleDoxygen (begin))
00596   {
00597     KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line());
00598     bool inMiddle = textLine->firstChar() > -1;
00599 
00600     int indent = calcIndent (begin, needContinue);
00601 
00602     if (indent > 0 || inMiddle)
00603     {
00604       QString filler = tabString (indent);
00605       doc->insertText (begin.line(), 0, filler);
00606       begin.setCol(filler.length());
00607 
00608       // Handles cases where user hits enter at the beginning or middle of text
00609       if (inMiddle)
00610       {
00611         processLine(begin);
00612         begin.setCol(textLine->firstChar());
00613       }
00614     }
00615     else
00616     {
00617       KateNormalIndent::processNewline (begin, needContinue);
00618     }
00619 
00620     if (begin.col() < 0)
00621       begin.setCol(0);
00622   }
00623 }
00624 
00625 void KateCSmartIndent::processChar(QChar c)
00626 {
00627   // You may be curious about 'n' among the triggers:
00628   // It is used to discriminate C#'s #region/#endregion which are indented
00629   // against normal preprocessing statements which aren't indented.
00630   static const QString triggers("}{)/:#n");
00631   static const QString firstTriggers("}{)/:#");
00632   static const QString lastTriggers(":n");
00633   if (triggers.find(c) < 0)
00634     return;
00635 
00636   KateView *view = doc->activeView();
00637   KateDocCursor begin(view->cursorLine(), 0, doc);
00638 
00639   KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line());
00640   const int first = textLine->firstChar();
00641   const QChar firstChar = textLine->getChar(first);
00642   if (c == 'n')
00643   {
00644     if (firstChar != '#')
00645       return;
00646   }
00647 
00648   if ( c == '/' )
00649   {
00650     // dominik: if line is "* /", change it to "*/"
00651     if ( textLine->attribute( begin.col() ) == doxyCommentAttrib )
00652     {
00653       // if the first char exists and is a '*', and the next non-space-char
00654       // is already the just typed '/', concatenate it to "*/".
00655       if ( first != -1
00656            && firstChar == '*'
00657            && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 )
00658         doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1);
00659     }
00660 
00661     // ls: never have comments change the indentation.
00662     return;
00663   }
00664 
00665   // ls: only reindent line if the user actually expects it
00666   // I. e. take action on single braces on line or last colon, but inhibit
00667   // any reindentation if any of those characters appear amidst some section
00668   // of the line
00669   const QChar lastChar = textLine->getChar(textLine->lastChar());
00670   if ((c == firstChar && firstTriggers.find(firstChar) >= 0)
00671       || (c == lastChar && lastTriggers.find(lastChar) >= 0))
00672     processLine(begin);
00673 }
00674 
00675 
00676 uint KateCSmartIndent::calcIndent(KateDocCursor &begin, bool needContinue)
00677 {
00678   KateTextLine::Ptr textLine;
00679   KateDocCursor cur = begin;
00680 
00681   uint anchorIndent = 0;
00682   int anchorPos = 0;
00683   int parenCount = 0;  // Possibly in a multiline for stmt.  Used to skip ';' ...
00684   bool found = false;
00685   bool isSpecial = false;
00686 
00687   //kdDebug(13030) << "calcIndent begin line:" << begin.line() << " col:" << begin.col() << endl;
00688 
00689   // Find Indent Anchor Point
00690   while (cur.gotoPreviousLine())
00691   {
00692     isSpecial = found = false;
00693     textLine = doc->plainKateTextLine(cur.line());
00694 
00695     // Skip comments and handle cases like if (...) { stmt;
00696     int pos = textLine->lastChar();
00697     int openCount = 0;
00698     int otherAnchor = -1;
00699     do
00700     {
00701       if (textLine->attribute(pos) == symbolAttrib)
00702       {
00703         QChar tc = textLine->getChar (pos);
00704         if ((tc == ';' || tc == ':' || tc == ',') && otherAnchor == -1 && parenCount <= 0)
00705           otherAnchor = pos;
00706         else if (tc == ')')
00707           parenCount++;
00708         else if (tc == '(')
00709           parenCount--;
00710         else if (tc == '}')
00711           openCount--;
00712         else if (tc == '{')
00713         {
00714           openCount++;
00715           if (openCount == 1)
00716             break;
00717         }
00718       }
00719     } while (--pos >= textLine->firstChar());
00720 
00721     if (openCount != 0 || otherAnchor != -1)
00722     {
00723       found = true;
00724       QChar c;
00725       if (openCount > 0)
00726         c = '{';
00727       else if (openCount < 0)
00728         c = '}';
00729       else if (otherAnchor >= 0)
00730         c = textLine->getChar (otherAnchor);
00731 
00732       int specialIndent = 0;
00733       if (c == ':' && needContinue)
00734       {
00735         QChar ch;
00736         specialIndent = textLine->firstChar();
00737         if (textLine->stringAtPos(specialIndent, "case"))
00738           ch = textLine->getChar(specialIndent + 4);
00739         else if (textLine->stringAtPos(specialIndent, "default"))
00740           ch = textLine->getChar(specialIndent + 7);
00741         else if (textLine->stringAtPos(specialIndent, "public"))
00742           ch = textLine->getChar(specialIndent + 6);
00743         else if (textLine->stringAtPos(specialIndent, "private"))
00744           ch = textLine->getChar(specialIndent + 7);
00745         else if (textLine->stringAtPos(specialIndent, "protected"))
00746           ch = textLine->getChar(specialIndent + 9);
00747         else if (textLine->stringAtPos(specialIndent, "signals"))
00748           ch = textLine->getChar(specialIndent + 7);
00749         else if (textLine->stringAtPos(specialIndent, "Q_SIGNALS"))
00750           ch = textLine->getChar(specialIndent + 9);
00751         else if (textLine->stringAtPos(specialIndent, "slots"))
00752           ch = textLine->getChar(specialIndent + 5);
00753         else if (textLine->stringAtPos(specialIndent, "Q_SLOTS"))
00754           ch = textLine->getChar(specialIndent + 7);
00755 
00756         if (ch.isNull() || (!ch.isSpace() && ch != '(' && ch != ':'))
00757           continue;
00758 
00759         KateDocCursor lineBegin = cur;
00760         lineBegin.setCol(specialIndent);
00761         specialIndent = measureIndent(lineBegin);
00762         isSpecial = true;
00763       }
00764 
00765       // Move forward past blank lines
00766       KateDocCursor skip = cur;
00767       skip.setCol(textLine->lastChar());
00768       bool result = skipBlanks(skip, begin, true);
00769 
00770       anchorPos = skip.col();
00771       anchorIndent = measureIndent(skip);
00772 
00773       //kdDebug(13030) << "calcIndent anchorPos:" << anchorPos << " anchorIndent:" << anchorIndent << " at line:" << skip.line() << endl;
00774 
00775       // Accept if it's before requested position or if it was special
00776       if (result && skip < begin)
00777       {
00778         cur = skip;
00779         break;
00780       }
00781       else if (isSpecial)
00782       {
00783         anchorIndent = specialIndent;
00784         break;
00785       }
00786 
00787       // Are these on a line by themselves? (i.e. both last and first char)
00788       if ((c == '{' || c == '}') && textLine->getChar(textLine->firstChar()) == c)
00789       {
00790         cur.setCol(anchorPos = textLine->firstChar());
00791         anchorIndent = measureIndent (cur);
00792         break;
00793       }
00794     }
00795   }
00796 
00797   if (!found)
00798     return 0;
00799 
00800   uint continueIndent = (needContinue) ? calcContinue (cur, begin) : 0;
00801   //kdDebug(13030) << "calcIndent continueIndent:" << continueIndent << endl;
00802 
00803   // Move forward from anchor and determine last known reference character
00804   // Braces take precedance over others ...
00805   textLine = doc->plainKateTextLine(cur.line());
00806   QChar lastChar = textLine->getChar (anchorPos);
00807   int lastLine = cur.line();
00808   if (lastChar == '#' || lastChar == '[')
00809   {
00810     // Never continue if # or [ is encountered at this point here
00811     // A fail-safe really... most likely an #include, #region, or a c# attribute
00812     continueIndent = 0;
00813   }
00814 
00815   int openCount = 0;
00816   while (cur.validPosition() && cur < begin)
00817   {
00818     if (!skipBlanks(cur, begin, true))
00819       return 0;
00820 
00821     QChar tc = cur.currentChar();
00822     //kdDebug(13030) << "  cur.line:" << cur.line() << " cur.col:" << cur.col() << " currentChar '" << tc << "' " << textLine->attribute(cur.col()) << endl;
00823     if (cur == begin || tc.isNull())
00824       break;
00825 
00826     if (!tc.isSpace() && cur < begin)
00827     {
00828       uchar attrib = cur.currentAttrib();
00829       if (tc == '{' && attrib == symbolAttrib)
00830         openCount++;
00831       else if (tc == '}' && attrib == symbolAttrib)
00832         openCount--;
00833 
00834       lastChar = tc;
00835       lastLine = cur.line();
00836     }
00837   }
00838   if (openCount > 0) // Open braces override
00839     lastChar = '{';
00840 
00841   uint indent = 0;
00842   //kdDebug(13030) << "calcIndent lastChar '" << lastChar << "'" << endl;
00843 
00844   if (lastChar == '{' || (lastChar == ':' && isSpecial && needContinue))
00845   {
00846     indent = anchorIndent + indentWidth;
00847   }
00848   else if (lastChar == '}')
00849   {
00850     indent = anchorIndent;
00851   }
00852   else if (lastChar == ';')
00853   {
00854     indent = anchorIndent + ((allowSemi && needContinue) ? continueIndent : 0);
00855   }
00856   else if (lastChar == ',')
00857   {
00858     textLine = doc->plainKateTextLine(lastLine);
00859     KateDocCursor start(lastLine, textLine->firstChar(), doc);
00860     KateDocCursor finish(lastLine, textLine->lastChar(), doc);
00861     uint pos = 0;
00862 
00863     if (isBalanced(start, finish, QChar('('), QChar(')'), pos))
00864       indent = anchorIndent;
00865     else
00866     {
00867       // TODO: Config option. If we're below 48, go ahead and line them up
00868       indent = ((pos < 48) ? pos : anchorIndent + (indentWidth * 2));
00869     }
00870   }
00871   else if (!lastChar.isNull())
00872   {
00873     if (anchorIndent != 0)
00874       indent = anchorIndent + continueIndent;
00875     else
00876       indent = continueIndent;
00877   }
00878 
00879   return indent;
00880 }
00881 
00882 uint KateCSmartIndent::calcContinue(KateDocCursor &start, KateDocCursor &end)
00883 {
00884   KateDocCursor cur = start;
00885 
00886   bool needsBalanced = true;
00887   bool isFor = false;
00888   allowSemi = false;
00889 
00890   KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line());
00891 
00892   // Handle cases such as  } while (s ... by skipping the leading symbol
00893   if (textLine->attribute(cur.col()) == symbolAttrib)
00894   {
00895     cur.moveForward(1);
00896     skipBlanks(cur, end, false);
00897   }
00898 
00899   if (textLine->getChar(cur.col()) == '}')
00900   {
00901     skipBlanks(cur, end, true);
00902     if (cur.line() != start.line())
00903       textLine = doc->plainKateTextLine(cur.line());
00904 
00905     if (textLine->stringAtPos(cur.col(), "else"))
00906       cur.setCol(cur.col() + 4);
00907     else
00908       return indentWidth * 2;
00909 
00910     needsBalanced = false;
00911   }
00912   else if (textLine->stringAtPos(cur.col(), "else"))
00913   {
00914     cur.setCol(cur.col() + 4);
00915     needsBalanced = false;
00916     int next = textLine->nextNonSpaceChar(cur.col());
00917     if (next >= 0 && textLine->stringAtPos(next, "if"))
00918     {
00919       cur.setCol(next + 2);
00920       needsBalanced = true;
00921     }
00922   }
00923   else if (textLine->stringAtPos(cur.col(), "if"))
00924   {
00925     cur.setCol(cur.col() + 2);
00926   }
00927   else if (textLine->stringAtPos(cur.col(), "do"))
00928   {
00929     cur.setCol(cur.col() + 2);
00930     needsBalanced = false;
00931   }
00932   else if (textLine->stringAtPos(cur.col(), "for"))
00933   {
00934     cur.setCol(cur.col() + 3);
00935     isFor = true;
00936   }
00937   else if (textLine->stringAtPos(cur.col(), "while"))
00938   {
00939     cur.setCol(cur.col() + 5);
00940   }
00941   else if (textLine->stringAtPos(cur.col(), "switch"))
00942   {
00943     cur.setCol(cur.col() + 6);
00944   }
00945   else if (textLine->stringAtPos(cur.col(), "using"))
00946   {
00947     cur.setCol(cur.col() + 5);
00948   }
00949   else
00950   {
00951     return indentWidth * 2;
00952   }
00953 
00954   uint openPos = 0;
00955   if (needsBalanced && !isBalanced (cur, end, QChar('('), QChar(')'), openPos))
00956   {
00957     allowSemi = isFor;
00958     if (openPos > 0)
00959       return (openPos - textLine->firstChar());
00960     else
00961       return indentWidth * 2;
00962   }
00963 
00964   // Check if this statement ends a line now
00965   skipBlanks(cur, end, false);
00966   if (cur == end)
00967     return indentWidth;
00968 
00969   if (skipBlanks(cur, end, true))
00970   {
00971     if (cur == end)
00972       return indentWidth;
00973     else
00974       return indentWidth + calcContinue(cur, end);
00975   }
00976 
00977   return 0;
00978 }
00979 
00980 uint KateCSmartIndent::findOpeningBrace(KateDocCursor &start)
00981 {
00982   KateDocCursor cur = start;
00983   int count = 1;
00984 
00985   // Move backwards 1 by 1 and find the opening brace
00986   // Return the indent of that line
00987   while (cur.moveBackward(1))
00988   {
00989     if (cur.currentAttrib() == symbolAttrib)
00990     {
00991       QChar ch = cur.currentChar();
00992       if (ch == '{')
00993         count--;
00994       else if (ch == '}')
00995         count++;
00996 
00997       if (count == 0)
00998       {
00999         KateDocCursor temp(cur.line(), doc->plainKateTextLine(cur.line())->firstChar(), doc);
01000         return measureIndent(temp);
01001       }
01002     }
01003   }
01004 
01005   return 0;
01006 }
01007 
01008 bool KateCSmartIndent::firstOpeningBrace(KateDocCursor &start)
01009 {
01010   KateDocCursor cur = start;
01011 
01012   // Are we the first opening brace at this level?
01013   while(cur.moveBackward(1))
01014   {
01015     if (cur.currentAttrib() == symbolAttrib)
01016     {
01017       QChar ch = cur.currentChar();
01018       if (ch == '{')
01019         return false;
01020       else if (ch == '}' && cur.col() == 0)
01021         break;
01022     }
01023   }
01024 
01025   return true;
01026 }
01027 
01028 uint KateCSmartIndent::findOpeningParen(KateDocCursor &start)
01029 {
01030   KateDocCursor cur = start;
01031   int count = 1;
01032 
01033   // Move backwards 1 by 1 and find the opening (
01034   // Return the indent of that line
01035   while (cur.moveBackward(1))
01036   {
01037     if (cur.currentAttrib() == symbolAttrib)
01038     {
01039       QChar ch = cur.currentChar();
01040       if (ch == '(')
01041         count--;
01042       else if (ch == ')')
01043         count++;
01044 
01045       if (count == 0)
01046         return measureIndent(cur);
01047     }
01048   }
01049 
01050   return 0;
01051 }
01052 
01053 uint KateCSmartIndent::findOpeningComment(KateDocCursor &start)
01054 {
01055   KateDocCursor cur = start;
01056 
01057   // Find the line with the opening /* and return the proper indent
01058   do
01059   {
01060     KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line());
01061 
01062     int pos = textLine->string().find("/*", false);
01063     if (pos >= 0)
01064     {
01065       KateDocCursor temp(cur.line(), pos, doc);
01066       return measureIndent(temp);
01067     }
01068 
01069   } while (cur.gotoPreviousLine());
01070 
01071   return 0;
01072 }
01073 
01074 //END
01075 
01076 //BEGIN KatePythonIndent
01077 
01078 QRegExp KatePythonIndent::endWithColon = QRegExp( "^[^#]*:\\s*(#.*)?$" );
01079 QRegExp KatePythonIndent::stopStmt = QRegExp( "^\\s*(break|continue|raise|return|pass)\\b.*" );
01080 QRegExp KatePythonIndent::blockBegin = QRegExp( "^\\s*(class|def|if|elif|else|for|while|try)\\b.*" );
01081 
01082 KatePythonIndent::KatePythonIndent (KateDocument *doc)
01083 : KateNormalIndent (doc)
01084 {
01085 }
01086 KatePythonIndent::~KatePythonIndent ()
01087 {
01088 }
01089 
01090 void KatePythonIndent::processNewline (KateDocCursor &begin, bool /*newline*/)
01091 {
01092   int prevLine = begin.line() - 1;
01093   int prevPos = begin.col();
01094 
01095   while ((prevLine > 0) && (prevPos < 0)) // search a not empty text line
01096     prevPos = doc->plainKateTextLine(--prevLine)->firstChar();
01097 
01098   int prevBlock = prevLine;
01099   int prevBlockPos = prevPos;
01100   int extraIndent = calcExtra (prevBlock, prevBlockPos, begin);
01101 
01102   int indent = doc->plainKateTextLine(prevBlock)->cursorX(prevBlockPos, tabWidth);
01103   if (extraIndent == 0)
01104   {
01105     if (!stopStmt.exactMatch(doc->plainKateTextLine(prevLine)->string()))
01106     {
01107       if (endWithColon.exactMatch(doc->plainKateTextLine(prevLine)->string()))
01108         indent += indentWidth;
01109       else
01110         indent = doc->plainKateTextLine(prevLine)->cursorX(prevPos, tabWidth);
01111     }
01112   }
01113   else
01114     indent += extraIndent;
01115 
01116   if (indent > 0)
01117   {
01118     QString filler = tabString (indent);
01119     doc->insertText (begin.line(), 0, filler);
01120     begin.setCol(filler.length());
01121   }
01122   else
01123     begin.setCol(0);
01124 }
01125 
01126 int KatePythonIndent::calcExtra (int &prevBlock, int &pos, KateDocCursor &end)
01127 {
01128   int nestLevel = 0;
01129   bool levelFound = false;
01130   while ((prevBlock > 0))
01131   {
01132     if (blockBegin.exactMatch(doc->plainKateTextLine(prevBlock)->string()))
01133     {
01134       if ((!levelFound && nestLevel == 0) || (levelFound && nestLevel - 1 <= 0))
01135       {
01136         pos = doc->plainKateTextLine(prevBlock)->firstChar();
01137         break;
01138       }
01139 
01140       nestLevel --;
01141     }
01142     else if (stopStmt.exactMatch(doc->plainKateTextLine(prevBlock)->string()))
01143     {
01144       nestLevel ++;
01145       levelFound = true;
01146     }
01147 
01148     --prevBlock;
01149   }
01150 
01151   KateDocCursor cur (prevBlock, pos, doc);
01152   QChar c;
01153   int extraIndent = 0;
01154   while (cur.line() < end.line())
01155   {
01156     c = cur.currentChar();
01157 
01158     if (c == '(')
01159       extraIndent += indentWidth;
01160     else if (c == ')')
01161       extraIndent -= indentWidth;
01162     else if (c == ':')
01163       break;
01164 
01165     if (c.isNull() || c == '#')
01166       cur.gotoNextLine();
01167     else
01168       cur.moveForward(1);
01169   }
01170 
01171   return extraIndent;
01172 }
01173 
01174 //END
01175 
01176 //BEGIN KateXmlIndent
01177 
01178 /* Explanation
01179 
01180 The XML indenter simply inherits the indentation of the previous line,
01181 with the first line starting at 0 (of course!). For each element that
01182 is opened on the previous line, the indentation is increased by one
01183 level; for each element that is closed, it is decreased by one.
01184 
01185 We also have a special case of opening an element on one line and then
01186 entering attributes on the following lines, in which case we would like
01187 to see the following layout:
01188 <elem attr="..."
01189       blah="..." />
01190 
01191 <x><a href="..."
01192       title="..." />
01193 </x>
01194 
01195 This is accomplished by checking for lines that contain an unclosed open
01196 tag.
01197 
01198 */
01199 
01200 const QRegExp KateXmlIndent::startsWithCloseTag("^[ \t]*</");
01201 const QRegExp KateXmlIndent::unclosedDoctype("<!DOCTYPE[^>]*$");
01202 
01203 KateXmlIndent::KateXmlIndent (KateDocument *doc)
01204 : KateNormalIndent (doc)
01205 {
01206 }
01207 
01208 KateXmlIndent::~KateXmlIndent ()
01209 {
01210 }
01211 
01212 void KateXmlIndent::processNewline (KateDocCursor &begin, bool /*newline*/)
01213 {
01214   begin.setCol(processLine(begin.line()));
01215 }
01216 
01217 void KateXmlIndent::processChar (QChar c)
01218 {
01219   if(c != '/') return;
01220 
01221   // only alter lines that start with a close element
01222   KateView *view = doc->activeView();
01223   QString text = doc->plainKateTextLine(view->cursorLine())->string();
01224   if(text.find(startsWithCloseTag) == -1) return;
01225 
01226   // process it
01227   processLine(view->cursorLine());
01228 }
01229 
01230 void KateXmlIndent::processLine (KateDocCursor &line)
01231 {
01232   processLine (line.line());
01233 }
01234 
01235 void KateXmlIndent::processSection (const KateDocCursor &start, const KateDocCursor &end)
01236 {
01237   KateDocCursor cur (start);
01238   int endLine = end.line();
01239 
01240   do {
01241     processLine(cur.line());
01242     if(!cur.gotoNextLine()) break;
01243   } while(cur.line() < endLine);
01244 }
01245 
01246 void KateXmlIndent::getLineInfo (uint line, uint &prevIndent, int &numTags,
01247   uint &attrCol, bool &unclosedTag)
01248 {
01249   prevIndent = 0;
01250   int firstChar;
01251   KateTextLine::Ptr prevLine = 0;
01252 
01253   // get the indentation of the first non-empty line
01254   while(true) {
01255     prevLine = doc->plainKateTextLine(line);
01256     if( (firstChar = prevLine->firstChar()) < 0) {
01257       if(!line--) return;
01258       continue;
01259     }
01260     break;
01261   }
01262   prevIndent = prevLine->cursorX(prevLine->firstChar(), tabWidth);
01263   QString text = prevLine->string();
01264 
01265   // special case:
01266   // <a>
01267   // </a>              <!-- indentation *already* decreased -->
01268   // requires that we discount the </a> from the number of closed tags
01269   if(text.find(startsWithCloseTag) != -1) ++numTags;
01270 
01271   // count the number of open and close tags
01272   int lastCh = 0;
01273   uint pos, len = text.length();
01274   bool seenOpen = false;
01275   for(pos = 0; pos < len; ++pos) {
01276     int ch = text.at(pos).unicode();
01277     switch(ch) {
01278       case '<':
01279         seenOpen = true;
01280         unclosedTag = true;
01281         attrCol = pos;
01282         ++numTags;
01283         break;
01284 
01285       // don't indent because of DOCTYPE, comment, CDATA, etc.
01286       case '!':
01287         if(lastCh == '<') --numTags;
01288         break;
01289 
01290       // don't indent because of xml decl or PI
01291       case '?':
01292         if(lastCh == '<') --numTags;
01293         break;
01294 
01295       case '>':
01296         if(!seenOpen) {
01297           // we are on a line like the second one here:
01298           // <element attr="val"
01299           //          other="val">
01300           // so we need to set prevIndent to the indent of the first line
01301           //
01302           // however, we need to special case "<!DOCTYPE" because
01303           // it's not an open tag
01304 
01305           prevIndent = 0;
01306 
01307           for(uint backLine = line; backLine; ) {
01308             // find first line with an open tag
01309             KateTextLine::Ptr x = doc->plainKateTextLine(--backLine);
01310             if(x->string().find('<') == -1) continue;
01311 
01312             // recalculate the indent
01313             if(x->string().find(unclosedDoctype) != -1) --numTags;
01314             getLineInfo(backLine, prevIndent, numTags, attrCol, unclosedTag);
01315             break;
01316           }
01317         }
01318         if(lastCh == '/') --numTags;
01319         unclosedTag = false;
01320         break;
01321 
01322       case '/':
01323         if(lastCh == '<') numTags -= 2; // correct for '<', above
01324         break;
01325     }
01326     lastCh = ch;
01327   }
01328 
01329   if(unclosedTag) {
01330     // find the start of the next attribute, so we can align with it
01331     do {
01332       lastCh = text.at(++attrCol).unicode();
01333     }while(lastCh && lastCh != ' ' && lastCh != '\t');
01334 
01335     while(lastCh == ' ' || lastCh == '\t') {
01336       lastCh = text.at(++attrCol).unicode();
01337     }
01338 
01339     attrCol = prevLine->cursorX(attrCol, tabWidth);
01340   }
01341 }
01342 
01343 uint KateXmlIndent::processLine (uint line)
01344 {
01345   KateTextLine::Ptr kateLine = doc->plainKateTextLine(line);
01346   if(!kateLine) return 0; // sanity check
01347 
01348   // get details from previous line
01349   uint prevIndent = 0, attrCol = 0;
01350   int numTags = 0;
01351   bool unclosedTag = false; // for aligning attributes
01352 
01353   if(line) {
01354     getLineInfo(line - 1, prevIndent, numTags, attrCol, unclosedTag);
01355   }
01356 
01357   // compute new indent
01358   int indent = 0;
01359   if(unclosedTag) indent = attrCol;
01360   else  indent = prevIndent + numTags * indentWidth;
01361   if(indent < 0) indent = 0;
01362 
01363   // unindent lines that start with a close tag
01364   if(kateLine->string().find(startsWithCloseTag) != -1) {
01365     indent -= indentWidth;
01366   }
01367   if(indent < 0) indent = 0;
01368 
01369   // apply new indent
01370   doc->removeText(line, 0, line, kateLine->firstChar());
01371   QString filler = tabString(indent);
01372   doc->insertText(line, 0, filler);
01373 
01374   return filler.length();
01375 }
01376 
01377 //END
01378 
01379 //BEGIN KateCSAndSIndent
01380 
01381 KateCSAndSIndent::KateCSAndSIndent (KateDocument *doc)
01382 :  KateNormalIndent (doc)
01383 {
01384 }
01385 
01386 void KateCSAndSIndent::updateIndentString()
01387 {
01388   if( useSpaces )
01389     indentString.fill( ' ', indentWidth );
01390   else
01391     indentString = '\t';
01392 }
01393 
01394 KateCSAndSIndent::~KateCSAndSIndent ()
01395 {
01396 }
01397 
01398 void KateCSAndSIndent::processLine (KateDocCursor &line)
01399 {
01400   KateTextLine::Ptr textLine = doc->plainKateTextLine(line.line());
01401 
01402   if (!textLine)
01403     return;
01404 
01405   updateIndentString();
01406 
01407   const int oldCol = line.col();
01408   QString whitespace = calcIndent(line);
01409   // strip off existing whitespace
01410   int oldIndent = textLine->firstChar();
01411   if ( oldIndent < 0 )
01412     oldIndent = doc->lineLength( line.line() );
01413   if( oldIndent > 0 )
01414     doc->removeText(line.line(), 0, line.line(), oldIndent);
01415   // add correct amount
01416   doc->insertText(line.line(), 0, whitespace);
01417 
01418   // try to preserve the cursor position in the line
01419   if ( int(oldCol + whitespace.length()) >= oldIndent )
01420     line.setCol( oldCol + whitespace.length() - oldIndent );
01421   else
01422     line.setCol( 0 );
01423 }
01424 
01425 void KateCSAndSIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end)
01426 {
01427   QTime t; t.start();
01428   for( KateDocCursor cur = begin; cur.line() <= end.line(); )
01429   {
01430     processLine (cur);
01431     if (!cur.gotoNextLine())
01432       break;
01433   }
01434   kdDebug(13030) << "+++ total: " << t.elapsed() << endl;
01435 }
01436 
01442 static QString initialWhitespace(const KateTextLine::Ptr &line, int chars, bool convert = true)
01443 {
01444   QString text = line->string(0, chars);
01445   if( (int)text.length() < chars )
01446   {
01447     QString filler; filler.fill(' ',chars - text.length());
01448     text += filler;
01449   }
01450   for( uint n = 0; n < text.length(); ++n )
01451   {
01452     if( text[n] != '\t' && text[n] != ' ' )
01453     {
01454       if( !convert )
01455         return text.left( n );
01456       text[n] = ' ';
01457     }
01458   }
01459   return text;
01460 }
01461 
01462 QString KateCSAndSIndent::findOpeningCommentIndentation(const KateDocCursor &start)
01463 {
01464   KateDocCursor cur = start;
01465 
01466   // Find the line with the opening /* and return the indentation of it
01467   do
01468   {
01469     KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line());
01470 
01471     int pos = textLine->string().findRev("/*");
01472     // FIXME: /* inside /* is possible. This screws up in that case...
01473     if (pos >= 0)
01474       return initialWhitespace(textLine, pos);
01475   } while (cur.gotoPreviousLine());
01476 
01477   // should never happen.
01478   kdWarning( 13030 ) << " in a comment, but can't find the start of it" << endl;
01479   return QString::null;
01480 }
01481 
01482 bool KateCSAndSIndent::handleDoxygen (KateDocCursor &begin)
01483 {
01484   // Look backwards for a nonempty line
01485   int line = begin.line();
01486   int first = -1;
01487   while ((line > 0) && (first < 0))
01488     first = doc->plainKateTextLine(--line)->firstChar();
01489 
01490   // no earlier nonempty line
01491   if (first < 0)
01492     return false;
01493 
01494   KateTextLine::Ptr textLine = doc->plainKateTextLine(line);
01495 
01496   // if the line doesn't end with a doxygen comment (that's not closed)
01497   // and doesn't start with a doxygen comment (that's not closed), we don't care.
01498   // note that we do need to check the start of the line, or lines ending with, say, @brief aren't
01499   // recognised.
01500   if ( !(textLine->attribute(textLine->lastChar()) == doxyCommentAttrib && !textLine->endingWith("*/")) &&
01501        !(textLine->attribute(textLine->firstChar()) == doxyCommentAttrib && !textLine->string().contains("*/")) )
01502     return false;
01503 
01504   // our line is inside a doxygen comment. align the *'s and then maybe insert one too ...
01505   textLine = doc->plainKateTextLine(begin.line());
01506   first = textLine->firstChar();
01507   QString indent = findOpeningCommentIndentation(begin);
01508 
01509   bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping;
01510 
01511   // starts with *: indent one space more to line up *s
01512   if ( textLine->stringAtPos(first, "*") )
01513     indent = indent + " ";
01514   // does not start with *: insert one if user wants that
01515   else if ( doxygenAutoInsert )
01516     indent = indent + " * ";
01517   // user doesn't want * inserted automatically: put in spaces?
01518   //else
01519   //  indent = indent + "   ";
01520 
01521   doc->removeText (begin.line(), 0, begin.line(), first);
01522   doc->insertText (begin.line(), 0, indent);
01523   begin.setCol(indent.length());
01524 
01525   return true;
01526 }
01527 
01534 void KateCSAndSIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/)
01535 {
01536   // in a comment, add a * doxygen-style.
01537   if( handleDoxygen(begin) )
01538     return;
01539 
01540   // TODO: if the user presses enter in the middle of a label, maybe the first half of the
01541   //  label should be indented?
01542 
01543   // where the cursor actually is...
01544   int cursorPos = doc->plainKateTextLine( begin.line() )->firstChar();
01545   if ( cursorPos < 0 )
01546     cursorPos = doc->lineLength( begin.line() );
01547   begin.setCol( cursorPos );
01548 
01549   processLine( begin );
01550 }
01551 
01556 bool KateCSAndSIndent::startsWithLabel( int line ) 
01557 { 
01558   // Get the current line.
01559   KateTextLine::Ptr indentLine = doc->plainKateTextLine(line);
01560   const int indentFirst = indentLine->firstChar();
01561   
01562   // Not entirely sure what this check does.
01563   int attrib = indentLine->attribute(indentFirst);
01564   if (attrib != 0 && attrib != keywordAttrib && attrib != normalAttrib && attrib != extensionAttrib)
01565     return false;
01566   
01567   // Get the line text.
01568   const QString lineContents = indentLine->string();
01569   const int indentLast = indentLine->lastChar();
01570   bool whitespaceFound = false;
01571   for ( int n = indentFirst; n <= indentLast; ++n )
01572   {
01573     // Get the character as latin1. Can't use QChar::isLetterOrNumber()
01574     // as that includes non 0-9 numbers.
01575     char c = lineContents[n].latin1();
01576     if ( c == ':' )
01577     {
01578       // See if the next character is ':' - if so, skip to the character after it.
01579       if ( n < lineContents.length() - 1 )
01580       {
01581         if ( lineContents[n+1].latin1() == ':' )
01582         {
01583           n += 2;
01584           continue;
01585         }
01586       }
01587       // Right this is the relevent ':'.
01588       if ( n == indentFirst)
01589       {
01590         // Just a line with a : on it.
01591         return false;
01592       }
01593       // It is a label of some kind!
01594       return true;
01595     }
01596     if (isspace(c))
01597     {
01598       if (!whitespaceFound)
01599       {
01600         if (lineContents.mid(indentFirst, n - indentFirst) == "class")
01601           return false;
01602         whitespaceFound = true;
01603       }
01604     }
01605     // All other characters don't indent.
01606     else if ( !isalnum(c) && c != '_' )
01607     {
01608       return false;
01609     }
01610   }
01611   return false;
01612 }
01613 
01614 template<class T> T min(T a, T b) { return (a < b) ? a : b; }
01615 
01616 int KateCSAndSIndent::lastNonCommentChar( const KateDocCursor &line )
01617 {
01618   KateTextLine::Ptr textLine = doc->plainKateTextLine( line.line() );
01619   QString str = textLine->string();
01620 
01621   // find a possible start-of-comment
01622   int p = -2; // so the first find starts at position 0
01623   do p = str.find( "//", p + 2 );
01624   while ( p >= 0 && textLine->attribute(p) != commentAttrib && textLine->attribute(p) != doxyCommentAttrib );
01625 
01626   // no // found? use whole string
01627   if ( p < 0 )
01628     p = str.length();
01629 
01630   // ignore trailing blanks. p starts one-past-the-end.
01631   while( p > 0 && str[p-1].isSpace() ) --p;
01632   return p - 1;
01633 }
01634 
01635 bool KateCSAndSIndent::inForStatement( int line )
01636 {
01637   // does this line end in a for ( ...
01638   // with no closing ) ?
01639   int parens = 0, semicolons = 0;
01640   for ( ; line >= 0; --line )
01641   {
01642     KateTextLine::Ptr textLine = doc->plainKateTextLine(line);
01643     const int first = textLine->firstChar();
01644     const int last = textLine->lastChar();
01645 
01646     // look backwards for a symbol: (){};
01647     // match ()s, {...; and }...; => not in a for
01648     // ; ; ; => not in a for
01649     // ( ; and ( ; ; => a for
01650     for ( int curr = last; curr >= first; --curr )
01651     {
01652       if ( textLine->attribute(curr) != symbolAttrib )
01653         continue;
01654 
01655       switch( textLine->getChar(curr) )
01656       {
01657       case ';':
01658         if( ++semicolons > 2 )
01659           return false;
01660         break;
01661       case '{': case '}':
01662         return false;
01663       case ')':
01664         ++parens;
01665         break;
01666       case '(':
01667         if( --parens < 0 )
01668           return true;
01669         break;
01670       }
01671     }
01672   }
01673   // no useful symbols before the ;?
01674   // not in a for then
01675   return false;
01676 }
01677 
01678 
01679 // is the start of the line containing 'begin' in a statement?
01680 bool KateCSAndSIndent::inStatement( const KateDocCursor &begin )
01681 {
01682   // if the current line starts with an open brace, it's not a continuation.
01683   // this happens after a function definition (which is treated as a continuation).
01684   KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line());
01685   const int first = textLine->firstChar();
01686   // note that if we're being called from processChar the attribute has not yet been calculated
01687   // should be reasonably safe to assume that unattributed {s are symbols; if the { is in a comment
01688   // we don't want to touch it anyway.
01689   const int attrib = textLine->attribute(first);
01690   if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && textLine->getChar(first) == '{' )
01691     return false;
01692 
01693   int line;
01694   for ( line = begin.line() - 1; line >= 0; --line )
01695   {
01696     textLine = doc->plainKateTextLine(line);
01697     const int first = textLine->firstChar();
01698     if ( first == -1 )
01699       continue;
01700 
01701     // starts with #: in a comment, don't care
01702     // outside a comment: preprocessor, don't care
01703     if ( textLine->getChar( first ) == '#' )
01704       continue;
01705     KateDocCursor currLine = begin;
01706     currLine.setLine( line );
01707     const int last = lastNonCommentChar( currLine );
01708     if ( last < first )
01709       continue;
01710 
01711     // HACK: if we see a comment, assume boldly that this isn't a continuation.
01712     //       detecting comments (using attributes) is HARD, since they may have
01713     //       embedded alerts, or doxygen stuff, or just about anything. this is
01714     //       wrong, and needs fixing. note that only multi-line comments and
01715     //       single-line comments continued with \ are affected.
01716     const int attrib = textLine->attribute(last);
01717     if ( attrib == commentAttrib || attrib == doxyCommentAttrib )
01718       return false;
01719 
01720     char c = textLine->getChar(last);
01721 
01722     // brace => not a continuation.
01723     if ( attrib == symbolAttrib && c == '{' || c == '}' )
01724       return false;
01725 
01726     // ; => not a continuation, unless in a for (;;)
01727     if ( attrib == symbolAttrib && c == ';' )
01728       return inForStatement( line );
01729 
01730     // found something interesting. maybe it's a label?
01731     if ( attrib == symbolAttrib && c == ':' )
01732     {
01733       // the : above isn't necessarily the : in the label, eg in
01734       // case 'x': a = b ? c :
01735       // this will say no continuation incorrectly. but continued statements
01736       // starting on a line with a label at the start is Bad Style (tm).
01737       if( startsWithLabel( line ) )
01738       {
01739         // either starts with a label or a continuation. if the current line
01740         // starts in a continuation, we're still in one. if not, this was
01741         // a label, so we're not in one now. so continue to the next line
01742         // upwards.
01743         continue;
01744       }
01745     }
01746 
01747     // any other character => in a continuation
01748     return true;
01749   }
01750   // no non-comment text found before here - not a continuation.
01751   return false;
01752 }
01753 
01754 QString KateCSAndSIndent::continuationIndent( const KateDocCursor &begin )
01755 {
01756   if( !inStatement( begin ) )
01757     return QString::null;
01758   return indentString;
01759 }
01760 
01764 QString KateCSAndSIndent::calcIndent (const KateDocCursor &begin)
01765 {
01766   KateTextLine::Ptr currLine = doc->plainKateTextLine(begin.line());
01767   int currLineFirst = currLine->firstChar();
01768 
01769   // if the line starts inside a comment, no change of indentation.
01770   // FIXME: this unnecessarily copies the current indentation over itself.
01771   // FIXME: on newline, this should copy from the previous line.
01772   if ( currLineFirst >= 0 &&
01773        (currLine->attribute(currLineFirst) == commentAttrib ||
01774         currLine->attribute(currLineFirst) == doxyCommentAttrib) )
01775     return currLine->string( 0, currLineFirst );
01776 
01777   // if the line starts with # (but isn't a c# region thingy), no indentation at all.
01778   if( currLineFirst >= 0 && currLine->getChar(currLineFirst) == '#' )
01779   {
01780     if( !currLine->stringAtPos( currLineFirst+1, QString::fromLatin1("region") ) &&
01781         !currLine->stringAtPos( currLineFirst+1, QString::fromLatin1("endregion") ) )
01782       return QString::null;
01783   }
01784 
01785   /* Strategy:
01786    * Look for an open bracket or brace, or a keyword opening a new scope, whichever comes latest.
01787    * Found a brace: indent one tab in.
01788    * Found a bracket: indent to the first non-white after it.
01789    * Found a keyword: indent one tab in. for try, catch and switch, if newline is set, also add
01790    *                  an open brace, a newline, and indent two tabs in.
01791    */
01792   KateDocCursor cur = begin;
01793   int pos, openBraceCount = 0, openParenCount = 0;
01794   bool lookingForScopeKeywords = true;
01795   const char * const scopeKeywords[] = { "for", "do", "while", "if", "else" };
01796   const char * const blockScopeKeywords[] = { "try", "catch", "switch" };
01797 
01798   while (cur.gotoPreviousLine())
01799   {
01800     KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line());
01801     const int lastChar = textLine->lastChar();
01802     const int firstChar = textLine->firstChar();
01803 
01804     // look through line backwards for interesting characters
01805     for( pos = lastChar; pos >= firstChar; --pos )
01806     {
01807       if (textLine->attribute(pos) == symbolAttrib)
01808       {
01809         char tc = textLine->getChar (pos);
01810         switch( tc )
01811         {
01812           case '(': case '[':
01813             if( ++openParenCount > 0 )
01814               return calcIndentInBracket( begin, cur, pos );
01815             break;
01816           case ')': case ']': openParenCount--; break;
01817           case '{':
01818             if( ++openBraceCount > 0 )
01819               return calcIndentInBrace( begin, cur, pos );
01820             break;
01821           case '}': openBraceCount--; lookingForScopeKeywords = false; break;
01822           case ';':
01823             if( openParenCount == 0 )
01824               lookingForScopeKeywords = false;
01825             break;
01826         }
01827       }
01828 
01829       // if we've not had a close brace or a semicolon yet, and we're at the same parenthesis level
01830       // as the cursor, and we're at the start of a scope keyword, indent from it.
01831       if ( lookingForScopeKeywords && openParenCount == 0 &&
01832            textLine->attribute(pos) == keywordAttrib &&
01833            (pos == 0 || textLine->attribute(pos-1) != keywordAttrib ) )
01834       {
01835         #define ARRLEN( array ) ( sizeof(array)/sizeof(array[0]) )
01836         for( uint n = 0; n < ARRLEN(scopeKeywords); ++n )
01837           if( textLine->stringAtPos(pos, QString::fromLatin1(scopeKeywords[n]) ) )
01838             return calcIndentAfterKeyword( begin, cur, pos, false );
01839         for( uint n = 0; n < ARRLEN(blockScopeKeywords); ++n )
01840           if( textLine->stringAtPos(pos, QString::fromLatin1(blockScopeKeywords[n]) ) )
01841             return calcIndentAfterKeyword( begin, cur, pos, true );
01842         #undef ARRLEN
01843       }
01844     }
01845   }
01846 
01847   // no active { in file.
01848   return QString::null;
01849 }
01850 
01851 QString KateCSAndSIndent::calcIndentInBracket(const KateDocCursor &indentCursor, const KateDocCursor &bracketCursor, int bracketPos)
01852 {
01853   KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line());
01854   KateTextLine::Ptr bracketLine = doc->plainKateTextLine(bracketCursor.line());
01855 
01856   // FIXME: hard-coded max indent to bracket width - use a kate variable
01857   // FIXME: expand tabs first...
01858   if ( bracketPos > 48 )
01859   {
01860     // how far to indent? we could look back for a brace or keyword, 2 from that.
01861     // as it is, we just indent one more than the line with the ( on it.
01862     // the potential problem with this is when
01863     //   you have code ( which does          <-- continuation + start of func call
01864     //     something like this );            <-- extra indentation for func call
01865     // then again (
01866     //   it works better than (
01867     //     the other method for (
01868     //       cases like this )));
01869     // consequently, i think this method wins.
01870     return indentString + initialWhitespace( bracketLine, bracketLine->firstChar() );
01871   }
01872 
01873   const int indentLineFirst = indentLine->firstChar();
01874 
01875   int indentTo;
01876   const int attrib = indentLine->attribute(indentLineFirst);
01877   if( indentLineFirst >= 0 && (attrib == 0 || attrib == symbolAttrib) &&
01878       ( indentLine->getChar(indentLineFirst) == ')' || indentLine->getChar(indentLineFirst) == ']' ) )
01879   {
01880     // If the line starts with a close bracket, line it up
01881     indentTo = bracketPos;
01882   }
01883   else
01884   {
01885     // Otherwise, line up with the text after the open bracket
01886     indentTo = bracketLine->nextNonSpaceChar( bracketPos + 1 );
01887     if( indentTo == -1 )
01888       indentTo = bracketPos + 2;
01889   }
01890   return initialWhitespace( bracketLine, indentTo );
01891 }
01892 
01893 QString KateCSAndSIndent::calcIndentAfterKeyword(const KateDocCursor &indentCursor, const KateDocCursor &keywordCursor, int keywordPos, bool blockKeyword)
01894 {
01895   KateTextLine::Ptr keywordLine = doc->plainKateTextLine(keywordCursor.line());
01896   KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line());
01897 
01898   QString whitespaceToKeyword = initialWhitespace( keywordLine, keywordPos, false );
01899   if( blockKeyword ) {
01900     // FIXME: we could add the open brace and subsequent newline here since they're definitely needed.
01901   }
01902 
01903   // If the line starts with an open brace, don't indent...
01904   int first = indentLine->firstChar();
01905   // if we're being called from processChar attribute won't be set
01906   const int attrib = indentLine->attribute(first);
01907   if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && indentLine->getChar(first) == '{' )
01908     return whitespaceToKeyword;
01909 
01910   // don't check for a continuation. rules are simple here:
01911   // if we're in a non-compound statement after a scope keyword, we indent all lines
01912   // once. so:
01913   // if ( some stuff
01914   //      goes here )
01915   //   apples, and         <-- continuation here is ignored. but this is Bad Style (tm) anyway.
01916   //   oranges too;
01917   return indentString + whitespaceToKeyword;
01918 }
01919 
01920 QString KateCSAndSIndent::calcIndentInBrace(const KateDocCursor &indentCursor, const KateDocCursor &braceCursor, int bracePos)
01921 {
01922   KateTextLine::Ptr braceLine = doc->plainKateTextLine(braceCursor.line());
01923   const int braceFirst = braceLine->firstChar();
01924 
01925   QString whitespaceToOpenBrace = initialWhitespace( braceLine, bracePos, false );
01926 
01927   // if the open brace is the start of a namespace, don't indent...
01928   // FIXME: this is an extremely poor heuristic. it looks on the line with
01929   //        the { and the line before to see if they start with a keyword
01930   //        beginning 'namespace'. that's 99% of usage, I'd guess.
01931   {
01932     if( braceFirst >= 0 && braceLine->attribute(braceFirst) == keywordAttrib &&
01933         braceLine->stringAtPos( braceFirst, QString::fromLatin1( "namespace" ) ) )
01934       return continuationIndent(indentCursor) + whitespaceToOpenBrace;
01935 
01936     if( braceCursor.line() > 0 )
01937     {
01938       KateTextLine::Ptr prevLine = doc->plainKateTextLine(braceCursor.line() - 1);
01939       int firstPrev = prevLine->firstChar();
01940       if( firstPrev >= 0 && prevLine->attribute(firstPrev) == keywordAttrib &&
01941           prevLine->stringAtPos( firstPrev, QString::fromLatin1( "namespace" ) ) )
01942         return continuationIndent(indentCursor) + whitespaceToOpenBrace;
01943     }
01944   }
01945 
01946   KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line());
01947   const int indentFirst = indentLine->firstChar();
01948 
01949   // if the line starts with a close brace, don't indent...
01950   if( indentFirst >= 0 && indentLine->getChar(indentFirst) == '}' )
01951     return whitespaceToOpenBrace;
01952 
01953   // if : is the first character (and not followed by another :), this is the start
01954   // of an initialization list, or a continuation of a ?:. either way, indent twice.
01955   if ( indentFirst >= 0 && indentLine->attribute(indentFirst) == symbolAttrib &&
01956        indentLine->getChar(indentFirst) == ':' && indentLine->getChar(indentFirst+1) != ':' )
01957   {
01958     return indentString + indentString + whitespaceToOpenBrace;
01959   }
01960 
01961   const bool continuation = inStatement(indentCursor);
01962   // if the current line starts with a label, don't indent...
01963   if( !continuation && startsWithLabel( indentCursor.line() ) )
01964     return whitespaceToOpenBrace;
01965 
01966   // the normal case: indent once for the brace, again if it's a continuation
01967   QString continuationIndent = continuation ? indentString : QString::null;
01968   return indentString + continuationIndent + whitespaceToOpenBrace;
01969 }
01970 
01971 void KateCSAndSIndent::processChar(QChar c)
01972 {
01973   // 'n' trigger is for c# regions.
01974   static const QString triggers("}{)]/:;#n");
01975   if (triggers.find(c) == -1)
01976     return;
01977 
01978   // for historic reasons, processChar doesn't get a cursor
01979   // to work on. so fabricate one.
01980   KateView *view = doc->activeView();
01981   KateDocCursor begin(view->cursorLine(), 0, doc);
01982 
01983   KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line());
01984   if ( c == 'n' )
01985   {
01986     int first = textLine->firstChar();
01987     if( first < 0 || textLine->getChar(first) != '#' )
01988       return;
01989   }
01990 
01991   if ( textLine->attribute( begin.col() ) == doxyCommentAttrib )
01992   {
01993     // dominik: if line is "* /", change it to "*/"
01994     if ( c == '/' )
01995     {
01996       int first = textLine->firstChar();
01997       // if the first char exists and is a '*', and the next non-space-char
01998       // is already the just typed '/', concatenate it to "*/".
01999       if ( first != -1
02000            && textLine->getChar( first ) == '*'
02001            && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 )
02002         doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1);
02003     }
02004 
02005     // anders: don't change the indent of doxygen lines here.
02006     return;
02007   }
02008 
02009   processLine(begin);
02010 }
02011 
02012 //END
02013 
02014 //BEGIN KateVarIndent
02015 class KateVarIndentPrivate {
02016   public:
02017     QRegExp reIndentAfter, reIndent, reUnindent;
02018     QString triggers;
02019     uint couples;
02020     uchar coupleAttrib;
02021 };
02022 
02023 KateVarIndent::KateVarIndent( KateDocument *doc )
02024 : QObject( 0, "variable indenter"), KateNormalIndent( doc )
02025 {
02026   d = new KateVarIndentPrivate;
02027   d->reIndentAfter = QRegExp( doc->variable( "var-indent-indent-after" ) );
02028   d->reIndent = QRegExp( doc->variable( "var-indent-indent" ) );
02029   d->reUnindent = QRegExp( doc->variable( "var-indent-unindent" ) );
02030   d->triggers = doc->variable( "var-indent-triggerchars" );
02031   d->coupleAttrib = 0;
02032 
02033   slotVariableChanged( "var-indent-couple-attribute", doc->variable( "var-indent-couple-attribute" ) );
02034   slotVariableChanged( "var-indent-handle-couples", doc->variable( "var-indent-handle-couples" ) );
02035 
02036   // update if a setting is changed
02037   connect( doc, SIGNAL(variableChanged( const QString&, const QString&) ),
02038            this, SLOT(slotVariableChanged( const QString&, const QString& )) );
02039 }
02040 
02041 KateVarIndent::~KateVarIndent()
02042 {
02043   delete d;
02044 }
02045 
02046 void KateVarIndent::processNewline ( KateDocCursor &begin, bool /*needContinue*/ )
02047 {
02048   // process the line left, as well as the one entered
02049   KateDocCursor left( begin.line()-1, 0, doc );
02050   processLine( left );
02051   processLine( begin );
02052 }
02053 
02054 void KateVarIndent::processChar ( QChar c )
02055 {
02056   // process line if the c is in our list, and we are not in comment text
02057   if ( d->triggers.contains( c ) )
02058   {
02059     KateTextLine::Ptr ln = doc->plainKateTextLine( doc->activeView()->cursorLine() );
02060     if ( ln->attribute( doc->activeView()->cursorColumn()-1 ) == commentAttrib )
02061       return;
02062 
02063     KateView *view = doc->activeView();
02064     KateDocCursor begin( view->cursorLine(), 0, doc );
02065     kdDebug(13030)<<"variable indenter: process char '"<<c<<", line "<<begin.line()<<endl;
02066     processLine( begin );
02067   }
02068 }
02069 
02070 void KateVarIndent::processLine ( KateDocCursor &line )
02071 {
02072   updateConfig(); // ### is it really nessecary *each time* ??
02073 
02074   QString indent; // store the indent string here
02075 
02076   // find the first line with content that is not starting with comment text,
02077   // and take the position from that
02078   int ln = line.line();
02079   int pos = -1;
02080   KateTextLine::Ptr ktl = doc->plainKateTextLine( ln );
02081   if ( ! ktl ) return; // no line!?
02082 
02083   // skip blank lines, except for the cursor line
02084   KateView *v = doc->activeView();
02085   if ( (ktl->firstChar() < 0) && (!v || (int)v->cursorLine() != ln ) )
02086     return;
02087 
02088   int fc;
02089   if ( ln > 0 )
02090   do
02091   {
02092 
02093     ktl = doc->plainKateTextLine( --ln );
02094     fc = ktl->firstChar();
02095     if ( ktl->attribute( fc ) != commentAttrib )
02096       pos = fc;
02097   }
02098   while ( (ln > 0) && (pos < 0) ); // search a not empty text line
02099 
02100   if ( pos < 0 )
02101     pos = 0;
02102   else
02103     pos = ktl->cursorX( pos, tabWidth );
02104 
02105   int adjustment = 0;
02106 
02107   // try 'couples' for an opening on the above line first. since we only adjust by 1 unit,
02108   // we only need 1 match.
02109   if ( d->couples & Parens && coupleBalance( ln, '(', ')' ) > 0 )
02110     adjustment++;
02111   else if ( d->couples & Braces && coupleBalance( ln, '{', '}' ) > 0 )
02112     adjustment++;
02113   else if ( d->couples & Brackets && coupleBalance( ln, '[', ']' ) > 0 )
02114     adjustment++;
02115 
02116   // Try 'couples' for a closing on this line first. since we only adjust by 1 unit,
02117   // we only need 1 match. For unindenting, we look for a closing character
02118   // *at the beginning of the line*
02119   // NOTE Assume that a closing brace with the configured attribute on the start
02120   // of the line is closing.
02121   // When acting on processChar, the character isn't highlighted. So I could
02122   // either not check, assuming that the first char *is* meant to close, or do a
02123   // match test if the attrib is 0. How ever, doing that is
02124   // a potentially huge job, if the match is several hundred lines away.
02125   // Currently, the check is done.
02126   {
02127     KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() );
02128     int i = tl->firstChar();
02129     if ( i > -1 )
02130     {
02131       QChar ch = tl->getChar( i );
02132       uchar at = tl->attribute( i );
02133       kdDebug(13030)<<"attrib is "<<at<<endl;
02134       if ( d->couples & Parens && ch == ')'
02135            && ( at == d->coupleAttrib
02136                 || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) ))
02137               )
02138          )
02139         adjustment--;
02140       else if ( d->couples & Braces && ch == '}'
02141                 && ( at == d->coupleAttrib
02142                      || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) ))
02143                    )
02144               )
02145         adjustment--;
02146       else if ( d->couples & Brackets && ch == ']'
02147                 && ( at == d->coupleAttrib
02148                      || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) ))
02149                    )
02150               )
02151         adjustment--;
02152     }
02153   }
02154 #define ISCOMMENTATTR(attr) (attr==commentAttrib||attr==doxyCommentAttrib)
02155 #define ISCOMMENT (ISCOMMENTATTR(ktl->attribute(ktl->firstChar()))||ISCOMMENTATTR(ktl->attribute(matchpos)))
02156   // check if we should indent, unless the line starts with comment text,
02157   // or the match is in comment text
02158   kdDebug(13030)<<"variable indenter: starting indent: "<<pos<<endl;
02159   // check if the above line indicates that we shuld add indentation
02160   int matchpos = 0;
02161   if ( ktl && ! d->reIndentAfter.isEmpty()
02162        && (matchpos = d->reIndentAfter.search( doc->textLine( ln ) )) > -1
02163        && ! ISCOMMENT )
02164     adjustment++;
02165 
02166   // else, check if this line should indent unless ...
02167   ktl = doc->plainKateTextLine( line.line() );
02168   if ( ! d->reIndent.isEmpty()
02169          && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1
02170          && ! ISCOMMENT )
02171     adjustment++;
02172 
02173   // else, check if the current line indicates if we should remove indentation unless ...
02174   if ( ! d->reUnindent.isEmpty()
02175        && (matchpos = d->reUnindent.search( doc->textLine( line.line() ) )) > -1
02176        && ! ISCOMMENT )
02177     adjustment--;
02178 
02179   kdDebug(13030)<<"variable indenter: adjusting by "<<adjustment<<" units"<<endl;
02180 
02181   if ( adjustment > 0 )
02182     pos += indentWidth;
02183   else if ( adjustment < 0 )
02184     pos -= indentWidth;
02185 
02186   ln = line.line();
02187   fc = doc->plainKateTextLine( ln )->firstChar();
02188 
02189   // dont change if there is no change.
02190   // ### should I actually compare the strings?
02191   // FIXME for some odd reason, the document gets marked as changed
02192   //       even if we don't change it !?
02193   if ( fc == pos )
02194     return;
02195 
02196   if ( fc > 0 )
02197     doc->removeText (ln, 0, ln, fc );
02198 
02199   if ( pos > 0 )
02200     indent = tabString( pos );
02201 
02202   if ( pos > 0 )
02203     doc->insertText (ln, 0, indent);
02204 
02205   // try to restore cursor ?
02206   line.setCol( pos );
02207 }
02208 
02209 void KateVarIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end)
02210 {
02211   KateDocCursor cur = begin;
02212   while (cur.line() <= end.line())
02213   {
02214     processLine (cur);
02215     if (!cur.gotoNextLine())
02216       break;
02217   }
02218 }
02219 
02220 void KateVarIndent::slotVariableChanged( const QString &var, const QString &val )
02221 {
02222   if ( ! var.startsWith("var-indent") )
02223     return;
02224 
02225   if ( var == "var-indent-indent-after" )
02226     d->reIndentAfter.setPattern( val );
02227   else if ( var == "var-indent-indent" )
02228     d->reIndent.setPattern( val );
02229   else if ( var == "var-indent-unindent" )
02230     d->reUnindent.setPattern( val );
02231   else if ( var == "var-indent-triggerchars" )
02232     d->triggers = val;
02233   else if ( var == "var-indent-handle-couples" )
02234   {
02235     d->couples = 0;
02236     QStringList l = QStringList::split( " ", val );
02237     if ( l.contains("parens") ) d->couples |= Parens;
02238     if ( l.contains("braces") ) d->couples |= Braces;
02239     if ( l.contains("brackets") ) d->couples |= Brackets;
02240   }
02241   else if ( var == "var-indent-couple-attribute" )
02242   {
02243     //read a named attribute of the config.
02244     KateHlItemDataList items;
02245     doc->highlight()->getKateHlItemDataListCopy (0, items);
02246 
02247     for (uint i=0; i<items.count(); i++)
02248     {
02249       if ( items.at(i)->name.section( ':', 1 ) == val )
02250       {
02251         d->coupleAttrib = i;
02252         break;
02253       }
02254     }
02255   }
02256 }
02257 
02258 int KateVarIndent::coupleBalance ( int line, const QChar &open, const QChar &close ) const
02259 {
02260   int r = 0;
02261 
02262   KateTextLine::Ptr ln = doc->plainKateTextLine( line );
02263   if ( ! ln || ! ln->length() ) return 0;
02264 
02265   for ( uint z=0; z < ln->length(); z++ )
02266   {
02267     QChar c = ln->getChar( z );
02268     if ( ln->attribute(z) == d->coupleAttrib )
02269     {
02270       kdDebug(13030)<<z<<", "<<c<<endl;
02271       if (c == open)
02272         r++;
02273       else if (c == close)
02274         r--;
02275     }
02276   }
02277   return r;
02278 }
02279 
02280 bool KateVarIndent::hasRelevantOpening( const KateDocCursor &end ) const
02281 {
02282   KateDocCursor cur = end;
02283   int count = 1;
02284 
02285   QChar close = cur.currentChar();
02286   QChar opener;
02287   if ( close == '}' ) opener = '{';
02288   else if ( close = ')' ) opener = '(';
02289   else if (close = ']' ) opener = '[';
02290   else return false;
02291 
02292   //Move backwards 1 by 1 and find the opening partner
02293   while (cur.moveBackward(1))
02294   {
02295     if (cur.currentAttrib() == d->coupleAttrib)
02296     {
02297       QChar ch = cur.currentChar();
02298       if (ch == opener)
02299         count--;
02300       else if (ch == close)
02301         count++;
02302 
02303       if (count == 0)
02304         return true;
02305     }
02306   }
02307 
02308   return false;
02309 }
02310 
02311 
02312 //END KateVarIndent
02313 
02314 //BEGIN KateScriptIndent
02315 KateScriptIndent::KateScriptIndent( KateDocument *doc )
02316   : KateNormalIndent( doc )
02317 {
02318     m_script=KateFactory::self()->indentScript ("script-indent-c1-test");
02319 }
02320 
02321 KateScriptIndent::~KateScriptIndent()
02322 {
02323 }
02324 
02325 void KateScriptIndent::processNewline( KateDocCursor &begin, bool needContinue )
02326 {
02327   kdDebug(13030) << "processNewline" << endl;
02328   KateView *view = doc->activeView();
02329 
02330   if (view)
02331   {
02332     QString errorMsg;
02333 
02334     QTime t;
02335     t.start();
02336     kdDebug(13030)<<"calling m_script.processChar"<<endl;
02337     if( !m_script.processNewline( view, begin, needContinue , errorMsg ) )
02338     {
02339       kdDebug(13030) << "Error in script-indent: " << errorMsg << endl;
02340     }
02341     kdDebug(13030) << "ScriptIndent::TIME in ms: " << t.elapsed() << endl;
02342   }
02343 }
02344 
02345 void KateScriptIndent::processChar( QChar c )
02346 {
02347   kdDebug(13030) << "processChar" << endl;
02348   KateView *view = doc->activeView();
02349 
02350   if (view)
02351   {
02352     QString errorMsg;
02353 
02354     QTime t;
02355     t.start();
02356     kdDebug(13030)<<"calling m_script.processChar"<<endl;
02357     if( !m_script.processChar( view, c , errorMsg ) )
02358     {
02359       kdDebug(13030) << "Error in script-indent: " << errorMsg << endl;
02360     }
02361     kdDebug(13030) << "ScriptIndent::TIME in ms: " << t.elapsed() << endl;
02362   }
02363 }
02364 
02365 void KateScriptIndent::processLine (KateDocCursor &line)
02366 {
02367   kdDebug(13030) << "processLine" << endl;
02368   KateView *view = doc->activeView();
02369 
02370   if (view)
02371   {
02372     QString errorMsg;
02373 
02374     QTime t;
02375     t.start();
02376     kdDebug(13030)<<"calling m_script.processLine"<<endl;
02377     if( !m_script.processLine( view, line , errorMsg ) )
02378     {
02379       kdDebug(13030) << "Error in script-indent: " << errorMsg << endl;
02380     }
02381     kdDebug(13030) << "ScriptIndent::TIME in ms: " << t.elapsed() << endl;
02382   }
02383 }
02384 //END KateScriptIndent
02385 
02386 //BEGIN ScriptIndentConfigPage, THIS IS ONLY A TEST! :)
02387 #include <qlabel.h>
02388 ScriptIndentConfigPage::ScriptIndentConfigPage ( QWidget *parent, const char *name )
02389   : IndenterConfigPage(parent, name)
02390 {
02391   QLabel* hello = new QLabel("Hello world! Dummy for testing purpose.", this);
02392   hello->show();
02393 }
02394 
02395 ScriptIndentConfigPage::~ScriptIndentConfigPage ()
02396 {
02397 }
02398 
02399 void ScriptIndentConfigPage::apply ()
02400 {
02401   kdDebug(13030) << "ScriptIndentConfigPagE::apply() was called, save config options now!" << endl;
02402 }
02403 //END ScriptIndentConfigPage
02404 
02405 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE Home | KDE Accessibility Home | Description of Access Keys