Skip to content

Commit

Permalink
Fix #539: InsertQuoteAction can wrongly overwrite trailing char
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbylight committed Jun 9, 2024
1 parent 13523f5 commit 052431f
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,9 @@ public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
boolean isComment = t != null && t.isComment();

if (tokenType == quoteType.validTokenType) {
if (offs == t.getEndOffset() - 1) {
// Ensure you're overwriting the quote char to support TokenMakers that
// render unterminated strings as "valid" strings
if (t != null && offs == t.getEndOffset() - 1 && t.endsWith(quoteType.ch)) {
textArea.moveCaretPosition(offs + 1); // Force a replacement to ensure undo is contiguous
textArea.replaceSelection(stringifiedQuoteTypeCh);
textArea.setCaretPosition(offs + 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,23 @@ StringBuilder appendHTMLRepresentation(StringBuilder sb,
int documentToToken(int pos);


/**
* Returns whether this token's lexeme ends with the specified character.
*
* @param ch The character.
* @return Whether this token's lexeme ends with the specified character.
* @see #endsWith(char[])
* @see #startsWith(char[])
*/
boolean endsWith(char ch);


/**
* Returns whether this token's lexeme ends with the specified characters.
*
* @param ch The characters.
* @return Whether this token's lexeme ends with the specified characters.
* @see #endsWith(char)
* @see #startsWith(char[])
*/
boolean endsWith(char[] ch);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ public int documentToToken(int pos) {
}


@Override
public boolean endsWith(char ch) {
return textCount > 0 && text[textOffset + textCount - 1] == ch;
}


@Override
public boolean endsWith(char[] ch) {
if (ch==null || ch.length>textCount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,64 @@ void testActionPerformedImpl_enabled_insertMode_inString_middle() {
Assertions.assertEquals("one \"wo\"".length(), textArea.getCaretPosition());
}

@Test
void testActionPerformedImpl_enabled_insertMode_inString_middle_languageWithOnlyValidStrings() {

String origContent = "one \"word here";

RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_PERL, 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 closing quote is inserted
Assertions.assertEquals("one \"wo\"rd here", textArea.getText());
Assertions.assertEquals("one \"wo\"".length(), textArea.getCaretPosition());
}

/**
* See <a href="https://github.com/bobbylight/RSyntaxTextArea/issues/539">issue 539</a>.
*/
@Test
void testActionPerformedImpl_enabled_insertMode_inString_beforeLastChar_languageWithOnlyValidStrings() {

String origContent = "one \"word here";

// Caret just before the last char, "e"
RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_PERL, origContent);
textArea.setCaretPosition(origContent.length() - 1);

RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test",
RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE);
ActionEvent e = createActionEvent(textArea, "\"");
a.actionPerformedImpl(e, textArea);

// A closing quote is inserted, trailing "e" pushed forward
Assertions.assertEquals("one \"word her\"e", textArea.getText());
Assertions.assertEquals(textArea.getText().length() - 1, textArea.getCaretPosition());
}

@Test
void testActionPerformedImpl_enabled_insertMode_inString_afterLastChar_languageWithOnlyValidStrings() {

String origContent = "one \"word here";

RSyntaxTextArea textArea = createTextArea(SyntaxConstants.SYNTAX_STYLE_PERL, origContent);
textArea.setCaretPosition(origContent.length());

RecordableTextAction a = new RSyntaxTextAreaEditorKit.InsertQuoteAction("test",
RSyntaxTextAreaEditorKit.InsertQuoteAction.QuoteType.DOUBLE_QUOTE);
ActionEvent e = createActionEvent(textArea, "\"");
a.actionPerformedImpl(e, textArea);

// A closing quote is inserted after all text
Assertions.assertEquals("one \"word here\"", textArea.getText());
Assertions.assertEquals(textArea.getText().length(), textArea.getCaretPosition());
}

@Test
void testActionPerformedImpl_enabled_insertMode_inString_atEndQuote() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,71 @@
class TokenImplTest {


@Test
void testEndsWith_charAt_happyPath() {

char[] ch = "for".toCharArray();
TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0);

Assertions.assertNotEquals('a', token.charAt(0));
Assertions.assertEquals('f', token.charAt(0));
Assertions.assertEquals('o', token.charAt(1));
Assertions.assertEquals('r', token.charAt(2));
}


@Test
void testEndsWith_char_happyPath() {

char[] ch = "for".toCharArray();
TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0);

Assertions.assertFalse(token.endsWith('a'));
Assertions.assertTrue(token.endsWith('r'));
}


@Test
void testEndsWith_char_nullToken() {
Assertions.assertFalse(new TokenImpl().endsWith('a'));
}


@Test
void testEndsWith_charArray_happyPath() {

char[] ch = "for".toCharArray();
TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0);

Assertions.assertFalse(token.endsWith("xxx".toCharArray()));
Assertions.assertFalse(token.endsWith("afor".toCharArray()));

Assertions.assertTrue(token.endsWith("r".toCharArray()));
Assertions.assertTrue(token.endsWith("or".toCharArray()));
Assertions.assertTrue(token.endsWith("for".toCharArray()));
}


@Test
void testEndsWith_charArray_null() {

char[] ch = "for".toCharArray();
TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0);

Assertions.assertFalse(token.endsWith(null));
}


@Test
void testEndsWith_charArray_emptyArray() {

char[] ch = "for".toCharArray();
TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.IDENTIFIER, 0);

Assertions.assertTrue(token.endsWith("".toCharArray()));
}


@Test
void testGetHTMLRepresentation_happyPath() {

Expand Down Expand Up @@ -188,7 +253,7 @@ void testIsComment_true() {


@Test
void testComment_false() {
void testIsComment_false() {
char[] ch = "for".toCharArray();
TokenImpl token = new TokenImpl(ch, 0, 2, 0, TokenTypes.RESERVED_WORD, 0);
Assertions.assertFalse(token.isComment());
Expand Down

0 comments on commit 052431f

Please sign in to comment.