/plugins/TextTools/tags/texttools-1_13/TextToolsComments.java

# · Java · 491 lines · 332 code · 27 blank · 132 comment · 56 complexity · 5a9e3bb30ee7f04611e8d19a66e0f628 MD5 · raw file

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