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