/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.plugins.groovy.editor; import com.intellij.codeInsight.editorActions.StringLiteralCopyPasteProcessor; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; 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.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.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes; import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; /** * @author peter */ public class GroovyLiteralCopyPasteProcessor extends StringLiteralCopyPasteProcessor { private static final Logger LOG = Logger.getInstance(GroovyLiteralCopyPasteProcessor.class); @Override protected boolean isCharLiteral(@NotNull PsiElement token) { return false; } @Override protected boolean isStringLiteral(@NotNull PsiElement token) { ASTNode node = token.getNode(); return node != null && (TokenSets.STRING_LITERALS.contains(node.getElementType()) || node.getElementType() == GroovyElementTypes.GSTRING_INJECTION || node.getElementType() == GroovyElementTypes.GSTRING_CONTENT); } @Override @Nullable protected PsiElement findLiteralTokenType(PsiFile file, int selectionStart, int selectionEnd) { PsiElement elementAtSelectionStart = file.findElementAt(selectionStart); if (elementAtSelectionStart == null) { return null; } IElementType elementType = elementAtSelectionStart.getNode().getElementType(); if ((elementType == GroovyTokenTypes.mREGEX_END || elementType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_END || elementType == GroovyTokenTypes.mGSTRING_END) && elementAtSelectionStart.getTextOffset() == selectionStart) { elementAtSelectionStart = elementAtSelectionStart.getPrevSibling(); if (elementAtSelectionStart == null) return null; elementType = elementAtSelectionStart.getNode().getElementType(); } if (elementType == GroovyTokenTypes.mDOLLAR) { elementAtSelectionStart = elementAtSelectionStart.getParent(); elementType = elementAtSelectionStart.getNode().getElementType(); } if (!isStringLiteral(elementAtSelectionStart)) { return null; } if (elementAtSelectionStart.getTextRange().getEndOffset() < selectionEnd) { final PsiElement elementAtSelectionEnd = file.findElementAt(selectionEnd); if (elementAtSelectionEnd == null) { return null; } if (elementAtSelectionEnd.getNode().getElementType() == elementType && elementAtSelectionEnd.getTextRange().getStartOffset() < selectionEnd) { return elementAtSelectionStart; } } final TextRange textRange = elementAtSelectionStart.getTextRange(); //content elements don't have quotes, so they are shorter than whole string literals if (elementType == GroovyTokenTypes.mREGEX_CONTENT || elementType == GroovyTokenTypes.mGSTRING_CONTENT || elementType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_CONTENT || elementType == GroovyElementTypes.GSTRING_INJECTION) { selectionStart++; selectionEnd--; } if (textRange.getLength() > 0 && (selectionStart <= textRange.getStartOffset() || selectionEnd >= textRange.getEndOffset())) { return null; } if (elementType == GroovyElementTypes.GSTRING_CONTENT) { elementAtSelectionStart = elementAtSelectionStart.getFirstChild(); } return elementAtSelectionStart; } @Override protected String getLineBreaker(@NotNull PsiElement token) { PsiElement parent = GrStringUtil.findContainingLiteral(token); final String text = parent.getText(); if (text.contains("'''") || text.contains("\"\"\"")) { return "\n"; } final IElementType type = token.getNode().getElementType(); if (type == GroovyTokenTypes.mGSTRING_LITERAL || type == GroovyTokenTypes.mGSTRING_CONTENT) { return super.getLineBreaker(token); } if (type == GroovyTokenTypes.mSTRING_LITERAL) { return super.getLineBreaker(token).replace('"', '\''); } return "\n"; } @NotNull @Override 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(); // 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; } 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(); } return text; } @NotNull @Override protected String escapeCharCharacters(@NotNull String s, @NotNull PsiElement token) { if (s.isEmpty()) return s; IElementType tokenType = token.getNode().getElementType(); if (tokenType == GroovyTokenTypes.mREGEX_CONTENT || tokenType == GroovyTokenTypes.mREGEX_LITERAL) { return GrStringUtil.escapeSymbolsForSlashyStrings(s); } if (tokenType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_CONTENT || tokenType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL) { return GrStringUtil.escapeSymbolsForDollarSlashyStrings(s); } if (tokenType == GroovyTokenTypes.mGSTRING_CONTENT || tokenType == GroovyTokenTypes.mGSTRING_LITERAL || tokenType == GroovyElementTypes.GSTRING_INJECTION) { boolean singleLine = !GrStringUtil.findContainingLiteral(token).getText().contains("\"\"\""); StringBuilder b = new StringBuilder(); GrStringUtil.escapeStringCharacters(s.length(), s, singleLine ? "\"" : "", singleLine, true, b); GrStringUtil.unescapeCharacters(b, singleLine ? "'" : "'\"", true); LOG.assertTrue(b.length() > 0, "s=" + s); for (int i = b.length() - 2; i >= 0; i--) { if (b.charAt(i) == '$') { final char next = b.charAt(i + 1); if (next != '{' && !Character.isLetter(next)) { b.insert(i, '\\'); } } } if (b.charAt(b.length() - 1) == '$') { b.insert(b.length() - 1, '\\'); } return b.toString(); } if (tokenType == GroovyTokenTypes.mSTRING_LITERAL) { return GrStringUtil.escapeSymbolsForString(s, !token.getText().contains("'''"), false); } return super.escapeCharCharacters(s, token); } @NotNull @Override protected String unescape(String s, PsiElement token) { final IElementType tokenType = token.getNode().getElementType(); if (tokenType == GroovyTokenTypes.mREGEX_CONTENT || tokenType == GroovyTokenTypes.mREGEX_LITERAL) { return GrStringUtil.unescapeSlashyString(s); } if (tokenType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_CONTENT || tokenType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL) { return GrStringUtil.unescapeDollarSlashyString(s); } if (tokenType == GroovyTokenTypes.mGSTRING_CONTENT || tokenType == GroovyTokenTypes.mGSTRING_LITERAL) { return GrStringUtil.unescapeString(s); } if (tokenType == GroovyTokenTypes.mSTRING_LITERAL) { return GrStringUtil.unescapeString(s); } return super.unescape(s, token); } }