From 292c890cc7472809a5fe2edefebf4561d63ea035 Mon Sep 17 00:00:00 2001 From: Nikita Eshkeev Date: Tue, 17 Nov 2020 19:07:02 +0300 Subject: [PATCH] [properties] IDEA-215803 Join and split string works incorrectly inside property files when cursor inside property value This patch amends PropertiesHighlighter with not highlighting invalid string escapes since Properties are meant to ignore backslashes if they don't precede a single quote, double quote or backslash character. This patch amends EnterInPropertiesFileHandler with splitting a property value right at the place of the caret and escaping optional whitespaces with a backslash on the next line if the caret is at a whitespace. Signed-off-by: Nikita Eshkeev GitOrigin-RevId: 0d8b66c63db1d81a6deada3c26b808fa3b61d46b --- ...aretAtAfterMultipleWhitespaces.properties} | 2 +- ...aretAtBeforeMultipleWhitespaces.properties | 3 ++ ...aretAtMiddleMultipleWhitespaces.properties | 3 ++ ...CaretAtAfterMultipleWhitespaces.properties | 2 + ...aretAtBeforeMultipleWhitespaces.properties | 2 + ...aretAtMiddleMultipleWhitespaces.properties | 2 + ...eforeCaretAtMultipleWhitespaces.properties | 2 - ...hEmptyLineWIthUnclosedBackslash.properties | 2 + ...erJoinWithLeadingWhitespaceLine.properties | 2 + ...hMultipleLeadingWhitespacesLine.properties | 2 + .../afterJoiningWithEmptyLine.properties | 2 - ...hEmptyLineWIthUnclosedBackslash.properties | 2 + ...reJoinWithLeadingWhitespaceLine.properties | 3 ++ ...hMultipleLeadingWhitespacesLine.properties | 3 ++ .../beforeJoiningWithEmptyLine.properties | 2 - .../highlighting/InvalidEscape.properties | 10 +++-- .../properties/PropertiesHighlighter.java | 13 +------ .../EnterInPropertiesFileHandler.java | 20 +++------- .../PropertiesJoinLinesHandler.java | 37 ++++++++++++++----- .../PropertiesBackSlashSlash_after.properties | 2 +- .../joinLines/Properties_after.properties | 2 +- 21 files changed, 71 insertions(+), 47 deletions(-) rename java/java-tests/testData/properties/enterhandler/{afterCaretAtMultipleWhitespaces.properties => afterCaretAtAfterMultipleWhitespaces.properties} (57%) create mode 100644 java/java-tests/testData/properties/enterhandler/afterCaretAtBeforeMultipleWhitespaces.properties create mode 100644 java/java-tests/testData/properties/enterhandler/afterCaretAtMiddleMultipleWhitespaces.properties create mode 100644 java/java-tests/testData/properties/enterhandler/beforeCaretAtAfterMultipleWhitespaces.properties create mode 100644 java/java-tests/testData/properties/enterhandler/beforeCaretAtBeforeMultipleWhitespaces.properties create mode 100644 java/java-tests/testData/properties/enterhandler/beforeCaretAtMiddleMultipleWhitespaces.properties delete mode 100644 java/java-tests/testData/properties/enterhandler/beforeCaretAtMultipleWhitespaces.properties create mode 100644 java/java-tests/testData/properties/joinlines/afterJoinWithEmptyLineWIthUnclosedBackslash.properties create mode 100644 java/java-tests/testData/properties/joinlines/afterJoinWithLeadingWhitespaceLine.properties create mode 100644 java/java-tests/testData/properties/joinlines/afterJoinWithMultipleLeadingWhitespacesLine.properties create mode 100644 java/java-tests/testData/properties/joinlines/beforeJoinWithEmptyLineWIthUnclosedBackslash.properties create mode 100644 java/java-tests/testData/properties/joinlines/beforeJoinWithLeadingWhitespaceLine.properties create mode 100644 java/java-tests/testData/properties/joinlines/beforeJoinWithMultipleLeadingWhitespacesLine.properties diff --git a/java/java-tests/testData/properties/enterhandler/afterCaretAtMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/afterCaretAtAfterMultipleWhitespaces.properties similarity index 57% rename from java/java-tests/testData/properties/enterhandler/afterCaretAtMultipleWhitespaces.properties rename to java/java-tests/testData/properties/enterhandler/afterCaretAtAfterMultipleWhitespaces.properties index ba36aa2b395d..c865d7c6a963 100644 --- a/java/java-tests/testData/properties/enterhandler/afterCaretAtMultipleWhitespaces.properties +++ b/java/java-tests/testData/properties/enterhandler/afterCaretAtAfterMultipleWhitespaces.properties @@ -1,3 +1,3 @@ # "_ignore" "true" -message=hello \ +message=hello \ world diff --git a/java/java-tests/testData/properties/enterhandler/afterCaretAtBeforeMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/afterCaretAtBeforeMultipleWhitespaces.properties new file mode 100644 index 000000000000..71742abe7478 --- /dev/null +++ b/java/java-tests/testData/properties/enterhandler/afterCaretAtBeforeMultipleWhitespaces.properties @@ -0,0 +1,3 @@ +# "_ignore" "true" +message=hello\ + \ world diff --git a/java/java-tests/testData/properties/enterhandler/afterCaretAtMiddleMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/afterCaretAtMiddleMultipleWhitespaces.properties new file mode 100644 index 000000000000..78abf375c77b --- /dev/null +++ b/java/java-tests/testData/properties/enterhandler/afterCaretAtMiddleMultipleWhitespaces.properties @@ -0,0 +1,3 @@ +# "_ignore" "true" +message=hello \ + \ world diff --git a/java/java-tests/testData/properties/enterhandler/beforeCaretAtAfterMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/beforeCaretAtAfterMultipleWhitespaces.properties new file mode 100644 index 000000000000..26295f5cb7fc --- /dev/null +++ b/java/java-tests/testData/properties/enterhandler/beforeCaretAtAfterMultipleWhitespaces.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello world diff --git a/java/java-tests/testData/properties/enterhandler/beforeCaretAtBeforeMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/beforeCaretAtBeforeMultipleWhitespaces.properties new file mode 100644 index 000000000000..405117efd4e1 --- /dev/null +++ b/java/java-tests/testData/properties/enterhandler/beforeCaretAtBeforeMultipleWhitespaces.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello world diff --git a/java/java-tests/testData/properties/enterhandler/beforeCaretAtMiddleMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/beforeCaretAtMiddleMultipleWhitespaces.properties new file mode 100644 index 000000000000..073c57d6ca8f --- /dev/null +++ b/java/java-tests/testData/properties/enterhandler/beforeCaretAtMiddleMultipleWhitespaces.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello world diff --git a/java/java-tests/testData/properties/enterhandler/beforeCaretAtMultipleWhitespaces.properties b/java/java-tests/testData/properties/enterhandler/beforeCaretAtMultipleWhitespaces.properties deleted file mode 100644 index fc9b2800cc53..000000000000 --- a/java/java-tests/testData/properties/enterhandler/beforeCaretAtMultipleWhitespaces.properties +++ /dev/null @@ -1,2 +0,0 @@ -# "_ignore" "true" -message=hello world diff --git a/java/java-tests/testData/properties/joinlines/afterJoinWithEmptyLineWIthUnclosedBackslash.properties b/java/java-tests/testData/properties/joinlines/afterJoinWithEmptyLineWIthUnclosedBackslash.properties new file mode 100644 index 000000000000..b9c72db1d728 --- /dev/null +++ b/java/java-tests/testData/properties/joinlines/afterJoinWithEmptyLineWIthUnclosedBackslash.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello, world \ No newline at end of file diff --git a/java/java-tests/testData/properties/joinlines/afterJoinWithLeadingWhitespaceLine.properties b/java/java-tests/testData/properties/joinlines/afterJoinWithLeadingWhitespaceLine.properties new file mode 100644 index 000000000000..d7d58b98b482 --- /dev/null +++ b/java/java-tests/testData/properties/joinlines/afterJoinWithLeadingWhitespaceLine.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello world diff --git a/java/java-tests/testData/properties/joinlines/afterJoinWithMultipleLeadingWhitespacesLine.properties b/java/java-tests/testData/properties/joinlines/afterJoinWithMultipleLeadingWhitespacesLine.properties new file mode 100644 index 000000000000..d7d58b98b482 --- /dev/null +++ b/java/java-tests/testData/properties/joinlines/afterJoinWithMultipleLeadingWhitespacesLine.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello world diff --git a/java/java-tests/testData/properties/joinlines/afterJoiningWithEmptyLine.properties b/java/java-tests/testData/properties/joinlines/afterJoiningWithEmptyLine.properties index 352d5085c594..209c463aad8e 100644 --- a/java/java-tests/testData/properties/joinlines/afterJoiningWithEmptyLine.properties +++ b/java/java-tests/testData/properties/joinlines/afterJoiningWithEmptyLine.properties @@ -1,5 +1,3 @@ # "_ignore" "true" -# hello -# world message=hello \ world diff --git a/java/java-tests/testData/properties/joinlines/beforeJoinWithEmptyLineWIthUnclosedBackslash.properties b/java/java-tests/testData/properties/joinlines/beforeJoinWithEmptyLineWIthUnclosedBackslash.properties new file mode 100644 index 000000000000..49d4655668d9 --- /dev/null +++ b/java/java-tests/testData/properties/joinlines/beforeJoinWithEmptyLineWIthUnclosedBackslash.properties @@ -0,0 +1,2 @@ +# "_ignore" "true" +message=hello, world\ diff --git a/java/java-tests/testData/properties/joinlines/beforeJoinWithLeadingWhitespaceLine.properties b/java/java-tests/testData/properties/joinlines/beforeJoinWithLeadingWhitespaceLine.properties new file mode 100644 index 000000000000..f801e8cd1bbd --- /dev/null +++ b/java/java-tests/testData/properties/joinlines/beforeJoinWithLeadingWhitespaceLine.properties @@ -0,0 +1,3 @@ +# "_ignore" "true" +message=hello \ + \ world diff --git a/java/java-tests/testData/properties/joinlines/beforeJoinWithMultipleLeadingWhitespacesLine.properties b/java/java-tests/testData/properties/joinlines/beforeJoinWithMultipleLeadingWhitespacesLine.properties new file mode 100644 index 000000000000..91e75464d40f --- /dev/null +++ b/java/java-tests/testData/properties/joinlines/beforeJoinWithMultipleLeadingWhitespacesLine.properties @@ -0,0 +1,3 @@ +# "_ignore" "true" +message=hello\ + \ world diff --git a/java/java-tests/testData/properties/joinlines/beforeJoiningWithEmptyLine.properties b/java/java-tests/testData/properties/joinlines/beforeJoiningWithEmptyLine.properties index 95f2df45755f..31bf21656085 100644 --- a/java/java-tests/testData/properties/joinlines/beforeJoiningWithEmptyLine.properties +++ b/java/java-tests/testData/properties/joinlines/beforeJoiningWithEmptyLine.properties @@ -1,6 +1,4 @@ # "_ignore" "true" -# hello -# world message=hello \ \ world diff --git a/plugins/java-i18n/testData/highlighting/InvalidEscape.properties b/plugins/java-i18n/testData/highlighting/InvalidEscape.properties index 180cbcba6ae7..214198a201ff 100644 --- a/plugins/java-i18n/testData/highlighting/InvalidEscape.properties +++ b/plugins/java-i18n/testData/highlighting/InvalidEscape.properties @@ -1,5 +1,7 @@ -\ a\ b\ =x\x +\ a\ b\ =x\x a\n\r\b2 = x\\\n\r v -spaceinthemiddle=a\ b -ii\dnnvalidkey -framingSpaces = \ xxx\ \ \ \ No newline at end of file +spaceinthemiddle=a\ b +ii\dnnvalidkey +framingSpaces = \ xxx\ \ \ +hello=world,\ + \ universe \ No newline at end of file diff --git a/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/PropertiesHighlighter.java b/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/PropertiesHighlighter.java index ad206c72454b..d297380f9117 100644 --- a/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/PropertiesHighlighter.java +++ b/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/PropertiesHighlighter.java @@ -19,7 +19,6 @@ import java.util.Map; public class PropertiesHighlighter extends SyntaxHighlighterBase { private static final Map keys1; - private static final Map keys2; @Override @NotNull @@ -46,10 +45,7 @@ public class PropertiesHighlighter extends SyntaxHighlighterBase { "PROPERTIES.KEY_VALUE_SEPARATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN ); - public static final TextAttributesKey PROPERTIES_VALID_STRING_ESCAPE = TextAttributesKey.createTextAttributesKey( - "PROPERTIES.VALID_STRING_ESCAPE", - DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE - ); + public static final TextAttributesKey PROPERTIES_INVALID_STRING_ESCAPE = TextAttributesKey.createTextAttributesKey( "PROPERTIES.INVALID_STRING_ESCAPE", DefaultLanguageHighlighterColors.INVALID_STRING_ESCAPE @@ -57,22 +53,18 @@ public class PropertiesHighlighter extends SyntaxHighlighterBase { static { keys1 = new HashMap<>(); - keys2 = new HashMap<>(); keys1.put(PropertiesTokenTypes.VALUE_CHARACTERS, PROPERTY_VALUE); keys1.put(PropertiesTokenTypes.END_OF_LINE_COMMENT, PROPERTY_COMMENT); keys1.put(PropertiesTokenTypes.KEY_CHARACTERS, PROPERTY_KEY); keys1.put(PropertiesTokenTypes.KEY_VALUE_SEPARATOR, PROPERTY_KEY_VALUE_SEPARATOR); - keys1.put(StringEscapesTokenTypes.VALID_STRING_ESCAPE_TOKEN, PROPERTIES_VALID_STRING_ESCAPE); - // in fact all back-slashed escapes are allowed - keys1.put(StringEscapesTokenTypes.INVALID_CHARACTER_ESCAPE_TOKEN, PROPERTIES_INVALID_STRING_ESCAPE); keys1.put(StringEscapesTokenTypes.INVALID_UNICODE_ESCAPE_TOKEN, PROPERTIES_INVALID_STRING_ESCAPE); } @Override public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) { - return SyntaxHighlighterBase.pack(keys1.get(tokenType), keys2.get(tokenType)); + return SyntaxHighlighterBase.pack(keys1.get(tokenType)); } public static final Map> DISPLAY_NAMES = ContainerUtil.>immutableMapBuilder() @@ -80,7 +72,6 @@ public class PropertiesHighlighter extends SyntaxHighlighterBase { .put(PROPERTY_VALUE, new Pair<>(PropertiesBundle.message("options.properties.attribute.descriptor.property.value"), null)) .put(PROPERTY_KEY_VALUE_SEPARATOR, new Pair<>(PropertiesBundle.message("options.properties.attribute.descriptor.key.value.separator"), null)) .put(PROPERTY_COMMENT, new Pair<>(PropertiesBundle.message("options.properties.attribute.descriptor.comment"), null)) - .put(PROPERTIES_VALID_STRING_ESCAPE, new Pair<>(PropertiesBundle.message("options.properties.attribute.descriptor.valid.string.escape"), null)) .put(PROPERTIES_INVALID_STRING_ESCAPE, Pair.create(PropertiesBundle.message("options.properties.attribute.descriptor.invalid.string.escape"), HighlightSeverity.WARNING)) .build(); } diff --git a/plugins/properties/src/com/intellij/lang/properties/EnterInPropertiesFileHandler.java b/plugins/properties/src/com/intellij/lang/properties/EnterInPropertiesFileHandler.java index ede28149f4e1..d73570d98cdb 100644 --- a/plugins/properties/src/com/intellij/lang/properties/EnterInPropertiesFileHandler.java +++ b/plugins/properties/src/com/intellij/lang/properties/EnterInPropertiesFileHandler.java @@ -49,11 +49,12 @@ final class EnterInPropertiesFileHandler extends EnterHandlerDelegateAdapter { final IElementType elementType = psiAtOffset == null ? null : psiAtOffset.getNode().getElementType(); if (elementType == PropertiesTokenTypes.VALUE_CHARACTERS) { - toInsert = "\\\n "; - // if the split at a whitespace, move the caret forward to keep all the whitespaces in the same line - if (text.charAt(caretOffset) == ' ') { - final int leadingWhitespaces = getLeadingWhitespacesNumber(text.substring(caretOffset)); - caretOffset += leadingWhitespaces; + if (text.charAt(caretOffset) == ' ' || text.charAt(caretOffset) == '\t') { + // escape the whitespace on the next line like "\ " + toInsert = "\\\n \\"; + } + else { + toInsert = "\\\n "; } } else if (elementType == PropertiesTokenTypes.END_OF_LINE_COMMENT && "#!".indexOf(document.getText().charAt(caretOffset)) == -1) { @@ -70,13 +71,4 @@ final class EnterInPropertiesFileHandler extends EnterHandlerDelegateAdapter { editor.getSelectionModel().removeSelection(); } - static int getLeadingWhitespacesNumber(String text) { - final long leadingWhitespaces = text - .chars() - .takeWhile(c -> c == ' ') - .count(); - - return (int)leadingWhitespaces; - } - } diff --git a/plugins/properties/src/com/intellij/lang/properties/PropertiesJoinLinesHandler.java b/plugins/properties/src/com/intellij/lang/properties/PropertiesJoinLinesHandler.java index 819fd0832be2..37f3fbf88882 100644 --- a/plugins/properties/src/com/intellij/lang/properties/PropertiesJoinLinesHandler.java +++ b/plugins/properties/src/com/intellij/lang/properties/PropertiesJoinLinesHandler.java @@ -19,20 +19,39 @@ import com.intellij.codeInsight.editorActions.JoinLinesHandlerDelegate; import com.intellij.lang.properties.psi.PropertiesFile; import com.intellij.openapi.editor.Document; import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class PropertiesJoinLinesHandler implements JoinLinesHandlerDelegate { @Override - public int tryJoinLines(@NotNull final Document doc, @NotNull final PsiFile psiFile, int start, final int end) { + public int tryJoinLines(@NotNull final Document doc, @NotNull final PsiFile psiFile, int start, int end) { if (!(psiFile instanceof PropertiesFile)) return -1; - // strip continuation char and two leading whitespaces - if (PropertiesUtil.isUnescapedBackSlashAtTheEnd(doc.getText().substring(0, start + 1))) { - final String text = doc.getText().substring(start + 1); - final int leadingWhitespaces = EnterInPropertiesFileHandler.getLeadingWhitespacesNumber(text); - doc.deleteString(start, start + 1 + leadingWhitespaces); - start--; + final String documentText = doc.getText(); + final String documentTextTillEndOfFirstLine = documentText.substring(0, start + 1); + if (!PropertiesUtil.isUnescapedBackSlashAtTheEnd(documentTextTillEndOfFirstLine)) return CANNOT_JOIN; + + if (end < documentText.length() && startsWithEscapedWhitespace(documentText.substring(end))) { + // if the second line starts with escaped whitespace (e.g. '\ '), then remove it too + end ++; } - return start + 1; + + // strip the continuation char '\', + // the leading whitespaces on the second line and + // the optional '\' if the text on the second line starts with '\ ' + doc.deleteString(start, end); + return start; } -} + + @Contract(value = "null -> false", pure = true) + public static boolean startsWithEscapedWhitespace(@Nullable String text) { + if (text == null) return false; + if (text.length() < 2) return false; + + final char backslash = text.charAt(0); + final char whitespace = text.charAt(1); + + return backslash == '\\' && (whitespace == ' ' || whitespace == '\t'); + } +} \ No newline at end of file diff --git a/plugins/properties/tests/testData/propertiesFile/joinLines/PropertiesBackSlashSlash_after.properties b/plugins/properties/tests/testData/propertiesFile/joinLines/PropertiesBackSlashSlash_after.properties index c7df67c00b79..855d661904de 100644 --- a/plugins/properties/tests/testData/propertiesFile/joinLines/PropertiesBackSlashSlash_after.properties +++ b/plugins/properties/tests/testData/propertiesFile/joinLines/PropertiesBackSlashSlash_after.properties @@ -1 +1 @@ -a=B\\ x=Y +a=B\\ x=Y diff --git a/plugins/properties/tests/testData/propertiesFile/joinLines/Properties_after.properties b/plugins/properties/tests/testData/propertiesFile/joinLines/Properties_after.properties index 94297e7d2a6a..da7371eb68c6 100644 --- a/plugins/properties/tests/testData/propertiesFile/joinLines/Properties_after.properties +++ b/plugins/properties/tests/testData/propertiesFile/joinLines/Properties_after.properties @@ -1 +1 @@ -a=B x=Y +a=B x=Y