kate Library API Documentation

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;
KDE Logo
This file is part of the documentation for kate Library Version 3.4.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Jul 20 12:57:21 2006 by doxygen 1.4.4 written by Dimitri van Heesch, © 1997-2003