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