00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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
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
00057 return 2 + KateGlobal::self()->scriptManager()->indentationScripts();
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)->information().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)->information().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)->information().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 : doc(_doc), m_normal (false), m_script (0)
00102 {
00103
00104 }
00105
00106 KateAutoIndent::~KateAutoIndent ()
00107 {
00108 }
00109
00110 QString KateAutoIndent::tabString (int length, int align) const
00111 {
00112 QString s;
00113 length = qMin (length, 256);
00114 int spaces = qBound(0, align - length, 256);
00115
00116 if (!useSpaces)
00117 {
00118 s.append (QString (length / tabWidth, '\t'));
00119 length = length % tabWidth;
00120 }
00121 s.append(QString(length + spaces, ' '));
00122
00123 return s;
00124 }
00125
00126 bool KateAutoIndent::doIndent(int line, int indentDepth, int align)
00127 {
00128 kDebug (13060) << "doIndent: line: " << line << " indentDepth: " << indentDepth << " align: " << align;
00129
00130 KateTextLine::Ptr textline = doc->plainKateTextLine(line);
00131
00132
00133 if (!textline)
00134 return false;
00135
00136
00137 if (indentDepth < 0)
00138 indentDepth = 0;
00139
00140 int first_char = textline->firstChar();
00141 if (first_char < 0)
00142 first_char = textline->length();
00143
00144
00145
00146
00147
00148
00149
00150 bool preserveAlignment = !useSpaces && keepExtra && indentWidth % tabWidth == 0;
00151 if (align == 0 && preserveAlignment)
00152 {
00153
00154 QString oldIndentation = textline->string(0, first_char);
00155 int i = oldIndentation.size() - 1;
00156 while (i >= 0 && oldIndentation.at(i) == ' ')
00157 --i;
00158
00159
00160 align = indentDepth;
00161 indentDepth = qMax(0, align - (oldIndentation.size() - 1 - i));
00162 }
00163
00164 QString indentString = tabString(indentDepth, align);
00165
00166
00167 doc->editStart ();
00168 doc->editRemoveText (line, 0, first_char);
00169 doc->editInsertText (line, 0, indentString);
00170 doc->editEnd ();
00171
00172 return true;
00173 }
00174
00175 bool KateAutoIndent::doIndentRelative(int line, int change)
00176 {
00177 kDebug (13060) << "doIndentRelative: line: " << line << " change: " << change;
00178
00179 KateTextLine::Ptr textline = doc->plainKateTextLine(line);
00180
00181
00182 int indentDepth = textline->indentDepth (tabWidth);
00183 int extraSpaces = indentDepth % indentWidth;
00184
00185
00186 indentDepth += change;
00187
00188
00189 if (!keepExtra && extraSpaces > 0)
00190 {
00191 if (change < 0)
00192 indentDepth += indentWidth - extraSpaces;
00193 else
00194 indentDepth -= extraSpaces;
00195 }
00196
00197
00198 return doIndent(line, indentDepth);
00199 }
00200
00201 void KateAutoIndent::keepIndent ( int line )
00202 {
00203
00204 if (line <= 0)
00205 return;
00206
00207 KateTextLine::Ptr prevTextLine = doc->plainKateTextLine(line-1);
00208 KateTextLine::Ptr textLine = doc->plainKateTextLine(line);
00209
00210
00211 if (!prevTextLine || !textLine)
00212 return;
00213
00214 const QString previousWhitespace = prevTextLine->leadingWhitespace();
00215
00216
00217 doc->editStart ();
00218
00219 if (!keepExtra)
00220 {
00221 const QString currentWhitespace = textLine->leadingWhitespace();
00222 doc->editRemoveText (line, 0, currentWhitespace.length());
00223 }
00224
00225 doc->editInsertText (line, 0, previousWhitespace);
00226 doc->editEnd ();
00227 }
00228
00229 void KateAutoIndent::scriptIndent (KateView *view, const KTextEditor::Cursor &position, QChar typedChar)
00230 {
00231 QPair<int, int> result = m_script->indent (view, position, typedChar, indentWidth);
00232 int newIndentInChars = result.first;
00233
00234
00235 if (newIndentInChars < -1)
00236 return;
00237
00238
00239 if (newIndentInChars == -1)
00240 {
00241
00242 keepIndent (position.line());
00243
00244 return;
00245 }
00246
00247 int align = result.second;
00248 if (align > 0)
00249 kDebug (13060) << "Align: " << align;
00250
00251
00252 doIndent (position.line(), newIndentInChars, align);
00253 }
00254
00255 bool KateAutoIndent::isStyleProvided(KateIndentScript *script)
00256 {
00257 QString requiredStyle = script->information().requiredStyle;
00258 return (requiredStyle.isEmpty() || requiredStyle == doc->highlight()->style());
00259 }
00260
00261 void KateAutoIndent::setMode (const QString &name)
00262 {
00263
00264 if (m_mode == name)
00265 return;
00266
00267
00268 m_script = 0;
00269 m_normal = false;
00270
00271
00272 if ( name.isEmpty() || name == MODE_NONE )
00273 {
00274 m_mode = MODE_NONE;
00275 return;
00276 }
00277
00278 if ( name == MODE_NORMAL )
00279 {
00280 m_normal = true;
00281 m_mode = MODE_NORMAL;
00282 return;
00283 }
00284
00285
00286 KateIndentScript *script = KateGlobal::self()->scriptManager()->indentationScript(name);
00287 if ( script )
00288 {
00289 if (isStyleProvided(script))
00290 {
00291 m_script = script;
00292 m_mode = name;
00293
00294 kDebug( 13060 ) << "mode: " << name << "accepted";
00295 return;
00296 }
00297 else
00298 {
00299 kWarning( 13060 ) << "mode" << name << "requires a different highlight style";
00300 }
00301 }
00302 else
00303 {
00304 kWarning( 13060 ) << "mode" << name << "does not exist";
00305 }
00306
00307
00308 m_normal = true;
00309 m_mode = MODE_NORMAL;
00310 }
00311
00312 void KateAutoIndent::checkRequiredStyle()
00313 {
00314 if (m_script)
00315 {
00316 if (!isStyleProvided(m_script))
00317 {
00318 kDebug( 13060 ) << "mode" << m_mode << "requires a different highlight style";
00319 doc->config()->setIndentationMode(MODE_NORMAL);
00320 }
00321 }
00322 }
00323
00324 void KateAutoIndent::updateConfig ()
00325 {
00326 KateDocumentConfig *config = doc->config();
00327
00328 useSpaces = config->configFlags() & KateDocumentConfig::cfReplaceTabsDyn;
00329 keepExtra = config->configFlags() & KateDocumentConfig::cfKeepExtraSpaces;
00330 tabWidth = config->tabWidth();
00331 indentWidth = config->indentationWidth();
00332 }
00333
00334
00335 bool KateAutoIndent::changeIndent (const KTextEditor::Range &range, int change)
00336 {
00337 QList<int> skippedLines;
00338
00339
00340 for (int line = range.start().line () < 0 ? 0 : range.start().line ();
00341 line <= qMin (range.end().line (), doc->lines()-1); ++line)
00342 {
00343
00344 if (doc->line(line).isEmpty())
00345 {
00346 skippedLines.append (line);
00347 continue;
00348 }
00349
00350 if (line == range.end().line() && range.end().column() == 0)
00351 {
00352 skippedLines.append (line);
00353 continue;
00354 }
00355
00356 doIndentRelative(line, change * indentWidth);
00357 }
00358
00359 if (skippedLines.count() > range.numberOfLines())
00360 {
00361
00362 foreach (int line, skippedLines)
00363 doIndentRelative(line, change * indentWidth);
00364 }
00365
00366 return true;
00367 }
00368
00369 void KateAutoIndent::indent (KateView *view, const KTextEditor::Range &range)
00370 {
00371
00372 if (!m_script)
00373 return;
00374
00375 doc->pushEditState();
00376 doc->editStart();
00377
00378 for (int line = range.start().line () < 0 ? 0 : range.start().line ();
00379 line <= qMin (range.end().line (), doc->lines()-1); ++line)
00380 {
00381
00382 scriptIndent (view, KTextEditor::Cursor (line, 0), QChar());
00383 }
00384 doc->editEnd ();
00385 doc->popEditState();
00386 }
00387
00388 void KateAutoIndent::userTypedChar (KateView *view, const KTextEditor::Cursor &position, QChar typedChar)
00389 {
00390
00391 if (m_normal)
00392 {
00393
00394 if (typedChar != '\n')
00395 return;
00396
00397
00398 keepIndent (position.line());
00399
00400 return;
00401 }
00402
00403
00404 if (!m_script)
00405 return;
00406
00407
00408 if (typedChar != '\n' && !m_script->triggerCharacters().contains(typedChar))
00409 return;
00410
00411
00412 scriptIndent (view, position, typedChar);
00413 }
00414
00415
00416
00417 KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject *parent)
00418 : KActionMenu (text, parent), doc(_doc)
00419 {
00420 connect(menu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow()));
00421 actionGroup = new QActionGroup(menu());
00422 }
00423
00424 void KateViewIndentationAction::slotAboutToShow()
00425 {
00426 QStringList modes = KateAutoIndent::listModes ();
00427
00428 menu()->clear ();
00429 foreach (QAction *action, actionGroup->actions()) {
00430 actionGroup->removeAction(action);
00431 }
00432 for (int z=0; z<modes.size(); ++z) {
00433 QAction *action = menu()->addAction( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&") );
00434 actionGroup->addAction(action);
00435 action->setCheckable( true );
00436 action->setData( z );
00437
00438 QString requiredStyle = KateAutoIndent::modeRequiredStyle(z);
00439 action->setEnabled(requiredStyle.isEmpty() || requiredStyle == doc->highlight()->style());
00440
00441 if ( doc->config()->indentationMode() == KateAutoIndent::modeName (z) )
00442 action->setChecked( true );
00443 }
00444
00445 disconnect( menu(), SIGNAL( triggered( QAction* ) ), this, SLOT( setMode( QAction* ) ) );
00446 connect( menu(), SIGNAL( triggered( QAction* ) ), this, SLOT( setMode( QAction* ) ) );
00447 }
00448
00449 void KateViewIndentationAction::setMode (QAction *action)
00450 {
00451
00452 doc->config()->setIndentationMode(KateAutoIndent::modeName (action->data().toInt()));
00453 }
00454
00455
00456