Kate
katecmds.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2003-2005 Anders Lund <anders@alweb.dk> 00004 * Copyright (C) 2001-2010 Christoph Cullmann <cullmann@kde.org> 00005 * Copyright (C) 2001 Charles Samuels <charles@kde.org> 00006 * 00007 * This library is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Library General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2 of the License, or (at your option) any later version. 00011 * 00012 * This library is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Library General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Library General Public License 00018 * along with this library; see the file COPYING.LIB. If not, write to 00019 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 * Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "katecmds.h" 00024 00025 #include "katedocument.h" 00026 #include "kateview.h" 00027 #include "kateconfig.h" 00028 #include "kateautoindent.h" 00029 #include "katetextline.h" 00030 #include "katesyntaxmanager.h" 00031 #include "kateglobal.h" 00032 #include "kateviglobal.h" 00033 #include "katerenderer.h" 00034 #include "katecmd.h" 00035 00036 #include <kdebug.h> 00037 #include <klocale.h> 00038 #include <kurl.h> 00039 #include <kshellcompletion.h> 00040 00041 #include <QtCore/QRegExp> 00042 00043 //BEGIN CoreCommands 00044 // this returns wheather the string s could be converted to 00045 // a bool value, one of on|off|1|0|true|false. the argument val is 00046 // set to the extracted value in case of success 00047 static bool getBoolArg( const QString &t, bool *val ) 00048 { 00049 bool res( false ); 00050 QString s = t.toLower(); 00051 res = (s == "on" || s == "1" || s == "true"); 00052 if ( res ) 00053 { 00054 *val = true; 00055 return true; 00056 } 00057 res = (s == "off" || s == "0" || s == "false"); 00058 if ( res ) 00059 { 00060 *val = false; 00061 return true; 00062 } 00063 return false; 00064 } 00065 00066 const QStringList &KateCommands::CoreCommands::cmds() 00067 { 00068 static QStringList l; 00069 00070 if (l.isEmpty()) 00071 l << "indent" << "unindent" << "cleanindent" 00072 << "comment" << "uncomment" << "goto" << "kill-line" 00073 << "set-tab-width" << "set-replace-tabs" << "set-show-tabs" 00074 << "set-remove-trailing-space" 00075 << "set-indent-width" 00076 << "set-indent-mode" << "set-auto-indent" 00077 << "set-line-numbers" << "set-folding-markers" << "set-icon-border" 00078 << "set-wrap-cursor" 00079 << "set-word-wrap" << "set-word-wrap-column" 00080 << "set-replace-tabs-save" << "set-remove-trailing-space-save" 00081 << "set-highlight" << "set-mode" << "set-show-indent" 00082 << "print"; 00083 00084 return l; 00085 } 00086 00087 bool KateCommands::CoreCommands::exec(KTextEditor::View *view, 00088 const QString &_cmd, 00089 QString &errorMsg) 00090 { 00091 return exec( view, _cmd, errorMsg, KTextEditor::Range::invalid() ); 00092 } 00093 00094 bool KateCommands::CoreCommands::exec(KTextEditor::View *view, 00095 const QString &_cmd, 00096 QString &errorMsg, 00097 const KTextEditor::Range& range) 00098 { 00099 #define KCC_ERR(s) { errorMsg=s; return false; } 00100 // cast it hardcore, we know that it is really a kateview :) 00101 KateView *v = (KateView*) view; 00102 00103 if ( ! v ) 00104 KCC_ERR( i18n("Could not access view") ); 00105 00106 //create a list of args 00107 QStringList args(_cmd.split( QRegExp("\\s+"), QString::SkipEmptyParts)) ; 00108 QString cmd ( args.takeFirst() ); 00109 00110 // ALL commands that takes no arguments. 00111 if ( cmd == "indent" ) 00112 { 00113 if ( range.isValid() ) { 00114 v->doc()->editStart(); 00115 for ( int line = range.start().line(); line <= range.end().line(); line++ ) { 00116 v->doc()->indent( KTextEditor::Range(line, 0, line, 0), 1 ); 00117 } 00118 v->doc()->editEnd(); 00119 } else { 00120 v->indent(); 00121 } 00122 return true; 00123 } 00124 else if ( cmd == "unindent" ) 00125 { 00126 if ( range.isValid() ) { 00127 v->doc()->editStart(); 00128 for ( int line = range.start().line(); line <= range.end().line(); line++ ) { 00129 v->doc()->indent( KTextEditor::Range(line, 0, line, 0), -1 ); 00130 } 00131 v->doc()->editEnd(); 00132 } else { 00133 v->unIndent(); 00134 } 00135 return true; 00136 } 00137 else if ( cmd == "cleanindent" ) 00138 { 00139 if ( range.isValid() ) { 00140 v->doc()->editStart(); 00141 for ( int line = range.start().line(); line <= range.end().line(); line++ ) { 00142 v->doc()->indent( KTextEditor::Range(line, 0, line, 0), 0 ); 00143 } 00144 v->doc()->editEnd(); 00145 } else { 00146 v->cleanIndent(); 00147 } 00148 return true; 00149 } 00150 else if ( cmd == "comment" ) 00151 { 00152 if ( range.isValid() ) { 00153 v->doc()->editStart(); 00154 for ( int line = range.start().line(); line <= range.end().line(); line++ ) { 00155 v->doc()->comment( v, line, 0, 1 ); 00156 } 00157 v->doc()->editEnd(); 00158 } else { 00159 v->comment(); 00160 } 00161 return true; 00162 } 00163 else if ( cmd == "uncomment" ) 00164 { 00165 if ( range.isValid() ) { 00166 v->doc()->editStart(); 00167 for ( int line = range.start().line(); line <= range.end().line(); line++ ) { 00168 v->doc()->comment( v, line, 0, -1 ); 00169 } 00170 v->doc()->editEnd(); 00171 } else { 00172 v->uncomment(); 00173 } 00174 return true; 00175 } 00176 else if ( cmd == "kill-line" ) 00177 { 00178 if ( range.isValid() ) { 00179 v->doc()->editStart(); 00180 for ( int line = range.start().line(); line <= range.end().line(); line++ ) { 00181 v->doc()->removeLine( range.start().line() ); 00182 } 00183 v->doc()->editEnd(); 00184 } else { 00185 v->killLine(); 00186 } 00187 return true; 00188 } 00189 else if ( cmd == "print" ) 00190 { 00191 v->doc()->printDialog(); 00192 return true; 00193 } 00194 00195 // ALL commands that take a string argument 00196 else if ( cmd == "set-indent-mode" || 00197 cmd == "set-highlight" || 00198 cmd == "set-mode" ) 00199 { 00200 // need at least one item, otherwise args.first() crashes 00201 if ( ! args.count() ) 00202 KCC_ERR( i18n("Missing argument. Usage: %1 <value>", cmd ) ); 00203 00204 if ( cmd == "set-indent-mode" ) 00205 { 00206 v->doc()->config()->setIndentationMode( args.first() ); 00207 return true; 00208 } 00209 else if ( cmd == "set-highlight" ) 00210 { 00211 if ( v->doc()->setHighlightingMode( args.first()) ) 00212 { 00213 ((KateDocument*)v->doc())->setDontChangeHlOnSave (); 00214 return true; 00215 } 00216 00217 KCC_ERR( i18n("No such highlighting '%1'", args.first() ) ); 00218 } 00219 else if ( cmd == "set-mode" ) 00220 { 00221 if ( v->doc()->setMode( args.first()) ) 00222 return true; 00223 00224 KCC_ERR( i18n("No such mode '%1'", args.first() ) ); 00225 } 00226 } 00227 // ALL commands that takes exactly one integer argument. 00228 else if ( cmd == "set-tab-width" || 00229 cmd == "set-indent-width" || 00230 cmd == "set-word-wrap-column" || 00231 cmd == "goto" ) 00232 { 00233 // find a integer value > 0 00234 if ( ! args.count() ) 00235 KCC_ERR( i18n("Missing argument. Usage: %1 <value>", cmd ) ); 00236 bool ok; 00237 int val ( args.first().toInt( &ok, 10 ) ); // use base 10 even if the string starts with '0' 00238 if ( !ok ) 00239 KCC_ERR( i18n("Failed to convert argument '%1' to integer.", 00240 args.first() ) ); 00241 00242 if ( cmd == "set-tab-width" ) 00243 { 00244 if ( val < 1 ) 00245 KCC_ERR( i18n("Width must be at least 1.") ); 00246 v->doc()->config()->setTabWidth( val ); 00247 } 00248 else if ( cmd == "set-indent-width" ) 00249 { 00250 if ( val < 1 ) 00251 KCC_ERR( i18n("Width must be at least 1.") ); 00252 v->doc()->config()->setIndentationWidth( val ); 00253 } 00254 else if ( cmd == "set-word-wrap-column" ) 00255 { 00256 if ( val < 2 ) 00257 KCC_ERR( i18n("Column must be at least 1.") ); 00258 v->doc()->setWordWrapAt( val ); 00259 } 00260 else if ( cmd == "goto" ) 00261 { 00262 if ( args.first().at(0) == '-' || args.first().at(0) == '+' ) { 00263 // if the number starts with a minus or plus sign, add/subract the number 00264 val = v->cursorPosition().line() + val; 00265 } else { 00266 val--; // convert given line number to the internal representation of line numbers 00267 } 00268 00269 // constrain cursor to the range [0, number of lines] 00270 if ( val < 0 ) { 00271 val = 0; 00272 } else if ( val > v->doc()->lines()-1 ) { 00273 val = v->doc()->lines()-1; 00274 } 00275 00276 v->setCursorPosition( KTextEditor::Cursor( val, 0 ) ); 00277 return true; 00278 } 00279 return true; 00280 } 00281 00282 // ALL commands that takes 1 boolean argument. 00283 else if ( cmd == "set-icon-border" || 00284 cmd == "set-folding-markers" || 00285 cmd == "set-line-numbers" || 00286 cmd == "set-replace-tabs" || 00287 cmd == "set-remove-trailing-space" || 00288 cmd == "set-show-tabs" || 00289 cmd == "set-word-wrap" || 00290 cmd == "set-wrap-cursor" || 00291 cmd == "set-replace-tabs-save" || 00292 cmd == "set-remove-trailing-space-save" || 00293 cmd == "set-show-indent" ) 00294 { 00295 if ( ! args.count() ) 00296 KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false", cmd ) ); 00297 bool enable = false; 00298 KateDocumentConfig * const config = v->doc()->config(); 00299 if ( getBoolArg( args.first(), &enable ) ) 00300 { 00301 if ( cmd == "set-icon-border" ) 00302 v->setIconBorder( enable ); 00303 else if (cmd == "set-folding-markers") 00304 v->setFoldingMarkersOn( enable ); 00305 else if ( cmd == "set-line-numbers" ) 00306 v->setLineNumbersOn( enable ); 00307 else if ( cmd == "set-show-indent" ) 00308 v->renderer()->setShowIndentLines( enable ); 00309 else if ( cmd == "set-replace-tabs" ) 00310 config->setReplaceTabsDyn( enable ); 00311 else if ( cmd == "set-remove-trailing-space" ) 00312 config->setRemoveTrailingDyn( enable ); 00313 else if ( cmd == "set-show-tabs" ) 00314 config->setShowTabs( enable ); 00315 else if ( cmd == "set-show-trailing-spaces" ) 00316 config->setShowSpaces( enable ); 00317 else if ( cmd == "set-word-wrap" ) 00318 v->doc()->setWordWrap( enable ); 00319 else if ( cmd == "set-remove-trailing-space-save" ) 00320 config->setRemoveSpaces( enable ); 00321 else if ( cmd == "set-wrap-cursor" ) 00322 config->setWrapCursor( enable ); 00323 00324 return true; 00325 } 00326 else 00327 KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false", 00328 args.first() , cmd ) ); 00329 } 00330 00331 // unlikely.. 00332 KCC_ERR( i18n("Unknown command '%1'", cmd) ); 00333 } 00334 00335 bool KateCommands::CoreCommands::supportsRange(const QString &range) 00336 { 00337 static QStringList l; 00338 00339 if (l.isEmpty()) 00340 l << "indent" << "unindent" << "cleanindent" 00341 << "comment" << "uncomment" << "kill-line"; 00342 00343 return l.contains(range); 00344 } 00345 00346 KCompletion *KateCommands::CoreCommands::completionObject( KTextEditor::View *view, const QString &cmd ) 00347 { 00348 Q_UNUSED(view) 00349 00350 if ( cmd == "set-highlight" ) 00351 { 00352 QStringList l; 00353 for ( int i = 0; i < KateHlManager::self()->highlights(); i++ ) 00354 l << KateHlManager::self()->hlName (i); 00355 00356 KateCmdShellCompletion *co = new KateCmdShellCompletion(); 00357 co->setItems( l ); 00358 co->setIgnoreCase( true ); 00359 return co; 00360 } 00361 return 0L; 00362 } 00363 //END CoreCommands 00364 00365 // BEGIN ViCommands 00366 const QStringList &KateCommands::ViCommands::cmds() 00367 { 00368 static QStringList l; 00369 00370 if (l.isEmpty()) 00371 l << "nnoremap" << "nn"; 00372 00373 return l; 00374 } 00375 00376 bool KateCommands::ViCommands::exec(KTextEditor::View *view, 00377 const QString &_cmd, 00378 QString &msg) 00379 { 00380 return exec( view, _cmd, msg, KTextEditor::Range::invalid() ); 00381 } 00382 00383 bool KateCommands::ViCommands::exec(KTextEditor::View *view, 00384 const QString &_cmd, 00385 QString &msg, 00386 const KTextEditor::Range& range) 00387 { 00388 Q_UNUSED(range) 00389 // cast it hardcore, we know that it is really a kateview :) 00390 KateView *v = (KateView*) view; 00391 00392 if ( !v ) { 00393 msg = i18n("Could not access view"); 00394 return false; 00395 } 00396 00397 //create a list of args 00398 QStringList args(_cmd.split( QRegExp("\\s+"), QString::SkipEmptyParts)) ; 00399 QString cmd ( args.takeFirst() ); 00400 00401 // ALL commands that takes no arguments. 00402 if ( cmd == "nnoremap" || cmd == "nn" ) 00403 { 00404 if ( args.count() == 1 ) { 00405 msg = KateGlobal::self()->viInputModeGlobal()->getMapping( NormalMode, args.at( 0 ), true ); 00406 if ( msg.isEmpty() ) { 00407 msg = i18n( "No mapping found for \"%1\"", args.at(0) ); 00408 return false; 00409 } else { 00410 msg = i18n( "\"%1\" is mapped to \"%2\"", args.at( 0 ), msg ); 00411 } 00412 } else if ( args.count() == 2 ) { 00413 KateGlobal::self()->viInputModeGlobal()->addMapping( NormalMode, args.at( 0 ), args.at( 1 ) ); 00414 } else { 00415 msg = i18n("Missing argument(s). Usage: %1 <from> [<to>]", cmd ); 00416 return false; 00417 } 00418 00419 return true; 00420 } 00421 00422 // should not happen :) 00423 msg = i18n("Unknown command '%1'", cmd); 00424 return false; 00425 } 00426 00427 bool KateCommands::ViCommands::supportsRange(const QString &range) 00428 { 00429 Q_UNUSED(range) 00430 return false; // no commands support a range yet 00431 } 00432 00433 KCompletion *KateCommands::ViCommands::completionObject( KTextEditor::View *view, const QString &cmd ) 00434 { 00435 Q_UNUSED(view) 00436 00437 KateView *v = (KateView*) view; 00438 00439 if ( v && ( cmd == "nn" || cmd == "nnoremap" ) ) 00440 { 00441 QStringList l = KateGlobal::self()->viInputModeGlobal()->getMappings( NormalMode ); 00442 00443 KateCmdShellCompletion *co = new KateCmdShellCompletion(); 00444 co->setItems( l ); 00445 co->setIgnoreCase( false ); 00446 return co; 00447 } 00448 return 0L; 00449 } 00450 //END ViCommands 00451 00452 // BEGIN AppCommands 00453 KateCommands::AppCommands::AppCommands() 00454 : KTextEditor::Command() 00455 { 00456 re_write.setPattern("w"); // temporarily add :w 00457 //re_write.setPattern("w(a)?"); 00458 //re_quit.setPattern("(w)?q?(a)?"); 00459 //re_exit.setPattern("x(a)?"); 00460 //re_changeBuffer.setPattern("b(n|p)"); 00461 //re_edit.setPattern("e(dit)?"); 00462 //re_new.setPattern("(v)?new"); 00463 } 00464 00465 const QStringList& KateCommands::AppCommands::cmds() 00466 { 00467 static QStringList l; 00468 00469 if (l.empty()) { 00470 //l << "q" << "qa" << "w" << "wq" << "wa" << "wqa" << "x" << "xa" 00471 //<< "bn" << "bp" << "new" << "vnew" << "e" << "edit" << "enew"; 00472 l << "w"; 00473 } 00474 00475 return l; 00476 } 00477 00478 bool KateCommands::AppCommands::exec(KTextEditor::View *view, 00479 const QString &cmd, QString &msg ) 00480 { 00481 QStringList args(cmd.split( QRegExp("\\s+"), QString::SkipEmptyParts)) ; 00482 QString command( args.takeFirst() ); 00483 00484 if (re_write.exactMatch(command)) { // w, wa 00485 /* if (!re_write.cap(1).isEmpty()) { // [a]ll 00486 view->document()->saveAll(); 00487 msg = i18n("All documents written to disk"); 00488 } else { // w*/ 00489 // Save file 00490 view->document()->documentSave(); 00491 msg = i18n("Document written to disk"); 00492 //} 00493 } 00494 /*else if (re_quit.exactMatch(command)) { // q qa wq wqa 00495 if (!re_quit.cap(2).isEmpty()) { // a[ll] qa wqa 00496 if (!re_quit.cap(1).isEmpty()) { // [w]rite wqa 00497 view->document()->saveAll(); 00498 } 00499 view->document()->closeAll(); 00500 } else { // q wq 00501 if (!re_quit.cap(1).isEmpty() && view->document()->isModified()) { // [w]rite wq 00502 view->document()->documentSave(); 00503 } 00504 view->document()->closeDocument(); 00505 } 00506 } else if (re_exit.exactMatch(command)) { // x xa 00507 if (!re_exit.cap(1).isEmpty()) { // a[ll] xa 00508 view->document()->saveAll(); 00509 view->document()->closeAll(); 00510 } else { // x 00511 if (view->document()->isModified()) { 00512 view->document()->documentSave(); 00513 } 00514 view->document()->closeDocument(); 00515 } 00516 } 00517 else if (re_changeBuffer.exactMatch(command)) { 00518 if (re_changeBuffer.cap(1) == "n") { // next document 00519 view->document()->switchToNextDocument(); 00520 } 00521 else { // previous document 00522 view->document()->switchToPreviousDocument(); 00523 } 00524 } 00525 else if (re_edit.exactMatch(command)) { 00526 view->document()->documentReload(); 00527 } 00528 else if (re_new.exactMatch(command)) { 00529 if (re_new.cap(1) == "v") { // vertical split 00530 view->document()->splitViewSpaceVert(); 00531 } else { // horizontal split 00532 view->document()->splitViewSpaceHoriz(); 00533 } 00534 view->document()->newDocument(); 00535 } 00536 else if (command == "enew") { 00537 view->document()->newDocument(); 00538 }*/ 00539 return true; 00540 } 00541 00542 bool KateCommands::AppCommands::help(KTextEditor::View *view, const QString &cmd, QString &msg) 00543 { 00544 Q_UNUSED(view); 00545 00546 if (re_write.exactMatch(cmd)) { 00547 msg = "<p><b>w/wa — write document(s) to disk</b></p>" 00548 "<p>Usage: <tt><b>w[a]</b></tt></p>" 00549 "<p>Writes the current document(s) to disk. " 00550 "It can be called in two ways:<br />" 00551 " <tt>w</tt> — writes the current document to disk<br />" 00552 " <tt>wa</tt> — writes all document to disk.</p>" 00553 "<p>If no file name is associated with the document, " 00554 "a file dialog will be shown.</p>"; 00555 return true; 00556 } 00557 /*else if (re_quit.exactMatch(cmd)) { 00558 msg = "<p><b>q/qa/wq/wqa — [write and] quit</b></p>" 00559 "<p>Usage: <tt><b>[w]q[a]</b></tt></p>" 00560 "<p>Quits the application. If <tt>w</tt> is prepended, it also writes" 00561 " the document(s) to disk. This command " 00562 "can be called in several ways:<br />" 00563 " <tt>q</tt> — closes the current view..<br />" 00564 " <tt>qa</tt> — closes all view, effectively quitting the application.<br />" 00565 " <tt>wq</tt> — writes the current document to disk and closes its view.<br />" 00566 " <tt>wqa</tt> — writes all document to disk and quits.</p>" 00567 "<p>In all cases, if the view being closed is the last view, the application quits. " 00568 "If no file name is associated with the document and it should be written to disk, " 00569 "a file dialog will be shown.</p>"; 00570 return true; 00571 } 00572 else if (re_exit.exactMatch(cmd)) { 00573 msg = "<p><b>x/xa — write and quit</b></p>" 00574 "<p>Usage: <tt><b>x[a]</b></tt></p>" 00575 "<p>Saves document(s) and quits (e<b>x</b>its). This command " 00576 "can be called in two ways:<br />" 00577 " <tt>x</tt> — closes the current view..<br />" 00578 " <tt>xa</tt> — closes all view, effectively quitting the application.</p>" 00579 "<p>In all cases, if the view being closed is the last view, the application quits. " 00580 "If no file name is associated with the document and it should be written to disk, " 00581 "a file dialog will be shown.</p>" 00582 "<p>Unlike the 'w' commands, this command only writes the doucment if it is modified." 00583 "</p>"; 00584 return true; 00585 } 00586 else if (re_changeBuffer.exactMatch(cmd)) { 00587 msg = "<p><b>bp/bn — switch no previous/next document</b></p>" 00588 "<p>Usage: <tt><b>bp/bn</b></tt></p>" 00589 "<p>Goes to <b>p</b>revious or <b>n</b>ext document (\"<b>b</b>uffer\"). The two" 00590 " commands are:<br />" 00591 " <tt>bp</tt> — goes to the document before the current one in the document" 00592 " list.<br />" 00593 " <tt>bn</tt> — goes to the document after the current one in the document" 00594 " list.<br />" 00595 "<p>Both commands wrap around, i.e., if you go past the last document you and up" 00596 " at the first and vice versa.</p>"; 00597 return true; 00598 } 00599 else if (re_new.exactMatch(cmd)) { 00600 msg = "<p><b>[v]new — split view and create new document</b></p>" 00601 "<p>Usage: <tt><b>[v]new</b></tt></p>" 00602 "<p>Splits the current view and opens a new document in the new view." 00603 " This command can be called in two ways:<br />" 00604 " <tt>new</tt> — splits the view horizontally and opens a new document.<br />" 00605 " <tt>vnew</tt> — splits the view vertically and opens a new document.<br />" 00606 "</p>"; 00607 return true; 00608 } 00609 else if (re_edit.exactMatch(cmd)) { 00610 msg = "<p><b>e[dit] — reload current document</b></p>" 00611 "<p>Usage: <tt><b>e[dit]</b></tt></p>" 00612 "<p>Starts <b>e</b>diting the current document again. This is useful to re-edit" 00613 " the current file, when it has been changed by another program.</p>"; 00614 return true; 00615 }*/ 00616 00617 return false; 00618 } 00619 //END AppCommands 00620 00621 //BEGIN SedReplace 00622 static void replace(QString &s, const QString &needle, const QString &with) 00623 { 00624 int pos=0; 00625 while (1) 00626 { 00627 pos=s.indexOf(needle, pos); 00628 if (pos==-1) break; 00629 s.replace(pos, needle.length(), with); 00630 pos+=with.length(); 00631 } 00632 00633 } 00634 00635 static int backslashString(const QString &haystack, const QString &needle, int index) 00636 { 00637 int len=haystack.length(); 00638 int searchlen=needle.length(); 00639 bool evenCount=true; 00640 while (index<len) 00641 { 00642 if (haystack[index]=='\\') 00643 { 00644 evenCount=!evenCount; 00645 } 00646 else 00647 { // isn't a slash 00648 if (!evenCount) 00649 { 00650 if (haystack.mid(index, searchlen)==needle) 00651 return index-1; 00652 } 00653 evenCount=true; 00654 } 00655 index++; 00656 00657 } 00658 00659 return -1; 00660 } 00661 00662 // exchange "\t" for the actual tab character, for example 00663 static void exchangeAbbrevs(QString &str) 00664 { 00665 // the format is (findreplace)*[nullzero] 00666 const char *magic="a\x07t\tn\n"; 00667 00668 while (*magic) 00669 { 00670 int index=0; 00671 char replace=magic[1]; 00672 while ((index=backslashString(str, QString (QChar::fromAscii(*magic)), index))!=-1) 00673 { 00674 str.replace(index, 2, QChar(replace)); 00675 index++; 00676 } 00677 magic++; 00678 magic++; 00679 } 00680 } 00681 00682 int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line, 00683 const QString &find, const QString &repOld, const QString &delim, 00684 bool noCase, bool repeat, 00685 int startcol, int endcol ) 00686 { 00687 Kate::TextLine ln = doc->kateTextLine( line ); 00688 if ( ! ln || ! ln->length() ) return 0; 00689 00690 // HANDLING "\n"s in PATTERN 00691 // * Create a list of patterns, splitting PATTERN on (unescaped) "\n" 00692 // * insert $s and ^s to match line ends/beginnings 00693 // * When matching patterhs after the first one, replace \N with the captured 00694 // text. 00695 // * If all patterns in the list match sequentiel lines, there is a match, so 00696 // * remove line/start to line + patterns.count()-1/patterns.last.length 00697 // * handle capatures by putting them in one list. 00698 // * the existing insertion is fine, including the line calculation. 00699 00700 QStringList patterns(find.split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), QString::KeepEmptyParts)); 00701 if ( patterns.count() > 1 ) 00702 { 00703 for ( int i = 0; i < patterns.count(); i++ ) 00704 { 00705 if ( i < patterns.count() - 1 ) 00706 patterns[i].append("$"); 00707 if ( i ) 00708 patterns[i].prepend("^"); 00709 00710 kDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i]; 00711 } 00712 } 00713 00714 QRegExp matcher(patterns[0], noCase ?Qt::CaseSensitive:Qt::CaseInsensitive); 00715 00716 int matches = 0; 00717 00718 while ( (startcol = matcher.indexIn(ln->string(), startcol)) >= 0 ) 00719 { 00720 const int len = matcher.matchedLength(); 00721 00722 if ( endcol >= 0 && startcol + len > endcol ) 00723 break; 00724 00725 matches++; 00726 00727 00728 QString rep=repOld; 00729 00730 // now set the backreferences in the replacement 00731 const QStringList backrefs=matcher.capturedTexts(); 00732 int refnum=1; 00733 00734 QStringList::ConstIterator i = backrefs.begin(); 00735 ++i; 00736 00737 for (; i!=backrefs.end(); ++i) 00738 { 00739 // I need to match "\\" or "", but not "\" 00740 QString number=QString::number(refnum); 00741 00742 int index=0; 00743 while (index!=-1) 00744 { 00745 index=backslashString(rep, number, index); 00746 if (index>=0) 00747 { 00748 rep.replace(index, 2, *i); 00749 index+=(*i).length(); 00750 } 00751 } 00752 00753 refnum++; 00754 } 00755 00756 replace(rep, "\\\\", "\\"); 00757 replace(rep, "\\" + delim, delim); 00758 00759 doc->removeText( KTextEditor::Range (line, startcol, line, startcol + len) ); 00760 doc->insertText( KTextEditor::Cursor (line, startcol), rep ); 00761 00762 // TODO if replace contains \n, 00763 // change the line number and 00764 // check for text that needs be searched behind the last inserted newline. 00765 int lns = rep.count(QChar::fromLatin1('\n')); 00766 if ( lns > 0 ) 00767 { 00768 line += lns; 00769 00770 if ( doc->lineLength( line ) > 0 && ( endcol < 0 || endcol >= startcol + len ) ) 00771 { 00772 // if ( endcol >= startcol + len ) 00773 endcol -= (startcol + len); 00774 uint sc = rep.length() - rep.lastIndexOf('\n') - 1; 00775 matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol ); 00776 } 00777 } 00778 00779 if (!repeat) break; 00780 startcol+=rep.length(); 00781 00782 // sanity check -- avoid infinite loops eg with %s,.*,,g ;) 00783 int ll = ln->length(); 00784 if ( ! ll || startcol > ll ) 00785 break; 00786 } 00787 00788 return matches; 00789 } 00790 00791 bool KateCommands::SedReplace::exec (KTextEditor::View *view, const QString &cmd, QString &msg) 00792 { 00793 return exec(view, cmd, msg, KTextEditor::Range::invalid()); 00794 } 00795 00796 bool KateCommands::SedReplace::exec (class KTextEditor::View *view, const QString &cmd, 00797 QString &msg, const KTextEditor::Range &r) 00798 { 00799 kDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )"; 00800 if (r.isValid()) { 00801 kDebug(13025)<<"Range: " << r; 00802 } 00803 00804 QRegExp delim("^s\\s*([^\\w\\s])"); 00805 if ( delim.indexIn( cmd ) < 0 ) return false; 00806 00807 bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i'; 00808 bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g'; 00809 00810 QString d = delim.cap(1); 00811 kDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'"; 00812 00813 QRegExp splitter( QString("^s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\" 00814 + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)(\\" + d + "[ig]{0,2})?$" ); 00815 if (splitter.indexIn(cmd)<0) return false; 00816 00817 QString find=splitter.cap(1); 00818 kDebug(13025)<< "SedReplace: find=" << find; 00819 00820 QString replace=splitter.cap(2); 00821 exchangeAbbrevs(replace); 00822 kDebug(13025)<< "SedReplace: replace=" << replace; 00823 00824 if ( find.contains("\\n") ) 00825 { 00826 // FIXME: make replacing newlines work 00827 msg = i18n("Sorry, but Kate is not able to replace newlines, yet"); 00828 return false; 00829 } 00830 00831 KateDocument *doc = ((KateView*)view)->doc(); 00832 if ( ! doc ) return false; 00833 00834 doc->editStart(); 00835 00836 int replacementsDone = 0; 00837 int linesTouched = 0; 00838 int linesAdded = 0; 00839 00840 if (r.isValid()) { // given range 00841 for (int line = r.start().line(); line <= r.end().line()+linesAdded; line++) { 00842 int temp = replacementsDone; 00843 int r = sedMagic( doc, line, find, replace, d, !noCase, repeat ); 00844 replacementsDone += r; 00845 00846 // if we replaced the text with n newlines, we have n new lines to look at 00847 if (replace.contains('\n') ) { 00848 linesAdded += r * replace.count('\n'); 00849 } 00850 00851 if (replacementsDone > temp) { 00852 linesTouched++; 00853 } 00854 } 00855 } else { // current line 00856 int line= view->cursorPosition().line(); 00857 replacementsDone += sedMagic(doc, line, find, replace, d, !noCase, repeat); 00858 if (replacementsDone > 0) { 00859 linesTouched = 1; 00860 } 00861 } 00862 00863 msg = i18ncp("%2 is the translation of the next message", 00864 "1 replacement done on %2", "%1 replacements done on %2", replacementsDone, 00865 i18ncp("substituted into the previous message", 00866 "1 line", "%1 lines", linesTouched)); 00867 00868 doc->editEnd(); 00869 00870 return true; 00871 } 00872 00873 //END SedReplace 00874 00875 //BEGIN Character 00876 bool KateCommands::Character::exec (KTextEditor::View *view, const QString &_cmd, QString &) 00877 { 00878 QString cmd = _cmd; 00879 00880 // hex, octal, base 9+1 00881 QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$"); 00882 if (num.indexIn(cmd)==-1) return false; 00883 00884 cmd=num.cap(1); 00885 00886 // identify the base 00887 00888 unsigned short int number=0; 00889 int base=10; 00890 if (cmd[0]=='x' || cmd.startsWith(QLatin1String("0x"))) 00891 { 00892 cmd.remove(QRegExp("^0?x")); 00893 base=16; 00894 } 00895 else if (cmd[0]=='0') 00896 base=8; 00897 bool ok; 00898 number=cmd.toUShort(&ok, base); 00899 if (!ok || number==0) return false; 00900 if (number<=255) 00901 { 00902 char buf[2]; 00903 buf[0]=(char)number; 00904 buf[1]=0; 00905 00906 view->document()->insertText(view->cursorPosition(), QString(buf)); 00907 } 00908 else 00909 { // do the unicode thing 00910 QChar c(number); 00911 00912 view->document()->insertText(view->cursorPosition(), QString(&c, 1)); 00913 } 00914 00915 return true; 00916 } 00917 00918 //END Character 00919 00920 //BEGIN Date 00921 bool KateCommands::Date::exec (KTextEditor::View *view, const QString &cmd, QString &) 00922 { 00923 if (!cmd.startsWith(QLatin1String("date"))) 00924 return false; 00925 00926 if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0) 00927 view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5))); 00928 else 00929 view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 00930 00931 return true; 00932 } 00933 00934 //END Date 00935 00936 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference