| # kate Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Namespaces | Source Files

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

This file is part of the documentation for kate Library Version 3.4.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat Jun 11 20:36:29 2005 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003