kde-workspace/kate/part/utils/kateautoindent.cpp
Ivailo Monev f68295ea28 generic: move sub-projects from kde-baseapps [ci reset]
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-05-14 21:56:54 +03:00

388 lines
10 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 2003 Jesse Yurkovich <yurkjes@iit.edu>
Copyright (C) 2004 >Anders Lund <anders@alweb.dk> (KateVarIndent class)
Copyright (C) 2005 Dominik Haumann <dhdev@gmx.de> (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 <klocale.h>
#include <kdebug.h>
#include <kmenu.h>
#include <cctype>
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<int> 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; z<modes.size(); ++z) {
QAction *action = menu()->addAction( '&' + 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;