| /* |
| * 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; |
| } |
| } |