From 03e8dd471fd3f1c6e6d83b291a9dc6eef5503632 Mon Sep 17 00:00:00 2001 From: bobbylight Date: Sat, 10 Dec 2022 17:12:07 -0500 Subject: [PATCH] Fix #473: Automatically insert a closing quote for string literals in languages that support it --- .../RSyntaxTextAreaDefaultInputMap.java | 1 + .../RSyntaxTextAreaEditorKit.java | 124 ++++++++- .../ui/rsyntaxtextarea/RSyntaxUtilities.java | 74 +++++- ...extAreaEditorKitInsertQuoteActionTest.java | 251 ++++++++++++++++++ .../ui/rsyntaxtextarea/TokenImplTest.java | 141 ++++++++++ 5 files changed, 580 insertions(+), 11 deletions(-) create mode 100644 RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitInsertQuoteActionTest.java diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaDefaultInputMap.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaDefaultInputMap.java index df2f1d974..ed48384ab 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaDefaultInputMap.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaDefaultInputMap.java @@ -48,6 +48,7 @@ public RSyntaxTextAreaDefaultInputMap() { put(KeyStroke.getKeyStroke('{'), RSyntaxTextAreaEditorKit.rstaOpenCurlyAction); put(KeyStroke.getKeyStroke('\''), RSyntaxTextAreaEditorKit.rstaSingleQuoteAction); put(KeyStroke.getKeyStroke('"'), RSyntaxTextAreaEditorKit.rstaDoubleQuoteAction); + put(KeyStroke.getKeyStroke('`'), RSyntaxTextAreaEditorKit.rstaBacktickAction); put(KeyStroke.getKeyStroke('/'), RSyntaxTextAreaEditorKit.rstaCloseMarkupTagAction); int os = RSyntaxUtilities.getOS(); 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 bcf503ce1..e39c70662 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKit.java @@ -71,6 +71,7 @@ public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit { private static final long serialVersionUID = 1L; + public static final String rstaBacktickAction = "RSTA.BacktickAction"; public static final String rstaCloseCurlyBraceAction = "RSTA.CloseCurlyBraceAction"; public static final String rstaCloseMarkupTagAction = "RSTA.CloseMarkupTagAction"; public static final String rstaCollapseAllFoldsAction = "RSTA.CollapseAllFoldsAction"; @@ -79,15 +80,15 @@ public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit { public static final String rstaCopyAsStyledTextAction = "RSTA.CopyAsStyledTextAction"; public static final String rstaCutAsStyledTextAction = "RSTA.CutAsStyledTextAction"; public static final String rstaDecreaseIndentAction = "RSTA.DecreaseIndentAction"; + public static final String rstaDoubleQuoteAction = "RSTA.DoubleQuoteAction"; public static final String rstaExpandAllFoldsAction = "RSTA.ExpandAllFoldsAction"; public static final String rstaExpandFoldAction = "RSTA.ExpandFoldAction"; public static final String rstaGoToMatchingBracketAction = "RSTA.GoToMatchingBracketAction"; - public static final String rstaOpenParenAction = "RSTA.openParenAction"; - public static final String rstaOpenSquareBracketAction = "RSTA.openSquareBracketAction"; - public static final String rstaOpenCurlyAction = "RSTA.openCurlyAction"; - public static final String rstaDoubleQuoteAction = "RSTA.doubleQuoteAction"; - public static final String rstaSingleQuoteAction = "RSTA.singleQuoteAction"; + public static final String rstaOpenParenAction = "RSTA.OpenParenAction"; + public static final String rstaOpenSquareBracketAction = "RSTA.OpenSquareBracketAction"; + public static final String rstaOpenCurlyAction = "RSTA.OpenCurlyAction"; public static final String rstaPossiblyInsertTemplateAction = "RSTA.TemplateAction"; + public static final String rstaSingleQuoteAction = "RSTA.SingleQuoteAction"; public static final String rstaToggleCommentAction = "RSTA.ToggleCommentAction"; public static final String rstaToggleCurrentFoldAction = "RSTA.ToggleCurrentFoldAction"; @@ -124,8 +125,9 @@ public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit { new InsertPairedCharacterAction(rstaOpenParenAction, '(', ')'), new InsertPairedCharacterAction(rstaOpenSquareBracketAction, '[', ']'), new InsertPairedCharacterAction(rstaOpenCurlyAction, '{', '}'), - new InsertPairedCharacterAction(rstaDoubleQuoteAction, '"', '"'), - new InsertPairedCharacterAction(rstaSingleQuoteAction, '\'', '\''), + new InsertQuoteAction(rstaDoubleQuoteAction, InsertQuoteAction.QuoteType.DOUBLE_QUOTE), + new InsertQuoteAction(rstaSingleQuoteAction, InsertQuoteAction.QuoteType.SINGLE_QUOTE), + new InsertQuoteAction(rstaBacktickAction, InsertQuoteAction.QuoteType.BACKTICK), new InsertTabAction(), new NextWordAction(nextWordAction, false), new NextWordAction(selectionNextWordAction, true), @@ -1700,6 +1702,114 @@ private void wrapSelection(RTextArea textArea) { } + /** + * Inserts a quote character. If the current language supports string literals with this + * quote character, the following additional logic occurs: + * + * This feature is meant to simplify the common case of typing single-line strings. + */ + public static class InsertQuoteAction extends InsertPairedCharacterAction { + + public enum QuoteType { + DOUBLE_QUOTE('"', TokenTypes.LITERAL_STRING_DOUBLE_QUOTE, TokenTypes.ERROR_STRING_DOUBLE), + SINGLE_QUOTE('\'', TokenTypes.LITERAL_CHAR, TokenTypes.ERROR_CHAR), + BACKTICK('`', TokenTypes.LITERAL_BACKQUOTE, -1); + + private final char ch; + private final int validTokenType; + private final int invalidTokenType; + + QuoteType(char ch, int validTokenType, int invalidTokenType) { + this.ch = ch; + this.validTokenType = validTokenType; + this.invalidTokenType = invalidTokenType; + } + } + + private final QuoteType quoteType; + private final String stringifiedQuoteTypeCh; + + public InsertQuoteAction(String actionName, QuoteType quoteType) { + super(actionName, quoteType.ch, quoteType.ch); + this.quoteType = quoteType; + stringifiedQuoteTypeCh = String.valueOf(quoteType.ch); + } + + @Override + public void actionPerformedImpl(ActionEvent e, RTextArea textArea) { + + if (!textArea.isEditable() || !textArea.isEnabled()) { + UIManager.getLookAndFeel().provideErrorFeedback(textArea); + return; + } + + RSyntaxTextArea rsta = (RSyntaxTextArea) textArea; + + if (!rsta.getInsertPairedCharacters() || + textArea.getSelectionStart() != textArea.getSelectionEnd() || + textArea.getTextMode() == RTextArea.OVERWRITE_MODE) { + super.actionPerformedImpl(e, textArea); + return; + } + + int offs = rsta.getCaretPosition(); + Token t = RSyntaxUtilities.getTokenAtOffsetOrLastTokenIfEndOfLine(rsta, offs); + int tokenType = t != null ? t.getType() : TokenTypes.NULL; + boolean isComment = t != null && t.isComment(); + + if (tokenType == quoteType.validTokenType) { + if (offs == t.getEndOffset() - 1) { + textArea.moveCaretPosition(offs + 1); // Force a replacement to ensure undo is contiguous + textArea.replaceSelection(stringifiedQuoteTypeCh); + textArea.setCaretPosition(offs + 1); + } + else { + super.actionPerformedImpl(e, textArea); + } + } + else if (isComment || tokenType == quoteType.invalidTokenType) { + // We could be smarter here for invalid quoted literals - if we knew whether the language + // used '\' as an escape character, and the caret is NOT between a '\' and the closing + // quote, we could then assume it's an invalid string due to e.g. a bad escape char, and + // overwrite the closing quote. But for now we're just doing nothing in this case + super.actionPerformedImpl(e, textArea); // Just insert the character + } + else { + insertEmptyQuoteLiteral(rsta); + } + } + + private void insertEmptyQuoteLiteral(RSyntaxTextArea textArea) { + + textArea.beginAtomicEdit(); + + try { + + textArea.replaceSelection(stringifiedQuoteTypeCh); + + // Check whether the starting quote started a string literal. If it did, + // enter the closing quote. This is done to sniff out language tht don't + // support string literals. + int caretPos = textArea.getCaretPosition(); + Token t = RSyntaxUtilities.getTokenAtOffsetOrLastTokenIfEndOfLine(textArea, caretPos); + int tokenType = t != null ? t.getType() : TokenTypes.NULL; + if (tokenType == quoteType.validTokenType || tokenType == quoteType.invalidTokenType) { + textArea.replaceSelection(stringifiedQuoteTypeCh); + textArea.setCaretPosition(textArea.getCaretPosition() - 1); + } + } finally { + textArea.endAtomicEdit(); + } + } + } + + /** * Action for inserting tabs. This is extended to "block indent" a * group of contiguous lines if they are selected. diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java index 56bb7c483..bc9891899 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java @@ -964,14 +964,33 @@ public static Token getPreviousImportantTokenFromOffs( * @return The token, or null if the offset is not valid. * @see #getTokenAtOffset(RSyntaxDocument, int) * @see #getTokenAtOffset(Token, int) + * @see #getTokenAtOffsetOrLastTokenIfEndOfLine(RSyntaxTextArea, int) */ - public static Token getTokenAtOffset(RSyntaxTextArea textArea, - int offset) { + public static Token getTokenAtOffset(RSyntaxTextArea textArea, int offset) { RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); return RSyntaxUtilities.getTokenAtOffset(doc, offset); } + /** + * Returns the token at the specified offset. If the offset + * is at the very end of a line, the "last" token in that line is returned + * instead (which may be {@code null} if the line is empty). + * + * @param textArea The text area. + * @param offset The offset at which to get the token. + * @return The token at offset, or null if + * the offset is invalid or there is no token at that offset. + * @see #getTokenAtOffset(RSyntaxTextArea, int) + * @see #getTokenAtOffset(RSyntaxDocument, int) + * @see #getTokenAtOffset(Token, int) + */ + public static Token getTokenAtOffsetOrLastTokenIfEndOfLine(RSyntaxTextArea textArea, int offset) { + RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); + return RSyntaxUtilities.getTokenAtOffsetOrLastTokenIfEndOfLine(doc, offset); + } + + /** * Returns the token at the specified offset. * @@ -980,9 +999,9 @@ public static Token getTokenAtOffset(RSyntaxTextArea textArea, * @return The token, or null if the offset is not valid. * @see #getTokenAtOffset(RSyntaxTextArea, int) * @see #getTokenAtOffset(Token, int) + * @see #getTokenAtOffsetOrLastTokenIfEndOfLine(RSyntaxDocument, int) */ - public static Token getTokenAtOffset(RSyntaxDocument doc, - int offset) { + public static Token getTokenAtOffset(RSyntaxDocument doc, int offset) { Element root = doc.getDefaultRootElement(); int lineIndex = root.getElementIndex(offset); Token t = doc.getTokenListForLine(lineIndex); @@ -990,6 +1009,25 @@ public static Token getTokenAtOffset(RSyntaxDocument doc, } + /** + * Returns the token at the specified offset. + * + * @param doc The document. + * @param offset The offset of the token. + * @return The token, or null if the offset is not valid or + * there is no token at that offset. + * @see #getTokenAtOffset(RSyntaxTextArea, int) + * @see #getTokenAtOffset(RSyntaxDocument, int) + * @see #getTokenAtOffset(Token, int) + */ + public static Token getTokenAtOffsetOrLastTokenIfEndOfLine(RSyntaxDocument doc, int offset) { + Element root = doc.getDefaultRootElement(); + int lineIndex = root.getElementIndex(offset); + Token t = doc.getTokenListForLine(lineIndex); + return RSyntaxUtilities.getTokenAtOffsetOrLastTokenIfEndOfLine(t, offset); + } + + /** * Returns the token at the specified index, or null if * the given offset isn't in this token list's range.
@@ -1002,6 +1040,7 @@ public static Token getTokenAtOffset(RSyntaxDocument doc, * none of the tokens are at that offset. * @see #getTokenAtOffset(RSyntaxTextArea, int) * @see #getTokenAtOffset(RSyntaxDocument, int) + * @see #getTokenAtOffsetOrLastTokenIfEndOfLine(Token, int) */ public static Token getTokenAtOffset(Token tokenList, int offset) { for (Token t=tokenList; t!=null && t.isPaintable(); t=t.getNextToken()){ @@ -1013,6 +1052,33 @@ public static Token getTokenAtOffset(Token tokenList, int offset) { } + /** + * Returns the token at the specified index, or null if + * the given offset isn't in this token list's range. If the offset + * is at the very end of the token list, the "last" token is returned + * (which may be {@code null} if the token list is empty).
+ * Note that this method does NOT check to see if tokenList + * is null; callers should check for themselves. + * + * @param tokenList The list of tokens in which to search. + * @param offset The offset at which to get the token. + * @return The token at offset, or null if + * none of the tokens are at that offset. + * @see #getTokenAtOffset(RSyntaxTextArea, int) + * @see #getTokenAtOffset(RSyntaxDocument, int) + * @see #getTokenAtOffset(Token, int) + */ + public static Token getTokenAtOffsetOrLastTokenIfEndOfLine(Token tokenList, int offset) { + for (Token t=tokenList; t!=null && t.isPaintable(); t=t.getNextToken()){ + if (t.containsPosition(offset) || + (offset == t.getEndOffset() && (t.getNextToken() == null || !t.getNextToken().isPaintable()))) { + return t; + } + } + return null; + } + + /** * Returns the end of the word at the given offset. * diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitInsertQuoteActionTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitInsertQuoteActionTest.java new file mode 100644 index 000000000..74df20689 --- /dev/null +++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaEditorKitInsertQuoteActionTest.java @@ -0,0 +1,251 @@ +/* + * This library is distributed under a modified BSD license. See the included + * LICENSE file for details. + */ +package org.fife.ui.rsyntaxtextarea; + +import org.fife.ui.SwingRunnerExtension; +import org.fife.ui.rtextarea.RecordableTextAction; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.awt.event.ActionEvent; + + +/** + * Unit tests for the {@link RSyntaxTextAreaEditorKit.InsertQuoteAction} class. + * + * @author Robert Futrell + * @version 1.0 + */ +@ExtendWith(SwingRunnerExtension.class) +class RSyntaxTextAreaEditorKitInsertQuoteActionTest extends AbstractRSyntaxTextAreaTest { + + @Test + void testActionPerformedImpl_notEditable() { + + RSyntaxTextArea textArea = createTextArea(); + textArea.setEditable(false); + String expected = textArea.getText(); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + Assertions.assertEquals(expected, textArea.getText()); // Unchanged + } + + @Test + void testActionPerformedImpl_notEnabled() { + + RSyntaxTextArea textArea = createTextArea(); + textArea.setEnabled(false); + String expected = textArea.getText(); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + Assertions.assertEquals(expected, textArea.getText()); // Unchanged + } + + @Test + void testActionPerformedImpl_disabled_noSelection() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setInsertPairedCharacters(false); + textArea.setCaretPosition(origContent.indexOf("word")); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + Assertions.assertEquals("one \"word here", textArea.getText()); + } + + @Test + void testActionPerformedImpl_disabled_selection() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setInsertPairedCharacters(false); + textArea.setCaretPosition(origContent.indexOf("word")); + textArea.moveCaretPosition(textArea.getCaretPosition() + 4); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + Assertions.assertEquals("one \" here", textArea.getText()); + } + + @Test + void testActionPerformedImpl_enabled_selection() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setCaretPosition(origContent.indexOf("word")); + textArea.moveCaretPosition(textArea.getCaretPosition() + 4); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // Selection is simply wrapped in quotes if there is a selection + Assertions.assertEquals("one \"word\" here", textArea.getText()); + } + + @Test + void testActionPerformedImpl_enabled_overwriteMode() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setTextMode(RSyntaxTextArea.OVERWRITE_MODE); + textArea.setCaretPosition(origContent.indexOf("word")); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // Overwrite mode inserts a single quote + Assertions.assertEquals("one \"ord here", textArea.getText()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_notInString_languageWithInvalidStrings() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setCaretPosition(origContent.indexOf("word")); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // Both the opening and closing quotes are inserted + Assertions.assertEquals("one \"\"word here", textArea.getText()); + Assertions.assertEquals("one \"".length(), textArea.getCaretPosition()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_notInString_languageWithOnlyValidStrings() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_PERL, origContent); + textArea.setCaretPosition(origContent.indexOf("word")); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // Both the opening and closing quotes are inserted + Assertions.assertEquals("one \"\"word here", textArea.getText()); + Assertions.assertEquals("one \"".length(), textArea.getCaretPosition()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_notInString_languageWithoutStrings() { + + String origContent = "one word here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_NONE, origContent); + textArea.setCaretPosition(origContent.indexOf("word")); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // Both the opening and closing quotes are inserted + Assertions.assertEquals("one \"word here", textArea.getText()); + Assertions.assertEquals("one \"".length(), textArea.getCaretPosition()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_inString_middle() { + + String origContent = "one \"word\" here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setCaretPosition(origContent.indexOf("rd")); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // A new closing quote is inserted + Assertions.assertEquals("one \"wo\"rd\" here", textArea.getText()); + Assertions.assertEquals("one \"wo\"".length(), textArea.getCaretPosition()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_inString_atEndQuote() { + + String origContent = "one \"word\" here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setCaretPosition(origContent.lastIndexOf('"')); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // The existing closing quote is overwritten + Assertions.assertEquals("one \"word\" here", textArea.getText()); + Assertions.assertEquals("one \"word\"".length(), textArea.getCaretPosition()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_inInvalidString() { + + String origContent = "one \"w\\lord\" here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setCaretPosition(origContent.lastIndexOf('"')); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // A single double quote is inserted - we don't do anything for invalid strings currently + Assertions.assertEquals("one \"w\\lord\"\" here", textArea.getText()); + Assertions.assertEquals("one \"w\\lord\"".length(), textArea.getCaretPosition()); + } + + @Test + void testActionPerformedImpl_enabled_insertMode_inComment_atEndQuote() { + + String origContent = "// one \"word\" here"; + + RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_JAVA, origContent); + textArea.setCaretPosition(origContent.lastIndexOf('"')); + + RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test", + RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE); + ActionEvent e = createActionEvent(textArea, "\""); + a.actionPerformedImpl(e, textArea); + + // A single double quote is inserted - we don't look for strings in comments + Assertions.assertEquals("// one \"word\"\" here", textArea.getText()); + Assertions.assertEquals("// one \"word\"".length(), textArea.getCaretPosition()); + } +} diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/TokenImplTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/TokenImplTest.java index 8caf5ff55..8dfd4b0f9 100755 --- a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/TokenImplTest.java +++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/TokenImplTest.java @@ -52,4 +52,145 @@ void testGetHTMLRepresentation_problemChars() { } + @Test + void testIs_1arg_charArray_differentLengths() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is("while".toCharArray())); + } + + + @Test + void testIs_1arg_charArray_sameLengthsButDifferent() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is("foo".toCharArray())); + } + + + @Test + void testIs_1arg_charArray_same() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertTrue(token.is("for".toCharArray())); + } + + + @Test + void testIs_2arg_charArray_differentLengths() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is(TokenTypes.IDENTIFIER, "while".toCharArray())); + } + + + @Test + void testIs_2arg_charArray_sameLengthsButDifferent() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is(TokenTypes.IDENTIFIER, "foo".toCharArray())); + } + + + @Test + void testIs_2arg_charArray_sameLexemeButDifferentType() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is(TokenTypes.RESERVED_WORD, "for".toCharArray())); + } + + + @Test + void testIs_2arg_charArray_same() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertTrue(token.is(token.getType(), "for".toCharArray())); + } + + + @Test + void testIs_2arg_string_differentLengths() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is(TokenTypes.IDENTIFIER, "while")); + } + + + @Test + void testIs_2arg_string_sameLengthsButDifferent() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is(TokenTypes.IDENTIFIER, "foo")); + } + + + @Test + void testIs_2arg_string_sameLexemeButDifferentType() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertFalse(token.is(TokenTypes.RESERVED_WORD, "for")); + } + + + @Test + void testIs_2arg_string_same() { + + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0); + + Assertions.assertTrue(token.is(token.getType(), "for")); + } + + + @Test + void testIsComment_true() { + + char[] ch = "for".toCharArray(); + + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.COMMENT_EOL, 0); + Assertions.assertTrue(token.isComment()); + + token = new TokenImpl(ch, 0, 2, 0, TokenTypes.COMMENT_MULTILINE, 0); + Assertions.assertTrue(token.isComment()); + + token = new TokenImpl(ch, 0, 2, 0, TokenTypes.COMMENT_DOCUMENTATION, 0); + Assertions.assertTrue(token.isComment()); + + token = new TokenImpl(ch, 0, 2, 0, TokenTypes.COMMENT_KEYWORD, 0); + Assertions.assertTrue(token.isComment()); + + token = new TokenImpl(ch, 0, 2, 0, TokenTypes.COMMENT_MARKUP, 0); + Assertions.assertTrue(token.isComment()); + + token = new TokenImpl(ch, 0, 2, 0, TokenTypes.MARKUP_COMMENT, 0); + Assertions.assertTrue(token.isComment()); + } + + + @Test + void testComment_false() { + char[] ch = "for".toCharArray(); + TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.RESERVED_WORD, 0); + Assertions.assertFalse(token.isComment()); + } }