katecmds.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2003 - 2005 Anders Lund <anders@alweb.dk> 00003 Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> 00004 Copyright (C) 2001 Charles Samuels <charles@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License version 2 as published by the Free Software Foundation. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "katecmds.h" 00022 00023 #include "katedocument.h" 00024 #include "kateview.h" 00025 #include "kateconfig.h" 00026 #include "kateautoindent.h" 00027 #include "katetextline.h" 00028 #include "katefactory.h" 00029 #include "katejscript.h" 00030 #include "katerenderer.h" 00031 00032 #include "../interfaces/katecmd.h" 00033 00034 #include <kdebug.h> 00035 #include <klocale.h> 00036 #include <kurl.h> 00037 #include <kshellcompletion.h> 00038 00039 #include <qregexp.h> 00040 00041 00042 //BEGIN CoreCommands 00043 // syncs a config flag in the document with a boolean value 00044 static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable, 00045 KateDocument *doc ) 00046 { 00047 doc->config()->setConfigFlags( flag, enable ); 00048 } 00049 00050 // this returns wheather the string s could be converted to 00051 // a bool value, one of on|off|1|0|true|false. the argument val is 00052 // set to the extracted value in case of success 00053 static bool getBoolArg( QString s, bool *val ) 00054 { 00055 bool res( false ); 00056 s = s.lower(); 00057 res = (s == "on" || s == "1" || s == "true"); 00058 if ( res ) 00059 { 00060 *val = true; 00061 return true; 00062 } 00063 res = (s == "off" || s == "0" || s == "false"); 00064 if ( res ) 00065 { 00066 *val = false; 00067 return true; 00068 } 00069 return false; 00070 } 00071 00072 QStringList KateCommands::CoreCommands::cmds() 00073 { 00074 QStringList l; 00075 l << "indent" << "unindent" << "cleanindent" 00076 << "comment" << "uncomment" << "goto" << "kill-line" 00077 << "set-tab-width" << "set-replace-tabs" << "set-show-tabs" 00078 << "set-remove-trailing-space" 00079 << "set-indent-spaces" << "set-indent-width" << "set-mixed-indent" 00080 << "set-indent-mode" << "set-auto-indent" 00081 << "set-line-numbers" << "set-folding-markers" << "set-icon-border" 00082 << "set-wrap-cursor" 00083 << "set-word-wrap" << "set-word-wrap-column" 00084 << "set-replace-tabs-save" << "set-remove-trailing-space-save" 00085 << "set-highlight" << "run-myself" << "set-show-indent"; 00086 return l; 00087 } 00088 00089 bool KateCommands::CoreCommands::exec(Kate::View *view, 00090 const QString &_cmd, 00091 QString &errorMsg) 00092 { 00093 #define KCC_ERR(s) { errorMsg=s; return false; } 00094 // cast it hardcore, we know that it is really a kateview :) 00095 KateView *v = (KateView*) view; 00096 00097 if ( ! v ) 00098 KCC_ERR( i18n("Could not access view") ); 00099 00100 //create a list of args 00101 QStringList args( QStringList::split( QRegExp("\\s+"), _cmd ) ); 00102 QString cmd ( args.first() ); 00103 args.remove( args.first() ); 00104 00105 // ALL commands that takes no arguments. 00106 if ( cmd == "indent" ) 00107 { 00108 v->indent(); 00109 return true; 00110 } 00111 else if ( cmd == "run-myself" ) 00112 { 00113 #ifndef Q_WS_WIN //todo 00114 return KateFactory::self()->jscript()->execute(v, v->doc()->text(), errorMsg); 00115 #else 00116 return 0; 00117 #endif 00118 } 00119 else if ( cmd == "unindent" ) 00120 { 00121 v->unIndent(); 00122 return true; 00123 } 00124 else if ( cmd == "cleanindent" ) 00125 { 00126 v->cleanIndent(); 00127 return true; 00128 } 00129 else if ( cmd == "comment" ) 00130 { 00131 v->comment(); 00132 return true; 00133 } 00134 else if ( cmd == "uncomment" ) 00135 { 00136 v->uncomment(); 00137 return true; 00138 } 00139 else if ( cmd == "kill-line" ) 00140 { 00141 v->killLine(); 00142 return true; 00143 } 00144 else if ( cmd == "set-indent-mode" ) 00145 { 00146 bool ok(false); 00147 int val ( args.first().toInt( &ok ) ); 00148 if ( ok ) 00149 { 00150 if ( val < 0 ) 00151 KCC_ERR( i18n("Mode must be at least 0.") ); 00152 v->doc()->config()->setIndentationMode( val ); 00153 } 00154 else 00155 v->doc()->config()->setIndentationMode( KateAutoIndent::modeNumber( args.first() ) ); 00156 return true; 00157 } 00158 else if ( cmd == "set-highlight" ) 00159 { 00160 QString val = _cmd.section( ' ', 1 ).lower(); 00161 for ( uint i=0; i < v->doc()->hlModeCount(); i++ ) 00162 { 00163 if ( v->doc()->hlModeName( i ).lower() == val ) 00164 { 00165 v->doc()->setHlMode( i ); 00166 return true; 00167 } 00168 } 00169 KCC_ERR( i18n("No such highlight '%1'").arg( args.first() ) ); 00170 } 00171 00172 // ALL commands that takes exactly one integer argument. 00173 else if ( cmd == "set-tab-width" || 00174 cmd == "set-indent-width" || 00175 cmd == "set-word-wrap-column" || 00176 cmd == "goto" ) 00177 { 00178 // find a integer value > 0 00179 if ( ! args.count() ) 00180 KCC_ERR( i18n("Missing argument. Usage: %1 <value>").arg( cmd ) ); 00181 bool ok; 00182 int val ( args.first().toInt( &ok ) ); 00183 if ( !ok ) 00184 KCC_ERR( i18n("Failed to convert argument '%1' to integer.") 00185 .arg( args.first() ) ); 00186 00187 if ( cmd == "set-tab-width" ) 00188 { 00189 if ( val < 1 ) 00190 KCC_ERR( i18n("Width must be at least 1.") ); 00191 v->setTabWidth( val ); 00192 } 00193 else if ( cmd == "set-indent-width" ) 00194 { 00195 if ( val < 1 ) 00196 KCC_ERR( i18n("Width must be at least 1.") ); 00197 v->doc()->config()->setIndentationWidth( val ); 00198 } 00199 else if ( cmd == "set-word-wrap-column" ) 00200 { 00201 if ( val < 2 ) 00202 KCC_ERR( i18n("Column must be at least 1.") ); 00203 v->doc()->setWordWrapAt( val ); 00204 } 00205 else if ( cmd == "goto" ) 00206 { 00207 if ( val < 1 ) 00208 KCC_ERR( i18n("Line must be at least 1") ); 00209 if ( (uint)val > v->doc()->numLines() ) 00210 KCC_ERR( i18n("There is not that many lines in this document") ); 00211 v->gotoLineNumber( val - 1 ); 00212 } 00213 return true; 00214 } 00215 00216 // ALL commands that takes 1 boolean argument. 00217 else if ( cmd == "set-icon-border" || 00218 cmd == "set-folding-markers" || 00219 cmd == "set-line-numbers" || 00220 cmd == "set-replace-tabs" || 00221 cmd == "set-remove-trailing-space" || 00222 cmd == "set-show-tabs" || 00223 cmd == "set-indent-spaces" || 00224 cmd == "set-mixed-indent" || 00225 cmd == "set-word-wrap" || 00226 cmd == "set-wrap-cursor" || 00227 cmd == "set-replace-tabs-save" || 00228 cmd == "set-remove-trailing-space-save" || 00229 cmd == "set-show-indent" ) 00230 { 00231 if ( ! args.count() ) 00232 KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false").arg( cmd ) ); 00233 bool enable; 00234 if ( getBoolArg( args.first(), &enable ) ) 00235 { 00236 if ( cmd == "set-icon-border" ) 00237 v->setIconBorder( enable ); 00238 else if (cmd == "set-folding-markers") 00239 v->setFoldingMarkersOn( enable ); 00240 else if ( cmd == "set-line-numbers" ) 00241 v->setLineNumbersOn( enable ); 00242 else if ( cmd == "set-show-indent" ) 00243 v->renderer()->setShowIndentLines( enable ); 00244 else if ( cmd == "set-replace-tabs" ) 00245 setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() ); 00246 else if ( cmd == "set-remove-trailing-space" ) 00247 setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() ); 00248 else if ( cmd == "set-show-tabs" ) 00249 setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() ); 00250 else if ( cmd == "set-indent-spaces" ) 00251 setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() ); 00252 else if ( cmd == "set-mixed-indent" ) 00253 { 00254 // this is special, in that everything is set up -- space-indent is enabled, 00255 // and a indent-width is set if it is 0 (to tabwidth/2) 00256 setDocFlag( KateDocumentConfig::cfMixedIndent, enable, v->doc() ); 00257 if ( enable ) 00258 { 00259 setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() ); 00260 if ( ! v->doc()->config()->indentationWidth() ) 00261 v->doc()->config()->setIndentationWidth( v->tabWidth()/2 ); 00262 } 00263 } 00264 else if ( cmd == "set-word-wrap" ) 00265 v->doc()->setWordWrap( enable ); 00266 else if ( cmd == "set-remove-trailing-space-save" ) 00267 setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() ); 00268 else if ( cmd == "set-wrap-cursor" ) 00269 setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() ); 00270 00271 return true; 00272 } 00273 else 00274 KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false") 00275 .arg( args.first() ).arg( cmd ) ); 00276 } 00277 00278 // unlikely.. 00279 KCC_ERR( i18n("Unknown command '%1'").arg(cmd) ); 00280 } 00281 00282 KCompletion *KateCommands::CoreCommands::completionObject( const QString &cmd, Kate::View *view ) 00283 { 00284 if ( cmd == "set-highlight" ) 00285 { 00286 KateView *v = (KateView*)view; 00287 QStringList l; 00288 for ( uint i = 0; i < v->doc()->hlModeCount(); i++ ) 00289 l << v->doc()->hlModeName( i ); 00290 00291 KateCmdShellCompletion *co = new KateCmdShellCompletion(); 00292 co->setItems( l ); 00293 co->setIgnoreCase( true ); 00294 return co; 00295 } 00296 return 0L; 00297 } 00298 //END CoreCommands 00299 00300 //BEGIN SedReplace 00301 static void replace(QString &s, const QString &needle, const QString &with) 00302 { 00303 int pos=0; 00304 while (1) 00305 { 00306 pos=s.find(needle, pos); 00307 if (pos==-1) break; 00308 s.replace(pos, needle.length(), with); 00309 pos+=with.length(); 00310 } 00311 00312 } 00313 00314 static int backslashString(const QString &haystack, const QString &needle, int index) 00315 { 00316 int len=haystack.length(); 00317 int searchlen=needle.length(); 00318 bool evenCount=true; 00319 while (index<len) 00320 { 00321 if (haystack[index]=='\\') 00322 { 00323 evenCount=!evenCount; 00324 } 00325 else 00326 { // isn't a slash 00327 if (!evenCount) 00328 { 00329 if (haystack.mid(index, searchlen)==needle) 00330 return index-1; 00331 } 00332 evenCount=true; 00333 } 00334 index++; 00335 00336 } 00337 00338 return -1; 00339 } 00340 00341 // exchange "\t" for the actual tab character, for example 00342 static void exchangeAbbrevs(QString &str) 00343 { 00344 // the format is (findreplace)*[nullzero] 00345 const char *magic="a\x07t\tn\n"; 00346 00347 while (*magic) 00348 { 00349 int index=0; 00350 char replace=magic[1]; 00351 while ((index=backslashString(str, QChar(*magic), index))!=-1) 00352 { 00353 str.replace(index, 2, QChar(replace)); 00354 index++; 00355 } 00356 magic++; 00357 magic++; 00358 } 00359 } 00360 00361 int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line, 00362 const QString &find, const QString &repOld, const QString &delim, 00363 bool noCase, bool repeat, 00364 uint startcol, int endcol ) 00365 { 00366 KateTextLine *ln = doc->kateTextLine( line ); 00367 if ( ! ln || ! ln->length() ) return 0; 00368 00369 // HANDLING "\n"s in PATTERN 00370 // * Create a list of patterns, splitting PATTERN on (unescaped) "\n" 00371 // * insert $s and ^s to match line ends/beginnings 00372 // * When matching patterhs after the first one, replace \N with the captured 00373 // text. 00374 // * If all patterns in the list match sequentiel lines, there is a match, so 00375 // * remove line/start to line + patterns.count()-1/patterns.last.length 00376 // * handle capatures by putting them in one list. 00377 // * the existing insertion is fine, including the line calculation. 00378 00379 QStringList patterns = QStringList::split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), find, true ); 00380 00381 if ( patterns.count() > 1 ) 00382 { 00383 for ( uint i = 0; i < patterns.count(); i++ ) 00384 { 00385 if ( i < patterns.count() - 1 ) 00386 patterns[i].append("$"); 00387 if ( i ) 00388 patterns[i].prepend("^"); 00389 00390 kdDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i]<<endl; 00391 } 00392 } 00393 00394 QRegExp matcher(patterns[0], noCase); 00395 00396 uint len; 00397 int matches = 0; 00398 00399 while ( ln->searchText( startcol, matcher, &startcol, &len ) ) 00400 { 00401 00402 if ( endcol >= 0 && startcol + len > (uint)endcol ) 00403 break; 00404 00405 matches++; 00406 00407 00408 QString rep=repOld; 00409 00410 // now set the backreferences in the replacement 00411 QStringList backrefs=matcher.capturedTexts(); 00412 int refnum=1; 00413 00414 QStringList::Iterator i = backrefs.begin(); 00415 ++i; 00416 00417 for (; i!=backrefs.end(); ++i) 00418 { 00419 // I need to match "\\" or "", but not "\" 00420 QString number=QString::number(refnum); 00421 00422 int index=0; 00423 while (index!=-1) 00424 { 00425 index=backslashString(rep, number, index); 00426 if (index>=0) 00427 { 00428 rep.replace(index, 2, *i); 00429 index+=(*i).length(); 00430 } 00431 } 00432 00433 refnum++; 00434 } 00435 00436 replace(rep, "\\\\", "\\"); 00437 replace(rep, "\\" + delim, delim); 00438 00439 doc->removeText( line, startcol, line, startcol + len ); 00440 doc->insertText( line, startcol, rep ); 00441 00442 // TODO if replace contains \n, 00443 // change the line number and 00444 // check for text that needs be searched behind the last inserted newline. 00445 int lns = rep.contains('\n'); 00446 if ( lns ) 00447 { 00448 line += lns; 00449 00450 if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) ) 00451 { 00452 // if ( endcol >= startcol + len ) 00453 endcol -= (startcol + len); 00454 uint sc = rep.length() - rep.findRev('\n') - 1; 00455 matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol ); 00456 } 00457 } 00458 00459 if (!repeat) break; 00460 startcol+=rep.length(); 00461 00462 // sanity check -- avoid infinite loops eg with %s,.*,,g ;) 00463 uint ll = ln->length(); 00464 if ( ! ll || startcol > ll ) 00465 break; 00466 } 00467 00468 return matches; 00469 } 00470 00471 bool KateCommands::SedReplace::exec (Kate::View *view, const QString &cmd, QString &msg) 00472 { 00473 kdDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )"<<endl; 00474 00475 QRegExp delim("^[$%]?s\\s*([^\\w\\s])"); 00476 if ( delim.search( cmd ) < 0 ) return false; 00477 00478 bool fullFile=cmd[0]=='%'; 00479 bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i'; 00480 bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g'; 00481 bool onlySelect=cmd[0]=='$'; 00482 00483 QString d = delim.cap(1); 00484 kdDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'"<<endl; 00485 00486 QRegExp splitter( QString("^[$%]?s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d + "[ig]{0,2}$" ); 00487 if (splitter.search(cmd)<0) return false; 00488 00489 QString find=splitter.cap(1); 00490 kdDebug(13025)<< "SedReplace: find=" << find.latin1() <<endl; 00491 00492 QString replace=splitter.cap(2); 00493 exchangeAbbrevs(replace); 00494 kdDebug(13025)<< "SedReplace: replace=" << replace.latin1() <<endl; 00495 00496 if ( find.contains("\\n") ) 00497 { 00498 msg = i18n("Sorry, but Kate is not able to replace newlines, yet"); 00499 return false; 00500 } 00501 00502 KateDocument *doc = ((KateView*)view)->doc(); 00503 if ( ! doc ) return false; 00504 00505 doc->editStart(); 00506 00507 int res = 0; 00508 00509 if (fullFile) 00510 { 00511 uint numLines=doc->numLines(); 00512 for (int line=0; (uint)line < numLines; line++) 00513 { 00514 res += sedMagic( doc, line, find, replace, d, !noCase, repeat ); 00515 if ( ! repeat && res ) break; 00516 } 00517 } 00518 else if (onlySelect) 00519 { 00520 int startline = doc->selStartLine(); 00521 uint startcol = doc->selStartCol(); 00522 int endcol = -1; 00523 do { 00524 if ( startline == doc->selEndLine() ) 00525 endcol = doc->selEndCol(); 00526 00527 res += sedMagic( doc, startline, find, replace, d, !noCase, repeat, startcol, endcol ); 00528 00529 /*if ( startcol )*/ startcol = 0; 00530 00531 startline++; 00532 } while ( (int)startline <= doc->selEndLine() ); 00533 } 00534 else // just this line 00535 { 00536 int line=view->cursorLine(); 00537 res += sedMagic(doc, line, find, replace, d, !noCase, repeat); 00538 } 00539 00540 msg = i18n("1 replacement done", "%n replacements done",res ); 00541 00542 doc->editEnd(); 00543 00544 return true; 00545 } 00546 //END SedReplace 00547 00548 //BEGIN Character 00549 bool KateCommands::Character::exec (Kate::View *view, const QString &_cmd, QString &) 00550 { 00551 QString cmd = _cmd; 00552 00553 // hex, octal, base 9+1 00554 QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,3})$"); 00555 if (num.search(cmd)==-1) return false; 00556 00557 cmd=num.cap(1); 00558 00559 // identify the base 00560 00561 unsigned short int number=0; 00562 int base=10; 00563 if (cmd[0]=='x' || cmd.left(2)=="0x") 00564 { 00565 cmd.replace(QRegExp("^0?x"), ""); 00566 base=16; 00567 } 00568 else if (cmd[0]=='0') 00569 base=8; 00570 bool ok; 00571 number=cmd.toUShort(&ok, base); 00572 if (!ok || number==0) return false; 00573 if (number<=255) 00574 { 00575 char buf[2]; 00576 buf[0]=(char)number; 00577 buf[1]=0; 00578 view->insertText(QString(buf)); 00579 } 00580 else 00581 { // do the unicode thing 00582 QChar c(number); 00583 view->insertText(QString(&c, 1)); 00584 } 00585 00586 return true; 00587 } 00588 //END Character 00589 00590 //BEGIN Date 00591 bool KateCommands::Date::exec (Kate::View *view, const QString &cmd, QString &) 00592 { 00593 if (cmd.left(4) != "date") 00594 return false; 00595 00596 if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0) 00597 view->insertText(QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5))); 00598 else 00599 view->insertText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 00600 00601 return true; 00602 } 00603 //END Date 00604 00605 // kate: space-indent on; indent-width 2; replace-tabs on;