/* This file is part of the KDE libraries Copyright (C) 2003 - 2005 Anders Lund Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001 Charles Samuels 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 #include #include #include #include //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( TQString 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; } TQStringList KateCommands::CoreCommands::cmds() { TQStringList 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 TQString &_cmd, TQString &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 TQStringList args( TQStringList::split( TQRegExp("\\s+"), _cmd ) ); TQString 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" ) { TQString val = TQString(_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 ").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) ); } TDECompletion *KateCommands::CoreCommands::completionObject( const TQString &cmd, Kate::View *view ) { if ( cmd == "set-highlight" ) { KateView *v = (KateView*)view; TQStringList 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(TQString &s, const TQString &needle, const TQString &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 TQString &haystack, const TQString &needle, int index) { int len=haystack.length(); int searchlen=needle.length(); bool evenCount=true; while (indexkateTextLine( 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. TQStringList patterns = TQStringList::split( TQRegExp("(^\\\\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["<searchText( startcol, matcher, &startcol, &len ) ) { if ( endcol >= 0 && startcol + len > (uint)endcol ) break; matches++; TQString rep=repOld; // now set the backreferences in the replacement TQStringList backrefs=matcher.capturedTexts(); int refnum=1; TQStringList::Iterator i = backrefs.begin(); ++i; for (; i!=backrefs.end(); ++i) { // I need to match "\\" or "", but not "\" TQString number=TQString::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 TQString &cmd, TQString &msg) { kdDebug(13025)<<"SedReplace::execCmd( "<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 TQString &_cmd, TQString &) { TQString cmd = _cmd; // hex, octal, base 9+1 TQRegExp 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(TQRegExp("^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(TQString(buf)); } else { // do the unicode thing TQChar c(number); view->insertText(TQString(&c, 1)); } return true; } //END Character //BEGIN Date bool KateCommands::Date::exec (Kate::View *view, const TQString &cmd, TQString &) { if (cmd.left(4) != "date") return false; if (TQDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0) view->insertText(TQDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5))); else view->insertText(TQDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); return true; } //END Date // kate: space-indent on; indent-width 2; replace-tabs on;