TDE core libraries
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

606 lines
17KB

  1. /* This file is part of the KDE libraries
  2. Copyright (C) 2003 - 2005 Anders Lund <anders@alweb.dk>
  3. Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org>
  4. Copyright (C) 2001 Charles Samuels <charles@kde.org>
  5. This library is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU Library General Public
  7. License version 2 as published by the Free Software Foundation.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Library General Public License for more details.
  12. You should have received a copy of the GNU Library General Public License
  13. along with this library; see the file COPYING.LIB. If not, write to
  14. the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  15. Boston, MA 02110-1301, USA.
  16. */
  17. #include "katecmds.h"
  18. #include "katedocument.h"
  19. #include "kateview.h"
  20. #include "kateconfig.h"
  21. #include "kateautoindent.h"
  22. #include "katetextline.h"
  23. #include "katefactory.h"
  24. #include "katejscript.h"
  25. #include "katerenderer.h"
  26. #include "../interfaces/katecmd.h"
  27. #include <kdebug.h>
  28. #include <tdelocale.h>
  29. #include <kurl.h>
  30. #include <kshellcompletion.h>
  31. #include <tqregexp.h>
  32. //BEGIN CoreCommands
  33. // syncs a config flag in the document with a boolean value
  34. static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable,
  35. KateDocument *doc )
  36. {
  37. doc->config()->setConfigFlags( flag, enable );
  38. }
  39. // this returns wheather the string s could be converted to
  40. // a bool value, one of on|off|1|0|true|false. the argument val is
  41. // set to the extracted value in case of success
  42. static bool getBoolArg( TQString s, bool *val )
  43. {
  44. bool res( false );
  45. s = s.lower();
  46. res = (s == "on" || s == "1" || s == "true");
  47. if ( res )
  48. {
  49. *val = true;
  50. return true;
  51. }
  52. res = (s == "off" || s == "0" || s == "false");
  53. if ( res )
  54. {
  55. *val = false;
  56. return true;
  57. }
  58. return false;
  59. }
  60. TQStringList KateCommands::CoreCommands::cmds()
  61. {
  62. TQStringList l;
  63. l << "indent" << "unindent" << "cleanindent"
  64. << "comment" << "uncomment" << "goto" << "kill-line"
  65. << "set-tab-width" << "set-replace-tabs" << "set-show-tabs"
  66. << "set-remove-trailing-space"
  67. << "set-indent-spaces" << "set-indent-width" << "set-mixed-indent"
  68. << "set-indent-mode" << "set-auto-indent"
  69. << "set-line-numbers" << "set-folding-markers" << "set-icon-border"
  70. << "set-wrap-cursor"
  71. << "set-word-wrap" << "set-word-wrap-column"
  72. << "set-replace-tabs-save" << "set-remove-trailing-space-save"
  73. << "set-highlight" << "run-myself" << "set-show-indent";
  74. return l;
  75. }
  76. bool KateCommands::CoreCommands::exec(Kate::View *view,
  77. const TQString &_cmd,
  78. TQString &errorMsg)
  79. {
  80. #define KCC_ERR(s) { errorMsg=s; return false; }
  81. // cast it hardcore, we know that it is really a kateview :)
  82. KateView *v = (KateView*) view;
  83. if ( ! v )
  84. KCC_ERR( i18n("Could not access view") );
  85. //create a list of args
  86. TQStringList args( TQStringList::split( TQRegExp("\\s+"), _cmd ) );
  87. TQString cmd ( args.first() );
  88. args.remove( args.first() );
  89. // ALL commands that takes no arguments.
  90. if ( cmd == "indent" )
  91. {
  92. v->indent();
  93. return true;
  94. }
  95. else if ( cmd == "run-myself" )
  96. {
  97. #ifndef Q_WS_WIN //todo
  98. return KateFactory::self()->jscript()->execute(v, v->doc()->text(), errorMsg);
  99. #else
  100. return 0;
  101. #endif
  102. }
  103. else if ( cmd == "unindent" )
  104. {
  105. v->unIndent();
  106. return true;
  107. }
  108. else if ( cmd == "cleanindent" )
  109. {
  110. v->cleanIndent();
  111. return true;
  112. }
  113. else if ( cmd == "comment" )
  114. {
  115. v->comment();
  116. return true;
  117. }
  118. else if ( cmd == "uncomment" )
  119. {
  120. v->uncomment();
  121. return true;
  122. }
  123. else if ( cmd == "kill-line" )
  124. {
  125. v->killLine();
  126. return true;
  127. }
  128. else if ( cmd == "set-indent-mode" )
  129. {
  130. bool ok(false);
  131. int val ( args.first().toInt( &ok ) );
  132. if ( ok )
  133. {
  134. if ( val < 0 )
  135. KCC_ERR( i18n("Mode must be at least 0.") );
  136. v->doc()->config()->setIndentationMode( val );
  137. }
  138. else
  139. v->doc()->config()->setIndentationMode( KateAutoIndent::modeNumber( args.first() ) );
  140. return true;
  141. }
  142. else if ( cmd == "set-highlight" )
  143. {
  144. TQString val = TQString(_cmd.section( ' ', 1 )).lower();
  145. for ( uint i=0; i < v->doc()->hlModeCount(); i++ )
  146. {
  147. if ( v->doc()->hlModeName( i ).lower() == val )
  148. {
  149. v->doc()->setHlMode( i );
  150. return true;
  151. }
  152. }
  153. KCC_ERR( i18n("No such highlight '%1'").arg( args.first() ) );
  154. }
  155. // ALL commands that takes exactly one integer argument.
  156. else if ( cmd == "set-tab-width" ||
  157. cmd == "set-indent-width" ||
  158. cmd == "set-word-wrap-column" ||
  159. cmd == "goto" )
  160. {
  161. // find a integer value > 0
  162. if ( ! args.count() )
  163. KCC_ERR( i18n("Missing argument. Usage: %1 <value>").arg( cmd ) );
  164. bool ok;
  165. int val ( args.first().toInt( &ok ) );
  166. if ( !ok )
  167. KCC_ERR( i18n("Failed to convert argument '%1' to integer.")
  168. .arg( args.first() ) );
  169. if ( cmd == "set-tab-width" )
  170. {
  171. if ( val < 1 )
  172. KCC_ERR( i18n("Width must be at least 1.") );
  173. v->setTabWidth( val );
  174. }
  175. else if ( cmd == "set-indent-width" )
  176. {
  177. if ( val < 1 )
  178. KCC_ERR( i18n("Width must be at least 1.") );
  179. v->doc()->config()->setIndentationWidth( val );
  180. }
  181. else if ( cmd == "set-word-wrap-column" )
  182. {
  183. if ( val < 2 )
  184. KCC_ERR( i18n("Column must be at least 1.") );
  185. v->doc()->setWordWrapAt( val );
  186. }
  187. else if ( cmd == "goto" )
  188. {
  189. if ( val < 1 )
  190. KCC_ERR( i18n("Line must be at least 1") );
  191. if ( (uint)val > v->doc()->numLines() )
  192. KCC_ERR( i18n("There is not that many lines in this document") );
  193. v->gotoLineNumber( val - 1 );
  194. }
  195. return true;
  196. }
  197. // ALL commands that takes 1 boolean argument.
  198. else if ( cmd == "set-icon-border" ||
  199. cmd == "set-folding-markers" ||
  200. cmd == "set-line-numbers" ||
  201. cmd == "set-replace-tabs" ||
  202. cmd == "set-remove-trailing-space" ||
  203. cmd == "set-show-tabs" ||
  204. cmd == "set-indent-spaces" ||
  205. cmd == "set-mixed-indent" ||
  206. cmd == "set-word-wrap" ||
  207. cmd == "set-wrap-cursor" ||
  208. cmd == "set-replace-tabs-save" ||
  209. cmd == "set-remove-trailing-space-save" ||
  210. cmd == "set-show-indent" )
  211. {
  212. if ( ! args.count() )
  213. KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false").arg( cmd ) );
  214. bool enable;
  215. if ( getBoolArg( args.first(), &enable ) )
  216. {
  217. if ( cmd == "set-icon-border" )
  218. v->setIconBorder( enable );
  219. else if (cmd == "set-folding-markers")
  220. v->setFoldingMarkersOn( enable );
  221. else if ( cmd == "set-line-numbers" )
  222. v->setLineNumbersOn( enable );
  223. else if ( cmd == "set-show-indent" )
  224. v->renderer()->setShowIndentLines( enable );
  225. else if ( cmd == "set-replace-tabs" )
  226. setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() );
  227. else if ( cmd == "set-remove-trailing-space" )
  228. setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() );
  229. else if ( cmd == "set-show-tabs" )
  230. setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() );
  231. else if ( cmd == "set-indent-spaces" )
  232. setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() );
  233. else if ( cmd == "set-mixed-indent" )
  234. {
  235. // this is special, in that everything is set up -- space-indent is enabled,
  236. // and a indent-width is set if it is 0 (to tabwidth/2)
  237. setDocFlag( KateDocumentConfig::cfMixedIndent, enable, v->doc() );
  238. if ( enable )
  239. {
  240. setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() );
  241. if ( ! v->doc()->config()->indentationWidth() )
  242. v->doc()->config()->setIndentationWidth( v->tabWidth()/2 );
  243. }
  244. }
  245. else if ( cmd == "set-word-wrap" )
  246. v->doc()->setWordWrap( enable );
  247. else if ( cmd == "set-remove-trailing-space-save" )
  248. setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() );
  249. else if ( cmd == "set-wrap-cursor" )
  250. setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() );
  251. return true;
  252. }
  253. else
  254. KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false")
  255. .arg( args.first() ).arg( cmd ) );
  256. }
  257. // unlikely..
  258. KCC_ERR( i18n("Unknown command '%1'").arg(cmd) );
  259. }
  260. TDECompletion *KateCommands::CoreCommands::completionObject( const TQString &cmd, Kate::View *view )
  261. {
  262. if ( cmd == "set-highlight" )
  263. {
  264. KateView *v = (KateView*)view;
  265. TQStringList l;
  266. for ( uint i = 0; i < v->doc()->hlModeCount(); i++ )
  267. l << v->doc()->hlModeName( i );
  268. KateCmdShellCompletion *co = new KateCmdShellCompletion();
  269. co->setItems( l );
  270. co->setIgnoreCase( true );
  271. return co;
  272. }
  273. return 0L;
  274. }
  275. //END CoreCommands
  276. //BEGIN SedReplace
  277. static void replace(TQString &s, const TQString &needle, const TQString &with)
  278. {
  279. int pos=0;
  280. while (1)
  281. {
  282. pos=s.find(needle, pos);
  283. if (pos==-1) break;
  284. s.replace(pos, needle.length(), with);
  285. pos+=with.length();
  286. }
  287. }
  288. static int backslashString(const TQString &haystack, const TQString &needle, int index)
  289. {
  290. int len=haystack.length();
  291. int searchlen=needle.length();
  292. bool evenCount=true;
  293. while (index<len)
  294. {
  295. if (haystack[index]=='\\')
  296. {
  297. evenCount=!evenCount;
  298. }
  299. else
  300. { // isn't a slash
  301. if (!evenCount)
  302. {
  303. if (haystack.mid(index, searchlen)==needle)
  304. return index-1;
  305. }
  306. evenCount=true;
  307. }
  308. index++;
  309. }
  310. return -1;
  311. }
  312. // exchange "\t" for the actual tab character, for example
  313. static void exchangeAbbrevs(TQString &str)
  314. {
  315. // the format is (findreplace)*[nullzero]
  316. const char *magic="a\x07t\tn\n";
  317. while (*magic)
  318. {
  319. int index=0;
  320. char replace=magic[1];
  321. while ((index=backslashString(str, TQChar(*magic), index))!=-1)
  322. {
  323. str.replace(index, 2, TQChar(replace));
  324. index++;
  325. }
  326. magic++;
  327. magic++;
  328. }
  329. }
  330. int KateCommands::SedReplace::sedMagic( KateDocument *doc, int &line,
  331. const TQString &find, const TQString &repOld, const TQString &delim,
  332. bool noCase, bool repeat,
  333. uint startcol, int endcol )
  334. {
  335. KateTextLine *ln = doc->kateTextLine( line );
  336. if ( ! ln || ! ln->length() ) return 0;
  337. // HANDLING "\n"s in PATTERN
  338. // * Create a list of patterns, splitting PATTERN on (unescaped) "\n"
  339. // * insert $s and ^s to match line ends/beginnings
  340. // * When matching patterhs after the first one, replace \N with the captured
  341. // text.
  342. // * If all patterns in the list match sequentiel lines, there is a match, so
  343. // * remove line/start to line + patterns.count()-1/patterns.last.length
  344. // * handle capatures by putting them in one list.
  345. // * the existing insertion is fine, including the line calculation.
  346. TQStringList patterns = TQStringList::split( TQRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), find, true );
  347. if ( patterns.count() > 1 )
  348. {
  349. for ( uint i = 0; i < patterns.count(); i++ )
  350. {
  351. if ( i < patterns.count() - 1 )
  352. patterns[i].append("$");
  353. if ( i )
  354. patterns[i].prepend("^");
  355. kdDebug(13025)<<"patterns["<<i<<"] ="<<patterns[i]<<endl;
  356. }
  357. }
  358. TQRegExp matcher(patterns[0], noCase);
  359. uint len;
  360. int matches = 0;
  361. while ( ln->searchText( startcol, matcher, &startcol, &len ) )
  362. {
  363. if ( endcol >= 0 && startcol + len > (uint)endcol )
  364. break;
  365. matches++;
  366. TQString rep=repOld;
  367. // now set the backreferences in the replacement
  368. TQStringList backrefs=matcher.capturedTexts();
  369. int refnum=1;
  370. TQStringList::Iterator i = backrefs.begin();
  371. ++i;
  372. for (; i!=backrefs.end(); ++i)
  373. {
  374. // I need to match "\\" or "", but not "\"
  375. TQString number=TQString::number(refnum);
  376. int index=0;
  377. while (index!=-1)
  378. {
  379. index=backslashString(rep, number, index);
  380. if (index>=0)
  381. {
  382. rep.replace(index, 2, *i);
  383. index+=(*i).length();
  384. }
  385. }
  386. refnum++;
  387. }
  388. replace(rep, "\\\\", "\\");
  389. replace(rep, "\\" + delim, delim);
  390. doc->removeText( line, startcol, line, startcol + len );
  391. doc->insertText( line, startcol, rep );
  392. // TODO if replace contains \n,
  393. // change the line number and
  394. // check for text that needs be searched behind the last inserted newline.
  395. int lns = rep.contains('\n');
  396. if ( lns )
  397. {
  398. line += lns;
  399. if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) )
  400. {
  401. // if ( endcol >= startcol + len )
  402. endcol -= (startcol + len);
  403. uint sc = rep.length() - rep.findRev('\n') - 1;
  404. matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol );
  405. }
  406. }
  407. if (!repeat) break;
  408. startcol+=rep.length();
  409. // sanity check -- avoid infinite loops eg with %s,.*,,g ;)
  410. uint ll = ln->length();
  411. if ( ! ll || startcol > ll )
  412. break;
  413. }
  414. return matches;
  415. }
  416. bool KateCommands::SedReplace::exec (Kate::View *view, const TQString &cmd, TQString &msg)
  417. {
  418. kdDebug(13025)<<"SedReplace::execCmd( "<<cmd<<" )"<<endl;
  419. TQRegExp delim("^[$%]?s\\s*([^\\w\\s])");
  420. if ( delim.search( cmd ) < 0 ) return false;
  421. bool fullFile=cmd[0]=='%';
  422. bool noCase=cmd[cmd.length()-1]=='i' || cmd[cmd.length()-2]=='i';
  423. bool repeat=cmd[cmd.length()-1]=='g' || cmd[cmd.length()-2]=='g';
  424. bool onlySelect=cmd[0]=='$';
  425. TQString d = delim.cap(1);
  426. kdDebug(13025)<<"SedReplace: delimiter is '"<<d<<"'"<<endl;
  427. TQRegExp splitter( TQString("^[$%]?s\\s*") + d + "((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d +"((?:[^\\\\\\" + d + "]|\\\\.)*)\\" + d + "[ig]{0,2}$" );
  428. if (splitter.search(cmd)<0) return false;
  429. TQString find=splitter.cap(1);
  430. kdDebug(13025)<< "SedReplace: find=" << find.latin1() <<endl;
  431. TQString replace=splitter.cap(2);
  432. exchangeAbbrevs(replace);
  433. kdDebug(13025)<< "SedReplace: replace=" << replace.latin1() <<endl;
  434. if ( find.contains("\\n") )
  435. {
  436. msg = i18n("Sorry, but Kate is not able to replace newlines, yet");
  437. return false;
  438. }
  439. KateDocument *doc = ((KateView*)view)->doc();
  440. if ( ! doc ) return false;
  441. doc->editStart();
  442. int res = 0;
  443. if (fullFile)
  444. {
  445. uint numLines=doc->numLines();
  446. for (int line=0; (uint)line < numLines; line++)
  447. {
  448. res += sedMagic( doc, line, find, replace, d, !noCase, repeat );
  449. if ( ! repeat && res ) break;
  450. }
  451. }
  452. else if (onlySelect)
  453. {
  454. int startline = doc->selStartLine();
  455. uint startcol = doc->selStartCol();
  456. int endcol = -1;
  457. do {
  458. if ( startline == doc->selEndLine() )
  459. endcol = doc->selEndCol();
  460. res += sedMagic( doc, startline, find, replace, d, !noCase, repeat, startcol, endcol );
  461. /*if ( startcol )*/ startcol = 0;
  462. startline++;
  463. } while ( (int)startline <= doc->selEndLine() );
  464. }
  465. else // just this line
  466. {
  467. int line=view->cursorLine();
  468. res += sedMagic(doc, line, find, replace, d, !noCase, repeat);
  469. }
  470. msg = i18n("1 replacement done", "%n replacements done",res );
  471. doc->editEnd();
  472. return true;
  473. }
  474. //END SedReplace
  475. //BEGIN Character
  476. bool KateCommands::Character::exec (Kate::View *view, const TQString &_cmd, TQString &)
  477. {
  478. TQString cmd = _cmd;
  479. // hex, octal, base 9+1
  480. TQRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,3})$");
  481. if (num.search(cmd)==-1) return false;
  482. cmd=num.cap(1);
  483. // identify the base
  484. unsigned short int number=0;
  485. int base=10;
  486. if (cmd[0]=='x' || cmd.left(2)=="0x")
  487. {
  488. cmd.replace(TQRegExp("^0?x"), "");
  489. base=16;
  490. }
  491. else if (cmd[0]=='0')
  492. base=8;
  493. bool ok;
  494. number=cmd.toUShort(&ok, base);
  495. if (!ok || number==0) return false;
  496. if (number<=255)
  497. {
  498. char buf[2];
  499. buf[0]=(char)number;
  500. buf[1]=0;
  501. view->insertText(TQString(buf));
  502. }
  503. else
  504. { // do the unicode thing
  505. TQChar c(number);
  506. view->insertText(TQString(&c, 1));
  507. }
  508. return true;
  509. }
  510. //END Character
  511. //BEGIN Date
  512. bool KateCommands::Date::exec (Kate::View *view, const TQString &cmd, TQString &)
  513. {
  514. if (cmd.left(4) != "date")
  515. return false;
  516. if (TQDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0)
  517. view->insertText(TQDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)));
  518. else
  519. view->insertText(TQDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
  520. return true;
  521. }
  522. //END Date
  523. // kate: space-indent on; indent-width 2; replace-tabs on;