Kate
kateautoindent.cpp
Go to the documentation of this file.
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 "kateglobal.h" 00027 #include "kateindentscript.h" 00028 #include "katescriptmanager.h" 00029 #include "kateview.h" 00030 #include "kateextendedattribute.h" 00031 #include "katedocument.h" 00032 00033 #include <klocale.h> 00034 #include <kdebug.h> 00035 #include <kmenu.h> 00036 00037 #include <cctype> 00038 00039 const QString MODE_NONE = QLatin1String("none"); 00040 const QString MODE_NORMAL = QLatin1String("normal"); 00041 00042 //BEGIN KateAutoIndent 00043 00044 QStringList KateAutoIndent::listModes () 00045 { 00046 QStringList l; 00047 00048 for (int i = 0; i < modeCount(); ++i) 00049 l << modeDescription(i); 00050 00051 return l; 00052 } 00053 00054 int KateAutoIndent::modeCount () 00055 { 00056 // inbuild modes + scripts 00057 return 2 + KateGlobal::self()->scriptManager()->indentationScriptCount(); 00058 } 00059 00060 00061 QString KateAutoIndent::modeName (int mode) 00062 { 00063 if (mode == 0 || mode >= modeCount ()) 00064 return MODE_NONE; 00065 00066 if (mode == 1) 00067 return MODE_NORMAL; 00068 00069 return KateGlobal::self()->scriptManager()->indentationScriptByIndex(mode-2)->indentHeader().baseName(); 00070 } 00071 00072 QString KateAutoIndent::modeDescription (int mode) 00073 { 00074 if (mode == 0 || mode >= modeCount ()) 00075 return i18nc ("Autoindent mode", "None"); 00076 00077 if (mode == 1) 00078 return i18nc ("Autoindent mode", "Normal"); 00079 00080 return KateGlobal::self()->scriptManager()->indentationScriptByIndex(mode-2)->indentHeader().name(); 00081 } 00082 00083 QString KateAutoIndent::modeRequiredStyle(int mode) 00084 { 00085 if (mode == 0 || mode == 1 || mode >= modeCount()) 00086 return QString(); 00087 00088 return KateGlobal::self()->scriptManager()->indentationScriptByIndex(mode-2)->indentHeader().requiredStyle(); 00089 } 00090 00091 uint KateAutoIndent::modeNumber (const QString &name) 00092 { 00093 for (int i = 0; i < modeCount(); ++i) 00094 if (modeName(i) == name) 00095 return i; 00096 00097 return 0; 00098 } 00099 00100 KateAutoIndent::KateAutoIndent (KateDocument *_doc) 00101 : QObject(_doc), doc(_doc), m_script (0) 00102 { 00103 // don't call updateConfig() here, document might is not ready for that.... 00104 00105 // on script reload, the script pointer is invalid -> force reload 00106 connect(KateGlobal::self()->scriptManager(), SIGNAL(reloaded()), 00107 this, SLOT(reloadScript())); 00108 } 00109 00110 KateAutoIndent::~KateAutoIndent () 00111 { 00112 } 00113 00114 QString KateAutoIndent::tabString (int length, int align) const 00115 { 00116 QString s; 00117 length = qMin (length, 256); // sanity check for large values of pos 00118 int spaces = qBound(0, align - length, 256); 00119 00120 if (!useSpaces) 00121 { 00122 s.append (QString (length / tabWidth, '\t')); 00123 length = length % tabWidth; 00124 } 00125 s.append(QString(length + spaces, ' ')); 00126 00127 return s; 00128 } 00129 00130 bool KateAutoIndent::doIndent(int line, int indentDepth, int align) 00131 { 00132 kDebug (13060) << "doIndent: line: " << line << " indentDepth: " << indentDepth << " align: " << align; 00133 00134 Kate::TextLine textline = doc->plainKateTextLine(line); 00135 00136 // textline not found, cu 00137 if (!textline) 00138 return false; 00139 00140 // sanity check 00141 if (indentDepth < 0) 00142 indentDepth = 0; 00143 00144 const QString oldIndentation = textline->leadingWhitespace(); 00145 00146 // Preserve existing "tabs then spaces" alignment if and only if: 00147 // - no alignment was passed to doIndent and 00148 // - we aren't using spaces for indentation and 00149 // - we aren't rounding indentation up to the next multiple of the indentation width and 00150 // - we aren't using a combination to tabs and spaces for alignment, or in other words 00151 // the indent width is a multiple of the tab width. 00152 bool preserveAlignment = !useSpaces && keepExtra && indentWidth % tabWidth == 0; 00153 if (align == 0 && preserveAlignment) 00154 { 00155 // Count the number of consecutive spaces at the end of the existing indentation 00156 int i = oldIndentation.size() - 1; 00157 while (i >= 0 && oldIndentation.at(i) == ' ') 00158 --i; 00159 // Use the passed indentDepth as the alignment, and set the indentDepth to 00160 // that value minus the number of spaces found (but don't let it get negative). 00161 align = indentDepth; 00162 indentDepth = qMax(0, align - (oldIndentation.size() - 1 - i)); 00163 } 00164 00165 QString indentString = tabString(indentDepth, align); 00166 00167 // remove leading whitespace, then insert the leading indentation 00168 doc->editStart (); 00169 doc->editRemoveText (line, 0, oldIndentation.length()); 00170 doc->editInsertText (line, 0, indentString); 00171 doc->editEnd (); 00172 00173 return true; 00174 } 00175 00176 bool KateAutoIndent::doIndentRelative(int line, int change) 00177 { 00178 kDebug (13060) << "doIndentRelative: line: " << line << " change: " << change; 00179 00180 Kate::TextLine textline = doc->plainKateTextLine(line); 00181 00182 // get indent width of current line 00183 int indentDepth = textline->indentDepth (tabWidth); 00184 int extraSpaces = indentDepth % indentWidth; 00185 00186 // add change 00187 indentDepth += change; 00188 00189 // if keepExtra is off, snap to a multiple of the indentWidth 00190 if (!keepExtra && extraSpaces > 0) 00191 { 00192 if (change < 0) 00193 indentDepth += indentWidth - extraSpaces; 00194 else 00195 indentDepth -= extraSpaces; 00196 } 00197 00198 // do indent 00199 return doIndent(line, indentDepth); 00200 } 00201 00202 void KateAutoIndent::keepIndent ( int line ) 00203 { 00204 // no line in front, no work... 00205 if (line <= 0) 00206 return; 00207 00208 Kate::TextLine prevTextLine = doc->plainKateTextLine(line-1); 00209 Kate::TextLine textLine = doc->plainKateTextLine(line); 00210 00211 // textline not found, cu 00212 if (!prevTextLine || !textLine) 00213 return; 00214 00215 const QString previousWhitespace = prevTextLine->leadingWhitespace(); 00216 00217 // remove leading whitespace, then insert the leading indentation 00218 doc->editStart (); 00219 00220 if (!keepExtra) 00221 { 00222 const QString currentWhitespace = textLine->leadingWhitespace(); 00223 doc->editRemoveText (line, 0, currentWhitespace.length()); 00224 } 00225 00226 doc->editInsertText (line, 0, previousWhitespace); 00227 doc->editEnd (); 00228 } 00229 00230 void KateAutoIndent::reloadScript() 00231 { 00232 // small trick to force reload 00233 m_script = 0; // prevent dangling pointer 00234 QString currentMode = m_mode; 00235 m_mode = QString(); 00236 setMode(currentMode); 00237 } 00238 00239 void KateAutoIndent::scriptIndent (KateView *view, const KTextEditor::Cursor &position, QChar typedChar) 00240 { 00241 QPair<int, int> result = m_script->indent (view, position, typedChar, indentWidth); 00242 int newIndentInChars = result.first; 00243 00244 // handle negative values special 00245 if (newIndentInChars < -1) 00246 return; 00247 00248 // reuse indentation of the previous line, just like the "normal" indenter 00249 if (newIndentInChars == -1) 00250 { 00251 // keep indent of previous line 00252 keepIndent (position.line()); 00253 00254 return; 00255 } 00256 00257 int align = result.second; 00258 if (align > 0) 00259 kDebug (13060) << "Align: " << align; 00260 00261 // we got a positive or zero indent to use... 00262 doIndent (position.line(), newIndentInChars, align); 00263 } 00264 00265 bool KateAutoIndent::isStyleProvided(const KateIndentScript *script, const KateHighlighting *highlight) 00266 { 00267 QString requiredStyle = script->indentHeader().requiredStyle(); 00268 return (requiredStyle.isEmpty() || requiredStyle == highlight->style()); 00269 } 00270 00271 void KateAutoIndent::setMode (const QString &name) 00272 { 00273 // bail out, already set correct mode... 00274 if (m_mode == name) 00275 return; 00276 00277 // cleanup 00278 m_script = 0; 00279 00280 // first, catch easy stuff... normal mode and none, easy... 00281 if ( name.isEmpty() || name == MODE_NONE ) 00282 { 00283 m_mode = MODE_NONE; 00284 return; 00285 } 00286 00287 if ( name == MODE_NORMAL ) 00288 { 00289 m_mode = MODE_NORMAL; 00290 return; 00291 } 00292 00293 // handle script indenters, if any for this name... 00294 KateIndentScript *script = KateGlobal::self()->scriptManager()->indentationScript(name); 00295 if ( script ) 00296 { 00297 if (isStyleProvided(script, doc->highlight())) 00298 { 00299 m_script = script; 00300 m_mode = name; 00301 00302 kDebug( 13060 ) << "mode: " << name << "accepted"; 00303 return; 00304 } 00305 else 00306 { 00307 kWarning( 13060 ) << "mode" << name << "requires a different highlight style"; 00308 } 00309 } 00310 else 00311 { 00312 kWarning( 13060 ) << "mode" << name << "does not exist"; 00313 } 00314 00315 // Fall back to normal 00316 m_mode = MODE_NORMAL; 00317 } 00318 00319 void KateAutoIndent::checkRequiredStyle() 00320 { 00321 if (m_script) 00322 { 00323 if (!isStyleProvided(m_script, doc->highlight())) 00324 { 00325 kDebug( 13060 ) << "mode" << m_mode << "requires a different highlight style"; 00326 doc->config()->setIndentationMode(MODE_NORMAL); 00327 } 00328 } 00329 } 00330 00331 void KateAutoIndent::updateConfig () 00332 { 00333 KateDocumentConfig *config = doc->config(); 00334 00335 useSpaces = config->replaceTabsDyn(); 00336 keepExtra = config->keepExtraSpaces(); 00337 tabWidth = config->tabWidth(); 00338 indentWidth = config->indentationWidth(); 00339 } 00340 00341 00342 bool KateAutoIndent::changeIndent (const KTextEditor::Range &range, int change) 00343 { 00344 QList<int> skippedLines; 00345 00346 // loop over all lines given... 00347 for (int line = range.start().line () < 0 ? 0 : range.start().line (); 00348 line <= qMin (range.end().line (), doc->lines()-1); ++line) 00349 { 00350 // don't indent empty lines 00351 if (doc->line(line).isEmpty()) 00352 { 00353 skippedLines.append (line); 00354 continue; 00355 } 00356 // don't indent the last line when the cursor is on the first column 00357 if (line == range.end().line() && range.end().column() == 0) 00358 { 00359 skippedLines.append (line); 00360 continue; 00361 } 00362 00363 doIndentRelative(line, change * indentWidth); 00364 } 00365 00366 if (skippedLines.count() > range.numberOfLines()) 00367 { 00368 // all lines were empty, so indent them nevertheless 00369 foreach (int line, skippedLines) 00370 doIndentRelative(line, change * indentWidth); 00371 } 00372 00373 return true; 00374 } 00375 00376 void KateAutoIndent::indent (KateView *view, const KTextEditor::Range &range) 00377 { 00378 // no script, do nothing... 00379 if (!m_script) 00380 return; 00381 00382 doc->pushEditState(); 00383 doc->editStart(); 00384 // loop over all lines given... 00385 for (int line = range.start().line () < 0 ? 0 : range.start().line (); 00386 line <= qMin (range.end().line (), doc->lines()-1); ++line) 00387 { 00388 // let the script indent for us... 00389 scriptIndent (view, KTextEditor::Cursor (line, 0), QChar()); 00390 } 00391 doc->editEnd (); 00392 doc->popEditState(); 00393 } 00394 00395 void KateAutoIndent::userTypedChar (KateView *view, const KTextEditor::Cursor &position, QChar typedChar) 00396 { 00397 // normal mode 00398 if (m_mode == MODE_NORMAL) 00399 { 00400 // only indent on new line, per default 00401 if (typedChar != '\n') 00402 return; 00403 00404 // keep indent of previous line 00405 keepIndent (position.line()); 00406 00407 return; 00408 } 00409 00410 // no script, do nothing... 00411 if (!m_script) 00412 return; 00413 00414 // does the script allow this char as trigger? 00415 if (typedChar != '\n' && !m_script->triggerCharacters().contains(typedChar)) 00416 return; 00417 00418 // let the script indent for us... 00419 scriptIndent (view, position, typedChar); 00420 } 00421 //END KateAutoIndent 00422 00423 //BEGIN KateViewIndentAction 00424 KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject *parent) 00425 : KActionMenu (text, parent), doc(_doc) 00426 { 00427 connect(menu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); 00428 actionGroup = new QActionGroup(menu()); 00429 } 00430 00431 void KateViewIndentationAction::slotAboutToShow() 00432 { 00433 QStringList modes = KateAutoIndent::listModes (); 00434 00435 menu()->clear (); 00436 foreach (QAction *action, actionGroup->actions()) { 00437 actionGroup->removeAction(action); 00438 } 00439 for (int z=0; z<modes.size(); ++z) { 00440 QAction *action = menu()->addAction( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&") ); 00441 actionGroup->addAction(action); 00442 action->setCheckable( true ); 00443 action->setData( z ); 00444 00445 QString requiredStyle = KateAutoIndent::modeRequiredStyle(z); 00446 action->setEnabled(requiredStyle.isEmpty() || requiredStyle == doc->highlight()->style()); 00447 00448 if ( doc->config()->indentationMode() == KateAutoIndent::modeName (z) ) 00449 action->setChecked( true ); 00450 } 00451 00452 disconnect( menu(), SIGNAL( triggered( QAction* ) ), this, SLOT( setMode( QAction* ) ) ); 00453 connect( menu(), SIGNAL( triggered( QAction* ) ), this, SLOT( setMode( QAction* ) ) ); 00454 } 00455 00456 void KateViewIndentationAction::setMode (QAction *action) 00457 { 00458 // set new mode 00459 doc->config()->setIndentationMode(KateAutoIndent::modeName (action->data().toInt())); 00460 } 00461 //END KateViewIndentationAction 00462 00463 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference