• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

Kate

kateautoindent.cpp

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

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal