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