diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java index c1e038deb..284725eef 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java @@ -2213,7 +2213,7 @@ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) { return; } - RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); + RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument(); Element map = doc.getDefaultRootElement(); Caret c = textArea.getCaret(); int dot = c.getDot(); @@ -2221,32 +2221,34 @@ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) { int line1 = map.getElementIndex(dot); int line2 = map.getElementIndex(mark); int start = Math.min(line1, line2); - int end = Math.max(line1, line2); + int end = Math.max(line1, line2); Token t = doc.getTokenListForLine(start); - int languageIndex = t!=null ? t.getLanguageIndex() : 0; + int languageIndex = t != null ? t.getLanguageIndex() : 0; String[] startEnd = doc.getLineCommentStartAndEnd(languageIndex); - if (startEnd==null) { + if (startEnd == null) { UIManager.getLookAndFeel().provideErrorFeedback(textArea); return; } // Don't toggle comment on last line if there is no // text selected on it. - if (start!=end) { + if (start != end) { Element elem = map.getElement(end); - if (Math.max(dot, mark)==elem.getStartOffset()) { + if (Math.max(dot, mark) == elem.getStartOffset()) { end--; } } textArea.beginAtomicEdit(); try { - boolean add = getDoAdd(doc,map, start,end, startEnd); - for (line1=start; line1<=end; line1++) { + for (line1 = end; line1 >= start; line1--) { Element elem = map.getElement(line1); - handleToggleComment(elem, doc, startEnd, add); + String lineText = doc.getText(elem.getStartOffset(), + elem.getEndOffset() - elem.getStartOffset() - 1); + int[] pos = searchMarkers(lineText, startEnd); + handleToggleComment(elem, doc, lineText, startEnd, pos); } } catch (BadLocationException ble) { ble.printStackTrace(); @@ -2254,42 +2256,95 @@ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) { } finally { textArea.endAtomicEdit(); } - } - private boolean getDoAdd(Document doc, Element map, int startLine, - int endLine, String[] startEnd) - throws BadLocationException { - boolean doAdd = false; - for (int i=startLine; i<=endLine; i++) { - Element elem = map.getElement(i); - int start = elem.getStartOffset(); - String t = doc.getText(start, elem.getEndOffset()-start-1); - if (!t.startsWith(startEnd[0]) || - (startEnd[1]!=null && !t.endsWith(startEnd[1]))) { - doAdd = true; + /** + * Search the end-comment mark, skipping trailing whitespaces + * + * @param text The line text + * @param startEnd start/end comment marks + * @return The index of the mark if present, otherwise -1 + */ + int endMatch(String text, String[] startEnd) { + if (startEnd[1] == null) { + return -1; + } + + char c; + int i = text.length() - 1; + + while (i >= 0) { + c = text.charAt(i); + + if (!Character.isWhitespace(c)) { break; } + + i--; + } + + i = i - startEnd[1].length() + 1; + + if (text.lastIndexOf(startEnd[1]) == i) { + return i; + } else { + return -1; } - return doAdd; } - private void handleToggleComment(Element elem, Document doc, - String[] startEnd, boolean add) throws BadLocationException { - int start = elem.getStartOffset(); - int end = elem.getEndOffset() - 1; - if (add) { - if (startEnd[1]!=null) { - doc.insertString(end, startEnd[1], null); - } - doc.insertString(start, startEnd[0], null); + /** + * Search the start-comment mark, skipping leading whitespaces + * + * @param text The line text + * @param startEnd start/end comment marks + * @return The index of the mark if present, otherwise -1 + */ + int startMatch(String text, String[] startEnd) { + String ws = RSyntaxUtilities.getLeadingWhitespace(text); + + if (text.indexOf(startEnd[0]) == ws.length()) { + return ws.length(); + } else { + return -1; } - else { - if (startEnd[1]!=null) { - int temp = startEnd[1].length(); - doc.remove(end-temp, temp); + } + + /** + * Search for start and end marks + * + * @return An array with the indices of the marks in the line. Negative indices means that the mark + * is not present in the line + */ + private int[] searchMarkers(String line, String[] startEnd) { + int startOffset = startMatch(line, startEnd); + int endOffset = endMatch(line, startEnd); + + return new int[]{startOffset, endOffset}; + } + + private void handleToggleComment(Element elem, Document doc, String line, String[] startEnd, int[] pos) + throws BadLocationException { + + int startOffset = elem.getStartOffset(); + + if (pos[0] >= 0 && ((pos[1] >= 0 && startEnd[1] != null) || (pos[1] < 0 && startEnd[1] == null))) { + // start-mark found (and also the end-mark for two-mark comments) + if (startEnd[1] != null && pos[1] >= 0) { + doc.remove(startOffset + pos[1], startEnd[1].length()); } - doc.remove(start, startEnd[0].length()); + + if (pos[0] >= 0) { + doc.remove(startOffset + pos[0], startEnd[0].length()); + } + } else { + if (startEnd[1] != null) { + // add the mark after the last char of the line + doc.insertString(elem.getEndOffset() - 1, startEnd[1], null); + } + + // insert the mark right before the first non-ws char + int n = RSyntaxUtilities.getLeadingWhitespace(line).length(); + doc.insertString(startOffset + n, startEnd[0], null); } } @@ -2297,7 +2352,6 @@ private void handleToggleComment(Element elem, Document doc, public final String getMacroID() { return rstaToggleCommentAction; } - } diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitToggleCommentActionTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitToggleCommentActionTest.java index 42d706da9..fff065c9b 100644 --- a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitToggleCommentActionTest.java +++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitToggleCommentActionTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import java.awt.event.ActionEvent; + /** * Unit tests for the {@link RSyntaxTextAreaEditorKit.ToggleCommentAction} class. @@ -144,4 +146,76 @@ void testGetMacroID() { Assertions.assertEquals(RSyntaxTextAreaEditorKit.rstaToggleCommentAction, new RSyntaxTextAreaEditorKit.ToggleCommentAction().getMacroID()); } -} + + @Test + void testLeadingTrailingWhitespaces() { + RSyntaxTextAreaEditorKit.ToggleCommentAction commentAction = new RSyntaxTextAreaEditorKit.ToggleCommentAction(); + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_C, ""); + ActionEvent e = new ActionEvent(textArea, 0, "command"); + + // multiline with leading WS + textArea.setText(" //line 1\n//line 2\nline 3"); + textArea.setCaretPosition(2); + textArea.moveCaretPosition(15); + + commentAction.actionPerformedImpl(e, textArea); + + String expectedText = " line 1\nline 2\nline 3"; + Assertions.assertEquals(expectedText, textArea.getText()); + + // start/end marks and leading and trailing WS + textArea.setText(" "); + textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML); + textArea.setCaretPosition(0); + textArea.moveCaretPosition(0); + + commentAction.actionPerformedImpl(e, textArea); + + expectedText = " xml "; + Assertions.assertEquals(expectedText, textArea.getText()); + + // comment + textArea.setText(" int a = 2; "); + textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + textArea.setCaretPosition(0); + textArea.moveCaretPosition(5); + + commentAction.actionPerformedImpl(e, textArea); + + expectedText = " //int a = 2; "; + Assertions.assertEquals(expectedText, textArea.getText()); + + // multiline with leading and trailing WS and start/end marks + textArea.setText(" \n "); + textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML); + textArea.setCaretPosition(0); + textArea.moveCaretPosition(textArea.getDocument().getLength()); + + commentAction.actionPerformedImpl(e, textArea); + + expectedText = " line 1 \nline 2 "; + Assertions.assertEquals(expectedText, textArea.getText()); + } + + /** + * Test for {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.ToggleCommentAction#startMatch(String, String[])} + * and {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.ToggleCommentAction#endMatch(String, String[])} + */ + @Test + void startEndSearch() { + String[] markers = {""}; + RSyntaxTextAreaEditorKit.ToggleCommentAction action = new RSyntaxTextAreaEditorKit.ToggleCommentAction(); + + Assertions.assertEquals(0, action.startMatch("", markers)); + Assertions.assertEquals(2, action.startMatch(" ", markers)); + Assertions.assertEquals(-1, action.startMatch("// text", markers)); + Assertions.assertEquals(-1, action.startMatch(" // text", markers)); + Assertions.assertEquals(-1, action.startMatch("", markers)); + + Assertions.assertEquals(10, action.endMatch("", markers)); + Assertions.assertEquals(12, action.endMatch(" ", markers)); + Assertions.assertEquals(-1, action.endMatch("// text", markers)); + Assertions.assertEquals(-1, action.endMatch(" // text", markers)); + Assertions.assertEquals(-1, action.endMatch("", markers)); + } +} \ No newline at end of file