WEB-24696, WEB-31891 Copy-paste from-to JSON strings now handles string escaping

This commit is contained in:
Anton Lobov
2018-08-06 13:53:52 +02:00
parent f288646ce3
commit 49fe41d4cb
5 changed files with 125 additions and 0 deletions
@@ -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);
}
}
@@ -16,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
public class JsonEditorOptions implements PersistentStateComponent<JsonEditorOptions> {
public boolean COMMA_ON_ENTER = true;
public boolean COMMA_ON_MATCHING_BRACES = true;
public boolean ESCAPE_PASTED_TEXT = true;
@Nullable
@Override
@@ -18,6 +18,9 @@ public class JsonSmartKeysConfigurable extends BeanConfigurable<JsonEditorOption
checkBox("Insert missing comma after matching braces and quotes",
() -> 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
@@ -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\": \"<selection>\\\"quoted\\\"</selection>\"}", "<caret>", "\"quoted\"");
}
public void testUnescapeWhitespaces() {
doTestFromJsonToText("{\"p\": \"<selection>lorem ipsum\\tdolor sit amet</selection>\"}", "<caret>", "lorem ipsum\tdolor sit amet");
}
public void testUnescapeFromPropNames() {
doTestFromJsonToText("{\"<selection>lorem ipsum\\tdolor sit amet</selection>\": \"foo\"}", "<caret>", "lorem ipsum\tdolor sit amet");
}
public void testEscapeQuotes() {
doTestFromTextToJson("<selection>\"quoted\"</selection>", "{\"p\": \"<caret>\"}", "{\"p\": \"\\\"quoted\\\"\"}");
}
public void testEscapeWhitespaces() {
doTestFromTextToJson("<selection>lorem ipsum\tdolor sit amet</selection>", "{\"p\": \"<caret>\"}", "{\"p\": \"lorem ipsum\\tdolor sit amet\"}");
}
public void testEscapeInPropNames() {
doTestFromTextToJson("<selection>lorem ipsum\tdolor sit amet</selection>", "{\"<caret>\": \"foo\"}", "{\"lorem ipsum\\tdolor sit amet\": \"foo\"}");
}
}
@@ -33,6 +33,7 @@
<enterBetweenBracesDelegate language="JSON" implementationClass="com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesAndBracketsDelegate"/>
<enterHandlerDelegate implementation="com.intellij.json.editor.JsonEnterHandler" id="JsonEnterHandler"/>
<typedHandler implementation="com.intellij.json.editor.JsonTypedHandler" id="JsonTypedHandler"/>
<copyPastePreProcessor implementation="com.intellij.json.editor.JsonCopyPasteProcessor"/>
<editorSmartKeysConfigurable instance="com.intellij.json.editor.JsonSmartKeysConfigurable"/>
<applicationService serviceInterface="com.intellij.json.editor.JsonEditorOptions"
serviceImplementation="com.intellij.json.editor.JsonEditorOptions"/>