[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 <nikita.eshkeev@jetbrains.com>

GitOrigin-RevId: 0d8b66c63db1d81a6deada3c26b808fa3b61d46b
This commit is contained in:
Nikita Eshkeev
2020-11-17 19:07:02 +03:00
committed by intellij-monorepo-bot
parent d60d8682fa
commit 292c890cc7
21 changed files with 71 additions and 47 deletions

View File

@@ -0,0 +1,3 @@
# "_ignore" "true"
message=hello\
\ world

View File

@@ -0,0 +1,3 @@
# "_ignore" "true"
message=hello \
\ world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
message=hello <caret>world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
message=hello<caret> world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
message=hello <caret> world

View File

@@ -1,2 +0,0 @@
# "_ignore" "true"
message=hello <caret> world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
message=hello, world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
message=hello world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
message=hello world

View File

@@ -1,5 +1,3 @@
# "_ignore" "true"
# hello
# world
message=hello \
world

View File

@@ -0,0 +1,2 @@
# "_ignore" "true"
<caret>message=hello, world\

View File

@@ -0,0 +1,3 @@
# "_ignore" "true"
<caret>message=hello \
\ world

View File

@@ -0,0 +1,3 @@
# "_ignore" "true"
<caret>message=hello\
\ world

View File

@@ -1,6 +1,4 @@
# "_ignore" "true"
# hello
# world
<caret>message=hello \
\
world

View File

@@ -1,5 +1,7 @@
\ a\ b\ =x<warning descr="Invalid string escape">\x</warning>
\ a\ b\ =x\x
a\n\r\b2 = x\\\n\r v
spaceinthemiddle=a<warning descr="Invalid string escape">\ </warning>b
ii<warning descr="Invalid string escape">\d</warning>nnvalidkey
framingSpaces = \ xxx\ \ \
spaceinthemiddle=a\ b
ii\dnnvalidkey
framingSpaces = \ xxx\ \ \
hello=world,\
\ universe

View File

@@ -19,7 +19,6 @@ import java.util.Map;
public class PropertiesHighlighter extends SyntaxHighlighterBase {
private static final Map<IElementType, TextAttributesKey> keys1;
private static final Map<IElementType, TextAttributesKey> 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<TextAttributesKey, Pair<@Nls String, HighlightSeverity>> DISPLAY_NAMES = ContainerUtil.<TextAttributesKey, Pair<@Nls String, HighlightSeverity>>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();
}

View File

@@ -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;
}
}

View File

@@ -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');
}
}