blob: ab256f8a31a361c77212ccd908b695e0eef7402e [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 org.jetbrains.plugins.groovy.intentions.conversions.strings;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle;
import org.jetbrains.plugins.groovy.intentions.base.Intention;
import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringContent;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrStringImpl;
import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
import java.util.Collections;
import java.util.List;
/**
* @author Max Medvedev
*/
public class ConvertStringToMultilineIntention extends Intention {
private static final Logger LOG = Logger.getInstance(ConvertStringToMultilineIntention.class);
public static final String hint = GroovyIntentionsBundle.message("convert.string.to.multiline.intention.name");
@Override
protected void processIntention(@NotNull PsiElement element, final Project project, final Editor editor) throws IncorrectOperationException {
final List<GrExpression> expressions;
if (editor.getSelectionModel().hasSelection()) {
expressions = Collections.singletonList(((GrExpression)element));
}
else {
final AccessToken accessToken = ReadAction.start();
try {
expressions = collectExpressions(element);
}
finally {
accessToken.finish();
}
}
if (expressions.size() == 1) {
invokeImpl(expressions.get(0), project, editor);
}
else if (ApplicationManager.getApplication().isUnitTestMode()) {
invokeImpl(expressions.get(expressions.size() - 1), project, editor);
}
else {
final Pass<GrExpression> callback = new Pass<GrExpression>() {
@Override
public void pass(@NotNull final GrExpression selectedValue) {
invokeImpl(selectedValue, project, editor);
}
};
final Function<GrExpression, String> renderer = new Function<GrExpression, String>() {
@Override
public String fun(@NotNull GrExpression grExpression) {
return grExpression.getText();
}
};
IntroduceTargetChooser.showChooser(editor, expressions, callback, renderer);
}
}
@NotNull
private static List<GrExpression> collectExpressions(@NotNull PsiElement element) {
assert element instanceof GrExpression;
List<GrExpression> result = ContainerUtil.newArrayList();
result.add((GrExpression)element);
while (element.getParent() instanceof GrBinaryExpression) {
final GrBinaryExpression binary = (GrBinaryExpression)element.getParent();
if (!isAppropriateBinary(binary, element)) break;
result.add(binary);
element = binary;
}
return result;
}
private static boolean isAppropriateBinary(@NotNull GrBinaryExpression binary, @Nullable PsiElement prevChecked) {
if (binary.getOperationTokenType() == GroovyTokenTypes.mPLUS) {
final GrExpression left = binary.getLeftOperand();
final GrExpression right = binary.getRightOperand();
if ((left != prevChecked || containsOnlyLiterals(right)) &&
(right != prevChecked || containsOnlyLiterals(left))) {
return true;
}
}
return false;
}
private static boolean containsOnlyLiterals(@Nullable GrExpression expression) {
if (expression instanceof GrLiteral) {
final String text = expression.getText();
if ("'".equals(GrStringUtil.getStartQuote(text))) return true;
if ("\"".equals(GrStringUtil.getStartQuote(text))) return true;
}
else if (expression instanceof GrBinaryExpression) {
final IElementType type = ((GrBinaryExpression)expression).getOperationTokenType();
if (type != GroovyTokenTypes.mPLUS) return false;
final GrExpression left = ((GrBinaryExpression)expression).getLeftOperand();
final GrExpression right = ((GrBinaryExpression)expression).getRightOperand();
return containsOnlyLiterals(left) && containsOnlyLiterals(right);
}
return false;
}
@NotNull
private static List<GrLiteral> collectOperands(@Nullable PsiElement element, @NotNull List<GrLiteral> initial) {
if (element instanceof GrLiteral) {
initial.add((GrLiteral)element);
}
else if (element instanceof GrBinaryExpression) {
collectOperands(((GrBinaryExpression)element).getLeftOperand(), initial);
collectOperands(((GrBinaryExpression)element).getRightOperand(), initial);
}
return initial;
}
private void invokeImpl(@NotNull final GrExpression element, @NotNull final Project project, @NotNull final Editor editor) {
final List<GrLiteral> literals = collectOperands(element, ContainerUtil.<GrLiteral>newArrayList());
if (literals.isEmpty()) return;
final StringBuilder buffer = prepareNewLiteralText(literals);
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
final int offset = editor.getCaretModel().getOffset();
final TextRange range = element.getTextRange();
int shift;
if (editor.getSelectionModel().hasSelection()) {
shift = 0;
}
else if (range.getStartOffset() == offset) {
shift = 0;
}
else if (range.getEndOffset() == offset + 1) {
shift = -2;
}
else {
shift = 2;
}
final GrExpression newLiteral = GroovyPsiElementFactory.getInstance(project).createExpressionFromText(buffer.toString());
element.replaceWithExpression(newLiteral, true);
if (shift != 0) {
editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() + shift);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
});
}
}, getText(), null);
}
private static StringBuilder prepareNewLiteralText(List<GrLiteral> literals) {
String quote = !containsInjections(literals) && literals.get(0).getText().startsWith("'") ? "'''" : "\"\"\"";
final StringBuilder buffer = new StringBuilder();
buffer.append(quote);
for (GrLiteral literal : literals) {
if (literal instanceof GrLiteralImpl) {
appendSimpleStringValue(literal, buffer, quote);
}
else {
final GrStringImpl gstring = (GrStringImpl)literal;
for (PsiElement child : gstring.getAllContentParts()) {
if (child instanceof GrStringContent) {
appendSimpleStringValue(child, buffer, "\"\"\"");
}
else if (child instanceof GrStringInjection) {
buffer.append(child.getText());
}
}
}
}
buffer.append(quote);
return buffer;
}
private static boolean containsInjections(@NotNull List<GrLiteral> literals) {
for (GrLiteral literal : literals) {
if (literal instanceof GrString && ((GrString)literal).getInjections().length > 0) {
return true;
}
}
return false;
}
private static void appendSimpleStringValue(PsiElement element, StringBuilder buffer, String quote) {
final String text = GrStringUtil.removeQuotes(element.getText());
final int position = buffer.length();
if ("'''".equals(quote)) {
GrStringUtil.escapeAndUnescapeSymbols(text, "", "'n", buffer);
GrStringUtil.fixAllTripleQuotes(buffer, position);
}
else {
GrStringUtil.escapeAndUnescapeSymbols(text, "", "\"n", buffer);
GrStringUtil.fixAllTripleDoubleQuotes(buffer, position);
}
}
@NotNull
@Override
protected PsiElementPredicate getElementPredicate() {
return new PsiElementPredicate() {
@Override
public boolean satisfiedBy(PsiElement element) {
return element instanceof GrLiteral && ("\"".equals(GrStringUtil.getStartQuote(element.getText())) ||
"\'".equals(GrStringUtil.getStartQuote(element.getText())))
|| element instanceof GrBinaryExpression && isAppropriateBinary((GrBinaryExpression)element, null);
}
};
}
@Override
public boolean startInWriteAction() {
return false;
}
}