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 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00016    Boston, MA 02111-1307, USA.
00017 */
00018 
00019 #include "kateautoindent.h"
00020 
00021 #include "kateconfig.h"
00022 #include "katehighlight.h"
00023 #include "kateview.h"
00024 
00025 #include <klocale.h>
00026 
00027 // BEGIN KateAutoIndent
00028 
00029 KateAutoIndent *KateAutoIndent::createIndenter (KateDocument *doc, uint mode)
00030 {
00031   if (mode == KateDocumentConfig::imCStyle)
00032     return new KateCSmartIndent (doc);
00033   else if (mode == KateDocumentConfig::imPythonStyle)
00034     return new KatePythonIndent (doc);
00035 
00036   return new KateAutoIndent (doc);
00037 }
00038 
00039 QStringList KateAutoIndent::listModes ()
00040 {
00041   QStringList l;
00042 
00043   l << modeDescription(KateDocumentConfig::imNormal);
00044   l << modeDescription(KateDocumentConfig::imCStyle);
00045   l << modeDescription(KateDocumentConfig::imPythonStyle);
00046 
00047   return l;
00048 }
00049 
00050 QString KateAutoIndent::modeName (uint mode)
00051 {
00052   if (mode == KateDocumentConfig::imCStyle)
00053     return QString ("cstyle");
00054   else if (mode == KateDocumentConfig::imPythonStyle)
00055     return QString ("python");
00056 
00057   return QString ("normal");
00058 }
00059 
00060 QString KateAutoIndent::modeDescription (uint mode)
00061 {
00062   if (mode == KateDocumentConfig::imCStyle)
00063     return i18n ("C Style");
00064   else if (mode == KateDocumentConfig::imPythonStyle)
00065     return i18n ("Python Style");
00066 
00067   return i18n ("Normal");
00068 }
00069 
00070 uint KateAutoIndent::modeNumber (const QString &name)
00071 {
00072   if (modeName(KateDocumentConfig::imCStyle) == name)
00073     return KateDocumentConfig::imCStyle;
00074   else if (modeName(KateDocumentConfig::imPythonStyle) == name)
00075     return KateDocumentConfig::imPythonStyle;
00076 
00077   return KateDocumentConfig::imNormal;
00078 }
00079 
00080 KateAutoIndent::KateAutoIndent (KateDocument *_doc)
00081  : doc(_doc)
00082 {
00083 }
00084 KateAutoIndent::~KateAutoIndent ()
00085 {
00086 }
00087 
00088 void KateAutoIndent::updateConfig ()
00089 {
00090   KateDocumentConfig *config = doc->config();
00091 
00092   useSpaces   = config->configFlags() & KateDocument::cfSpaceIndent;
00093   tabWidth    = config->tabWidth();
00094   indentWidth = (useSpaces) ? config->indentationWidth() : tabWidth;
00095 
00096   commentAttrib = 0;
00097   ItemDataList items;
00098   doc->highlight()->getItemDataListCopy (0, items);
00099 
00100   for (uint i=0; i<items.count(); i++)
00101   {
00102     if (items.at(i)->name.find("Comment") != -1)
00103     {
00104       commentAttrib = i;
00105       break;
00106     }
00107   }
00108 }
00109 
00110 bool KateAutoIndent::isBalanced (KateDocCursor &begin, const KateDocCursor &end, QChar open, QChar close) const
00111 {
00112   int parenOpen = 0;
00113   int curLine = begin.line();
00114   uchar attrib = 0;
00115   bool parenFound = false;
00116 
00117   TextLine::Ptr textLine = doc->kateTextLine(curLine);
00118 
00119   // Iterate one-by-one finding opening and closing chars
00120   // We assume that the opening and ending chars appear in same context
00121   // meaning we can check their attribute to skip comments and strings etc.
00122   while (begin < end)
00123   {
00124     if (curLine != begin.line())
00125     {
00126       curLine = begin.line();
00127       textLine = doc->kateTextLine(curLine);
00128     }
00129 
00130     QChar c = textLine->getChar(begin.col());
00131     if (c == open)
00132     {
00133       if (!parenFound) // assume first open encountered is the good one
00134       {
00135         parenFound = true;
00136         attrib = textLine->attribute(begin.col());
00137       }
00138       else if (textLine->attribute(begin.col()) != attrib)
00139       {
00140         begin.moveForward(1);
00141         continue;
00142       }
00143 
00144       parenOpen ++;
00145     }
00146     else if (c == close && textLine->attribute(begin.col()) == attrib)
00147     {
00148       parenOpen --;
00149     }
00150     else if (!parenFound && !c.isSpace())
00151     {
00152       return false;
00153     }
00154 
00155     if (parenFound && parenOpen <= 0)
00156       return true;
00157 
00158     begin.moveForward(1);
00159   }
00160 
00161   return false;
00162 }
00163 
00164 bool KateAutoIndent::skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const
00165 {
00166   int curLine = cur.line();
00167   if (newline)
00168     cur.moveForward(1);
00169 
00170   if (cur >= max)
00171     return false;
00172 
00173   TextLine::Ptr textLine = doc->kateTextLine(curLine);
00174   do
00175   {
00176     if (textLine->attribute(cur.col()) != commentAttrib)
00177     {
00178       QChar c = textLine->getChar(cur.col());
00179       if (!c.isNull() && !c.isSpace())
00180         break;
00181     }
00182 
00183     // Make sure col is 0 if we spill into next line  i.e. count the '\n' as a character
00184     if (!cur.moveForward(1))
00185       break;
00186     if (curLine != cur.line())
00187     {
00188       if (!newline)
00189         break;
00190       textLine = doc->kateTextLine(curLine = cur.line());
00191       cur.setCol(0);
00192     }
00193   } while (cur < max);
00194 
00195   if (cur > max)
00196     cur = max;
00197   return true;
00198 }
00199 
00200 uint KateAutoIndent::measureIndent (KateDocCursor &cur) const
00201 {
00202   if (useSpaces)
00203     return cur.col();
00204 
00205   return doc->kateTextLine(cur.line())->cursorX(cur.col(), tabWidth);
00206 }
00207 
00208 QString KateAutoIndent::tabString(uint pos) const
00209 {
00210   QString s;
00211   pos = QMIN (pos, 80); // sanity check for large values of pos
00212 
00213   if (!useSpaces)
00214   {
00215     while (pos >= tabWidth)
00216     {
00217       s += '\t';
00218       pos -= tabWidth;
00219     }
00220   }
00221   while (pos > 0)
00222   {
00223     s += ' ';
00224     pos--;
00225   }
00226   return s;
00227 }
00228 
00229 void KateAutoIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/)
00230 {
00231   int line = begin.line() - 1;
00232   int pos = begin.col();
00233 
00234   while ((line > 0) && (pos < 0)) // search a not empty text line
00235     pos = doc->kateTextLine(--line)->firstChar();
00236 
00237   if (pos > 0)
00238   {
00239     uint indent = doc->kateTextLine(line)->cursorX(pos, tabWidth);
00240     QString filler = tabString (indent);
00241     doc->insertText (begin.line(), 0, filler);
00242     begin.setCol(filler.length());
00243   }
00244   else
00245     begin.setCol(0);
00246 }
00247 
00248 //END
00249 
00250 // BEGIN KateCSmartIndent
00251 
00252 KateCSmartIndent::KateCSmartIndent (KateDocument *doc)
00253  :  KateAutoIndent (doc),
00254     allowSemi (false)
00255 {
00256 
00257 }
00258 
00259 KateCSmartIndent::~KateCSmartIndent ()
00260 {
00261 
00262 }
00263 
00264 void KateCSmartIndent::processNewline (KateDocCursor &begin, bool needContinue)
00265 {
00266   uint indent = calcIndent (begin, needContinue);
00267 
00268   if (indent > 0)
00269   {
00270     QString filler = tabString (indent);
00271     doc->insertText (begin.line(), 0, filler);
00272     begin.setCol(filler.length());
00273   }
00274   else
00275   {
00276     // fall back to normal autoindent (improves some corner cases)
00277     KateAutoIndent::processNewline (begin, needContinue);
00278   }
00279 }
00280 
00281 void KateCSmartIndent::processChar(QChar c)
00282 {
00283   if (c != '}' && c != '{' && c != '#' && c != ':')
00284     return;
00285   KateView *view = doc->activeView();
00286   KateDocCursor begin(view->cursorLine(), view->cursorColumnReal() - 1, doc);
00287 
00288   // Make sure this is the only character on the line if it isn't a ':'
00289   TextLine::Ptr textLine = doc->kateTextLine(begin.line());
00290   if (c != ':')
00291   {
00292     if (textLine->firstChar() != begin.col())
00293       return;
00294   }
00295 
00296   // For # directives just remove the entire beginning of the line
00297   if (c == '#')
00298   {
00299     doc->removeText(begin.line(), 0, begin.line(), begin.col());
00300   }
00301   else if (c == ':')  // For a ':' line everything up :)
00302   {
00303     int lineStart = textLine->firstChar();
00304     if (textLine->stringAtPos (lineStart, "public") ||
00305         textLine->stringAtPos (lineStart, "private") ||
00306         textLine->stringAtPos (lineStart, "protected") ||
00307         textLine->stringAtPos (lineStart, "case"))
00308     {
00309       int line = begin.line();
00310       int pos = 0;
00311       while (line > 0) // search backwards until we find an ending ':' and go from there
00312       {
00313         textLine = doc->kateTextLine(--line);
00314         pos = textLine->lastChar();
00315         if (pos >= 0 && textLine->getChar(pos) == '{')
00316           return;
00317         if (pos >= 0 && textLine->getChar(pos) == ':')
00318           break;
00319       }
00320 
00321       KateDocCursor temp(line, textLine->firstChar(), doc);
00322       doc->removeText(begin.line(), 0, begin.line(), lineStart);
00323       doc->insertText(begin.line(), 0, tabString( measureIndent(temp) ));
00324     }
00325   }
00326   else  // Must be left with a brace. Put it where it belongs
00327   {
00328     int indent = calcIndent(begin, false);
00329     if (c == '}')
00330     {
00331       if (indent - (int)indentWidth >= 0)
00332         indent -= indentWidth;
00333     }
00334 
00335     if (indent > (int)measureIndent(begin))
00336       indent = measureIndent(begin);
00337 
00338     doc->removeText(begin.line(), 0, begin.line(), begin.col());
00339     doc->insertText(begin.line(), 0, tabString(indent));
00340   }
00341 }
00342 
00343 uint KateCSmartIndent::calcIndent(KateDocCursor &begin, bool needContinue)
00344 {
00345   TextLine::Ptr textLine;
00346   KateDocCursor cur = begin;
00347 
00348   uint anchorIndent = 0;
00349   int anchorPos = 0;
00350   bool found = false;
00351   bool isSpecial = false;
00352 
00353   // Find Indent Anchor Point
00354   while (cur.gotoPreviousLine())
00355   {
00356     isSpecial = found = false;
00357     textLine = doc->kateTextLine(cur.line());
00358 
00359     // Skip comments and handle cases like if (...) { stmt;
00360     int pos = textLine->lastChar();
00361     int openCount = 0;
00362     int otherAnchor = -1;
00363     do
00364     {
00365       if (textLine->attribute (pos) != commentAttrib)
00366       {
00367         QChar tc = textLine->getChar (pos);
00368         if ((tc == ';' || tc == ':') && otherAnchor == -1)
00369           otherAnchor = pos;
00370         else if (tc == '}')
00371           openCount --;
00372         else if (tc == '{')
00373         {
00374           openCount ++;
00375           if (openCount == 1)
00376             break;
00377         }
00378         else if (tc == '(' || tc == ')')
00379           break;
00380       }
00381     } while (--pos >= textLine->firstChar());
00382 
00383     if (openCount != 0 || otherAnchor != -1)
00384     {
00385       found = true;
00386       QChar c;
00387       if (openCount > 0)
00388         c = '{';
00389       else if (openCount < 0)
00390         c = '}';
00391       else if (otherAnchor >= 0)
00392         c = textLine->getChar (otherAnchor);
00393 
00394       int specialIndent = 0;
00395       if (c == ':' && needContinue)
00396       {
00397         QChar ch;
00398         specialIndent = textLine->firstChar();
00399         if (textLine->stringAtPos(specialIndent, "case"))
00400           ch = textLine->getChar(specialIndent + 4);
00401         else if (textLine->stringAtPos(specialIndent, "public"))
00402           ch = textLine->getChar(specialIndent + 6);
00403         else if (textLine->stringAtPos(specialIndent, "private"))
00404           ch = textLine->getChar(specialIndent + 7);
00405         else if (textLine->stringAtPos(specialIndent, "protected"))
00406           ch = textLine->getChar(specialIndent + 9);
00407 
00408         if (ch.isNull() || (!ch.isSpace() && ch != '(' && ch != ':'))
00409           continue;
00410 
00411         KateDocCursor lineBegin = cur;
00412         lineBegin.setCol(specialIndent);
00413         specialIndent = measureIndent(lineBegin);
00414         isSpecial = true;
00415       }
00416 
00417       // Move forward past blank lines
00418       KateDocCursor skip = cur;
00419       skip.setCol(textLine->lastChar());
00420       bool result = skipBlanks(skip, begin, true);
00421 
00422       anchorPos = skip.col();
00423       anchorIndent = measureIndent(skip);
00424 
00425       // Accept if it's before requested position or if it was special
00426       if (result && skip < begin)
00427       {
00428         cur = skip;
00429         break;
00430       }
00431       else if (isSpecial)
00432       {
00433         anchorIndent = specialIndent;
00434         break;
00435       }
00436 
00437       // Are these on a line by themselves? (i.e. both last and first char)
00438       if ((c == '{' || c == '}') && textLine->getChar(textLine->firstChar()) == c)
00439       {
00440         cur.setCol(anchorPos = textLine->firstChar());
00441         anchorIndent = measureIndent (cur);
00442         break;
00443       }
00444     }
00445   }
00446 
00447   if (!found)
00448     return 0;
00449 
00450   uint continueIndent = (needContinue) ? calcContinue (cur, begin) : 0;
00451 
00452   // Move forward from anchor and determine last known reference character
00453   // Braces take precedance over others ...
00454   QChar lastChar = textLine->getChar (anchorPos);
00455   int openCount = 0;
00456   if (cur < begin)
00457   {
00458     do
00459     {
00460       if (!skipBlanks(cur, begin, true))
00461         return 0;
00462 
00463       QChar tc = cur.currentChar();
00464       if (cur == begin || tc.isNull())
00465         break;
00466 
00467       if (!tc.isSpace() && cur < begin)
00468       {
00469         if (tc == '{')
00470           openCount ++;
00471         else if (tc == '}')
00472           openCount --;
00473 
00474         lastChar = tc;
00475       }
00476     } while (cur.validPosition() && cur < begin);
00477   }
00478   // Open braces override
00479   if (openCount > 0)
00480     lastChar = '{';
00481 
00482   uint indent = 0;
00483   if (lastChar == '{' || (lastChar == ':' && isSpecial && needContinue))
00484   {
00485     indent = anchorIndent + indentWidth;
00486   }
00487   else if (lastChar == '}')
00488   {
00489     indent = anchorIndent;
00490   }
00491   else if (lastChar == ';')
00492   {
00493     indent = anchorIndent + ((allowSemi && needContinue) ? continueIndent : 0);
00494   }
00495   else if (!lastChar.isNull() && anchorIndent != 0)
00496   {
00497     indent = anchorIndent + continueIndent;
00498   }
00499 
00500   return indent;
00501 }
00502 
00503 uint KateCSmartIndent::calcContinue(KateDocCursor &start, KateDocCursor &end)
00504 {
00505   KateDocCursor cur = start;
00506 
00507   bool needsBalanced = false;
00508   bool isFor = false;
00509   allowSemi = false;
00510 
00511   TextLine::Ptr textLine = doc->kateTextLine(cur.line());
00512   uint length = textLine->length();
00513 
00514   if (textLine->getChar(cur.col()) == '}')
00515   {
00516     skipBlanks(cur, end, true);
00517     if (cur.line() != start.line())
00518       textLine = doc->kateTextLine(cur.line());
00519 
00520     if (textLine->stringAtPos(cur.col(), "else"))
00521       cur.setCol(cur.col() + 4);
00522     else
00523       return indentWidth * 2;
00524   }
00525   else if (textLine->stringAtPos(cur.col(), "else"))
00526   {
00527     cur.setCol(cur.col() + 4);
00528     if (textLine->stringAtPos(textLine->nextNonSpaceChar(cur.col()), "if"))
00529     {
00530       cur.setCol(textLine->nextNonSpaceChar(cur.col()) + 2);
00531       needsBalanced = true;
00532     }
00533   }
00534   else if (textLine->stringAtPos(cur.col(), "do"))
00535   {
00536     cur.setCol(cur.col() + 2);
00537   }
00538   else if (textLine->stringAtPos(cur.col(), "for"))
00539   {
00540     cur.setCol(cur.col() + 3);
00541     isFor = needsBalanced = true;
00542   }
00543   else if (textLine->stringAtPos(cur.col(), "if"))
00544   {
00545     cur.setCol(cur.col() + 2);
00546     needsBalanced = true;
00547   }
00548   else if (textLine->stringAtPos(cur.col(), "while"))
00549   {
00550     cur.setCol(cur.col() + 5);
00551     needsBalanced = true;
00552   }
00553   else
00554     return indentWidth * 2;
00555 
00556   if (needsBalanced && !isBalanced (cur, end, QChar('('), QChar(')')))
00557   {
00558     allowSemi = isFor;
00559     return indentWidth * 2;
00560   }
00561   // Check if this statement ends a line now
00562   skipBlanks(cur, end, false);
00563   if (cur == end || (cur.col() == (int)length-1))
00564     return indentWidth;
00565 
00566   if (skipBlanks(cur, end, true))
00567   {
00568     if (cur == end)
00569       return indentWidth;
00570     else
00571       return indentWidth + calcContinue(cur, end);
00572   }
00573 
00574   return 0;
00575 }
00576 
00577 // END
00578 
00579 // BEGIN KatePythonIndent
00580 
00581 QRegExp KatePythonIndent::endWithColon = QRegExp( "^[^#]*:\\s*(#.*)?$" );
00582 QRegExp KatePythonIndent::stopStmt = QRegExp( "^\\s*(break|continue|raise|return|pass)\\b.*" );
00583 QRegExp KatePythonIndent::blockBegin = QRegExp( "^\\s*(def|if|elif|else|for|while|try)\\b.*" );
00584 
00585 KatePythonIndent::KatePythonIndent (KateDocument *doc)
00586   : KateAutoIndent (doc)
00587 {
00588 }
00589 KatePythonIndent::~KatePythonIndent ()
00590 {
00591 }
00592 
00593 void KatePythonIndent::processNewline (KateDocCursor &begin, bool /*newline*/)
00594 {
00595   int prevLine = begin.line() - 1;
00596   int prevPos = begin.col();
00597 
00598   while ((prevLine > 0) && (prevPos < 0)) // search a not empty text line
00599     prevPos = doc->kateTextLine(--prevLine)->firstChar();
00600 
00601   int prevBlock = prevLine;
00602   int prevBlockPos = prevPos;
00603   int extraIndent = calcExtra (prevBlock, prevBlockPos, begin);
00604 
00605   int indent = doc->kateTextLine(prevBlock)->cursorX(prevBlockPos, tabWidth);
00606   if (extraIndent == 0)
00607   {
00608     if (!stopStmt.exactMatch(doc->kateTextLine(prevLine)->string()))
00609     {
00610       if (endWithColon.exactMatch(doc->kateTextLine(prevLine)->string()))
00611         indent += indentWidth;
00612       else
00613         indent = doc->kateTextLine(prevLine)->cursorX(prevPos, tabWidth);
00614     }
00615   }
00616   else
00617     indent += extraIndent;
00618 
00619   if (indent > 0)
00620   {
00621     QString filler = tabString (indent);
00622     doc->insertText (begin.line(), 0, filler);
00623     begin.setCol(filler.length());
00624   }
00625   else
00626     begin.setCol(0);
00627 }
00628 
00629 int KatePythonIndent::calcExtra (int &prevBlock, int &pos, KateDocCursor &end)
00630 {
00631   int nestLevel = 0;
00632   bool levelFound = false;
00633   while ((prevBlock > 0))
00634   {
00635     if (blockBegin.exactMatch(doc->kateTextLine(prevBlock)->string()))
00636     {
00637       if ((!levelFound && nestLevel == 0) || (levelFound && nestLevel - 1 <= 0))
00638       {
00639         pos = doc->kateTextLine(prevBlock)->firstChar();
00640         break;
00641       }
00642 
00643       nestLevel --;
00644     }
00645     else if (stopStmt.exactMatch(doc->kateTextLine(prevBlock)->string()))
00646     {
00647       nestLevel ++;
00648       levelFound = true;
00649     }
00650 
00651     --prevBlock;
00652   }
00653 
00654   KateDocCursor cur (prevBlock, pos, doc);
00655   QChar c;
00656   int extraIndent = 0;
00657   while (cur.line() < end.line())
00658   {
00659     c = cur.currentChar();
00660 
00661     if (c == '(')
00662       extraIndent += indentWidth;
00663     else if (c == ')')
00664       extraIndent -= indentWidth;
00665     else if (c == ':')
00666       break;
00667 
00668     if (c.isNull() || c == '#')
00669       cur.gotoNextLine();
00670     else
00671       cur.moveForward(1);
00672   }
00673 
00674   return extraIndent;
00675 }
00676 
00677 // END
00678 
00679 // kate: space-indent on; indent-width 2; replace-tabs on; indent-mode cstyle;
KDE Logo
This file is part of the documentation for kate Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Apr 22 14:26:24 2004 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2003