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.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Oct 8 11:16:24 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003