blob: 6047c1e6ef58d50349115a0bcd266234f15d84a3 [file] [log] [blame]
/*
* 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 com.intellij.codeInsight.editorActions;
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.JavaTokenType;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.openapi.util.text.StringUtil.unescapeStringCharacters;
public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor {
@SuppressWarnings("ForLoopThatDoesntUseLoopVariable")
@Override
public String preprocessOnCopy(final PsiFile file, final int[] startOffsets, final int[] endOffsets, final String text) {
// The main idea is to un-escape string/char literals content if necessary.
// Example:
// Suppose we have a following text at the editor: String s = "first <selection>line \n second</selection> line"
// When user copies the selection we want to transform text \n to the real line feed, i.e. clipboard should contains the following:
// line
// second
//
// However, we don't want to un-escape literal content if it's copied completely.
// Example:
// String s = <selection>"my string"</selection>;
StringBuilder buffer = new StringBuilder();
int givenTextOffset = 0;
boolean textWasChanged = false;
for (int i = 0; i < startOffsets.length && givenTextOffset < text.length(); i++, givenTextOffset++) {
if (i > 0) {
buffer.append('\n'); // LF is added for block selection
}
// Calculate offsets offsets of the selection interval being processed now.
final int fileStartOffset = startOffsets[i];
final int fileEndOffset = endOffsets[i];
int givenTextStartOffset = Math.min(givenTextOffset, text.length());
final int givenTextEndOffset = Math.min(givenTextOffset + (fileEndOffset - fileStartOffset), text.length());
givenTextOffset = givenTextEndOffset;
for (
PsiElement element = file.findElementAt(fileStartOffset);
givenTextStartOffset < givenTextEndOffset;
element = PsiTreeUtil.nextLeaf(element)) {
if (element == null) {
buffer.append(text.substring(givenTextStartOffset, givenTextEndOffset));
break;
}
TextRange elementRange = element.getTextRange();
int escapedStartOffset;
int escapedEndOffset;
if ((isStringLiteral(element) || isCharLiteral(element))
// We don't want to un-escape if complete literal is copied.
&& (elementRange.getStartOffset() < fileStartOffset || elementRange.getEndOffset() > fileEndOffset)) {
escapedStartOffset = elementRange.getStartOffset() + 1 /* String/char literal quote */;
escapedEndOffset = elementRange.getEndOffset() - 1 /* String/char literal quote */;
}
else {
escapedStartOffset = escapedEndOffset = elementRange.getStartOffset();
}
// Process text to the left of the escaped fragment (if any).
int numberOfSymbolsToCopy = escapedStartOffset - Math.max(fileStartOffset, elementRange.getStartOffset());
if (numberOfSymbolsToCopy > 0) {
buffer.append(text.substring(givenTextStartOffset, givenTextStartOffset + numberOfSymbolsToCopy));
givenTextStartOffset += numberOfSymbolsToCopy;
}
// Process escaped text (un-escape it).
numberOfSymbolsToCopy = Math.min(escapedEndOffset, fileEndOffset) - Math.max(fileStartOffset, escapedStartOffset);
if (numberOfSymbolsToCopy > 0) {
textWasChanged = true;
buffer.append(unescape(text.substring(givenTextStartOffset, givenTextStartOffset + numberOfSymbolsToCopy), element));
givenTextStartOffset += numberOfSymbolsToCopy;
}
// Process text to the right of the escaped fragment (if any).
numberOfSymbolsToCopy = Math.min(fileEndOffset, elementRange.getEndOffset()) - Math.max(fileStartOffset, escapedEndOffset);
if (numberOfSymbolsToCopy > 0) {
buffer.append(text.substring(givenTextStartOffset, givenTextStartOffset + numberOfSymbolsToCopy));
givenTextStartOffset += numberOfSymbolsToCopy;
}
}
}
return textWasChanged ? buffer.toString() : null;
}
@NotNull
protected String unescape(String text, PsiElement token) {
return unescapeStringCharacters(text);
}
@NotNull
@Override
public String preprocessOnPaste(final Project project, final PsiFile file, final Editor editor, String text, final 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) {
buffer.append(breaker);
}
else if (text.endsWith("\n")) {
buffer.append("\\n");
}
}
text = buffer.toString();
}
else if (isCharLiteral(token)) {
return escapeCharCharacters(text, token);
}
return text;
}
protected String getLineBreaker(PsiElement token) {
CommonCodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(token.getProject()).getCommonSettings(token.getLanguage());
return codeStyleSettings.BINARY_OPERATION_SIGN_ON_NEXT_LINE ? "\\n\"\n+ \"" : "\\n\" +\n\"";
}
@Nullable
protected PsiElement findLiteralTokenType(PsiFile file, int selectionStart, int selectionEnd) {
final PsiElement elementAtSelectionStart = file.findElementAt(selectionStart);
if (elementAtSelectionStart == null) {
return null;
}
if (!isStringLiteral(elementAtSelectionStart) && !isCharLiteral(elementAtSelectionStart)) {
return null;
}
if (elementAtSelectionStart.getTextRange().getEndOffset() < selectionEnd) {
final PsiElement elementAtSelectionEnd = file.findElementAt(selectionEnd);
if (elementAtSelectionEnd == null) {
return null;
}
if (elementAtSelectionEnd.getNode().getElementType() == elementAtSelectionStart.getNode().getElementType() &&
elementAtSelectionEnd.getTextRange().getStartOffset() < selectionEnd) {
return elementAtSelectionStart;
}
}
final TextRange textRange = elementAtSelectionStart.getTextRange();
if (selectionStart <= textRange.getStartOffset() || selectionEnd >= textRange.getEndOffset()) {
return null;
}
return elementAtSelectionStart;
}
protected boolean isCharLiteral(@NotNull PsiElement token) {
ASTNode node = token.getNode();
return node != null && node.getElementType() == JavaTokenType.CHARACTER_LITERAL;
}
protected boolean isStringLiteral(@NotNull PsiElement token) {
ASTNode node = token.getNode();
return node != null && node.getElementType() == JavaTokenType.STRING_LITERAL;
}
@NotNull
protected String escapeCharCharacters(@NotNull String s, @NotNull PsiElement token) {
StringBuilder buffer = new StringBuilder();
StringUtil.escapeStringCharacters(s.length(), s, isStringLiteral(token) ? "\"" : "\'", buffer);
return buffer.toString();
}
}