diff --git a/plugins/yaml/src/org/jetbrains/yaml/psi/impl/YAMLQuotedTextImpl.java b/plugins/yaml/src/org/jetbrains/yaml/psi/impl/YAMLQuotedTextImpl.java index 22ed171db900..33356c55a85f 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/psi/impl/YAMLQuotedTextImpl.java +++ b/plugins/yaml/src/org/jetbrains/yaml/psi/impl/YAMLQuotedTextImpl.java @@ -1,9 +1,12 @@ package org.jetbrains.yaml.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.YAMLTokenTypes; import org.jetbrains.yaml.YAMLUtil; @@ -12,6 +15,7 @@ import org.jetbrains.yaml.psi.YAMLQuotedText; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText { private final boolean myIsSingleQuoted; @@ -73,32 +77,29 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText } } + @SuppressWarnings("AssignmentToForLoopParameter") @Override protected List> getDecodeReplacements(@NotNull CharSequence input) { List> result = new ArrayList<>(); for (int i = 0; i + 1 < input.length(); ++i) { - final CharSequence subSequence = input.subSequence(i, i + 2); - final TextRange textRange = TextRange.create(i, i + 2); - if (isSingleQuote() && "''".equals(subSequence)) { - result.add(Pair.create(textRange, "'")); + if (isSingleQuote() && input.charAt(i) == '\'' && input.charAt(i + 1) == '\'') { + result.add(Pair.create(TextRange.from(i, 2), "'")); + i++; } - else if (!isSingleQuote() && "\\\n".equals(subSequence)) { - result.add(Pair.create(textRange, "")); + else if (!isSingleQuote() && input.charAt(i) == '\\') { + if (input.charAt(i + 1) == '\n') { + result.add(Pair.create(TextRange.from(i, 2), "")); + i++; + continue; + } + final int length = Escaper.findEscapementLength(input, i); + final int charCode = Escaper.toUnicodeChar(input, i, length); + final TextRange range = TextRange.create(i, Math.min(i + length + 1, input.length())); + result.add(Pair.create(range, Character.toString((char)charCode))); + i += range.getLength() - 1; } - else if (!isSingleQuote() && "\\ ".equals(subSequence)) { - result.add(Pair.create(textRange, " ")); - } - else if (!isSingleQuote() && "\\\"".equals(subSequence)) { - result.add(Pair.create(textRange, "\"")); - } - else { - //noinspection AssignmentToForLoopParameter - i--; - } - //noinspection AssignmentToForLoopParameter - i++; } return result; } @@ -120,9 +121,10 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText final List> result = new ArrayList<>(); int currentLength = 0; for (int i = 0; i < input.length(); ++i) { - if (input.charAt(i) == '\n') { + final char c = input.charAt(i); + if (c == '\n') { if (!isSingleQuote() && i + 1 < input.length() && YAMLGrammarCharUtil.isSpaceLike(input.charAt(i + 1))) { - result.add(Pair.create(TextRange.from(i, 1), "\\\n" + indentString + "\\")); + result.add(Pair.create(TextRange.from(i, 1), "\\n\\\n" + indentString + "\\")); } else { result.add(Pair.create(TextRange.from(i, 1), "\n\n" + indentString)); @@ -133,12 +135,12 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText if (currentLength > MAX_SCALAR_LENGTH_PREDEFINED - && (!isSingleQuote() || (input.charAt(i) == ' ' && isSurroundedByNoSpace(input, i)))) { + && (!isSingleQuote() || (c == ' ' && isSurroundedByNoSpace(input, i)))) { final String replacement; if (isSingleQuote()) { replacement = "\n" + indentString; } - else if (YAMLGrammarCharUtil.isSpaceLike(input.charAt(i))) { + else if (YAMLGrammarCharUtil.isSpaceLike(c)) { replacement = "\\\n" + indentString + "\\"; } else { @@ -150,16 +152,16 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText currentLength++; - if (isSingleQuote() && input.charAt(i) == '\'') { + if (isSingleQuote() && c == '\'') { result.add(Pair.create(TextRange.from(i, 1), "''")); continue; } if (!isSingleQuote()) { - if (input.charAt(i) == '"') { + if (c == '"') { result.add(Pair.create(TextRange.from(i, 1), "\\\"")); } - else if (input.charAt(i) == '\\') { + else if (c == '\\') { result.add(Pair.create(TextRange.from(i, 1), "\\\\")); } } @@ -180,4 +182,87 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText public String toString() { return "YAML quoted text"; } + + private static class Escaper { + private static final int[][] ONE_LETTER_CONVERSIONS = new int[][] { + {'0', 0}, + {'a', 7}, + {'b', 8}, + {'t', 9}, + {9, 9}, + {'n', 10}, + {'v', 11}, + {'f', 12}, + {'r', 13}, + {'e', 27}, + {' ', 32}, + {'"', 34}, + {'/', 47}, + {'\\', 92}, + {'N', 133}, + {'_', 160}, + {'L', 8232}, + {'P', 8233}, + }; + + private static final NotNullLazyValue> ESC_TO_CODE = new NotNullLazyValue>() { + @NotNull + @Override + protected Map compute() { + final HashMap map = new HashMap<>(ONE_LETTER_CONVERSIONS.length); + for (int[] conversion : ONE_LETTER_CONVERSIONS) { + map.put(conversion[0], conversion[1]); + } + return map; + } + }; + + private static final NotNullLazyValue> CODE_TO_ESC = new NotNullLazyValue>() { + @NotNull + @Override + protected Map compute() { + final HashMap map = new HashMap<>(ONE_LETTER_CONVERSIONS.length); + for (int[] conversion : ONE_LETTER_CONVERSIONS) { + map.put(conversion[1], conversion[2]); + } + return map; + } + }; + + static int findEscapementLength(@NotNull CharSequence text, int pos) { + if (pos + 1 >= text.length() || text.charAt(pos) != '\\') { + throw new IllegalArgumentException("This is not an escapement start"); + } + + final char c = text.charAt(pos + 1); + if (c == 'x') { + return 3; + } + else if (c == 'u') { + return 5; + } + else if (c == 'U') { + return 9; + } + else { + return 1; + } + } + + static int toUnicodeChar(@NotNull CharSequence text, int pos, int length) { + if (length > 1) { + CharSequence s = text.subSequence(pos + 2, Math.min(text.length(), pos + length + 1)); + try { + return Integer.parseInt(s.toString(), 16); + } + catch (NumberFormatException e) { + return (int)'?'; + } + } + else { + final Integer result = ESC_TO_CODE.getValue().get((int)text.charAt(pos + 1)); + return ObjectUtils.notNull(result, (int)text.charAt(pos + 1)); + } + } + } } diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/psi/YAMLScalarContentTest.java b/plugins/yaml/testSrc/org/jetbrains/yaml/psi/YAMLScalarContentTest.java index e3f18742529b..14d8054ab308 100644 --- a/plugins/yaml/testSrc/org/jetbrains/yaml/psi/YAMLScalarContentTest.java +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/psi/YAMLScalarContentTest.java @@ -73,6 +73,10 @@ public class YAMLScalarContentTest extends LightPlatformCodeInsightFixtureTestCa public void testDoubleQuote2() { doTest(); } + + public void testDoubleQuote4() { + doTest(); + } private void doTest() { myFixture.configureByFile(getTestName(true) + ".yml"); diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/psi/data/doubleQuote4.txt b/plugins/yaml/testSrc/org/jetbrains/yaml/psi/data/doubleQuote4.txt new file mode 100644 index 000000000000..8c6ee06ed624 Binary files /dev/null and b/plugins/yaml/testSrc/org/jetbrains/yaml/psi/data/doubleQuote4.txt differ diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/psi/data/doubleQuote4.yml b/plugins/yaml/testSrc/org/jetbrains/yaml/psi/data/doubleQuote4.yml new file mode 100644 index 000000000000..2313956c09b8 --- /dev/null +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/psi/data/doubleQuote4.yml @@ -0,0 +1,5 @@ +"\n\n\nFun with \\ +\" \a \b \e \f \ +\n \r \t \v \0 \ +\ \_ \N \L \P \ +\x41 \u0041 \U00000041" \ No newline at end of file