kateautoindent.cpp

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