/* This file is part of the KDE libraries Copyright (C) 2003 Jesse Yurkovich Copyright (C) 2004 >Anders Lund (KateVarIndent class) Copyright (C) 2005 Dominik Haumann (basic support for config page) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateautoindent.h" #include "kateautoindent.moc" #include "kateconfig.h" #include "katehighlight.h" #include "katefactory.h" #include "katejscript.h" #include "kateview.h" #include #include #include #include //BEGIN KateAutoIndent KateAutoIndent *KateAutoIndent::createIndenter (KateDocument *doc, uint mode) { if (mode == KateDocumentConfig::imNormal) return new KateNormalIndent (doc); else if (mode == KateDocumentConfig::imCStyle) return new KateCSmartIndent (doc); else if (mode == KateDocumentConfig::imPythonStyle) return new KatePythonIndent (doc); else if (mode == KateDocumentConfig::imXmlStyle) return new KateXmlIndent (doc); else if (mode == KateDocumentConfig::imCSAndS) return new KateCSAndSIndent (doc); else if ( mode == KateDocumentConfig::imVarIndent ) return new KateVarIndent ( doc ); // else if ( mode == KateDocumentConfig::imScriptIndent) // return new KateScriptIndent ( doc ); return new KateAutoIndent (doc); } TQStringList KateAutoIndent::listModes () { TQStringList l; l << modeDescription(KateDocumentConfig::imNone); l << modeDescription(KateDocumentConfig::imNormal); l << modeDescription(KateDocumentConfig::imCStyle); l << modeDescription(KateDocumentConfig::imPythonStyle); l << modeDescription(KateDocumentConfig::imXmlStyle); l << modeDescription(KateDocumentConfig::imCSAndS); l << modeDescription(KateDocumentConfig::imVarIndent); // l << modeDescription(KateDocumentConfig::imScriptIndent); return l; } TQString KateAutoIndent::modeName (uint mode) { if (mode == KateDocumentConfig::imNormal) return TQString ("normal"); else if (mode == KateDocumentConfig::imCStyle) return TQString ("cstyle"); else if (mode == KateDocumentConfig::imPythonStyle) return TQString ("python"); else if (mode == KateDocumentConfig::imXmlStyle) return TQString ("xml"); else if (mode == KateDocumentConfig::imCSAndS) return TQString ("csands"); else if ( mode == KateDocumentConfig::imVarIndent ) return TQString( "varindent" ); // else if ( mode == KateDocumentConfig::imScriptIndent ) // return TQString( "scriptindent" ); return TQString ("none"); } TQString KateAutoIndent::modeDescription (uint mode) { if (mode == KateDocumentConfig::imNormal) return i18n ("Normal"); else if (mode == KateDocumentConfig::imCStyle) return i18n ("C Style"); else if (mode == KateDocumentConfig::imPythonStyle) return i18n ("Python Style"); else if (mode == KateDocumentConfig::imXmlStyle) return i18n ("XML Style"); else if (mode == KateDocumentConfig::imCSAndS) return i18n ("S&S C Style"); else if ( mode == KateDocumentConfig::imVarIndent ) return i18n("Variable Based Indenter"); // else if ( mode == KateDocumentConfig::imScriptIndent ) // return i18n("JavaScript Indenter"); return i18n ("None"); } uint KateAutoIndent::modeNumber (const TQString &name) { if (modeName(KateDocumentConfig::imNormal) == name) return KateDocumentConfig::imNormal; else if (modeName(KateDocumentConfig::imCStyle) == name) return KateDocumentConfig::imCStyle; else if (modeName(KateDocumentConfig::imPythonStyle) == name) return KateDocumentConfig::imPythonStyle; else if (modeName(KateDocumentConfig::imXmlStyle) == name) return KateDocumentConfig::imXmlStyle; else if (modeName(KateDocumentConfig::imCSAndS) == name) return KateDocumentConfig::imCSAndS; else if ( modeName( KateDocumentConfig::imVarIndent ) == name ) return KateDocumentConfig::imVarIndent; // else if ( modeName( KateDocumentConfig::imScriptIndent ) == name ) // return KateDocumentConfig::imScriptIndent; return KateDocumentConfig::imNone; } bool KateAutoIndent::hasConfigPage (uint mode) { // if ( mode == KateDocumentConfig::imScriptIndent ) // return true; return false; } IndenterConfigPage* KateAutoIndent::configPage(TQWidget *parent, uint mode) { // if ( mode == KateDocumentConfig::imScriptIndent ) // return new ScriptIndentConfigPage(parent, "script_indent_config_page"); return 0; } KateAutoIndent::KateAutoIndent (KateDocument *_doc) : TQObject(), doc(_doc) { } KateAutoIndent::~KateAutoIndent () { } //END KateAutoIndent //BEGIN KateViewIndentAction KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const TQString& text, TQObject* parent, const char* name) : TDEActionMenu (text, parent, name), doc(_doc) { connect(popupMenu(),TQT_SIGNAL(aboutToShow()),this,TQT_SLOT(slotAboutToShow())); } void KateViewIndentationAction::slotAboutToShow() { TQStringList modes = KateAutoIndent::listModes (); popupMenu()->clear (); for (uint z=0; zinsertItem ( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&"), this, TQT_SLOT(setMode(int)), 0, z); popupMenu()->setItemChecked (doc->config()->indentationMode(), true); } void KateViewIndentationAction::setMode (int mode) { doc->config()->setIndentationMode((uint)mode); } //END KateViewIndentationAction //BEGIN KateNormalIndent KateNormalIndent::KateNormalIndent (KateDocument *_doc) : KateAutoIndent (_doc) { // if highlighting changes, update attributes connect(_doc, TQT_SIGNAL(hlChanged()), this, TQT_SLOT(updateConfig())); } KateNormalIndent::~KateNormalIndent () { } void KateNormalIndent::updateConfig () { KateDocumentConfig *config = doc->config(); useSpaces = config->configFlags() & KateDocument::cfSpaceIndent || config->configFlags() & KateDocumentConfig::cfReplaceTabsDyn; mixedIndent = useSpaces && config->configFlags() & KateDocumentConfig::cfMixedIndent; keepProfile = config->configFlags() & KateDocument::cfKeepIndentProfile; tabWidth = config->tabWidth(); indentWidth = useSpaces? config->indentationWidth() : tabWidth; commentAttrib = 255; doxyCommentAttrib = 255; regionAttrib = 255; symbolAttrib = 255; alertAttrib = 255; tagAttrib = 255; wordAttrib = 255; keywordAttrib = 255; normalAttrib = 255; extensionAttrib = 255; preprocessorAttrib = 255; stringAttrib = 255; charAttrib = 255; KateHlItemDataList items; doc->highlight()->getKateHlItemDataListCopy (0, items); for (uint i=0; iname; if (name.find("Comment") != -1 && commentAttrib == 255) { commentAttrib = i; } else if (name.find("Region Marker") != -1 && regionAttrib == 255) { regionAttrib = i; } else if (name.find("Symbol") != -1 && symbolAttrib == 255) { symbolAttrib = i; } else if (name.find("Alert") != -1) { alertAttrib = i; } else if (name.find("Comment") != -1 && commentAttrib != 255 && doxyCommentAttrib == 255) { doxyCommentAttrib = i; } else if (name.find("Tags") != -1 && tagAttrib == 255) { tagAttrib = i; } else if (name.find("Word") != -1 && wordAttrib == 255) { wordAttrib = i; } else if (name.find("Keyword") != -1 && keywordAttrib == 255) { keywordAttrib = i; } else if (name.find("Normal") != -1 && normalAttrib == 255) { normalAttrib = i; } else if (name.find("Extensions") != -1 && extensionAttrib == 255) { extensionAttrib = i; } else if (name.find("Preprocessor") != -1 && preprocessorAttrib == 255) { preprocessorAttrib = i; } else if (name.find("String") != -1 && stringAttrib == 255) { stringAttrib = i; } else if (name.find("Char") != -1 && charAttrib == 255) { charAttrib = i; } } } bool KateNormalIndent::isBalanced (KateDocCursor &begin, const KateDocCursor &end, TQChar open, TQChar close, uint &pos) const { int parenOpen = 0; bool atLeastOne = false; bool getNext = false; pos = doc->plainKateTextLine(begin.line())->firstChar(); // Iterate one-by-one finding opening and closing chars // Assume that open and close are 'Symbol' characters while (begin < end) { TQChar c = begin.currentChar(); if (begin.currentAttrib() == symbolAttrib) { if (c == open) { if (!atLeastOne) { atLeastOne = true; getNext = true; pos = measureIndent(begin) + 1; } parenOpen++; } else if (c == close) { parenOpen--; } } else if (getNext && !c.isSpace()) { getNext = false; pos = measureIndent(begin); } if (atLeastOne && parenOpen <= 0) return true; if (!begin.moveForward(1)) break; } return (atLeastOne) ? false : true; } bool KateNormalIndent::skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const { int curLine = cur.line(); if (newline) cur.moveForward(1); if (cur >= max) return false; do { uchar attrib = cur.currentAttrib(); const TQString hlFile = doc->highlight()->hlKeyForAttrib( attrib ); if (attrib != commentAttrib && attrib != regionAttrib && attrib != alertAttrib && attrib != preprocessorAttrib && !hlFile.endsWith("doxygen.xml")) { TQChar c = cur.currentChar(); if (!c.isNull() && !c.isSpace()) break; } if (!cur.moveForward(1)) { // not able to move forward, so set cur to max cur = max; break; } // Make sure col is 0 if we spill into next line i.e. count the '\n' as a character if (curLine != cur.line()) { if (!newline) break; curLine = cur.line(); cur.setCol(0); } } while (cur < max); if (cur > max) cur = max; return true; } uint KateNormalIndent::measureIndent (KateDocCursor &cur) const { // We cannot short-cut by checking for useSpaces because there may be // tabs in the line despite this setting. return doc->plainKateTextLine(cur.line())->cursorX(cur.col(), tabWidth); } TQString KateNormalIndent::tabString(uint pos) const { TQString s; pos = kMin (pos, 80U); // sanity check for large values of pos if (!useSpaces || mixedIndent) { while (pos >= tabWidth) { s += '\t'; pos -= tabWidth; } } while (pos > 0) { s += ' '; pos--; } return s; } void KateNormalIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/) { int line = begin.line() - 1; int pos = begin.col(); while ((line > 0) && (pos < 0)) // search a not empty text line pos = doc->plainKateTextLine(--line)->firstChar(); if (pos > 0) { TQString filler = doc->text(line, 0, line, pos); doc->insertText(begin.line(), 0, filler); begin.setCol(filler.length()); } else begin.setCol(0); } //END //BEGIN KateCSmartIndent KateCSmartIndent::KateCSmartIndent (KateDocument *doc) : KateNormalIndent (doc), allowSemi (false), processingBlock (false) { kdDebug(13030)<<"CREATING KATECSMART INTDETER"<plainKateTextLine(line.line()); int firstChar = textLine->firstChar(); // Empty line is worthless ... but only when doing more than 1 line if (firstChar == -1 && processingBlock) return; uint indent = 0; // TODO Here we do not check for beginning and ending comments ... TQChar first = textLine->getChar(firstChar); TQChar last = textLine->getChar(textLine->lastChar()); if (first == '}') { indent = findOpeningBrace(line); } else if (first == ')') { indent = findOpeningParen(line); } else if (first == '{') { // If this is the first brace, we keep the indent at 0 KateDocCursor temp(line.line(), firstChar, doc); if (!firstOpeningBrace(temp)) indent = calcIndent(temp, false); } else if (first == ':') { // Initialization lists (handle c++ and c#) int pos = findOpeningBrace(line); if (pos == 0) indent = indentWidth; else indent = pos + (indentWidth * 2); } else if (last == ':') { if (textLine->stringAtPos (firstChar, "case") || textLine->stringAtPos (firstChar, "default") || textLine->stringAtPos (firstChar, "public") || textLine->stringAtPos (firstChar, "private") || textLine->stringAtPos (firstChar, "protected") || textLine->stringAtPos (firstChar, "signals") || textLine->stringAtPos (firstChar, "Q_SIGNALS") || textLine->stringAtPos (firstChar, "Q_SLOTS") || textLine->stringAtPos (firstChar, "slots")) { indent = findOpeningBrace(line) + indentWidth; } } else if (first == '*') { if (last == '/') { int lineEnd = textLine->lastChar(); if (lineEnd > 0 && textLine->getChar(lineEnd - 1) == '*') { indent = findOpeningComment(line); if (textLine->attribute(firstChar) == doxyCommentAttrib) indent++; } else return; } else { KateDocCursor temp = line; if (textLine->attribute(firstChar) == doxyCommentAttrib) indent = calcIndent(temp, false) + 1; else indent = calcIndent(temp, true); } } else if (first == '#') { // c# regions if (textLine->stringAtPos (firstChar, "#region") || textLine->stringAtPos (firstChar, "#endregion")) { KateDocCursor temp = line; indent = calcIndent(temp, true); } } else { // Everything else ... if (first == '/' && last != '/') return; KateDocCursor temp = line; indent = calcIndent(temp, true); if (indent == 0) { KateNormalIndent::processNewline(line, true); return; } } // Slightly faster if we don't indent what we don't have to if (indent != measureIndent(line) || first == '}' || first == '{' || first == '#') { doc->removeText(line.line(), 0, line.line(), firstChar); TQString filler = tabString(indent); if (indent > 0) doc->insertText(line.line(), 0, filler); if (!processingBlock) line.setCol(filler.length()); } } void KateCSmartIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) { kdDebug(13030)<<"PROCESS SECTION"< 0) ? true : false; while (cur.line() <= end.line()) { processLine (cur); if (!cur.gotoNextLine()) break; } processingBlock = false; kdDebug(13030) << "+++ total: " << t.elapsed() << endl; } bool KateCSmartIndent::handleDoxygen (KateDocCursor &begin) { // Factor out the rather involved Doxygen stuff here ... int line = begin.line(); int first = -1; while ((line > 0) && (first < 0)) first = doc->plainKateTextLine(--line)->firstChar(); if (first >= 0) { KateTextLine::Ptr textLine = doc->plainKateTextLine(line); bool insideDoxygen = false; bool justAfterDoxygen = false; if (textLine->attribute(first) == doxyCommentAttrib || textLine->attribute(textLine->lastChar()) == doxyCommentAttrib) { const int last = textLine->lastChar(); if (last <= 0 || !(justAfterDoxygen = textLine->stringAtPos(last-1, "*/"))) insideDoxygen = true; if (justAfterDoxygen) justAfterDoxygen &= textLine->string().find("/**") < 0; while (textLine->attribute(first) != doxyCommentAttrib && first <= textLine->lastChar()) first++; if (textLine->stringAtPos(first, "//")) return false; } // Align the *'s and then go ahead and insert one too ... if (insideDoxygen) { textLine = doc->plainKateTextLine(begin.line()); first = textLine->firstChar(); int indent = findOpeningComment(begin); TQString filler = tabString (indent); bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping; if ( doxygenAutoInsert && ((first < 0) || (!textLine->stringAtPos(first, "*/") && !textLine->stringAtPos(first, "*")))) { filler = filler + " * "; } doc->removeText (begin.line(), 0, begin.line(), first); doc->insertText (begin.line(), 0, filler); begin.setCol(filler.length()); return true; } // Align position with beginning of doxygen comment. Otherwise the // indentation is one too much. else if (justAfterDoxygen) { textLine = doc->plainKateTextLine(begin.line()); first = textLine->firstChar(); int indent = findOpeningComment(begin); TQString filler = tabString (indent); doc->removeText (begin.line(), 0, begin.line(), first); doc->insertText (begin.line(), 0, filler); begin.setCol(filler.length()); return true; } } return false; } void KateCSmartIndent::processNewline (KateDocCursor &begin, bool needContinue) { if (!handleDoxygen (begin)) { KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); bool inMiddle = textLine->firstChar() > -1; int indent = calcIndent (begin, needContinue); if (indent > 0 || inMiddle) { TQString filler = tabString (indent); doc->insertText (begin.line(), 0, filler); begin.setCol(filler.length()); // Handles cases where user hits enter at the beginning or middle of text if (inMiddle) { processLine(begin); begin.setCol(textLine->firstChar()); } } else { KateNormalIndent::processNewline (begin, needContinue); } if (begin.col() < 0) begin.setCol(0); } } /** * Returns true when the given attribute matches any "colon influence immune" * attribute * @param indenter indenter * @param attr1 attribute of previous char * @param attr2 attribute of char preceding previous char * @param prev1 previous character (0 if none) * @param prev2 character preceding previous character (0 if none) */ static inline bool isColonImmune(const KateNormalIndent &indenter, uchar attr1, uchar attr2, TQChar prev1, TQChar prev2) { return attr1 == indenter.preprocessorAttrib // FIXME: no way to discriminate against multiline comment and single // line comment. Therefore, using prev? is futile. || attr1 == indenter.commentAttrib /*&& prev2 != '*' && prev1 != '/'*/ || attr1 == indenter.doxyCommentAttrib || attr1 == indenter.stringAttrib && (attr2 != indenter.stringAttrib || (prev1 != '"' || prev2 == '\\' && attr2 == indenter.charAttrib)) || prev1 == '\'' && attr1 != indenter.charAttrib; } /** * Returns true when the colon is allowed to reindent the current line * @param indenter current indenter * @param line current line * @param curCol column of most recently input character */ static inline bool colonPermitsReindent(const KateNormalIndent &indenter, const KateTextLine::Ptr &line, int curCol ) { const TQString txt = line->string(0,curCol); // do we have any significant preceding colon? for (int pos = 0; (pos = txt.find(':', pos)) >= 0; pos++) { if (line->attribute(pos) == indenter.symbolAttrib) // yes, it has already contributed to this line's indentation, don't // indent again return false; } // otherwise, check whether this colon is not within an influence // immune attribute range return !isColonImmune(indenter, line->attribute(curCol - 1), line->attribute(curCol - 2), txt[curCol - 1], txt[curCol - 2]); } void KateCSmartIndent::processChar(TQChar c) { // You may be curious about 'n' among the triggers: // It is used to discriminate C#'s #region/#endregion which are indented // against normal preprocessing statements which aren't indented. static const TQString triggers("}{)/:#n"); static const TQString firstTriggers("}{)/:#"); static const TQString lastTriggers(":n"); if (triggers.find(c) < 0) return; KateView *view = doc->activeView(); int curCol = view->cursorColumnReal() - 1; KateDocCursor begin(view->cursorLine(), 0, doc); KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); const TQChar curChar = textLine->getChar(curCol); const int first = textLine->firstChar(); const TQChar firstChar = textLine->getChar(first); #if 0 // nice try // Only indent on symbols or preprocessing directives -- never on // anything else kdDebug() << "curChar " << curChar << " curCol " << curCol << " textlen " << textLine->length() << " a " << textLine->attribute( curCol ) << " sym " << symbolAttrib << " pp " << preprocessorAttrib << endl; if (!(((curChar == '#' || curChar == 'n') && textLine->attribute( curCol ) == preprocessorAttrib) || textLine->attribute( curCol ) == symbolAttrib) ) return; kdDebug() << "curChar " << curChar << endl; #endif if (c == 'n') { if (firstChar != '#' || textLine->string(curCol-5, 5) != TQString::fromLatin1("regio")) return; } if ( c == '/' ) { // dominik: if line is "* /", change it to "*/" if ( textLine->attribute( begin.col() ) == doxyCommentAttrib ) { // if the first char exists and is a '*', and the next non-space-char // is already the just typed '/', concatenate it to "*/". if ( first != -1 && firstChar == '*' && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 ) doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1); } // ls: never have comments change the indentation. return; } // ls: only reindent line if the user actually expects it // I. e. take action on single braces on line or last colon, but inhibit // any reindentation if any of those characters appear amidst some section // of the line const TQChar lastChar = textLine->getChar(textLine->lastChar()); int pos; if (((c == firstChar && firstTriggers.find(firstChar) >= 0) || (c == lastChar && lastTriggers.find(lastChar) >= 0)) && (c != ':' || colonPermitsReindent(*this, textLine, curCol))) processLine(begin); } uint KateCSmartIndent::calcIndent(KateDocCursor &begin, bool needContinue) { KateTextLine::Ptr textLine; KateDocCursor cur = begin; uint anchorIndent = 0; int anchorPos = 0; int parenCount = 0; // Possibly in a multiline for stmt. Used to skip ';' ... bool found = false; bool isSpecial = false; bool potentialAnchorSeen = false; bool isArg = false; // ...arg, bool parenthesizedArg = false; // ...(arg, //kdDebug(13030) << "calcIndent begin line:" << begin.line() << " col:" << begin.col() << endl; // Find Indent Anchor Point while (cur.gotoPreviousLine()) { isSpecial = found = false; textLine = doc->plainKateTextLine(cur.line()); // Skip comments and handle cases like if (...) { stmt; int pos = textLine->lastChar(); int openCount = 0; int otherAnchor = -1; do { if (textLine->attribute(pos) == symbolAttrib) { TQChar tc = textLine->getChar (pos); if ((tc == ';' || tc == ':' || tc == ',') && otherAnchor == -1 && parenCount <= 0) { otherAnchor = pos, potentialAnchorSeen = true; isArg = tc == ','; } else if (tc == ')') parenCount++; else if (tc == '(') parenCount--, parenthesizedArg = isArg, potentialAnchorSeen = true; else if (tc == '}') openCount--; else if (tc == '{') { openCount++, potentialAnchorSeen = true; if (openCount == 1) break; } } } while (--pos >= textLine->firstChar()); if (openCount != 0 || otherAnchor != -1) { found = true; TQChar c; if (openCount > 0) c = '{'; else if (openCount < 0) c = '}'; else if (otherAnchor >= 0) c = textLine->getChar (otherAnchor); int specialIndent = 0; if (c == ':' && needContinue) { TQChar ch; specialIndent = textLine->firstChar(); if (textLine->stringAtPos(specialIndent, "case")) ch = textLine->getChar(specialIndent + 4); else if (textLine->stringAtPos(specialIndent, "default")) ch = textLine->getChar(specialIndent + 7); else if (textLine->stringAtPos(specialIndent, "public")) ch = textLine->getChar(specialIndent + 6); else if (textLine->stringAtPos(specialIndent, "private")) ch = textLine->getChar(specialIndent + 7); else if (textLine->stringAtPos(specialIndent, "protected")) ch = textLine->getChar(specialIndent + 9); else if (textLine->stringAtPos(specialIndent, "signals")) ch = textLine->getChar(specialIndent + 7); else if (textLine->stringAtPos(specialIndent, "Q_SIGNALS")) ch = textLine->getChar(specialIndent + 9); else if (textLine->stringAtPos(specialIndent, "slots")) ch = textLine->getChar(specialIndent + 5); else if (textLine->stringAtPos(specialIndent, "Q_SLOTS")) ch = textLine->getChar(specialIndent + 7); if (ch.isNull() || (!ch.isSpace() && ch != '(' && ch != ':')) continue; KateDocCursor lineBegin = cur; lineBegin.setCol(specialIndent); specialIndent = measureIndent(lineBegin); isSpecial = true; } // Move forward past blank lines KateDocCursor skip = cur; skip.setCol(textLine->lastChar()); bool result = skipBlanks(skip, begin, true); anchorPos = skip.col(); anchorIndent = measureIndent(skip); //kdDebug(13030) << "calcIndent anchorPos:" << anchorPos << " anchorIndent:" << anchorIndent << " at line:" << skip.line() << endl; // Accept if it's before requested position or if it was special if (result && skip < begin) { cur = skip; break; } else if (isSpecial) { anchorIndent = specialIndent; break; } // Are these on a line by themselves? (i.e. both last and first char) if ((c == '{' || c == '}') && textLine->getChar(textLine->firstChar()) == c) { cur.setCol(anchorPos = textLine->firstChar()); anchorIndent = measureIndent (cur); break; } } } // treat beginning of document as anchor position if (cur.line() == 0 && cur.col() == 0 && potentialAnchorSeen) found = true; if (!found) return 0; uint continueIndent = (needContinue) ? calcContinue (cur, begin) : 0; //kdDebug(13030) << "calcIndent continueIndent:" << continueIndent << endl; // Move forward from anchor and determine last known reference character // Braces take precedance over others ... textLine = doc->plainKateTextLine(cur.line()); TQChar lastChar = textLine->getChar (anchorPos); int lastLine = cur.line(); if (lastChar == '#' || lastChar == '[') { // Never continue if # or [ is encountered at this point here // A fail-safe really... most likely an #include, #region, or a c# attribute continueIndent = 0; } int openCount = 0; while (cur.validPosition() && cur < begin) { if (!skipBlanks(cur, begin, true)) return isArg && !parenthesizedArg ? begin.col() : 0; TQChar tc = cur.currentChar(); //kdDebug(13030) << " cur.line:" << cur.line() << " cur.col:" << cur.col() << " currentChar '" << tc << "' " << textLine->attribute(cur.col()) << endl; if (cur == begin || tc.isNull()) break; if (!tc.isSpace() && cur < begin) { uchar attrib = cur.currentAttrib(); if (tc == '{' && attrib == symbolAttrib) openCount++; else if (tc == '}' && attrib == symbolAttrib) openCount--; lastChar = tc; lastLine = cur.line(); } } if (openCount > 0) // Open braces override lastChar = '{'; uint indent = 0; //kdDebug(13030) << "calcIndent lastChar '" << lastChar << "'" << endl; if (lastChar == '{' || (lastChar == ':' && isSpecial && needContinue)) { indent = anchorIndent + indentWidth; } else if (lastChar == '}') { indent = anchorIndent; } else if (lastChar == ';') { indent = anchorIndent + ((allowSemi && needContinue) ? continueIndent : 0); } else if (lastChar == ',' || lastChar == '(') { textLine = doc->plainKateTextLine(lastLine); KateDocCursor start(lastLine, textLine->firstChar(), doc); KateDocCursor finish(lastLine, textLine->lastChar() + 1, doc); uint pos = 0; if (isBalanced(start, finish, TQChar('('), TQChar(')'), pos) && false) indent = anchorIndent; else { // TODO: Config option. If we're below 48, go ahead and line them up indent = ((pos < 48) ? pos : anchorIndent + (indentWidth * 2)); } } else if (!lastChar.isNull()) { if (anchorIndent != 0) indent = anchorIndent + continueIndent; else indent = continueIndent; } return indent; } uint KateCSmartIndent::calcContinue(KateDocCursor &start, KateDocCursor &end) { KateDocCursor cur = start; bool needsBalanced = true; bool isFor = false; allowSemi = false; KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); // Handle cases such as } while (s ... by skipping the leading symbol if (textLine->attribute(cur.col()) == symbolAttrib) { cur.moveForward(1); skipBlanks(cur, end, false); } if (textLine->getChar(cur.col()) == '}') { skipBlanks(cur, end, true); if (cur.line() != start.line()) textLine = doc->plainKateTextLine(cur.line()); if (textLine->stringAtPos(cur.col(), "else")) cur.setCol(cur.col() + 4); else return indentWidth * 2; needsBalanced = false; } else if (textLine->stringAtPos(cur.col(), "else")) { cur.setCol(cur.col() + 4); needsBalanced = false; int next = textLine->nextNonSpaceChar(cur.col()); if (next >= 0 && textLine->stringAtPos(next, "if")) { cur.setCol(next + 2); needsBalanced = true; } } else if (textLine->stringAtPos(cur.col(), "if")) { cur.setCol(cur.col() + 2); } else if (textLine->stringAtPos(cur.col(), "do")) { cur.setCol(cur.col() + 2); needsBalanced = false; } else if (textLine->stringAtPos(cur.col(), "for")) { cur.setCol(cur.col() + 3); isFor = true; } else if (textLine->stringAtPos(cur.col(), "while")) { cur.setCol(cur.col() + 5); } else if (textLine->stringAtPos(cur.col(), "switch")) { cur.setCol(cur.col() + 6); } else if (textLine->stringAtPos(cur.col(), "using")) { cur.setCol(cur.col() + 5); } else { return indentWidth * 2; } uint openPos = 0; if (needsBalanced && !isBalanced (cur, end, TQChar('('), TQChar(')'), openPos)) { allowSemi = isFor; if (openPos > 0) return (openPos - textLine->firstChar()); else return indentWidth * 2; } // Check if this statement ends a line now skipBlanks(cur, end, false); if (cur == end) return indentWidth; if (skipBlanks(cur, end, true)) { if (cur == end) return indentWidth; else return indentWidth + calcContinue(cur, end); } return 0; } uint KateCSmartIndent::findOpeningBrace(KateDocCursor &start) { KateDocCursor cur = start; int count = 1; // Move backwards 1 by 1 and find the opening brace // Return the indent of that line while (cur.moveBackward(1)) { if (cur.currentAttrib() == symbolAttrib) { TQChar ch = cur.currentChar(); if (ch == '{') count--; else if (ch == '}') count++; if (count == 0) { KateDocCursor temp(cur.line(), doc->plainKateTextLine(cur.line())->firstChar(), doc); return measureIndent(temp); } } } return 0; } bool KateCSmartIndent::firstOpeningBrace(KateDocCursor &start) { KateDocCursor cur = start; // Are we the first opening brace at this level? while(cur.moveBackward(1)) { if (cur.currentAttrib() == symbolAttrib) { TQChar ch = cur.currentChar(); if (ch == '{') return false; else if (ch == '}' && cur.col() == 0) break; } } return true; } uint KateCSmartIndent::findOpeningParen(KateDocCursor &start) { KateDocCursor cur = start; int count = 1; // Move backwards 1 by 1 and find the opening ( // Return the indent of that line while (cur.moveBackward(1)) { if (cur.currentAttrib() == symbolAttrib) { TQChar ch = cur.currentChar(); if (ch == '(') count--; else if (ch == ')') count++; if (count == 0) return measureIndent(cur); } } return 0; } uint KateCSmartIndent::findOpeningComment(KateDocCursor &start) { KateDocCursor cur = start; // Find the line with the opening /* and return the proper indent do { KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); int pos = textLine->string().find("/*", false); if (pos >= 0) { KateDocCursor temp(cur.line(), pos, doc); return measureIndent(temp); } } while (cur.gotoPreviousLine()); return 0; } //END //BEGIN KatePythonIndent TQRegExp KatePythonIndent::endWithColon = TQRegExp( "^[^#]*:\\s*(#.*)?$" ); TQRegExp KatePythonIndent::stopStmt = TQRegExp( "^\\s*(break|continue|raise|return|pass)\\b.*" ); TQRegExp KatePythonIndent::blockBegin = TQRegExp( "^\\s*(class|def|if|elif|else|for|while|try)\\b.*" ); KatePythonIndent::KatePythonIndent (KateDocument *doc) : KateNormalIndent (doc) { } KatePythonIndent::~KatePythonIndent () { } void KatePythonIndent::processNewline (KateDocCursor &begin, bool /*newline*/) { int prevLine = begin.line() - 1; int prevPos = begin.col(); while ((prevLine > 0) && (prevPos < 0)) // search a not empty text line prevPos = doc->plainKateTextLine(--prevLine)->firstChar(); int prevBlock = prevLine; int prevBlockPos = prevPos; int extraIndent = calcExtra (prevBlock, prevBlockPos, begin); int indent = doc->plainKateTextLine(prevBlock)->cursorX(prevBlockPos, tabWidth); if (extraIndent == 0) { if (!stopStmt.exactMatch(doc->plainKateTextLine(prevLine)->string())) { if (endWithColon.exactMatch(doc->plainKateTextLine(prevLine)->string())) indent += indentWidth; else indent = doc->plainKateTextLine(prevLine)->cursorX(prevPos, tabWidth); } } else indent += extraIndent; if (indent > 0) { TQString filler = tabString (indent); doc->insertText (begin.line(), 0, filler); begin.setCol(filler.length()); } else begin.setCol(0); } int KatePythonIndent::calcExtra (int &prevBlock, int &pos, KateDocCursor &end) { int nestLevel = 0; bool levelFound = false; while ((prevBlock > 0)) { if (blockBegin.exactMatch(doc->plainKateTextLine(prevBlock)->string())) { if ((!levelFound && nestLevel == 0) || (levelFound && nestLevel - 1 <= 0)) { pos = doc->plainKateTextLine(prevBlock)->firstChar(); break; } nestLevel --; } else if (stopStmt.exactMatch(doc->plainKateTextLine(prevBlock)->string())) { nestLevel ++; levelFound = true; } --prevBlock; } KateDocCursor cur (prevBlock, pos, doc); TQChar c; int extraIndent = 0; while (cur.line() < end.line()) { c = cur.currentChar(); if (c == '(') extraIndent += indentWidth; else if (c == ')') extraIndent -= indentWidth; else if (c == ':') break; else if (c == '\'' || c == '"' ) traverseString( c, cur, end ); if (c.isNull() || c == '#') cur.gotoNextLine(); else cur.moveForward(1); } return extraIndent; } void KatePythonIndent::traverseString( const TQChar &stringChar, KateDocCursor &cur, KateDocCursor &end ) { TQChar c; bool escape = false; cur.moveForward(1); c = cur.currentChar(); while ( ( c != stringChar || escape ) && cur.line() < end.line() ) { if ( escape ) escape = false; else if ( c == '\\' ) escape = !escape; cur.moveForward(1); c = cur.currentChar(); } } //END //BEGIN KateXmlIndent /* Explanation The XML indenter simply inherits the indentation of the previous line, with the first line starting at 0 (of course!). For each element that is opened on the previous line, the indentation is increased by one level; for each element that is closed, it is decreased by one. We also have a special case of opening an element on one line and then entering attributes on the following lines, in which case we would like to see the following layout: This is accomplished by checking for lines that contain an unclosed open tag. */ const TQRegExp KateXmlIndent::startsWithCloseTag("^[ \t]*]*$"); KateXmlIndent::KateXmlIndent (KateDocument *doc) : KateNormalIndent (doc) { } KateXmlIndent::~KateXmlIndent () { } void KateXmlIndent::processNewline (KateDocCursor &begin, bool /*newline*/) { begin.setCol(processLine(begin.line())); } void KateXmlIndent::processChar (TQChar c) { if(c != '/') return; // only alter lines that start with a close element KateView *view = doc->activeView(); TQString text = doc->plainKateTextLine(view->cursorLine())->string(); if(text.find(startsWithCloseTag) == -1) return; // process it processLine(view->cursorLine()); } void KateXmlIndent::processLine (KateDocCursor &line) { processLine (line.line()); } void KateXmlIndent::processSection (const KateDocCursor &start, const KateDocCursor &end) { KateDocCursor cur (start); int endLine = end.line(); do { processLine(cur.line()); if(!cur.gotoNextLine()) break; } while(cur.line() < endLine); } void KateXmlIndent::getLineInfo (uint line, uint &prevIndent, int &numTags, uint &attrCol, bool &unclosedTag) { prevIndent = 0; int firstChar; KateTextLine::Ptr prevLine = 0; // get the indentation of the first non-empty line while(true) { prevLine = doc->plainKateTextLine(line); if( (firstChar = prevLine->firstChar()) < 0) { if(!line--) return; continue; } break; } prevIndent = prevLine->cursorX(prevLine->firstChar(), tabWidth); TQString text = prevLine->string(); // special case: // // // requires that we discount the from the number of closed tags if(text.find(startsWithCloseTag) != -1) ++numTags; // count the number of open and close tags int lastCh = 0; uint pos, len = text.length(); bool seenOpen = false; for(pos = 0; pos < len; ++pos) { int ch = text.at(pos).unicode(); switch(ch) { case '<': seenOpen = true; unclosedTag = true; attrCol = pos; ++numTags; break; // don't indent because of DOCTYPE, comment, CDATA, etc. case '!': if(lastCh == '<') --numTags; break; // don't indent because of xml decl or PI case '?': if(lastCh == '<') --numTags; break; case '>': if(!seenOpen) { // we are on a line like the second one here: // // so we need to set prevIndent to the indent of the first line // // however, we need to special case "plainKateTextLine(--backLine); if(x->string().find('<') == -1) continue; // recalculate the indent if(x->string().find(unclosedDoctype) != -1) --numTags; getLineInfo(backLine, prevIndent, numTags, attrCol, unclosedTag); break; } } if(lastCh == '/') --numTags; unclosedTag = false; break; case '/': if(lastCh == '<') numTags -= 2; // correct for '<', above break; } lastCh = ch; } if(unclosedTag) { // find the start of the next attribute, so we can align with it do { lastCh = text.at(++attrCol).unicode(); }while(lastCh && lastCh != ' ' && lastCh != '\t'); while(lastCh == ' ' || lastCh == '\t') { lastCh = text.at(++attrCol).unicode(); } attrCol = prevLine->cursorX(attrCol, tabWidth); } } uint KateXmlIndent::processLine (uint line) { KateTextLine::Ptr kateLine = doc->plainKateTextLine(line); if(!kateLine) return 0; // sanity check // get details from previous line uint prevIndent = 0, attrCol = 0; int numTags = 0; bool unclosedTag = false; // for aligning attributes if(line) { getLineInfo(line - 1, prevIndent, numTags, attrCol, unclosedTag); } // compute new indent int indent = 0; if(unclosedTag) indent = attrCol; else indent = prevIndent + numTags * indentWidth; if(indent < 0) indent = 0; // unindent lines that start with a close tag if(kateLine->string().find(startsWithCloseTag) != -1) { indent -= indentWidth; } if(indent < 0) indent = 0; // apply new indent doc->removeText(line, 0, line, kateLine->firstChar()); TQString filler = tabString(indent); doc->insertText(line, 0, filler); return filler.length(); } //END //BEGIN KateCSAndSIndent KateCSAndSIndent::KateCSAndSIndent (KateDocument *doc) : KateNormalIndent (doc) { } void KateCSAndSIndent::updateIndentString() { if( useSpaces ) indentString.fill( ' ', indentWidth ); else indentString = '\t'; } KateCSAndSIndent::~KateCSAndSIndent () { } void KateCSAndSIndent::processLine (KateDocCursor &line) { KateTextLine::Ptr textLine = doc->plainKateTextLine(line.line()); if (!textLine) return; updateIndentString(); const int oldCol = line.col(); TQString whitespace = calcIndent(line); // strip off existing whitespace int oldIndent = textLine->firstChar(); if ( oldIndent < 0 ) oldIndent = doc->lineLength( line.line() ); if( oldIndent > 0 ) doc->removeText(line.line(), 0, line.line(), oldIndent); // add correct amount doc->insertText(line.line(), 0, whitespace); // try to preserve the cursor position in the line if ( int(oldCol + whitespace.length()) >= oldIndent ) line.setCol( oldCol + whitespace.length() - oldIndent ); else line.setCol( 0 ); } void KateCSAndSIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) { TQTime t; t.start(); for( KateDocCursor cur = begin; cur.line() <= end.line(); ) { processLine (cur); if (!cur.gotoNextLine()) break; } kdDebug(13030) << "+++ total: " << t.elapsed() << endl; } /** * Returns the first @p chars characters of @p line, converted to whitespace. * If @p convert is set to false, characters at and after the first non-whitespace * character are removed, not converted. */ static TQString initialWhitespace(const KateTextLine::Ptr &line, int chars, bool convert = true) { TQString text = line->string(0, chars); if( (int)text.length() < chars ) { TQString filler; filler.fill(' ',chars - text.length()); text += filler; } for( uint n = 0; n < text.length(); ++n ) { if( text[n] != '\t' && text[n] != ' ' ) { if( !convert ) return text.left( n ); text[n] = ' '; } } return text; } TQString KateCSAndSIndent::findOpeningCommentIndentation(const KateDocCursor &start) { KateDocCursor cur = start; // Find the line with the opening /* and return the indentation of it do { KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); int pos = textLine->string().findRev("/*"); // FIXME: /* inside /* is possible. This screws up in that case... if (pos >= 0) return initialWhitespace(textLine, pos); } while (cur.gotoPreviousLine()); // should never happen. kdWarning( 13030 ) << " in a comment, but can't find the start of it" << endl; return TQString::null; } bool KateCSAndSIndent::handleDoxygen (KateDocCursor &begin) { // Look backwards for a nonempty line int line = begin.line(); int first = -1; while ((line > 0) && (first < 0)) first = doc->plainKateTextLine(--line)->firstChar(); // no earlier nonempty line if (first < 0) return false; KateTextLine::Ptr textLine = doc->plainKateTextLine(line); // if the line doesn't end with a doxygen comment (that's not closed) // and doesn't start with a doxygen comment (that's not closed), we don't care. // note that we do need to check the start of the line, or lines ending with, say, @brief aren't // recognised. if ( !(textLine->attribute(textLine->lastChar()) == doxyCommentAttrib && !textLine->endingWith("*/")) && !(textLine->attribute(textLine->firstChar()) == doxyCommentAttrib && !textLine->string().contains("*/")) ) return false; // our line is inside a doxygen comment. align the *'s and then maybe insert one too ... textLine = doc->plainKateTextLine(begin.line()); first = textLine->firstChar(); TQString indent = findOpeningCommentIndentation(begin); bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping; // starts with *: indent one space more to line up *s if ( first >= 0 && textLine->stringAtPos(first, "*") ) indent = indent + " "; // does not start with *: insert one if user wants that else if ( doxygenAutoInsert ) indent = indent + " * "; // user doesn't want * inserted automatically: put in spaces? //else // indent = indent + " "; doc->removeText (begin.line(), 0, begin.line(), first); doc->insertText (begin.line(), 0, indent); begin.setCol(indent.length()); return true; } /** * @brief User pressed enter. Line has been split; begin is on the new line. * @param begin Three unrelated variables: the new line number, where the first * non-whitespace char was on the previous line, and the document. * @param needContinue Something to do with indenting the current line; always true. */ void KateCSAndSIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/) { // in a comment, add a * doxygen-style. if( handleDoxygen(begin) ) return; // TODO: if the user presses enter in the middle of a label, maybe the first half of the // label should be indented? // where the cursor actually is... int cursorPos = doc->plainKateTextLine( begin.line() )->firstChar(); if ( cursorPos < 0 ) cursorPos = doc->lineLength( begin.line() ); begin.setCol( cursorPos ); processLine( begin ); } /** * Does the line @p line start with a label? * @note May also return @c true if the line starts in a continuation. */ bool KateCSAndSIndent::startsWithLabel( int line ) { // Get the current line. KateTextLine::Ptr indentLine = doc->plainKateTextLine(line); const int indentFirst = indentLine->firstChar(); // Not entirely sure what this check does. int attrib = indentLine->attribute(indentFirst); if (attrib != 0 && attrib != keywordAttrib && attrib != normalAttrib && attrib != extensionAttrib) return false; // Get the line text. const TQString lineContents = indentLine->string(); const int indentLast = indentLine->lastChar(); bool whitespaceFound = false; for ( int n = indentFirst; n <= indentLast; ++n ) { // Get the character as latin1. Can't use TQChar::isLetterOrNumber() // as that includes non 0-9 numbers. char c = lineContents[n].latin1(); if ( c == ':' ) { // See if the next character is ':' - if so, skip to the character after it. if ( n < lineContents.length() - 1 ) { if ( lineContents[n+1].latin1() == ':' ) { n += 2; continue; } } // Right this is the relevent ':'. if ( n == indentFirst) { // Just a line with a : on it. return false; } // It is a label of some kind! return true; } if (isspace(c)) { if (!whitespaceFound) { if (lineContents.mid(indentFirst, n - indentFirst) == "case") return true; else if (lineContents.mid(indentFirst, n - indentFirst) == "class") return false; whitespaceFound = true; } } // All other characters don't indent. else if ( !isalnum(c) && c != '_' ) { return false; } } return false; } template T min(T a, T b) { return (a < b) ? a : b; } int KateCSAndSIndent::lastNonCommentChar( const KateDocCursor &line ) { KateTextLine::Ptr textLine = doc->plainKateTextLine( line.line() ); TQString str = textLine->string(); // find a possible start-of-comment int p = -2; // so the first find starts at position 0 do p = str.find( "//", p + 2 ); while ( p >= 0 && textLine->attribute(p) != commentAttrib && textLine->attribute(p) != doxyCommentAttrib ); // no // found? use whole string if ( p < 0 ) p = str.length(); // ignore trailing blanks. p starts one-past-the-end. while( p > 0 && str[p-1].isSpace() ) --p; return p - 1; } bool KateCSAndSIndent::inForStatement( int line ) { // does this line end in a for ( ... // with no closing ) ? int parens = 0, semicolons = 0; for ( ; line >= 0; --line ) { KateTextLine::Ptr textLine = doc->plainKateTextLine(line); const int first = textLine->firstChar(); const int last = textLine->lastChar(); // look backwards for a symbol: (){}; // match ()s, {...; and }...; => not in a for // ; ; ; => not in a for // ( ; and ( ; ; => a for for ( int curr = last; curr >= first; --curr ) { if ( textLine->attribute(curr) != symbolAttrib ) continue; switch( textLine->getChar(curr) ) { case ';': if( ++semicolons > 2 ) return false; break; case '{': case '}': return false; case ')': ++parens; break; case '(': if( --parens < 0 ) return true; break; } } } // no useful symbols before the ;? // not in a for then return false; } // is the start of the line containing 'begin' in a statement? bool KateCSAndSIndent::inStatement( const KateDocCursor &begin ) { // if the current line starts with an open brace, it's not a continuation. // this happens after a function definition (which is treated as a continuation). KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); const int first = textLine->firstChar(); // note that if we're being called from processChar the attribute has not yet been calculated // should be reasonably safe to assume that unattributed {s are symbols; if the { is in a comment // we don't want to touch it anyway. const int attrib = textLine->attribute(first); if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && textLine->getChar(first) == '{' ) return false; int line; for ( line = begin.line() - 1; line >= 0; --line ) { textLine = doc->plainKateTextLine(line); const int first = textLine->firstChar(); if ( first == -1 ) continue; // starts with #: in a comment, don't care // outside a comment: preprocessor, don't care if ( textLine->getChar( first ) == '#' ) continue; KateDocCursor currLine = begin; currLine.setLine( line ); const int last = lastNonCommentChar( currLine ); if ( last < first ) continue; // HACK: if we see a comment, assume boldly that this isn't a continuation. // detecting comments (using attributes) is HARD, since they may have // embedded alerts, or doxygen stuff, or just about anything. this is // wrong, and needs fixing. note that only multi-line comments and // single-line comments continued with \ are affected. const int attrib = textLine->attribute(last); if ( attrib == commentAttrib || attrib == doxyCommentAttrib ) return false; char c = textLine->getChar(last); // brace => not a continuation. if ( attrib == symbolAttrib && c == '{' || c == '}' ) return false; // ; => not a continuation, unless in a for (;;) if ( attrib == symbolAttrib && c == ';' ) return inForStatement( line ); // found something interesting. maybe it's a label? if ( attrib == symbolAttrib && c == ':' ) { // the : above isn't necessarily the : in the label, eg in // case 'x': a = b ? c : // this will say no continuation incorrectly. but continued statements // starting on a line with a label at the start is Bad Style (tm). if( startsWithLabel( line ) ) { // either starts with a label or a continuation. if the current line // starts in a continuation, we're still in one. if not, this was // a label, so we're not in one now. so continue to the next line // upwards. continue; } } // any other character => in a continuation return true; } // no non-comment text found before here - not a continuation. return false; } TQString KateCSAndSIndent::continuationIndent( const KateDocCursor &begin ) { if( !inStatement( begin ) ) return TQString::null; return indentString; } /** * Figure out how indented the line containing @p begin should be. */ TQString KateCSAndSIndent::calcIndent (const KateDocCursor &begin) { KateTextLine::Ptr currLine = doc->plainKateTextLine(begin.line()); int currLineFirst = currLine->firstChar(); // if the line starts inside a comment, no change of indentation. // FIXME: this unnecessarily copies the current indentation over itself. // FIXME: on newline, this should copy from the previous line. if ( currLineFirst >= 0 && (currLine->attribute(currLineFirst) == commentAttrib || currLine->attribute(currLineFirst) == doxyCommentAttrib) ) return currLine->string( 0, currLineFirst ); // if the line starts with # (but isn't a c# region thingy), no indentation at all. if( currLineFirst >= 0 && currLine->getChar(currLineFirst) == '#' ) { if( !currLine->stringAtPos( currLineFirst+1, TQString::fromLatin1("region") ) && !currLine->stringAtPos( currLineFirst+1, TQString::fromLatin1("endregion") ) ) return TQString::null; } /* Strategy: * Look for an open bracket or brace, or a keyword opening a new scope, whichever comes latest. * Found a brace: indent one tab in. * Found a bracket: indent to the first non-white after it. * Found a keyword: indent one tab in. for try, catch and switch, if newline is set, also add * an open brace, a newline, and indent two tabs in. */ KateDocCursor cur = begin; int pos, openBraceCount = 0, openParenCount = 0; bool lookingForScopeKeywords = true; const char * const scopeKeywords[] = { "for", "do", "while", "if", "else" }; const char * const blockScopeKeywords[] = { "try", "catch", "switch" }; while (cur.gotoPreviousLine()) { KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); const int lastChar = textLine->lastChar(); const int firstChar = textLine->firstChar(); // look through line backwards for interesting characters for( pos = lastChar; pos >= firstChar; --pos ) { if (textLine->attribute(pos) == symbolAttrib) { char tc = textLine->getChar (pos); switch( tc ) { case '(': case '[': if( ++openParenCount > 0 ) return calcIndentInBracket( begin, cur, pos ); break; case ')': case ']': openParenCount--; break; case '{': if( ++openBraceCount > 0 ) return calcIndentInBrace( begin, cur, pos ); break; case '}': openBraceCount--; lookingForScopeKeywords = false; break; case ';': if( openParenCount == 0 ) lookingForScopeKeywords = false; break; } } // if we've not had a close brace or a semicolon yet, and we're at the same parenthesis level // as the cursor, and we're at the start of a scope keyword, indent from it. if ( lookingForScopeKeywords && openParenCount == 0 && textLine->attribute(pos) == keywordAttrib && (pos == 0 || textLine->attribute(pos-1) != keywordAttrib ) ) { #define ARRLEN( array ) ( sizeof(array)/sizeof(array[0]) ) for( uint n = 0; n < ARRLEN(scopeKeywords); ++n ) if( textLine->stringAtPos(pos, TQString::fromLatin1(scopeKeywords[n]) ) ) return calcIndentAfterKeyword( begin, cur, pos, false ); for( uint n = 0; n < ARRLEN(blockScopeKeywords); ++n ) if( textLine->stringAtPos(pos, TQString::fromLatin1(blockScopeKeywords[n]) ) ) return calcIndentAfterKeyword( begin, cur, pos, true ); #undef ARRLEN } } } // no active { in file. return TQString::null; } TQString KateCSAndSIndent::calcIndentInBracket(const KateDocCursor &indentCursor, const KateDocCursor &bracketCursor, int bracketPos) { KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); KateTextLine::Ptr bracketLine = doc->plainKateTextLine(bracketCursor.line()); // FIXME: hard-coded max indent to bracket width - use a kate variable // FIXME: expand tabs first... if ( bracketPos > 48 ) { // how far to indent? we could look back for a brace or keyword, 2 from that. // as it is, we just indent one more than the line with the ( on it. // the potential problem with this is when // you have code ( which does <-- continuation + start of func call // something like this ); <-- extra indentation for func call // then again ( // it works better than ( // the other method for ( // cases like this ))); // consequently, i think this method wins. return indentString + initialWhitespace( bracketLine, bracketLine->firstChar() ); } const int indentLineFirst = indentLine->firstChar(); int indentTo; const int attrib = indentLine->attribute(indentLineFirst); if( indentLineFirst >= 0 && (attrib == 0 || attrib == symbolAttrib) && ( indentLine->getChar(indentLineFirst) == ')' || indentLine->getChar(indentLineFirst) == ']' ) ) { // If the line starts with a close bracket, line it up indentTo = bracketPos; } else { // Otherwise, line up with the text after the open bracket indentTo = bracketLine->nextNonSpaceChar( bracketPos + 1 ); if( indentTo == -1 ) indentTo = bracketPos + 2; } return initialWhitespace( bracketLine, indentTo ); } TQString KateCSAndSIndent::calcIndentAfterKeyword(const KateDocCursor &indentCursor, const KateDocCursor &keywordCursor, int keywordPos, bool blockKeyword) { KateTextLine::Ptr keywordLine = doc->plainKateTextLine(keywordCursor.line()); KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); TQString whitespaceToKeyword = initialWhitespace( keywordLine, keywordPos, false ); if( blockKeyword ) { // FIXME: we could add the open brace and subsequent newline here since they're definitely needed. } // If the line starts with an open brace, don't indent... int first = indentLine->firstChar(); // if we're being called from processChar attribute won't be set const int attrib = indentLine->attribute(first); if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && indentLine->getChar(first) == '{' ) return whitespaceToKeyword; // don't check for a continuation. rules are simple here: // if we're in a non-compound statement after a scope keyword, we indent all lines // once. so: // if ( some stuff // goes here ) // apples, and <-- continuation here is ignored. but this is Bad Style (tm) anyway. // oranges too; return indentString + whitespaceToKeyword; } TQString KateCSAndSIndent::calcIndentInBrace(const KateDocCursor &indentCursor, const KateDocCursor &braceCursor, int bracePos) { KateTextLine::Ptr braceLine = doc->plainKateTextLine(braceCursor.line()); const int braceFirst = braceLine->firstChar(); TQString whitespaceToOpenBrace = initialWhitespace( braceLine, bracePos, false ); // if the open brace is the start of a namespace, don't indent... // FIXME: this is an extremely poor heuristic. it looks on the line with // the { and the line before to see if they start with a keyword // beginning 'namespace'. that's 99% of usage, I'd guess. { if( braceFirst >= 0 && braceLine->attribute(braceFirst) == keywordAttrib && braceLine->stringAtPos( braceFirst, TQString::fromLatin1( "namespace" ) ) ) return continuationIndent(indentCursor) + whitespaceToOpenBrace; if( braceCursor.line() > 0 ) { KateTextLine::Ptr prevLine = doc->plainKateTextLine(braceCursor.line() - 1); int firstPrev = prevLine->firstChar(); if( firstPrev >= 0 && prevLine->attribute(firstPrev) == keywordAttrib && prevLine->stringAtPos( firstPrev, TQString::fromLatin1( "namespace" ) ) ) return continuationIndent(indentCursor) + whitespaceToOpenBrace; } } KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); const int indentFirst = indentLine->firstChar(); // if the line starts with a close brace, don't indent... if( indentFirst >= 0 && indentLine->getChar(indentFirst) == '}' ) return whitespaceToOpenBrace; // if : is the first character (and not followed by another :), this is the start // of an initialization list, or a continuation of a ?:. either way, indent twice. if ( indentFirst >= 0 && indentLine->attribute(indentFirst) == symbolAttrib && indentLine->getChar(indentFirst) == ':' && indentLine->getChar(indentFirst+1) != ':' ) { return indentString + indentString + whitespaceToOpenBrace; } const bool continuation = inStatement(indentCursor); // if the current line starts with a label, don't indent... if( !continuation && startsWithLabel( indentCursor.line() ) ) return whitespaceToOpenBrace; // the normal case: indent once for the brace, again if it's a continuation TQString continuationIndent = continuation ? indentString : TQString::null; return indentString + continuationIndent + whitespaceToOpenBrace; } void KateCSAndSIndent::processChar(TQChar c) { // 'n' trigger is for c# regions. static const TQString triggers("}{)]/:;#n"); if (triggers.find(c) == -1) return; // for historic reasons, processChar doesn't get a cursor // to work on. so fabricate one. KateView *view = doc->activeView(); KateDocCursor begin(view->cursorLine(), 0, doc); KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); if ( c == 'n' ) { int first = textLine->firstChar(); if( first < 0 || textLine->getChar(first) != '#' ) return; } if ( textLine->attribute( begin.col() ) == doxyCommentAttrib ) { // dominik: if line is "* /", change it to "*/" if ( c == '/' ) { int first = textLine->firstChar(); // if the first char exists and is a '*', and the next non-space-char // is already the just typed '/', concatenate it to "*/". if ( first != -1 && textLine->getChar( first ) == '*' && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 ) doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1); } // anders: don't change the indent of doxygen lines here. return; } processLine(begin); } //END //BEGIN KateVarIndent class KateVarIndentPrivate { public: TQRegExp reIndentAfter, reIndent, reUnindent; TQString triggers; uint couples; uchar coupleAttrib; }; KateVarIndent::KateVarIndent( KateDocument *doc ) : KateNormalIndent( doc ) { d = new KateVarIndentPrivate; d->reIndentAfter = TQRegExp( doc->variable( "var-indent-indent-after" ) ); d->reIndent = TQRegExp( doc->variable( "var-indent-indent" ) ); d->reUnindent = TQRegExp( doc->variable( "var-indent-unindent" ) ); d->triggers = doc->variable( "var-indent-triggerchars" ); d->coupleAttrib = 0; slotVariableChanged( "var-indent-couple-attribute", doc->variable( "var-indent-couple-attribute" ) ); slotVariableChanged( "var-indent-handle-couples", doc->variable( "var-indent-handle-couples" ) ); // update if a setting is changed connect( doc, TQT_SIGNAL(variableChanged( const TQString&, const TQString&) ), this, TQT_SLOT(slotVariableChanged( const TQString&, const TQString& )) ); } KateVarIndent::~KateVarIndent() { delete d; } void KateVarIndent::processNewline ( KateDocCursor &begin, bool /*needContinue*/ ) { // process the line left, as well as the one entered KateDocCursor left( begin.line()-1, 0, doc ); processLine( left ); processLine( begin ); } void KateVarIndent::processChar ( TQChar c ) { // process line if the c is in our list, and we are not in comment text if ( d->triggers.contains( c ) ) { KateTextLine::Ptr ln = doc->plainKateTextLine( doc->activeView()->cursorLine() ); if ( ln->attribute( doc->activeView()->cursorColumn()-1 ) == commentAttrib ) return; KateView *view = doc->activeView(); KateDocCursor begin( view->cursorLine(), 0, doc ); kdDebug(13030)<<"variable indenter: process char '"<plainKateTextLine( ln ); if ( ! ktl ) return; // no line!? // skip blank lines, except for the cursor line KateView *v = doc->activeView(); if ( (ktl->firstChar() < 0) && (!v || (int)v->cursorLine() != ln ) ) return; int fc; if ( ln > 0 ) do { ktl = doc->plainKateTextLine( --ln ); fc = ktl->firstChar(); if ( ktl->attribute( fc ) != commentAttrib ) pos = fc; } while ( (ln > 0) && (pos < 0) ); // search a not empty text line if ( pos < 0 ) pos = 0; else pos = ktl->cursorX( pos, tabWidth ); int adjustment = 0; // try 'couples' for an opening on the above line first. since we only adjust by 1 unit, // we only need 1 match. if ( d->couples & Parens && coupleBalance( ln, '(', ')' ) > 0 ) adjustment++; else if ( d->couples & Braces && coupleBalance( ln, '{', '}' ) > 0 ) adjustment++; else if ( d->couples & Brackets && coupleBalance( ln, '[', ']' ) > 0 ) adjustment++; // Try 'couples' for a closing on this line first. since we only adjust by 1 unit, // we only need 1 match. For unindenting, we look for a closing character // *at the beginning of the line* // NOTE Assume that a closing brace with the configured attribute on the start // of the line is closing. // When acting on processChar, the character isn't highlighted. So I could // either not check, assuming that the first char *is* meant to close, or do a // match test if the attrib is 0. How ever, doing that is // a potentially huge job, if the match is several hundred lines away. // Currently, the check is done. { KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() ); int i = tl->firstChar(); if ( i > -1 ) { TQChar ch = tl->getChar( i ); uchar at = tl->attribute( i ); kdDebug(13030)<<"attrib is "<couples & Parens && ch == ')' && ( at == d->coupleAttrib || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) ) ) adjustment--; else if ( d->couples & Braces && ch == '}' && ( at == d->coupleAttrib || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) ) ) adjustment--; else if ( d->couples & Brackets && ch == ']' && ( at == d->coupleAttrib || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) ) ) adjustment--; } } #define ISCOMMENTATTR(attr) (attr==commentAttrib||attr==doxyCommentAttrib) #define ISCOMMENT (ISCOMMENTATTR(ktl->attribute(ktl->firstChar()))||ISCOMMENTATTR(ktl->attribute(matchpos))) // check if we should indent, unless the line starts with comment text, // or the match is in comment text kdDebug(13030)<<"variable indenter: starting indent: "<reIndentAfter.isEmpty() && (matchpos = d->reIndentAfter.search( doc->textLine( ln ) )) > -1 && ! ISCOMMENT ) adjustment++; // else, check if this line should indent unless ... ktl = doc->plainKateTextLine( line.line() ); if ( ! d->reIndent.isEmpty() && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1 && ! ISCOMMENT ) adjustment++; // else, check if the current line indicates if we should remove indentation unless ... if ( ! d->reUnindent.isEmpty() && (matchpos = d->reUnindent.search( doc->textLine( line.line() ) )) > -1 && ! ISCOMMENT ) adjustment--; kdDebug(13030)<<"variable indenter: adjusting by "< 0 ) pos += indentWidth; else if ( adjustment < 0 ) pos -= indentWidth; ln = line.line(); fc = doc->plainKateTextLine( ln )->firstChar(); // dont change if there is no change. // ### should I actually compare the strings? // FIXME for some odd reason, the document gets marked as changed // even if we don't change it !? if ( fc == pos ) return; if ( fc > 0 ) doc->removeText (ln, 0, ln, fc ); if ( pos > 0 ) indent = tabString( pos ); if ( pos > 0 ) doc->insertText (ln, 0, indent); // try to restore cursor ? line.setCol( pos ); } void KateVarIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) { KateDocCursor cur = begin; while (cur.line() <= end.line()) { processLine (cur); if (!cur.gotoNextLine()) break; } } void KateVarIndent::slotVariableChanged( const TQString &var, const TQString &val ) { if ( ! var.startsWith("var-indent") ) return; if ( var == "var-indent-indent-after" ) d->reIndentAfter.setPattern( val ); else if ( var == "var-indent-indent" ) d->reIndent.setPattern( val ); else if ( var == "var-indent-unindent" ) d->reUnindent.setPattern( val ); else if ( var == "var-indent-triggerchars" ) d->triggers = val; else if ( var == "var-indent-handle-couples" ) { d->couples = 0; TQStringList l = TQStringList::split( " ", val ); if ( l.contains("parens") ) d->couples |= Parens; if ( l.contains("braces") ) d->couples |= Braces; if ( l.contains("brackets") ) d->couples |= Brackets; } else if ( var == "var-indent-couple-attribute" ) { //read a named attribute of the config. KateHlItemDataList items; doc->highlight()->getKateHlItemDataListCopy (0, items); for (uint i=0; iname.section( ':', 1 ) == val ) { d->coupleAttrib = i; break; } } } } int KateVarIndent::coupleBalance ( int line, const TQChar &open, const TQChar &close ) const { int r = 0; KateTextLine::Ptr ln = doc->plainKateTextLine( line ); if ( ! ln || ! ln->length() ) return 0; for ( uint z=0; z < ln->length(); z++ ) { TQChar c = ln->getChar( z ); if ( ln->attribute(z) == d->coupleAttrib ) { kdDebug(13030)<coupleAttrib) { TQChar ch = cur.currentChar(); if (ch == opener) count--; else if (ch == close) count++; if (count == 0) return true; } } return false; } //END KateVarIndent //BEGIN KateScriptIndent KateScriptIndent::KateScriptIndent( KateDocument *doc ) : KateNormalIndent( doc ) { m_script=KateFactory::self()->indentScript ("script-indent-c1-test"); } KateScriptIndent::~KateScriptIndent() { } void KateScriptIndent::processNewline( KateDocCursor &begin, bool needContinue ) { kdDebug(13030) << "processNewline" << endl; KateView *view = doc->activeView(); if (view) { TQString errorMsg; TQTime t; t.start(); kdDebug(13030)<<"calling m_script.processChar"<activeView(); if (view) { TQString errorMsg; TQTime t; t.start(); kdDebug(13030)<<"calling m_script.processChar"<activeView(); if (view) { TQString errorMsg; TQTime t; t.start(); kdDebug(13030)<<"calling m_script.processLine"< ScriptIndentConfigPage::ScriptIndentConfigPage ( TQWidget *parent, const char *name ) : IndenterConfigPage(parent, name) { TQLabel* hello = new TQLabel("Hello world! Dummy for testing purpose.", this); hello->show(); } ScriptIndentConfigPage::~ScriptIndentConfigPage () { } void ScriptIndentConfigPage::apply () { kdDebug(13030) << "ScriptIndentConfigPagE::apply() was called, save config options now!" << endl; } //END ScriptIndentConfigPage // kate: space-indent on; indent-width 2; replace-tabs on;