00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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 "katesyntaxmanager.h"
00029 #include "kateglobal.h"
00030 #include "katerenderer.h"
00031 #include "katecmd.h"
00032
00033 #include <kdebug.h>
00034 #include <klocale.h>
00035 #include <kurl.h>
00036 #include <kshellcompletion.h>
00037
00038 #include <QtCore/QRegExp>
00039
00040
00041
00042 static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable,
00043 KateDocument *doc )
00044 {
00045 doc->config()->setConfigFlags( flag, enable );
00046 }
00047
00048
00049
00050
00051 static bool getBoolArg( const QString &t, bool *val )
00052 {
00053 bool res( false );
00054 QString s = t.toLower();
00055 res = (s == "on" || s == "1" || s == "true");
00056 if ( res )
00057 {
00058 *val = true;
00059 return true;
00060 }
00061 res = (s == "off" || s == "0" || s == "false");
00062 if ( res )
00063 {
00064 *val = false;
00065 return true;
00066 }
00067 return false;
00068 }
00069
00070 const QStringList &KateCommands::CoreCommands::cmds()
00071 {
00072 static QStringList l;
00073
00074 if (l.isEmpty())
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-width"
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" << "set-mode" << "set-show-indent"
00086 << "w" << "print" << "hardcopy";
00087
00088 return l;
00089 }
00090
00091 bool KateCommands::CoreCommands::exec(KTextEditor::View *view,
00092 const QString &_cmd,
00093 QString &errorMsg)
00094 {
00095 return exec( view, _cmd, errorMsg, KTextEditor::Range::invalid() );
00096 }
00097
00098 bool KateCommands::CoreCommands::exec(KTextEditor::View *view,
00099 const QString &_cmd,
00100 QString &errorMsg,
00101 const KTextEditor::Range& range)
00102 {
00103 #define KCC_ERR(s) { errorMsg=s; return false; }
00104
00105 KateView *v = (KateView*) view;
00106
00107 if ( ! v )
00108 KCC_ERR( i18n("Could not access view") );
00109
00110
00111 QStringList args(_cmd.split( QRegExp("\\s+"), QString::SkipEmptyParts)) ;
00112 QString cmd ( args.takeFirst() );
00113
00114
00115 if ( cmd == "indent" )
00116 {
00117 if ( range.isValid() ) {
00118 v->doc()->editStart();
00119 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00120 v->doc()->indent( v, line, 1 );
00121 }
00122 v->doc()->editEnd();
00123 } else {
00124 v->indent();
00125 }
00126 return true;
00127 }
00128 #if 0
00129 else if ( cmd == "run-myself" )
00130 {
00131 #ifndef Q_WS_WIN //todo
00132 return KateGlobal::self()->jscript()->execute(v, v->doc()->text(), errorMsg);
00133 #else
00134 return 0;
00135 #endif
00136 }
00137 #endif
00138 else if ( cmd == "unindent" )
00139 {
00140 if ( range.isValid() ) {
00141 v->doc()->editStart();
00142 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00143 v->doc()->indent( v, line, -1 );
00144 }
00145 v->doc()->editEnd();
00146 } else {
00147 v->unIndent();
00148 }
00149 return true;
00150 }
00151 else if ( cmd == "cleanindent" )
00152 {
00153 if ( range.isValid() ) {
00154 v->doc()->editStart();
00155 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00156 v->doc()->indent( v, line, 0 );
00157 }
00158 v->doc()->editEnd();
00159 } else {
00160 v->cleanIndent();
00161 }
00162 return true;
00163 }
00164 else if ( cmd == "comment" )
00165 {
00166 if ( range.isValid() ) {
00167 v->doc()->editStart();
00168 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00169 v->doc()->comment( v, line, 0, 1 );
00170 }
00171 v->doc()->editEnd();
00172 } else {
00173 v->comment();
00174 }
00175 return true;
00176 }
00177 else if ( cmd == "uncomment" )
00178 {
00179 if ( range.isValid() ) {
00180 v->doc()->editStart();
00181 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00182 v->doc()->comment( v, line, 0, -1 );
00183 }
00184 v->doc()->editEnd();
00185 } else {
00186 v->uncomment();
00187 }
00188 return true;
00189 }
00190 else if ( cmd == "kill-line" )
00191 {
00192 if ( range.isValid() ) {
00193 v->doc()->editStart();
00194 for ( int line = range.start().line(); line <= range.end().line(); line++ ) {
00195 v->doc()->removeLine( range.start().line() );
00196 }
00197 v->doc()->editEnd();
00198 } else {
00199 v->killLine();
00200 }
00201 return true;
00202 }
00203 else if ( cmd == "w" )
00204 {
00205 v->doc()->documentSave();
00206 return true;
00207 }
00208 else if ( cmd == "print" || cmd == "hardcopy" )
00209 {
00210 v->doc()->printDialog();
00211 return true;
00212 }
00213 else if ( cmd == "set-indent-mode" )
00214 {
00215 v->doc()->config()->setIndentationMode( args.first() );
00216 return true;
00217 }
00218 else if ( cmd == "set-highlight" )
00219 {
00220 if ( v->doc()->setHighlightingMode( args.first()) )
00221 return true;
00222
00223 KCC_ERR( i18n("No such highlighting '%1'", args.first() ) );
00224 }
00225 else if ( cmd == "set-mode" )
00226 {
00227 if ( v->doc()->setMode( args.first()) )
00228 return true;
00229
00230 KCC_ERR( i18n("No such mode '%1'", args.first() ) );
00231 }
00232
00233
00234 else if ( cmd == "set-tab-width" ||
00235 cmd == "set-indent-width" ||
00236 cmd == "set-word-wrap-column" ||
00237 cmd == "goto" )
00238 {
00239
00240 if ( ! args.count() )
00241 KCC_ERR( i18n("Missing argument. Usage: %1 <value>", cmd ) );
00242 bool ok;
00243 int val ( args.first().toInt( &ok, 10 ) );
00244 if ( !ok )
00245 KCC_ERR( i18n("Failed to convert argument '%1' to integer.",
00246 args.first() ) );
00247
00248 if ( cmd == "set-tab-width" )
00249 {
00250 if ( val < 1 )
00251 KCC_ERR( i18n("Width must be at least 1.") );
00252 v->doc()->config()->setTabWidth( val );
00253 }
00254 else if ( cmd == "set-indent-width" )
00255 {
00256 if ( val < 1 )
00257 KCC_ERR( i18n("Width must be at least 1.") );
00258 v->doc()->config()->setIndentationWidth( val );
00259 }
00260 else if ( cmd == "set-word-wrap-column" )
00261 {
00262 if ( val < 2 )
00263 KCC_ERR( i18n("Column must be at least 1.") );
00264 v->doc()->setWordWrapAt( val );
00265 }
00266 else if ( cmd == "goto" )
00267 {
00268 if ( args.first().at(0) == '-' || args.first().at(0) == '+' ) {
00269
00270 val = v->cursorPosition().line() + val;
00271 } else {
00272 val--;
00273 }
00274
00275
00276 if ( val < 0 ) {
00277 val = 0;
00278 } else if ( val > v->doc()->lines()-1 ) {
00279 val = v->doc()->lines()-1;
00280 }
00281
00282 v->setCursorPosition( KTextEditor::Cursor( val, 0 ) );
00283 return true;
00284 }
00285 return true;
00286 }
00287
00288
00289 else if ( cmd == "set-icon-border" ||
00290 cmd == "set-folding-markers" ||
00291 cmd == "set-line-numbers" ||
00292 cmd == "set-replace-tabs" ||
00293 cmd == "set-remove-trailing-space" ||
00294 cmd == "set-show-tabs" ||
00295 cmd == "set-word-wrap" ||
00296 cmd == "set-wrap-cursor" ||
00297 cmd == "set-replace-tabs-save" ||
00298 cmd == "set-remove-trailing-space-save" ||
00299 cmd == "set-show-indent" )
00300 {
00301 if ( ! args.count() )
00302 KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false", cmd ) );
00303 bool enable = false;
00304 if ( getBoolArg( args.first(), &enable ) )
00305 {
00306 if ( cmd == "set-icon-border" )
00307 v->setIconBorder( enable );
00308 else if (cmd == "set-folding-markers")
00309 v->setFoldingMarkersOn( enable );
00310 else if ( cmd == "set-line-numbers" )
00311 v->setLineNumbersOn( enable );
00312 else if ( cmd == "set-show-indent" )
00313 v->renderer()->setShowIndentLines( enable );
00314 else if ( cmd == "set-replace-tabs" )
00315 setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() );
00316 else if ( cmd == "set-remove-trailing-space" )
00317 setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() );
00318 else if ( cmd == "set-show-tabs" )
00319 setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() );
00320 else if ( cmd == "set-show-trailing-spaces" )
00321 setDocFlag( KateDocumentConfig::cfShowSpaces, enable, v->doc() );
00322 else if ( cmd == "set-word-wrap" )
00323 v->doc()->setWordWrap( enable );
00324 else if ( cmd == "set-remove-trailing-space-save" )
00325 setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() );
00326 else if ( cmd == "set-wrap-cursor" )
00327 setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() );
00328
00329 return true;
00330 }
00331 else
00332 KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false",
00333 args.first() , cmd ) );
00334 }
00335
00336
00337 KCC_ERR( i18n("Unknown command '%1'", cmd) );
00338 }
00339
00340 bool KateCommands::CoreCommands::supportsRange(const QString &range)
00341 {
00342 static QStringList l;
00343
00344 if (l.isEmpty())
00345 l << "indent" << "unindent" << "cleanindent"
00346 << "comment" << "uncomment" << "kill-line";
00347
00348 return l.contains(range);
00349 }
00350
00351 KCompletion *KateCommands::CoreCommands::completionObject( KTextEditor::View *view, const QString &cmd )
00352 {
00353 Q_UNUSED(view);
00354
00355 if ( cmd == "set-highlight" )
00356 {
00357 QStringList l;
00358 for ( int i = 0; i < KateHlManager::self()->highlights(); i++ )
00359 l << KateHlManager::self()->hlName (i);
00360
00361 KateCmdShellCompletion *co = new KateCmdShellCompletion();
00362 co->setItems( l );
00363 co->setIgnoreCase( true );
00364 return co;
00365 }
00366 return 0L;
00367 }
00368
00369
00370
00371 static void replace(QString &s, const QString &needle, const QString &with)
00372 {
00373 int pos=0;
00374 while (1)
00375 {
00376 pos=s.indexOf(needle, pos);
00377 if (pos==-1) break;
00378 s.replace(pos, needle.length(), with);
00379 pos+=with.length();
00380 }
00381
00382 }
00383
00384 static int backslashString(const QString &haystack, const QString &needle, int index)
00385 {
00386 int len=haystack.length();
00387 int searchlen=needle.length();
00388 bool evenCount=true;
00389 while (index<len)
00390 {
00391 if (haystack[index]=='\\')
00392 {
00393 evenCount=!evenCount;
00394 }
00395 else
00396 {
00397 if (!evenCount)
00398 {
00399 if (haystack.mid(index, searchlen)==needle)
00400 return index-1;
00401 }
00402 evenCount=true;
00403 }
00404 index++;
00405
00406 }
00407
00408 return -1;
00409 }
00410
00411
00412 static void exchangeAbbrevs(QString &str)
00413 {
00414
00415 const char *magic="a\x07t\tn\n";
00416
00417 while (*magic)
00418 {
00419 int index=0;
00420 char replace=magic[1];
00421 while ((index=backslashString(str, QString (QChar::fromAscii(*magic)), index))!=-1)
00422 {
00423 str.replace(index, 2, QChar(replace));
00424 index++;
00425 }
00426 magic++;
00427 magic++;
00428 }
00429 }
00430
00431 int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line,
00432 const QString &find, const QString &repOld, const QString &delim,
00433 bool noCase, bool repeat,
00434 uint startcol, int endcol )
00435 {
00436 KateTextLine::Ptr ln = doc->kateTextLine( line );
00437 if ( ! ln || ! ln->length() ) return 0;
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449 QStringList patterns(find.split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), QString::KeepEmptyParts));
00450 if ( patterns.count() > 1 )
00451 {
00452 for ( int i = 0; i < patterns.count(); i++ )
00453 {
00454 if ( i < patterns.count() - 1 )
00455 patterns[i].append("$");
00456 if ( i )
00457 patterns[i].prepend("^");
00458
00459 kDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i];
00460 }
00461 }
00462
00463 QRegExp matcher(patterns[0], noCase ?Qt::CaseSensitive:Qt::CaseInsensitive);
00464
00465 uint len;
00466 int matches = 0;
00467
00468 while ( ln->searchText( startcol, matcher, &startcol, &len ) )
00469 {
00470
00471 if ( endcol >= 0 && startcol + len > (uint)endcol )
00472 break;
00473
00474 matches++;
00475
00476
00477 QString rep=repOld;
00478
00479
00480 const QStringList backrefs=matcher.capturedTexts();
00481 int refnum=1;
00482
00483 QStringList::ConstIterator i = backrefs.begin();
00484 ++i;
00485
00486 for (; i!=backrefs.end(); ++i)
00487 {
00488
00489 QString number=QString::number(refnum);
00490
00491 int index=0;
00492 while (index!=-1)
00493 {
00494 index=backslashString(rep, number, index);
00495 if (index>=0)
00496 {
00497 rep.replace(index, 2, *i);
00498 index+=(*i).length();
00499 }
00500 }
00501
00502 refnum++;
00503 }
00504
00505 replace(rep, "\\\\", "\\");
00506 replace(rep, "\\" + delim, delim);
00507
00508 doc->removeText( KTextEditor::Range (line, startcol, line, startcol + len) );
00509 doc->insertText( KTextEditor::Cursor (line, startcol), rep );
00510
00511
00512
00513
00514 int lns = rep.count(QChar::fromLatin1('\n'));
00515 if ( lns > 0 )
00516 {
00517 line += lns;
00518
00519 if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) )
00520 {
00521
00522 endcol -= (startcol + len);
00523 uint sc = rep.length() - rep.lastIndexOf('\n') - 1;
00524 matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol );
00525 }
00526 }
00527
00528 if (!repeat) break;
00529 startcol+=rep.length();
00530
00531
00532 uint ll = ln->length();
00533 if ( ! ll || startcol > ll )
00534 break;
00535 }
00536
00537 return matches;
00538 }
00539
00540 bool KateCommands::SedReplace::exec (KTextEditor::View *view, const QString &cmd, QString &msg)
00541 {
00542 return exec(view, cmd, msg, KTextEditor::Range::invalid());
00543 }
00544
00545 bool KateCommands::SedReplace::exec (class KTextEditor::View *view, const QString &cmd,
00546 QString &msg, const KTextEditor::Range &r)
00547 {
00548 kDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )";
00549 if (r.isValid())
00550 kDebug(13025)<<"Range: " << r;
00551
00552 QRegExp delim("^s\\s*([^\\w\\s])");
00553 if ( delim.indexIn( cmd ) < 0 ) return false;
00554
00555 bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i';
00556 bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g';
00557
00558 QString d = delim.cap(1);
00559 kDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'";
00560
00561 QRegExp splitter( QString("^s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\"
00562 + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)(\\" + d + "[ig]{0,2})?$" );
00563 if (splitter.indexIn(cmd)<0) return false;
00564
00565 QString find=splitter.cap(1);
00566 kDebug(13025)<< "SedReplace: find=" << find;
00567
00568 QString replace=splitter.cap(2);
00569 exchangeAbbrevs(replace);
00570 kDebug(13025)<< "SedReplace: replace=" << replace;
00571
00572 if ( find.contains("\\n") )
00573 {
00574
00575 msg = i18n("Sorry, but Kate is not able to replace newlines, yet");
00576 return false;
00577 }
00578
00579 KateDocument *doc = ((KateView*)view)->doc();
00580 if ( ! doc ) return false;
00581
00582 doc->editStart();
00583
00584 int replacementsDone = 0;
00585 int linesTouched = 0;
00586
00587 if (r.isValid()) {
00588 for (int line = r.start().line(); line <= r.end().line(); line++) {
00589 int temp = replacementsDone;
00590 replacementsDone += sedMagic( doc, line, find, replace, d, !noCase, repeat );
00591 if (replacementsDone > temp) {
00592 linesTouched++;
00593 }
00594 }
00595 } else {
00596 int line= view->cursorPosition().line();
00597 replacementsDone += sedMagic(doc, line, find, replace, d, !noCase, repeat);
00598 if (replacementsDone > 0) {
00599 linesTouched = 1;
00600 }
00601 }
00602
00603 msg = i18ncp("%2 is the translation of the next message",
00604 "1 replacement done on %2", "%1 replacements done on %2", replacementsDone,
00605 i18ncp("substituted into the previous message",
00606 "1 line", "%1 lines", linesTouched));
00607
00608 doc->editEnd();
00609
00610 return true;
00611 }
00612
00613
00614
00615
00616 bool KateCommands::Character::exec (KTextEditor::View *view, const QString &_cmd, QString &)
00617 {
00618 QString cmd = _cmd;
00619
00620
00621 QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$");
00622 if (num.indexIn(cmd)==-1) return false;
00623
00624 cmd=num.cap(1);
00625
00626
00627
00628 unsigned short int number=0;
00629 int base=10;
00630 if (cmd[0]=='x' || cmd.startsWith(QLatin1String("0x")))
00631 {
00632 cmd.remove(QRegExp("^0?x"));
00633 base=16;
00634 }
00635 else if (cmd[0]=='0')
00636 base=8;
00637 bool ok;
00638 number=cmd.toUShort(&ok, base);
00639 if (!ok || number==0) return false;
00640 if (number<=255)
00641 {
00642 char buf[2];
00643 buf[0]=(char)number;
00644 buf[1]=0;
00645
00646 view->document()->insertText(view->cursorPosition(), QString(buf));
00647 }
00648 else
00649 {
00650 QChar c(number);
00651
00652 view->document()->insertText(view->cursorPosition(), QString(&c, 1));
00653 }
00654
00655 return true;
00656 }
00657
00658
00659
00660
00661 bool KateCommands::Date::exec (KTextEditor::View *view, const QString &cmd, QString &)
00662 {
00663 if (!cmd.startsWith(QLatin1String("date")))
00664 return false;
00665
00666 if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0)
00667 view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)));
00668 else
00669 view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
00670
00671 return true;
00672 }
00673
00674
00675
00676