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