/* 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 "moc_kateautoindent.cpp" #include "kateconfig.h" #include "katehighlight.h" #include "kateglobal.h" #include "kateview.h" #include "kateextendedattribute.h" #include "katedocument.h" // #include "katebuffer.h" #include #include #include #include const QString MODE_NONE = QLatin1String("none"); const QString MODE_NORMAL = QLatin1String("normal"); //BEGIN KateAutoIndent QStringList KateAutoIndent::listModes () { QStringList l; for (int i = 0; i < modeCount(); ++i) l << modeDescription(i); return l; } QStringList KateAutoIndent::listIdentifiers () { QStringList l; for (int i = 0; i < modeCount(); ++i) l << modeName(i); return l; } int KateAutoIndent::modeCount () { // inbuild modes return 2; } QString KateAutoIndent::modeName (int mode) { if (mode == 0 || mode >= modeCount ()) return MODE_NONE; if (mode == 1) return MODE_NORMAL; return QString(); } QString KateAutoIndent::modeDescription (int mode) { if (mode == 0 || mode >= modeCount ()) return i18nc ("Autoindent mode", "None"); if (mode == 1) return i18nc ("Autoindent mode", "Normal"); return QString(); } QString KateAutoIndent::modeRequiredStyle(int mode) { if (mode == 0 || mode == 1 || mode >= modeCount()) return QString(); return QString(); } uint KateAutoIndent::modeNumber (const QString &name) { for (int i = 0; i < modeCount(); ++i) if (modeName(i) == name) return i; return 0; } KateAutoIndent::KateAutoIndent (KateDocument *_doc) : QObject(_doc), doc(_doc) { // don't call updateConfig() here, document might is not ready for that.... } KateAutoIndent::~KateAutoIndent () { } QString KateAutoIndent::tabString (int length, int align) const { QString s; length = qMin (length, 256); // sanity check for large values of pos int spaces = qBound(0, align - length, 256); if (!useSpaces) { s.append (QString (length / tabWidth, '\t')); length = length % tabWidth; } s.append(QString(length + spaces, ' ')); return s; } bool KateAutoIndent::doIndent(int line, int indentDepth, int align) { kDebug (13060) << "doIndent: line: " << line << " indentDepth: " << indentDepth << " align: " << align; Kate::TextLine textline = doc->plainKateTextLine(line); // textline not found, cu if (!textline) return false; // sanity check if (indentDepth < 0) indentDepth = 0; const QString oldIndentation = textline->leadingWhitespace(); // Preserve existing "tabs then spaces" alignment if and only if: // - no alignment was passed to doIndent and // - we aren't using spaces for indentation and // - we aren't rounding indentation up to the next multiple of the indentation width and // - we aren't using a combination to tabs and spaces for alignment, or in other words // the indent width is a multiple of the tab width. bool preserveAlignment = !useSpaces && keepExtra && indentWidth % tabWidth == 0; if (align == 0 && preserveAlignment) { // Count the number of consecutive spaces at the end of the existing indentation int i = oldIndentation.size() - 1; while (i >= 0 && oldIndentation.at(i) == ' ') --i; // Use the passed indentDepth as the alignment, and set the indentDepth to // that value minus the number of spaces found (but don't let it get negative). align = indentDepth; indentDepth = qMax(0, align - (oldIndentation.size() - 1 - i)); } QString indentString = tabString(indentDepth, align); // Modify the document *ONLY* if smth has really changed! if (oldIndentation != indentString) { // remove leading whitespace, then insert the leading indentation doc->editStart (); doc->editRemoveText (line, 0, oldIndentation.length()); doc->editInsertText (line, 0, indentString); doc->editEnd (); } return true; } bool KateAutoIndent::doIndentRelative(int line, int change) { kDebug (13060) << "doIndentRelative: line: " << line << " change: " << change; Kate::TextLine textline = doc->plainKateTextLine(line); // get indent width of current line int indentDepth = textline->indentDepth (tabWidth); int extraSpaces = indentDepth % indentWidth; // add change indentDepth += change; // if keepExtra is off, snap to a multiple of the indentWidth if (!keepExtra && extraSpaces > 0) { if (change < 0) indentDepth += indentWidth - extraSpaces; else indentDepth -= extraSpaces; } // do indent return doIndent(line, indentDepth); } void KateAutoIndent::keepIndent ( int line ) { // no line in front, no work... if (line <= 0) return; // keep indentation: find line with content int nonEmptyLine = line - 1; while (nonEmptyLine >= 0) { if (doc->lineLength(nonEmptyLine) > 0) { break; } --nonEmptyLine; } Kate::TextLine prevTextLine = doc->plainKateTextLine(nonEmptyLine); Kate::TextLine textLine = doc->plainKateTextLine(line); // textline not found, cu if (!prevTextLine || !textLine) return; const QString previousWhitespace = prevTextLine->leadingWhitespace(); // remove leading whitespace, then insert the leading indentation doc->editStart (); if (!keepExtra) { const QString currentWhitespace = textLine->leadingWhitespace(); doc->editRemoveText (line, 0, currentWhitespace.length()); } doc->editInsertText (line, 0, previousWhitespace); doc->editEnd (); } void KateAutoIndent::reloadScript() { // small trick to force reload QString currentMode = m_mode; m_mode = QString(); setMode(currentMode); } void KateAutoIndent::setMode (const QString &name) { // bail out, already set correct mode... if (m_mode == name) return; // first, catch easy stuff... normal mode and none, easy... if ( name.isEmpty() || name == MODE_NONE ) { m_mode = MODE_NONE; return; } if ( name == MODE_NORMAL ) { m_mode = MODE_NORMAL; return; } // Fall back to normal m_mode = MODE_NORMAL; } void KateAutoIndent::checkRequiredStyle() { doc->config()->setIndentationMode(MODE_NORMAL); } void KateAutoIndent::updateConfig () { KateDocumentConfig *config = doc->config(); useSpaces = config->replaceTabsDyn(); keepExtra = config->keepExtraSpaces(); tabWidth = config->tabWidth(); indentWidth = config->indentationWidth(); } bool KateAutoIndent::changeIndent (const KTextEditor::Range &range, int change) { QList skippedLines; // loop over all lines given... for (int line = range.start().line () < 0 ? 0 : range.start().line (); line <= qMin (range.end().line (), doc->lines()-1); ++line) { // don't indent empty lines if (doc->line(line).isEmpty()) { skippedLines.append (line); continue; } // don't indent the last line when the cursor is on the first column if (line == range.end().line() && range.end().column() == 0) { skippedLines.append (line); continue; } doIndentRelative(line, change * indentWidth); } if (skippedLines.count() > range.numberOfLines()) { // all lines were empty, so indent them nevertheless foreach (int line, skippedLines) doIndentRelative(line, change * indentWidth); } return true; } void KateAutoIndent::indent (KateView *view, const KTextEditor::Range &range) { } void KateAutoIndent::userTypedChar (KateView *view, const KTextEditor::Cursor &position, QChar typedChar) { // normal mode if (m_mode == MODE_NORMAL) { // only indent on new line, per default if (typedChar != '\n') return; // keep indent of previous line keepIndent (position.line()); } } //END KateAutoIndent //BEGIN KateViewIndentAction KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject *parent) : KActionMenu (text, parent), doc(_doc) { connect(menu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); actionGroup = new QActionGroup(menu()); } void KateViewIndentationAction::slotAboutToShow() { QStringList modes = KateAutoIndent::listModes (); menu()->clear (); foreach (QAction *action, actionGroup->actions()) { actionGroup->removeAction(action); } for (int z=0; zaddAction( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&") ); actionGroup->addAction(action); action->setCheckable( true ); action->setData( z ); QString requiredStyle = KateAutoIndent::modeRequiredStyle(z); action->setEnabled(requiredStyle.isEmpty() || requiredStyle == doc->highlight()->style()); if ( doc->config()->indentationMode() == KateAutoIndent::modeName (z) ) action->setChecked( true ); } disconnect( menu(), SIGNAL(triggered(QAction*)), this, SLOT(setMode(QAction*)) ); connect( menu(), SIGNAL(triggered(QAction*)), this, SLOT(setMode(QAction*)) ); } void KateViewIndentationAction::setMode (QAction *action) { // set new mode doc->config()->setIndentationMode(KateAutoIndent::modeName (action->data().toInt())); doc->rememberUserDidSetIndentationMode (); } //END KateViewIndentationAction // kate: space-indent on; indent-width 2; replace-tabs on;