summaryrefslogtreecommitdiffstats
path: root/kate/part/katecmds.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kate/part/katecmds.cpp')
-rw-r--r--kate/part/katecmds.cpp605
1 files changed, 605 insertions, 0 deletions
diff --git a/kate/part/katecmds.cpp b/kate/part/katecmds.cpp
new file mode 100644
index 000000000..17846dd7d
--- /dev/null
+++ b/kate/part/katecmds.cpp
@@ -0,0 +1,605 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2003 - 2005 Anders Lund <anders@alweb.dk>
+ Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org>
+ Copyright (C) 2001 Charles Samuels <charles@kde.org>
+
+ 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 "katecmds.h"
+
+#include "katedocument.h"
+#include "kateview.h"
+#include "kateconfig.h"
+#include "kateautoindent.h"
+#include "katetextline.h"
+#include "katefactory.h"
+#include "katejscript.h"
+#include "katerenderer.h"
+
+#include "../interfaces/katecmd.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kurl.h>
+#include <kshellcompletion.h>
+
+#include <qregexp.h>
+
+
+//BEGIN CoreCommands
+// syncs a config flag in the document with a boolean value
+static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable,
+ KateDocument *doc )
+{
+ doc->config()->setConfigFlags( flag, enable );
+}
+
+// this returns wheather the string s could be converted to
+// a bool value, one of on|off|1|0|true|false. the argument val is
+// set to the extracted value in case of success
+static bool getBoolArg( QString s, bool *val )
+{
+ bool res( false );
+ s = s.lower();
+ res = (s == "on" || s == "1" || s == "true");
+ if ( res )
+ {
+ *val = true;
+ return true;
+ }
+ res = (s == "off" || s == "0" || s == "false");
+ if ( res )
+ {
+ *val = false;
+ return true;
+ }
+ return false;
+}
+
+QStringList KateCommands::CoreCommands::cmds()
+{
+ QStringList l;
+ l << "indent" << "unindent" << "cleanindent"
+ << "comment" << "uncomment" << "goto" << "kill-line"
+ << "set-tab-width" << "set-replace-tabs" << "set-show-tabs"
+ << "set-remove-trailing-space"
+ << "set-indent-spaces" << "set-indent-width" << "set-mixed-indent"
+ << "set-indent-mode" << "set-auto-indent"
+ << "set-line-numbers" << "set-folding-markers" << "set-icon-border"
+ << "set-wrap-cursor"
+ << "set-word-wrap" << "set-word-wrap-column"
+ << "set-replace-tabs-save" << "set-remove-trailing-space-save"
+ << "set-highlight" << "run-myself" << "set-show-indent";
+ return l;
+}
+
+bool KateCommands::CoreCommands::exec(Kate::View *view,
+ const QString &_cmd,
+ QString &errorMsg)
+{
+#define KCC_ERR(s) { errorMsg=s; return false; }
+ // cast it hardcore, we know that it is really a kateview :)
+ KateView *v = (KateView*) view;
+
+ if ( ! v )
+ KCC_ERR( i18n("Could not access view") );
+
+ //create a list of args
+ QStringList args( QStringList::split( QRegExp("\\s+"), _cmd ) );
+ QString cmd ( args.first() );
+ args.remove( args.first() );
+
+ // ALL commands that takes no arguments.
+ if ( cmd == "indent" )
+ {
+ v->indent();
+ return true;
+ }
+ else if ( cmd == "run-myself" )
+ {
+#ifndef Q_WS_WIN //todo
+ return KateFactory::self()->jscript()->execute(v, v->doc()->text(), errorMsg);
+#else
+ return 0;
+#endif
+ }
+ else if ( cmd == "unindent" )
+ {
+ v->unIndent();
+ return true;
+ }
+ else if ( cmd == "cleanindent" )
+ {
+ v->cleanIndent();
+ return true;
+ }
+ else if ( cmd == "comment" )
+ {
+ v->comment();
+ return true;
+ }
+ else if ( cmd == "uncomment" )
+ {
+ v->uncomment();
+ return true;
+ }
+ else if ( cmd == "kill-line" )
+ {
+ v->killLine();
+ return true;
+ }
+ else if ( cmd == "set-indent-mode" )
+ {
+ bool ok(false);
+ int val ( args.first().toInt( &ok ) );
+ if ( ok )
+ {
+ if ( val < 0 )
+ KCC_ERR( i18n("Mode must be at least 0.") );
+ v->doc()->config()->setIndentationMode( val );
+ }
+ else
+ v->doc()->config()->setIndentationMode( KateAutoIndent::modeNumber( args.first() ) );
+ return true;
+ }
+ else if ( cmd == "set-highlight" )
+ {
+ QString val = _cmd.section( ' ', 1 ).lower();
+ for ( uint i=0; i < v->doc()->hlModeCount(); i++ )
+ {
+ if ( v->doc()->hlModeName( i ).lower() == val )
+ {
+ v->doc()->setHlMode( i );
+ return true;
+ }
+ }
+ KCC_ERR( i18n("No such highlight '%1'").arg( args.first() ) );
+ }
+
+ // ALL commands that takes exactly one integer argument.
+ else if ( cmd == "set-tab-width" ||
+ cmd == "set-indent-width" ||
+ cmd == "set-word-wrap-column" ||
+ cmd == "goto" )
+ {
+ // find a integer value > 0
+ if ( ! args.count() )
+ KCC_ERR( i18n("Missing argument. Usage: %1 <value>").arg( cmd ) );
+ bool ok;
+ int val ( args.first().toInt( &ok ) );
+ if ( !ok )
+ KCC_ERR( i18n("Failed to convert argument '%1' to integer.")
+ .arg( args.first() ) );
+
+ if ( cmd == "set-tab-width" )
+ {
+ if ( val < 1 )
+ KCC_ERR( i18n("Width must be at least 1.") );
+ v->setTabWidth( val );
+ }
+ else if ( cmd == "set-indent-width" )
+ {
+ if ( val < 1 )
+ KCC_ERR( i18n("Width must be at least 1.") );
+ v->doc()->config()->setIndentationWidth( val );
+ }
+ else if ( cmd == "set-word-wrap-column" )
+ {
+ if ( val < 2 )
+ KCC_ERR( i18n("Column must be at least 1.") );
+ v->doc()->setWordWrapAt( val );
+ }
+ else if ( cmd == "goto" )
+ {
+ if ( val < 1 )
+ KCC_ERR( i18n("Line must be at least 1") );
+ if ( (uint)val > v->doc()->numLines() )
+ KCC_ERR( i18n("There is not that many lines in this document") );
+ v->gotoLineNumber( val - 1 );
+ }
+ return true;
+ }
+
+ // ALL commands that takes 1 boolean argument.
+ else if ( cmd == "set-icon-border" ||
+ cmd == "set-folding-markers" ||
+ cmd == "set-line-numbers" ||
+ cmd == "set-replace-tabs" ||
+ cmd == "set-remove-trailing-space" ||
+ cmd == "set-show-tabs" ||
+ cmd == "set-indent-spaces" ||
+ cmd == "set-mixed-indent" ||
+ cmd == "set-word-wrap" ||
+ cmd == "set-wrap-cursor" ||
+ cmd == "set-replace-tabs-save" ||
+ cmd == "set-remove-trailing-space-save" ||
+ cmd == "set-show-indent" )
+ {
+ if ( ! args.count() )
+ KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false").arg( cmd ) );
+ bool enable;
+ if ( getBoolArg( args.first(), &enable ) )
+ {
+ if ( cmd == "set-icon-border" )
+ v->setIconBorder( enable );
+ else if (cmd == "set-folding-markers")
+ v->setFoldingMarkersOn( enable );
+ else if ( cmd == "set-line-numbers" )
+ v->setLineNumbersOn( enable );
+ else if ( cmd == "set-show-indent" )
+ v->renderer()->setShowIndentLines( enable );
+ else if ( cmd == "set-replace-tabs" )
+ setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() );
+ else if ( cmd == "set-remove-trailing-space" )
+ setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() );
+ else if ( cmd == "set-show-tabs" )
+ setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() );
+ else if ( cmd == "set-indent-spaces" )
+ setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() );
+ else if ( cmd == "set-mixed-indent" )
+ {
+ // this is special, in that everything is set up -- space-indent is enabled,
+ // and a indent-width is set if it is 0 (to tabwidth/2)
+ setDocFlag( KateDocumentConfig::cfMixedIndent, enable, v->doc() );
+ if ( enable )
+ {
+ setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() );
+ if ( ! v->doc()->config()->indentationWidth() )
+ v->doc()->config()->setIndentationWidth( v->tabWidth()/2 );
+ }
+ }
+ else if ( cmd == "set-word-wrap" )
+ v->doc()->setWordWrap( enable );
+ else if ( cmd == "set-remove-trailing-space-save" )
+ setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() );
+ else if ( cmd == "set-wrap-cursor" )
+ setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() );
+
+ return true;
+ }
+ else
+ KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false")
+ .arg( args.first() ).arg( cmd ) );
+ }
+
+ // unlikely..
+ KCC_ERR( i18n("Unknown command '%1'").arg(cmd) );
+}
+
+KCompletion *KateCommands::CoreCommands::completionObject( const QString &cmd, Kate::View *view )
+{
+ if ( cmd == "set-highlight" )
+ {
+ KateView *v = (KateView*)view;
+ QStringList l;
+ for ( uint i = 0; i < v->doc()->hlModeCount(); i++ )
+ l << v->doc()->hlModeName( i );
+
+ KateCmdShellCompletion *co = new KateCmdShellCompletion();
+ co->setItems( l );
+ co->setIgnoreCase( true );
+ return co;
+ }
+ return 0L;
+}
+//END CoreCommands
+
+//BEGIN SedReplace
+static void replace(QString &s, const QString &needle, const QString &with)
+{
+ int pos=0;
+ while (1)
+ {
+ pos=s.find(needle, pos);
+ if (pos==-1) break;
+ s.replace(pos, needle.length(), with);
+ pos+=with.length();
+ }
+
+}
+
+static int backslashString(const QString &haystack, const QString &needle, int index)
+{
+ int len=haystack.length();
+ int searchlen=needle.length();
+ bool evenCount=true;
+ while (index<len)
+ {
+ if (haystack[index]=='\\')
+ {
+ evenCount=!evenCount;
+ }
+ else
+ { // isn't a slash
+ if (!evenCount)
+ {
+ if (haystack.mid(index, searchlen)==needle)
+ return index-1;
+ }
+ evenCount=true;
+ }
+ index++;
+
+ }
+
+ return -1;
+}
+
+// exchange "\t" for the actual tab character, for example
+static void exchangeAbbrevs(QString &str)
+{
+ // the format is (findreplace)*[nullzero]
+ const char *magic="a\x07t\tn\n";
+
+ while (*magic)
+ {
+ int index=0;
+ char replace=magic[1];
+ while ((index=backslashString(str, QChar(*magic), index))!=-1)
+ {
+ str.replace(index, 2, QChar(replace));
+ index++;
+ }
+ magic++;
+ magic++;
+ }
+}
+
+int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line,
+ const QString &find, const QString &repOld, const QString &delim,
+ bool noCase, bool repeat,
+ uint startcol, int endcol )
+{
+ KateTextLine *ln = doc->kateTextLine( line );
+ if ( ! ln || ! ln->length() ) return 0;
+
+ // HANDLING "\n"s in PATTERN
+ // * Create a list of patterns, splitting PATTERN on (unescaped) "\n"
+ // * insert $s and ^s to match line ends/beginnings
+ // * When matching patterhs after the first one, replace \N with the captured
+ // text.
+ // * If all patterns in the list match sequentiel lines, there is a match, so
+ // * remove line/start to line + patterns.count()-1/patterns.last.length
+ // * handle capatures by putting them in one list.
+ // * the existing insertion is fine, including the line calculation.
+
+ QStringList patterns = QStringList::split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), find, true );
+
+ if ( patterns.count() > 1 )
+ {
+ for ( uint i = 0; i < patterns.count(); i++ )
+ {
+ if ( i < patterns.count() - 1 )
+ patterns[i].append("$");
+ if ( i )
+ patterns[i].prepend("^");
+
+ kdDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i]<<endl;
+ }
+ }
+
+ QRegExp matcher(patterns[0], noCase);
+
+ uint len;
+ int matches = 0;
+
+ while ( ln->searchText( startcol, matcher, &startcol, &len ) )
+ {
+
+ if ( endcol >= 0 && startcol + len > (uint)endcol )
+ break;
+
+ matches++;
+
+
+ QString rep=repOld;
+
+ // now set the backreferences in the replacement
+ QStringList backrefs=matcher.capturedTexts();
+ int refnum=1;
+
+ QStringList::Iterator i = backrefs.begin();
+ ++i;
+
+ for (; i!=backrefs.end(); ++i)
+ {
+ // I need to match "\\" or "", but not "\"
+ QString number=QString::number(refnum);
+
+ int index=0;
+ while (index!=-1)
+ {
+ index=backslashString(rep, number, index);
+ if (index>=0)
+ {
+ rep.replace(index, 2, *i);
+ index+=(*i).length();
+ }
+ }
+
+ refnum++;
+ }
+
+ replace(rep, "\\\\", "\\");
+ replace(rep, "\\" + delim, delim);
+
+ doc->removeText( line, startcol, line, startcol + len );
+ doc->insertText( line, startcol, rep );
+
+ // TODO if replace contains \n,
+ // change the line number and
+ // check for text that needs be searched behind the last inserted newline.
+ int lns = rep.contains('\n');
+ if ( lns )
+ {
+ line += lns;
+
+ if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) )
+ {
+ // if ( endcol >= startcol + len )
+ endcol -= (startcol + len);
+ uint sc = rep.length() - rep.findRev('\n') - 1;
+ matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol );
+ }
+ }
+
+ if (!repeat) break;
+ startcol+=rep.length();
+
+ // sanity check -- avoid infinite loops eg with %s,.*,,g ;)
+ uint ll = ln->length();
+ if ( ! ll || startcol > ll )
+ break;
+ }
+
+ return matches;
+}
+
+bool KateCommands::SedReplace::exec (Kate::View *view, const QString &cmd, QString &msg)
+{
+ kdDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )"<<endl;
+
+ QRegExp delim("^[$%]?s\\s*([^\\w\\s])");
+ if ( delim.search( cmd ) < 0 ) return false;
+
+ bool fullFile=cmd[0]=='%';
+ bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i';
+ bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g';
+ bool onlySelect=cmd[0]=='$';
+
+ QString d = delim.cap(1);
+ kdDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'"<<endl;
+
+ QRegExp splitter( QString("^[$%]?s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d + "[ig]{0,2}$" );
+ if (splitter.search(cmd)<0) return false;
+
+ QString find=splitter.cap(1);
+ kdDebug(13025)<< "SedReplace: find=" << find.latin1() <<endl;
+
+ QString replace=splitter.cap(2);
+ exchangeAbbrevs(replace);
+ kdDebug(13025)<< "SedReplace: replace=" << replace.latin1() <<endl;
+
+ if ( find.contains("\\n") )
+ {
+ msg = i18n("Sorry, but Kate is not able to replace newlines, yet");
+ return false;
+ }
+
+ KateDocument *doc = ((KateView*)view)->doc();
+ if ( ! doc ) return false;
+
+ doc->editStart();
+
+ int res = 0;
+
+ if (fullFile)
+ {
+ uint numLines=doc->numLines();
+ for (int line=0; (uint)line < numLines; line++)
+ {
+ res += sedMagic( doc, line, find, replace, d, !noCase, repeat );
+ if ( ! repeat && res ) break;
+ }
+ }
+ else if (onlySelect)
+ {
+ int startline = doc->selStartLine();
+ uint startcol = doc->selStartCol();
+ int endcol = -1;
+ do {
+ if ( startline == doc->selEndLine() )
+ endcol = doc->selEndCol();
+
+ res += sedMagic( doc, startline, find, replace, d, !noCase, repeat, startcol, endcol );
+
+ /*if ( startcol )*/ startcol = 0;
+
+ startline++;
+ } while ( (int)startline <= doc->selEndLine() );
+ }
+ else // just this line
+ {
+ int line=view->cursorLine();
+ res += sedMagic(doc, line, find, replace, d, !noCase, repeat);
+ }
+
+ msg = i18n("1 replacement done", "%n replacements done",res );
+
+ doc->editEnd();
+
+ return true;
+}
+//END SedReplace
+
+//BEGIN Character
+bool KateCommands::Character::exec (Kate::View *view, const QString &_cmd, QString &)
+{
+ QString cmd = _cmd;
+
+ // hex, octal, base 9+1
+ QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,3})$");
+ if (num.search(cmd)==-1) return false;
+
+ cmd=num.cap(1);
+
+ // identify the base
+
+ unsigned short int number=0;
+ int base=10;
+ if (cmd[0]=='x' || cmd.left(2)=="0x")
+ {
+ cmd.replace(QRegExp("^0?x"), "");
+ base=16;
+ }
+ else if (cmd[0]=='0')
+ base=8;
+ bool ok;
+ number=cmd.toUShort(&ok, base);
+ if (!ok || number==0) return false;
+ if (number<=255)
+ {
+ char buf[2];
+ buf[0]=(char)number;
+ buf[1]=0;
+ view->insertText(QString(buf));
+ }
+ else
+ { // do the unicode thing
+ QChar c(number);
+ view->insertText(QString(&c, 1));
+ }
+
+ return true;
+}
+//END Character
+
+//BEGIN Date
+bool KateCommands::Date::exec (Kate::View *view, const QString &cmd, QString &)
+{
+ if (cmd.left(4) != "date")
+ return false;
+
+ if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0)
+ view->insertText(QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)));
+ else
+ view->insertText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
+
+ return true;
+}
+//END Date
+
+// kate: space-indent on; indent-width 2; replace-tabs on;