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   bool potentialAnchorSeen = false;
00718 
00719   //kdDebug(13030) << "calcIndent begin line:" << begin.line() << " col:" << begin.col() << endl;
00720 
00721   // Find Indent Anchor Point
00722   while (cur.gotoPreviousLine())
00723   {
00724     isSpecial = found = false;
00725     textLine = doc->plainKateTextLine(cur.line());
00726 
00727     // Skip comments and handle cases like if (...) { stmt;
00728     int pos = textLine->lastChar();
00729     int openCount = 0;
00730     int otherAnchor = -1;
00731     do
00732     {
00733       if (textLine->attribute(pos) == symbolAttrib)
00734       {
00735         QChar tc = textLine->getChar (pos);
00736         if ((tc == ';' || tc == ':' || tc == ',') && otherAnchor == -1 && parenCount <= 0)
00737           otherAnchor = pos, potentialAnchorSeen = true;
00738         else if (tc == ')')
00739           parenCount++;
00740         else if (tc == '(')
00741           parenCount--;
00742         else if (tc == '}')
00743           openCount--;
00744         else if (tc == '{')
00745         {
00746           openCount++, potentialAnchorSeen = true;
00747           if (openCount == 1)
00748             break;
00749         }
00750       }
00751     } while (--pos >= textLine->firstChar());
00752 
00753     if (openCount != 0 || otherAnchor != -1)
00754     {
00755       found = true;
00756       QChar c;
00757       if (openCount > 0)
00758         c = '{';
00759       else if (openCount < 0)
00760         c = '}';
00761       else if (otherAnchor >= 0)
00762         c = textLine->getChar (otherAnchor);
00763 
00764       int specialIndent = 0;
00765       if (c == ':' && needContinue)
00766       {
00767         QChar ch;
00768         specialIndent = textLine->firstChar();
00769         if (textLine->stringAtPos(specialIndent, "case"))
00770           ch = textLine->getChar(specialIndent + 4);
00771         else if (textLine->stringAtPos(specialIndent, "default"))
00772           ch = textLine->getChar(specialIndent + 7);
00773         else if (textLine->stringAtPos(specialIndent, "public"))
00774           ch = textLine->getChar(specialIndent + 6);
00775         else if (textLine->stringAtPos(specialIndent, "private"))
00776           ch = textLine->getChar(specialIndent + 7);
00777         else if (textLine->stringAtPos(specialIndent, "protected"))
00778           ch = textLine->getChar(specialIndent + 9);
00779         else if (textLine->stringAtPos(specialIndent, "signals"))
00780           ch = textLine->getChar(specialIndent + 7);
00781         else if (textLine->stringAtPos(specialIndent, "Q_SIGNALS"))
00782           ch = textLine->getChar(specialIndent + 9);
00783         else if (textLine->stringAtPos(specialIndent, "slots"))
00784           ch = textLine->getChar(specialIndent + 5);
00785         else if (textLine->stringAtPos(specialIndent, "Q_SLOTS"))
00786           ch = textLine->getChar(specialIndent + 7);
00787 
00788         if (ch.isNull() || (!ch.isSpace() && ch != '(' && ch != ':'))
00789           continue;
00790 
00791         KateDocCursor lineBegin = cur;
00792         lineBegin.setCol(specialIndent);
00793         specialIndent = measureIndent(lineBegin);
00794         isSpecial = true;
00795       }
00796 
00797       // Move forward past blank lines
00798       KateDocCursor skip = cur;
00799       skip.setCol(textLine->lastChar());
00800       bool result = skipBlanks(skip, begin, true);
00801 
00802       anchorPos = skip.col();
00803       anchorIndent = measureIndent(skip);
00804 
00805       //kdDebug(13030) << "calcIndent anchorPos:" << anchorPos << " anchorIndent:" << anchorIndent << " at line:" << skip.line() << endl;
00806 
00807       // Accept if it's before requested position or if it was special
00808       if (result && skip < begin)
00809       {
00810         cur = skip;
00811         break;
00812       }
00813       else if (isSpecial)
00814       {
00815         anchorIndent = specialIndent;
00816         break;
00817       }
00818 
00819       // Are these on a line by themselves? (i.e. both last and first char)
00820       if ((c == '{' || c == '}') && textLine->getChar(textLine->firstChar()) == c)
00821       {
00822         cur.setCol(anchorPos = textLine->firstChar());
00823         anchorIndent = measureIndent (cur);
00824         break;
00825       }
00826     }
00827   }
00828 
00829   // treat beginning of document as anchor position
00830   if (cur.line() == 0 && cur.col() == 0 && potentialAnchorSeen)
00831     found = true;
00832 
00833   if (!found)
00834     return 0;
00835 
00836   uint continueIndent = (needContinue) ? calcContinue (cur, begin) : 0;
00837   //kdDebug(13030) << "calcIndent continueIndent:" << continueIndent << endl;
00838 
00839   // Move forward from anchor and determine last known reference character
00840   // Braces take precedance over others ...
00841   textLine = doc->plainKateTextLine(cur.line());
00842   QChar lastChar = textLine->getChar (anchorPos);
00843   int lastLine = cur.line();
00844   if (lastChar == '#' || lastChar == '[')
00845   {
00846     // Never continue if # or [ is encountered at this point here
00847     // A fail-safe really... most likely an #include, #region, or a c# attribute
00848     continueIndent = 0;
00849   }
00850 
00851   int openCount = 0;
00852   while (cur.validPosition() && cur < begin)
00853   {
00854     if (!skipBlanks(cur, begin, true))
00855       return 0;
00856 
00857     QChar tc = cur.currentChar();
00858     //kdDebug(13030) << "  cur.line:" << cur.line() << " cur.col:" << cur.col() << " currentChar '" << tc << "' " << textLine->attribute(cur.col()) << endl;
00859     if (cur == begin || tc.isNull())
00860       break;
00861 
00862     if (!tc.isSpace() && cur < begin)
00863     {
00864       uchar attrib = cur.currentAttrib();
00865       if (tc == '{' && attrib == symbolAttrib)
00866         openCount++;
00867       else if (tc == '}' && attrib == symbolAttrib)
00868         openCount--;
00869 
00870       lastChar = tc;
00871       lastLine = cur.line();
00872     }
00873   }
00874   if (openCount > 0) // Open braces override
00875     lastChar = '{';
00876 
00877   uint indent = 0;
00878   //kdDebug(13030) << "calcIndent lastChar '" << lastChar << "'" << endl;
00879 
00880   if (lastChar == '{' || (lastChar == ':' && isSpecial && needContinue))
00881   {
00882     indent = anchorIndent + indentWidth;
00883   }
00884   else if (lastChar == '}')
00885   {
00886     indent = anchorIndent;
00887   }
00888   else if (lastChar == ';')
00889   {
00890     indent = anchorIndent + ((allowSemi && needContinue) ? continueIndent : 0);
00891   }
00892   else if (lastChar == ',')
00893   {
00894     textLine = doc->plainKateTextLine(lastLine);
00895     KateDocCursor start(lastLine, textLine->firstChar(), doc);
00896     KateDocCursor finish(lastLine, textLine->lastChar(), doc);
00897     uint pos = 0;
00898 
00899     if (isBalanced(start, finish, QChar('('), QChar(')'), pos))
00900       indent = anchorIndent;
00901     else
00902     {
00903       // TODO: Config option. If we're below 48, go ahead and line them up
00904       indent = ((pos < 48) ? pos : anchorIndent + (indentWidth * 2));
00905     }
00906   }
00907   else if (!lastChar.isNull())
00908   {
00909     if (anchorIndent != 0)
00910       indent = anchorIndent + continueIndent;
00911     else
00912       indent = continueIndent;
00913   }
00914 
00915   return indent;
00916 }
00917 
00918 uint KateCSmartIndent::calcContinue(KateDocCursor &start, KateDocCursor &end)
00919 {
00920   KateDocCursor cur = start;
00921 
00922   bool needsBalanced = true;
00923   bool isFor = false;
00924   allowSemi = false;
00925 
00926   KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line());
00927 
00928   // Handle cases such as  } while (s ... by skipping the leading symbol
00929   if (textLine->attribute(cur.col()) == symbolAttrib)
00930   {
00931     cur.moveForward(1);
00932     skipBlanks(cur, end, false);
00933   }
00934 
00935   if (textLine->getChar(cur.col()) == '}')
00936   {
00937     skipBlanks(cur, end, true);
00938     if (cur.line() != start.line())
00939       textLine = doc->plainKateTextLine(cur.line());
00940 
00941     if (textLine->stringAtPos(cur.col(), "else"))
00942       cur.setCol(cur.col() + 4);
00943     else
00944       return indentWidth * 2;
00945 
00946     needsBalanced = false;
00947   }
00948   else if (textLine->stringAtPos(cur.col(), "else"))
00949   {
00950     cur.setCol(cur.col() + 4);
00951     needsBalanced = false;
00952     int next = textLine->nextNonSpaceChar(cur.col());
00953     if (next >= 0 && textLine->stringAtPos(next, "if"))
00954     {
00955       cur.setCol(next + 2);
00956       needsBalanced = true;
00957     }
00958   }
00959   else if (textLine->stringAtPos(cur.col(), "if"))
00960   {
00961     cur.setCol(cur.col() + 2);
00962   }
00963   else if (textLine->stringAtPos(cur.col(), "do"))
00964   {
00965     cur.setCol(cur.col() + 2);
00966     needsBalanced = false;
00967   }
00968   else if (textLine->stringAtPos(cur.col(), "for"))
00969   {
00970     cur.setCol(cur.col() + 3);
00971     isFor = true;
00972   }
00973   else if (textLine->stringAtPos(cur.col(), "while"))
00974   {
00975     cur.setCol(cur.col() + 5);
00976   }
00977   else if (textLine->stringAtPos(cur.col(), "switch"))
00978   {
00979     cur.setCol(cur.col() + 6);
00980   }
00981   else if (textLine->stringAtPos(cur.col(), "using"))
00982   {
00983     cur.setCol(cur.col() + 5);
00984   }
00985   else
00986   {
00987     return indentWidth * 2;
00988   }
00989 
00990   uint openPos = 0;
00991   if (needsBalanced && !isBalanced (cur, end, QChar('('), QChar(')'), openPos))
00992   {
00993     allowSemi = isFor;
00994     if (openPos > 0)
00995       return (openPos - textLine->firstChar());
00996     else
00997       return indentWidth * 2;
00998   }
00999 
01000   // Check if this statement ends a line now
01001   skipBlanks(cur, end, false);
01002   if (cur == end)
01003     return indentWidth;
01004 
01005   if (skipBlanks(cur, end, true))
01006   {
01007     if (cur == end)
01008       return indentWidth;
01009     else
01010       return indentWidth + calcContinue(cur, end);
01011   }
01012 
01013   return 0;
01014 }
01015 
01016 uint KateCSmartIndent::findOpeningBrace(KateDocCursor &start)
01017 {
01018   KateDocCursor cur = start;
01019   int count = 1;
01020 
01021   // Move backwards 1 by 1 and find the opening brace
01022   // Return the indent of that line
01023   while (cur.moveBackward(1))
01024   {
01025     if (cur.currentAttrib() == symbolAttrib)
01026     {
01027       QChar ch = cur.currentChar();
01028       if (ch == '{')
01029         count--;
01030       else if (ch == '}')
01031         count++;
01032 
01033       if (count == 0)
01034       {
01035         KateDocCursor temp(cur.line(), doc->plainKateTextLine(cur.line())->firstChar(), doc);
01036         return measureIndent(temp);
01037       }
01038     }
01039   }
01040 
01041   return 0;
01042 }
01043 
01044 bool KateCSmartIndent::firstOpeningBrace(KateDocCursor &start)
01045 {
01046   KateDocCursor cur = start;
01047 
01048   // Are we the first opening brace at this level?
01049   while(cur.moveBackward(1))
01050   {
01051     if (cur.currentAttrib() == symbolAttrib)
01052     {
01053       QChar ch = cur.currentChar();
01054       if (ch == '{')
01055         return false;
01056       else if (ch == '}' && cur.col() == 0)
01057         break;
01058     }
01059   }
01060