kate Library API Documentation

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