From 1355b64db4ab222e91b8842c81d0a39a92132fd4 Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Wed, 21 Dec 2011 13:26:21 +0400 Subject: [PATCH] always escape symbols when text is pasted to groovy string literal --- .../StringLiteralCopyPasteProcessor.java | 14 +-- .../GroovyLiteralCopyPasteProcessor.java | 103 ++++++++++++------ .../groovy/lang/psi/util/GrStringUtil.java | 79 ++++++++++++-- 3 files changed, 143 insertions(+), 53 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java index 31ffdc25d1a7..f79099f4c2db 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java @@ -132,11 +132,7 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor { } if (isStringLiteral(token)) { - boolean escapeSlashes = true; if (rawText != null && rawText.rawText != null) { - //is is assumed that all slashes are escaped in the raw text but some unescaped symbols can present (for example " copied from CharLiteral '"') - //so we should escape all needed symbols except slash. - escapeSlashes = false; text = rawText.rawText; } @@ -144,7 +140,7 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor { @NonNls String breaker = getLineBreaker(token); final String[] lines = LineTokenizer.tokenize(text.toCharArray(), false, true); for (int i = 0; i < lines.length; i++) { - buffer.append(escapeCharCharacters(lines[i], token, escapeSlashes)); + buffer.append(escapeCharCharacters(lines[i], token)); if (i != lines.length - 1 || "\n".equals(breaker) && text.endsWith("\n")) { buffer.append(breaker); } @@ -153,10 +149,10 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor { } else if (isCharLiteral(token)) { if (rawText != null && rawText.rawText != null) { - return escapeCharCharacters(rawText.rawText, token, false); + return escapeCharCharacters(rawText.rawText, token); } else { - return escapeCharCharacters(text, token, true); + return escapeCharCharacters(text, token); } } return text; @@ -206,9 +202,9 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor { } @NotNull - protected String escapeCharCharacters(@NotNull String s, @NotNull PsiElement token, boolean escapeSlashes) { + protected String escapeCharCharacters(@NotNull String s, @NotNull PsiElement token) { StringBuilder buffer = new StringBuilder(); - StringUtil.escapeStringCharacters(s.length(), s, isStringLiteral(token) ? "\"" : "\'",escapeSlashes, buffer); + StringUtil.escapeStringCharacters(s.length(), s, isStringLiteral(token) ? "\"" : "\'", buffer); return buffer.toString(); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/editor/GroovyLiteralCopyPasteProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/editor/GroovyLiteralCopyPasteProcessor.java index db50abfabe87..650d9c1bbd24 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/editor/GroovyLiteralCopyPasteProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/editor/GroovyLiteralCopyPasteProcessor.java @@ -17,14 +17,23 @@ package org.jetbrains.plugins.groovy.lang.editor; import com.intellij.codeInsight.editorActions.StringLiteralCopyPasteProcessor; import com.intellij.lang.ASTNode; +import com.intellij.openapi.editor.Document; +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.LineTokenizer; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; +import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*; @@ -106,51 +115,81 @@ public class GroovyLiteralCopyPasteProcessor extends StringLiteralCopyPasteProce } - @NotNull @Override - protected String escapeCharCharacters(@NotNull String s, @NotNull PsiElement token, boolean escapeSlashes) { - IElementType tokenType = token.getNode().getElementType(); + public String preprocessOnPaste(Project project, PsiFile file, Editor editor, String text, RawText rawText) { + final Document document = editor.getDocument(); + PsiDocumentManager.getInstance(project).commitDocument(document); + final SelectionModel selectionModel = editor.getSelectionModel(); - if (tokenType == mREGEX_CONTENT || tokenType == mDOLLAR_SLASH_REGEX_CONTENT) { - if (escapeSlashes) { - return StringUtil.escapeSlashes(s); - } - else { - return s; - } + // pastes in block selection mode (column mode) are not handled by a CopyPasteProcessor + final int selectionStart = selectionModel.getSelectionStart(); + final int selectionEnd = selectionModel.getSelectionEnd(); + PsiElement token = findLiteralTokenType(file, selectionStart, selectionEnd); + if (token == null) { + return text; } - String chars; - if (tokenType == mGSTRING_CONTENT || tokenType == mGSTRING_LITERAL) { - if (token.getText().contains("\"\"\"")) { - chars = "$"; - } - else { - chars = "\"$"; + if (isStringLiteral(token)) { + StringBuilder buffer = new StringBuilder(text.length()); + @NonNls String breaker = getLineBreaker(token); + final String[] lines = LineTokenizer.tokenize(text.toCharArray(), false, true); + for (int i = 0; i < lines.length; i++) { + buffer.append(escapeCharCharacters(lines[i], token)); + if (i != lines.length - 1 || "\n".equals(breaker) && text.endsWith("\n")) { + buffer.append(breaker); + } } + text = buffer.toString(); } - else if (tokenType == mSTRING_LITERAL && !token.getText().contains("'''")) { - chars = "'"; - } - else { - chars = ""; - } - - StringBuilder buffer = new StringBuilder(); - StringUtil.escapeStringCharacters(s.length(), s, chars, escapeSlashes, buffer); - return buffer.toString(); + return text; } @NotNull @Override - protected String unescape(String text, PsiElement token) { - final IElementType tokenType = token.getNode().getElementType(); + protected String escapeCharCharacters(@NotNull String s, @NotNull PsiElement token) { + IElementType tokenType = token.getNode().getElementType(); - if (tokenType == mREGEX_CONTENT || tokenType == mDOLLAR_SLASH_REGEX_CONTENT) { - return StringUtil.unescapeSlashes(text); + if (tokenType == mREGEX_CONTENT || tokenType == mREGEX_LITERAL) { + return GrStringUtil.escapeForSlashyStrings(s); } - return super.unescape(text, token); + if (tokenType == mDOLLAR_SLASH_REGEX_CONTENT || tokenType == mDOLLAR_SLASH_REGEX_LITERAL) { + return GrStringUtil.escapeSymbolsForDollarSlashyStrings(s); + } + + if (tokenType == mGSTRING_CONTENT || tokenType == mGSTRING_LITERAL) { + return GrStringUtil.escapeSymbolsForGString(s, !token.getText().contains("\"\"\"")); + } + + if (tokenType == mSTRING_LITERAL) { + return GrStringUtil.escapeSymbolsForString(s, !token.getText().contains("'''")); + } + + return super.escapeCharCharacters(s, token); + } + + @NotNull + @Override + protected String unescape(String s, PsiElement token) { + final IElementType tokenType = token.getNode().getElementType(); + + if (tokenType == mREGEX_CONTENT || tokenType == mREGEX_LITERAL) { + return GrStringUtil.unescapeSlashyString(s); + } + + if (tokenType == mDOLLAR_SLASH_REGEX_CONTENT || tokenType == mDOLLAR_SLASH_REGEX_LITERAL) { + return GrStringUtil.unescapeDollarSlashyString(s); + } + + if (tokenType == mGSTRING_CONTENT || tokenType == mGSTRING_LITERAL) { + return StringUtil.unescapeStringCharacters(s); + } + + if (tokenType == mSTRING_LITERAL) { + return StringUtil.unescapeStringCharacters(s); + } + + return super.unescape(s, token); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java index 68037fe17eef..e5fed4659e7b 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java @@ -35,6 +35,67 @@ public class GrStringUtil { private GrStringUtil() { } + public static String unescapeSlashyString(String s) { + return unescapeRegex(s, true); + } + + public static String unescapeDollarSlashyString(String s) { + return unescapeRegex(s, false); + } + + private static String unescapeRegex(String s, boolean unescapeSlash) { + final int length = s.length(); + StringBuilder buffer = new StringBuilder(length); + + boolean escaped = false; + for (int idx = 0; idx < length; idx++) { + char ch = s.charAt(idx); + if (!escaped) { + if (ch == '\\') { + escaped = true; + } + else { + buffer.append(ch); + } + } + else { + switch (ch) { + case '/': + if (!unescapeSlash) { + buffer.append('\\'); + } + buffer.append('/'); + break; + case 'u': + if (idx + 4 < length) { + try { + int code = Integer.valueOf(s.substring(idx + 1, idx + 5), 16).intValue(); + idx += 4; + buffer.append((char)code); + } + catch (NumberFormatException e) { + buffer.append("\\u"); + } + } + else { + buffer.append("\\u"); + } + break; + + default: + buffer.append('\\'); + buffer.append(ch); + break; + } + escaped = false; + } + } + + if (escaped) buffer.append('\\'); + return buffer.toString(); + } + + public static String escapeForSlashyStrings(String str) { final StringBuilder buffer = new StringBuilder(str.length()); escapeSymbolsForSlashyStrings(buffer, str); @@ -105,7 +166,7 @@ public class GrStringUtil { public static String escapeSymbolsForGString(String s, boolean escapeDoubleQuotes, boolean forInjection) { StringBuilder b = new StringBuilder(); - escapeStringCharacters(s.length(), s, escapeDoubleQuotes ? "$\"" : "$", false, forInjection, b); + escapeStringCharacters(s.length(), s, escapeDoubleQuotes ? "$\"" : "$", forInjection, b); if (!forInjection) { unescapeCharacters(b, escapeDoubleQuotes ? "'" : "'\"", true); } @@ -118,7 +179,7 @@ public class GrStringUtil { public static String escapeSymbolsForString(String s, boolean escapeQuotes, boolean forInjection) { final StringBuilder builder = new StringBuilder(); - escapeStringCharacters(s.length(), s, escapeQuotes ? "'" : "", false, forInjection, builder); + escapeStringCharacters(s.length(), s, escapeQuotes ? "'" : "", forInjection, builder); if (!forInjection) { unescapeCharacters(builder, escapeQuotes ? "$\"" : "$'\"", true); } @@ -129,8 +190,7 @@ public class GrStringUtil { public static StringBuilder escapeStringCharacters(int length, @NotNull String str, @Nullable String additionalChars, - boolean escapeNR, - boolean escapeSlash, + boolean escapeLineFeeds, @NotNull @NonNls StringBuilder buffer) { for (int idx = 0; idx < length; idx++) { char ch = str.charAt(idx); @@ -148,16 +208,11 @@ public class GrStringUtil { break; case '\\': - if (escapeSlash) { - buffer.append("\\\\"); - } - else { - buffer.append("\\"); - } + buffer.append("\\\\"); break; case '\n': - if (escapeNR) { + if (escapeLineFeeds) { buffer.append("\\n"); } else { @@ -166,7 +221,7 @@ public class GrStringUtil { break; case '\r': - if (escapeNR) { + if (escapeLineFeeds) { buffer.append("\\r"); } else {