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