PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/bundles/plugins-trunk/TextTools/TextToolsComments.java

#
Java | 547 lines | 368 code | 39 blank | 140 comment | 63 complexity | 3c0ee84de00a1b0ad18fa9c7bcb69b70 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * TextToolsComments.java - Actions for toggling range and line comments
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 2003 Robert Fletcher
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  21. */
  22. //{{{ Imports
  23. import java.io.PrintWriter;
  24. import java.io.StringWriter;
  25. import java.util.Iterator;
  26. import javax.swing.text.Segment;
  27. import org.gjt.sp.jedit.MiscUtilities;
  28. import org.gjt.sp.jedit.View;
  29. import org.gjt.sp.jedit.jEdit;
  30. import org.gjt.sp.jedit.buffer.JEditBuffer;
  31. import org.gjt.sp.jedit.textarea.JEditTextArea;
  32. import org.gjt.sp.jedit.textarea.Selection;
  33. import org.gjt.sp.util.Log;
  34. import org.gjt.sp.util.StandardUtilities;
  35. //}}}
  36. /**
  37. * Actions for toggling range and line comments.
  38. *
  39. * @author <a href="mailto:rfletch6@yahoo.co.uk">Robert Fletcher</a>
  40. * @version $Revision: 20902 $ $Date: 2012-01-21 09:51:18 +0100 (Sat, 21 Jan 2012) $
  41. */
  42. public class TextToolsComments
  43. {
  44. //{{{ toggleLineComments() method
  45. /**
  46. * Toggles a line comment on or off at all currently selected lines or, if
  47. * there are none, at the line where the caret currently sits.
  48. *
  49. * @param view a jEdit view.
  50. */
  51. public static void toggleLineComments(View view)
  52. {
  53. JEditTextArea textArea = view.getTextArea();
  54. JEditBuffer buffer = textArea.getBuffer();
  55. try
  56. {
  57. if(!buffer.isEditable())
  58. {
  59. view.getToolkit().beep();
  60. return;
  61. }
  62. int magicCaretPos = textArea.getMagicCaretPosition();
  63. // get an array of all selected lines, or if there are no selections,
  64. // just the line of the caret
  65. Selection[] selections = textArea.getSelection();
  66. int selectionNo;
  67. if (selections.length < 1)
  68. {
  69. selectionNo = 1;
  70. }
  71. else
  72. {
  73. selectionNo = selections.length;
  74. }
  75. int[] lines;
  76. for (int j=0; j < selectionNo; j++)
  77. {
  78. //Get the lines for this selection.
  79. if (selections.length < 1)
  80. {
  81. lines = new int[]{textArea.getCaretLine()};
  82. }
  83. else
  84. {
  85. lines = new int[selections[j].getEndLine() - selections[j].getStartLine() + 1];
  86. for (int i=0; i < lines.length; i++) {
  87. lines[i] = i + selections[j].getStartLine();
  88. }
  89. }
  90. // If we're inserting as block find the leftmost indent
  91. int leftmost = Integer.MAX_VALUE;
  92. String line;
  93. if(jEdit.getBooleanProperty("options.toggle-comments.indentAsBlock"))
  94. {
  95. for(int i = 0; i < lines.length; i++)
  96. {
  97. line = buffer.getLineText(lines[i]);
  98. if(line.trim().length() < 1)
  99. {
  100. continue;
  101. }
  102. leftmost = Math.min(leftmost, StandardUtilities.getLeadingWhiteSpaceWidth(line, buffer.getTabSize()));
  103. }
  104. }
  105. // if block commenting we need to check for un-commented lines, if
  106. // any are found NO lines will be uncommented
  107. boolean doUncomment = true;
  108. if(jEdit.getBooleanProperty("options.toggle-comments.commentAsBlock"))
  109. {
  110. for(int i = 0; doUncomment && i < lines.length; i++)
  111. {
  112. line = buffer.getLineText(lines[i]).trim();
  113. String lineComment = buffer.getContextSensitiveProperty(buffer.getLineStartOffset(lines[i]), "lineComment");
  114. if(lineComment == null || lineComment.length() == 0)
  115. {
  116. continue;
  117. }
  118. if(line.length() > 0 && !line.startsWith(lineComment))
  119. {
  120. doUncomment = false;
  121. }
  122. }
  123. }
  124. // loop through each line
  125. boolean noCommentableLines = true;
  126. String lineComment = null;
  127. for(int i = 0; i < lines.length; i++)
  128. {
  129. Log.log(Log.DEBUG, TextToolsComments.class, "looping line: "+lines[i]);
  130. line = buffer.getLineText(lines[i]);
  131. int lineStart = buffer.getLineStartOffset(lines[i]);
  132. // get position after any leading whitespace
  133. int pos = lineStart + StandardUtilities.getLeadingWhiteSpace(line);
  134. if (i == 0)
  135. {
  136. //first time through get the line comment.
  137. lineComment = buffer.getContextSensitiveProperty(pos + 1, "lineComment");
  138. }
  139. // skip over blank lines
  140. if(line.trim().length() < 1)
  141. {
  142. continue;
  143. }
  144. // re-get the lineComment property as it can vary
  145. if(lineComment == null || lineComment.length() == 0)
  146. {
  147. Log.log(Log.DEBUG, TextToolsComments.class, "No line comment: "+lines[i]);
  148. continue;
  149. }
  150. else
  151. {
  152. noCommentableLines = false;
  153. }
  154. lockBuffer(buffer);
  155. // if the first non-whitespace char in the line is the line
  156. // comment symbol and we are doing an uncomment
  157. // then, remove, otherwise insert
  158. if(line.trim().startsWith(lineComment) && doUncomment)
  159. {
  160. buffer.remove(pos, lineComment.length());
  161. if(Character.isWhitespace(buffer.getText(pos, 1).charAt(0)))
  162. {
  163. buffer.remove(pos, 1);
  164. }
  165. }
  166. else
  167. {
  168. // depending on options, add comment symbol at:
  169. // - start of line
  170. if(jEdit.getBooleanProperty("options.toggle-comments.indentAtLineStart"))
  171. {
  172. buffer.insert(lineStart, lineComment + " ");
  173. }
  174. // - leftmost indent of selected lines
  175. else if(jEdit.getBooleanProperty("options.toggle-comments.indentAsBlock"))
  176. {
  177. Segment seg = new Segment();
  178. buffer.getLineText(lines[i], seg);
  179. Log.log(Log.DEBUG, TextToolsComments.class, "commenting line: "+lines[i]);
  180. buffer.insert(lineStart + StandardUtilities.getOffsetOfVirtualColumn(seg, buffer.getTabSize(), leftmost, null), lineComment + " ");
  181. }
  182. // - or after all leading whitespace
  183. else
  184. {
  185. buffer.insert(pos, lineComment + " ");
  186. }
  187. }
  188. }
  189. // if there were no commentable lines, beep & return
  190. if(noCommentableLines)
  191. {
  192. view.getToolkit().beep();
  193. return;
  194. }
  195. // optionally retain selection - by default behave as jEdit
  196. if(!jEdit.getBooleanProperty("options.toggle-comments.keepSelected"))
  197. {
  198. Selection[] sArr = textArea.getSelection();
  199. if(sArr.length > 0)
  200. {
  201. textArea.setCaretPosition(sArr[sArr.length - 1].getEnd());
  202. }
  203. }
  204. }
  205. // restore magic caret position - (un)commenting should not change it
  206. textArea.setMagicCaretPosition(magicCaretPos);
  207. // optionally move line down
  208. if(jEdit.getBooleanProperty("options.toggle-comments.moveDown"))
  209. {
  210. // we don't do it if some text is selected
  211. // we'd better avoid beeping if trying to go past the last line
  212. if (textArea.getSelection().length==0 &&
  213. textArea.getCaretLine()+1 < textArea.getLineCount())
  214. {
  215. textArea.goToNextLine(false);
  216. }
  217. }
  218. }
  219. finally
  220. {
  221. unlockBuffer(buffer);
  222. }
  223. } //}}}
  224. //{{{ toggleRangeComments() method
  225. /**
  226. * Toggles all range comments in the specified view by applying {@link
  227. * #toggleRangeComment(Buffer, int, int)} to each selection. If there are no
  228. * selections {@link #toggleRangeComment(Buffer, int)} will be applied at the
  229. * current caret position.
  230. *
  231. * @param view a jEdit view.
  232. */
  233. public static void toggleRangeComments(View view)
  234. {
  235. JEditTextArea textArea = view.getTextArea();
  236. JEditBuffer buffer = textArea.getBuffer();
  237. if(!buffer.isEditable() || !usesRangeComments(buffer))
  238. {
  239. view.getToolkit().beep();
  240. return;
  241. }
  242. try
  243. {
  244. // get an array of all selections, if there are none, use the method
  245. // that toggles comments around the caret
  246. Selection[] selections = textArea.getSelection();
  247. if(selections.length < 1)
  248. {
  249. toggleRangeComment(buffer, textArea.getCaretPosition());
  250. }
  251. // loop through each selection
  252. String selectTxt;
  253. for(int i = 0; i < selections.length; i++)
  254. {
  255. selectTxt = textArea.getSelectedText(selections[i]);
  256. try
  257. {
  258. lockBuffer(buffer);
  259. // get the start & end of the selection so we can retain it
  260. int start = selections[i].getStart();
  261. int end = selections[i].getEnd();
  262. // toggle comments in this selection
  263. int length = toggleRangeComment(buffer, start, end);
  264. // optionally retain selection - by default behave as jEdit
  265. if(jEdit.getBooleanProperty("options.toggle-comments.keepSelected"))
  266. {
  267. textArea.addToSelection(new Selection.Range(start, start + length));
  268. }
  269. }
  270. catch(IndexOutOfBoundsException e)
  271. {
  272. StringWriter sw = new StringWriter();
  273. e.printStackTrace(new PrintWriter(sw));
  274. Log.log(Log.ERROR, TextToolsComments.class, sw.toString());
  275. }
  276. }
  277. }
  278. finally
  279. {
  280. unlockBuffer(buffer);
  281. }
  282. } //}}}
  283. //{{{ isInRangeComment() method
  284. /**
  285. * Tests whether the specified buffer index is within a range comment block.
  286. *
  287. * @param buffer a jEdit buffer
  288. * @param offset an index within <code>buffer</code>
  289. * @return <code>true</code> if <code>offset</code> is within a range comment,
  290. * <code>false</code> otherwise
  291. */
  292. private static boolean isInRangeComment(JEditBuffer buffer, int offset)
  293. {
  294. String preceding = buffer.getText(0, offset);
  295. String commentStart = buffer.getContextSensitiveProperty(offset, "commentStart");
  296. String commentEnd = buffer.getContextSensitiveProperty(offset, "commentEnd");
  297. int lastStart = preceding.lastIndexOf(commentStart);
  298. int lastEnd = preceding.lastIndexOf(commentEnd);
  299. return (lastStart != -1 && lastStart > lastEnd);
  300. } //}}}
  301. //{{{ lockBuffer() method
  302. /**
  303. * If the specified <code>JEditBuffer</code> is not currently inside a compound
  304. * edit, this method will start one, lock the buffer and return <code>true</code>
  305. * , otherwise it will return <code>false</code>
  306. *
  307. * @param buffer a jEdit buffer
  308. * @return <code>true</code> if this method locked the buffer, <code>false</code>
  309. * if it was already locked
  310. */
  311. private static boolean lockBuffer(JEditBuffer buffer)
  312. {
  313. if(!buffer.insideCompoundEdit())
  314. {
  315. buffer.writeLock();
  316. buffer.beginCompoundEdit();
  317. return true;
  318. }
  319. return false;
  320. }//}}}
  321. //{{{ toggleRangeComment() method
  322. /**
  323. * Toggles range comments around a specified buffer index. If the index is
  324. * within a range comment, the comment block will be un-commented. If the index
  325. * is not within a range comment, the method behaves as {@link
  326. * #toggleRangeComment(Buffer, int, int)} with <code>start</code> and <code>end</code>
  327. * values equal to the start and end indexes of non-whitespace text on the same
  328. * line as <code>offset</code> .
  329. *
  330. * @param buffer a jEdit buffer.
  331. * @param offset a index in <code>buffer</code> .
  332. */
  333. private static void toggleRangeComment(JEditBuffer buffer, int offset)
  334. {
  335. String commentStart = buffer.getContextSensitiveProperty(offset, "commentStart");
  336. String commentEnd = buffer.getContextSensitiveProperty(offset, "commentEnd");
  337. if(isInRangeComment(buffer, offset))
  338. {
  339. String preceding = buffer.getText(0, offset);
  340. String following = buffer.getText(offset, buffer.getLength() - offset);
  341. int start = preceding.lastIndexOf(commentStart);
  342. int end = following.indexOf(commentEnd) + offset;
  343. try
  344. {
  345. lockBuffer(buffer);
  346. buffer.remove(end, commentEnd.length());
  347. buffer.remove(start, commentStart.length());
  348. }
  349. finally
  350. {
  351. unlockBuffer(buffer);
  352. }
  353. }
  354. else
  355. {
  356. int line = buffer.getLineOfOffset(offset);
  357. String lineTxt = buffer.getLineText(line);
  358. int start = buffer.getLineStartOffset(line) + StandardUtilities.getLeadingWhiteSpace(lineTxt);
  359. int end = buffer.getLineEndOffset(line) - (StandardUtilities.getTrailingWhiteSpace(lineTxt) + 1); // the +1 catches the \n
  360. toggleRangeComment(buffer, start, end);
  361. }
  362. } //}}}
  363. //{{{ toggleRangeComment() method
  364. /**
  365. * Toggles range commenting in a buffer around/within the range from <code>start
  366. * </code> to <code>end</code> . In the simplest case comment symbols are added /
  367. * removed around the specified range. If, however, there are already range
  368. * comments within the specified range, the method will reverse their
  369. * commenting state.
  370. *
  371. * @param buffer a jEdit buffer.
  372. * @param start the start index of a range within <code>buffer</code> .
  373. * @param end the end index of a range within <code>buffer</code> .
  374. * @return the length of the text the specified range has been replaced
  375. * with.
  376. */
  377. private static int toggleRangeComment(JEditBuffer buffer, int start, int end)
  378. {
  379. String commentStart = buffer.getContextSensitiveProperty(start, "commentStart");
  380. String commentEnd = buffer.getContextSensitiveProperty(start, "commentEnd");
  381. StringBuilder buf = new StringBuilder(buffer.getText(start, end - start));
  382. // use a state machine to step through text and reverse commenting
  383. final byte REMOVE_COMMENT_START = 0;
  384. final byte REMOVE_COMMENT_END = 1;
  385. final byte LOOK_FOR_COMMENT_END = 2;
  386. final byte LOOK_FOR_COMMENT_START = 3;
  387. final byte INSERT_COMMENT_START = 4;
  388. final byte INSERT_COMMENT_END = 5;
  389. byte state;
  390. if(buf.indexOf(commentStart) == 0)
  391. {
  392. state = REMOVE_COMMENT_START;
  393. }
  394. else if(buf.indexOf(commentEnd) == 0)
  395. {
  396. state = REMOVE_COMMENT_END;
  397. }
  398. else if(isInRangeComment(buffer, start))
  399. {
  400. state = INSERT_COMMENT_END;
  401. }
  402. else
  403. {
  404. state = INSERT_COMMENT_START;
  405. }
  406. int i = 0;
  407. boolean atStart;
  408. while(i > -1 && i < buf.length())
  409. {
  410. switch(state)
  411. {
  412. case REMOVE_COMMENT_START:
  413. atStart = i == 0;
  414. buf.delete(i, i + commentStart.length());
  415. state = atStart ? LOOK_FOR_COMMENT_END : INSERT_COMMENT_END;
  416. break;
  417. case REMOVE_COMMENT_END:
  418. atStart = i == 0;
  419. buf.delete(i, i + commentEnd.length());
  420. state = atStart ? LOOK_FOR_COMMENT_START : INSERT_COMMENT_START;
  421. break;
  422. case INSERT_COMMENT_START:
  423. buf.insert(i, commentStart);
  424. i += commentStart.length();
  425. state = LOOK_FOR_COMMENT_START;
  426. break;
  427. case INSERT_COMMENT_END:
  428. buf.insert(i, commentEnd);
  429. i += commentEnd.length();
  430. state = LOOK_FOR_COMMENT_END;
  431. break;
  432. case LOOK_FOR_COMMENT_END:
  433. i = buf.indexOf(commentEnd, i);
  434. if(i == -1)
  435. {
  436. buf.append(commentStart);
  437. }
  438. else
  439. {
  440. state = REMOVE_COMMENT_END;
  441. }
  442. break;
  443. case LOOK_FOR_COMMENT_START:
  444. i = buf.indexOf(commentStart, i);
  445. if(i == -1)
  446. {
  447. buf.append(commentEnd);
  448. }
  449. else
  450. {
  451. state = REMOVE_COMMENT_START;
  452. }
  453. break;
  454. default:
  455. throw new IllegalStateException("unknown state "+state);
  456. }
  457. }
  458. boolean newEdit = false;
  459. try
  460. {
  461. newEdit = lockBuffer(buffer);
  462. buffer.remove(start, end - start);
  463. buffer.insert(start, buf.toString());
  464. return buf.length();
  465. }
  466. finally
  467. {
  468. // only unlock if this method started the edit
  469. if(newEdit)
  470. {
  471. unlockBuffer(buffer);
  472. }
  473. }
  474. } //}}}
  475. //{{{ unlockBuffer() method
  476. /**
  477. * If the specified <code>JEditBuffer</code> is currently inside a compound edit,
  478. * this method will end it, unlock the buffer and return <code>true</code>,
  479. * otherwise it will return <code>false</code>
  480. *
  481. * @param buffer a jEdit buffer
  482. * @return <code>true</code> if this method unlocked the buffer, <code>false</code>
  483. * if it was already unlocked
  484. */
  485. private static boolean unlockBuffer(JEditBuffer buffer)
  486. {
  487. if(buffer.insideCompoundEdit())
  488. {
  489. buffer.endCompoundEdit();
  490. buffer.writeUnlock();
  491. return true;
  492. }
  493. return false;
  494. }//}}}
  495. //{{{ usesRangeComments() method
  496. /**
  497. * Determines if the given buffer has defined range comment settings.
  498. *
  499. * @param buffer a jEdit buffer.
  500. * @return <code>true</code> if <code>buffer</code> uses range comments, <code>
  501. * false</code> otherwise.
  502. */
  503. private static boolean usesRangeComments(JEditBuffer buffer)
  504. {
  505. return buffer.getStringProperty("commentStart") != null &&
  506. buffer.getStringProperty("commentEnd") != null;
  507. } //}}}
  508. }