From 49fe41d4cb05d4516710767751366647f8a06c8f Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Mon, 6 Aug 2018 13:50:47 +0200 Subject: [PATCH] WEB-24696, WEB-31891 Copy-paste from-to JSON strings now handles string escaping --- .../json/editor/JsonCopyPasteProcessor.java | 72 +++++++++++++++++++ .../json/editor/JsonEditorOptions.java | 1 + .../editor/JsonSmartKeysConfigurable.java | 3 + .../com/intellij/json/JsonCopyPasteTest.java | 48 +++++++++++++ .../src/META-INF/JsonPlugin.xml | 1 + 5 files changed, 125 insertions(+) create mode 100644 json/src/com/intellij/json/editor/JsonCopyPasteProcessor.java create mode 100644 json/tests/test/com/intellij/json/JsonCopyPasteTest.java diff --git a/json/src/com/intellij/json/editor/JsonCopyPasteProcessor.java b/json/src/com/intellij/json/editor/JsonCopyPasteProcessor.java new file mode 100644 index 000000000000..e85e71677e00 --- /dev/null +++ b/json/src/com/intellij/json/editor/JsonCopyPasteProcessor.java @@ -0,0 +1,72 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.json.editor; + +import com.intellij.codeInsight.editorActions.CopyPastePreProcessor; +import com.intellij.json.psi.JsonStringLiteral; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RawText; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class JsonCopyPasteProcessor implements CopyPastePreProcessor { + @Nullable + @Override + public String preprocessOnCopy(PsiFile file, int[] startOffsets, int[] endOffsets, String text) { + if (!JsonEditorOptions.getInstance().ESCAPE_PASTED_TEXT) { + return null; + } + if (!file.isPhysical() || startOffsets.length > 1 || endOffsets.length > 1) { + return null; + } + final int selectionStart = startOffsets[0]; + final int selectionEnd = endOffsets[0]; + final JsonStringLiteral literalExpression = getSingleElementFromSelectionOrNull(file, selectionStart, selectionEnd); + + if (literalExpression == null) { + return null; + } + + return StringUtil.unescapeStringCharacters(StringUtil.replaceUnicodeEscapeSequences(text)); + } + + @Nullable + private static JsonStringLiteral getSingleElementFromSelectionOrNull(PsiFile file, int start, int end) { + final PsiElement element = file.findElementAt(start); + final JsonStringLiteral literalExpression = PsiTreeUtil.getParentOfType(element, JsonStringLiteral.class); + if (literalExpression == null) return null; + TextRange textRange = literalExpression.getTextRange(); + if (start <= textRange.getStartOffset() || end >= textRange.getEndOffset()) return null; + String text = literalExpression.getText(); + if (!text.startsWith("\"") || !text.endsWith("\"")) return null; + return literalExpression; + } + + @NotNull + @Override + public String preprocessOnPaste(Project project, PsiFile file, Editor editor, String text, RawText rawText) { + if (!JsonEditorOptions.getInstance().ESCAPE_PASTED_TEXT) { + return text; + } + if (!file.isPhysical()) { + return text; + } + + final SelectionModel selectionModel = editor.getSelectionModel(); + final int selectionStart = selectionModel.getSelectionStart(); + final int selectionEnd = selectionModel.getSelectionEnd(); + + final JsonStringLiteral literalExpression = getSingleElementFromSelectionOrNull(file, selectionStart, selectionEnd); + if (literalExpression == null) { + return text; + } + + return StringUtil.escapeStringCharacters(text); + } +} diff --git a/json/src/com/intellij/json/editor/JsonEditorOptions.java b/json/src/com/intellij/json/editor/JsonEditorOptions.java index fbba51d4ac89..f2d2f80a3280 100644 --- a/json/src/com/intellij/json/editor/JsonEditorOptions.java +++ b/json/src/com/intellij/json/editor/JsonEditorOptions.java @@ -16,6 +16,7 @@ import org.jetbrains.annotations.Nullable; public class JsonEditorOptions implements PersistentStateComponent { public boolean COMMA_ON_ENTER = true; public boolean COMMA_ON_MATCHING_BRACES = true; + public boolean ESCAPE_PASTED_TEXT = true; @Nullable @Override diff --git a/json/src/com/intellij/json/editor/JsonSmartKeysConfigurable.java b/json/src/com/intellij/json/editor/JsonSmartKeysConfigurable.java index a24e6fa716ea..6ebc99fc4efb 100644 --- a/json/src/com/intellij/json/editor/JsonSmartKeysConfigurable.java +++ b/json/src/com/intellij/json/editor/JsonSmartKeysConfigurable.java @@ -18,6 +18,9 @@ public class JsonSmartKeysConfigurable extends BeanConfigurable settings.COMMA_ON_MATCHING_BRACES, v -> settings.COMMA_ON_MATCHING_BRACES = v); + checkBox("Escape text on paste in string literals", + () -> settings.ESCAPE_PASTED_TEXT, + v -> settings.ESCAPE_PASTED_TEXT = v); } @Override diff --git a/json/tests/test/com/intellij/json/JsonCopyPasteTest.java b/json/tests/test/com/intellij/json/JsonCopyPasteTest.java new file mode 100644 index 000000000000..728b66eb332d --- /dev/null +++ b/json/tests/test/com/intellij/json/JsonCopyPasteTest.java @@ -0,0 +1,48 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.json; + +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.testFramework.fixtures.CodeInsightFixtureTestCase; + +public class JsonCopyPasteTest extends CodeInsightFixtureTestCase { + + private void doCopyPasteTest(String source, String dest, String expected, String filename1, String filename2) { + myFixture.configureByText(filename1, source); + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_COPY); + myFixture.configureByText(filename2, dest); + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE); + myFixture.checkResult(expected); + } + + private void doTestFromTextToJson(String source, String dest, String expected) { + doCopyPasteTest(source, dest, expected, "dummy.txt", "dummy.json"); + } + + private void doTestFromJsonToText(String source, String dest, String expected) { + doCopyPasteTest(source, dest, expected, "dummy.json", "dummy.txt"); + } + + public void testUnescapeQuotes() { + doTestFromJsonToText("{\"p\": \"\\\"quoted\\\"\"}", "", "\"quoted\""); + } + + public void testUnescapeWhitespaces() { + doTestFromJsonToText("{\"p\": \"lorem ipsum\\tdolor sit amet\"}", "", "lorem ipsum\tdolor sit amet"); + } + + public void testUnescapeFromPropNames() { + doTestFromJsonToText("{\"lorem ipsum\\tdolor sit amet\": \"foo\"}", "", "lorem ipsum\tdolor sit amet"); + } + + public void testEscapeQuotes() { + doTestFromTextToJson("\"quoted\"", "{\"p\": \"\"}", "{\"p\": \"\\\"quoted\\\"\"}"); + } + + public void testEscapeWhitespaces() { + doTestFromTextToJson("lorem ipsum\tdolor sit amet", "{\"p\": \"\"}", "{\"p\": \"lorem ipsum\\tdolor sit amet\"}"); + } + + public void testEscapeInPropNames() { + doTestFromTextToJson("lorem ipsum\tdolor sit amet", "{\"\": \"foo\"}", "{\"lorem ipsum\\tdolor sit amet\": \"foo\"}"); + } +} diff --git a/platform/platform-resources/src/META-INF/JsonPlugin.xml b/platform/platform-resources/src/META-INF/JsonPlugin.xml index ba1b42657b42..9b3582c180f4 100644 --- a/platform/platform-resources/src/META-INF/JsonPlugin.xml +++ b/platform/platform-resources/src/META-INF/JsonPlugin.xml @@ -33,6 +33,7 @@ +