blob: 1fd552db96d238acc263d0ad95b3caf633683a4f [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.generation.surroundWith;
import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.template.CustomLiveTemplate;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.codeInsight.template.impl.InvokeTemplateAction;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.WrapWithCustomTemplateAction;
import com.intellij.ide.DataManager;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageSurrounders;
import com.intellij.lang.folding.CustomFoldingSurroundDescriptor;
import com.intellij.lang.surroundWith.SurroundDescriptor;
import com.intellij.lang.surroundWith.Surrounder;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class SurroundWithHandler implements CodeInsightActionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler");
private static final String CHOOSER_TITLE = CodeInsightBundle.message("surround.with.chooser.title");
@Override
public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file) {
invoke(project, editor, file, null);
}
@Override
public boolean startInWriteAction() {
return true;
}
public static void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, Surrounder surrounder) {
if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return;
if (file instanceof PsiCompiledElement) {
HintManager.getInstance().showErrorHint(editor, "Can't modify decompiled code");
return;
}
List<AnAction> applicable = buildSurroundActions(project, editor, file, surrounder);
if (applicable != null) {
showPopup(editor, applicable);
}
else if (!ApplicationManager.getApplication().isUnitTestMode()) {
HintManager.getInstance().showErrorHint(editor, "Couldn't find Surround With variants applicable to the current context");
}
}
@Nullable
public static List<AnAction> buildSurroundActions(final Project project, final Editor editor, PsiFile file, @Nullable Surrounder surrounder){
SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasSelection() && !selectionModel.hasBlockSelection()) {
selectionModel.selectLineAtCaret();
}
int startOffset = selectionModel.getSelectionStart();
int endOffset = selectionModel.getSelectionEnd();
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiElement element1 = file.findElementAt(startOffset);
PsiElement element2 = file.findElementAt(endOffset - 1);
if (element1 == null || element2 == null) return null;
TextRange textRange = new TextRange(startOffset, endOffset);
for(SurroundWithRangeAdjuster adjuster: Extensions.getExtensions(SurroundWithRangeAdjuster.EP_NAME)) {
textRange = adjuster.adjustSurroundWithRange(file, textRange);
if (textRange == null) return null;
}
startOffset = textRange.getStartOffset();
endOffset = textRange.getEndOffset();
element1 = file.findElementAt(startOffset);
final Language baseLanguage = file.getViewProvider().getBaseLanguage();
assert element1 != null;
final Language l = element1.getParent().getLanguage();
List<SurroundDescriptor> surroundDescriptors = new ArrayList<SurroundDescriptor>();
surroundDescriptors.addAll(LanguageSurrounders.INSTANCE.allForLanguage(l));
if (l != baseLanguage) surroundDescriptors.addAll(LanguageSurrounders.INSTANCE.allForLanguage(baseLanguage));
surroundDescriptors.add(CustomFoldingSurroundDescriptor.INSTANCE);
int exclusiveCount = 0;
List<SurroundDescriptor> exclusiveSurroundDescriptors = new ArrayList<SurroundDescriptor>();
for (SurroundDescriptor sd : surroundDescriptors) {
if (sd.isExclusive()) {
exclusiveCount++;
exclusiveSurroundDescriptors.add(sd);
}
}
if (exclusiveCount > 0) {
surroundDescriptors = exclusiveSurroundDescriptors;
}
if (surrounder != null) {
invokeSurrounderInTests(project, editor, file, surrounder, startOffset, endOffset, surroundDescriptors);
return null;
}
Map<Surrounder, PsiElement[]> surrounders = ContainerUtil.newLinkedHashMap();
for (SurroundDescriptor descriptor : surroundDescriptors) {
final PsiElement[] elements = descriptor.getElementsToSurround(file, startOffset, endOffset);
if (elements.length > 0) {
for (PsiElement element : elements) {
assert element != null : "descriptor " + descriptor + " returned null element";
assert element.isValid() : descriptor;
}
for (Surrounder s: descriptor.getSurrounders()) {
surrounders.put(s, elements);
}
}
}
return doBuildSurroundActions(project, editor, file, surrounders);
}
private static void invokeSurrounderInTests(Project project,
Editor editor,
PsiFile file,
Surrounder surrounder,
int startOffset,
int endOffset, List<SurroundDescriptor> surroundDescriptors) {
assert ApplicationManager.getApplication().isUnitTestMode();
for (SurroundDescriptor descriptor : surroundDescriptors) {
final PsiElement[] elements = descriptor.getElementsToSurround(file, startOffset, endOffset);
if (elements.length > 0) {
for (Surrounder descriptorSurrounder : descriptor.getSurrounders()) {
if (surrounder.getClass().equals(descriptorSurrounder.getClass())) {
doSurround(project, editor, surrounder, elements);
return;
}
}
}
}
}
private static void showPopup(Editor editor, List<AnAction> applicable) {
DataContext context = DataManager.getInstance().getDataContext(editor.getContentComponent());
JBPopupFactory.ActionSelectionAid mnemonics = JBPopupFactory.ActionSelectionAid.MNEMONICS;
DefaultActionGroup group = new DefaultActionGroup(applicable.toArray(new AnAction[applicable.size()]));
JBPopupFactory.getInstance().createActionGroupPopup(CHOOSER_TITLE, group, context, mnemonics, true).showInBestPositionFor(editor);
}
static void doSurround(final Project project, final Editor editor, final Surrounder surrounder, final PsiElement[] elements) {
if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
return;
}
try {
PsiDocumentManager.getInstance(project).commitAllDocuments();
int col = editor.getCaretModel().getLogicalPosition().column;
int line = editor.getCaretModel().getLogicalPosition().line;
if (!editor.getCaretModel().supportsMultipleCarets()) {
LogicalPosition pos = new LogicalPosition(0, 0);
editor.getCaretModel().moveToLogicalPosition(pos);
}
TextRange range = surrounder.surroundElements(project, editor, elements);
if (TemplateManager.getInstance(project).getActiveTemplate(editor) == null) {
LogicalPosition pos1 = new LogicalPosition(line, col);
editor.getCaretModel().moveToLogicalPosition(pos1);
}
if (range != null) {
int offset = range.getStartOffset();
editor.getCaretModel().removeSecondaryCarets();
editor.getCaretModel().moveToOffset(offset);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
@Nullable
private static List<AnAction> doBuildSurroundActions(Project project,
Editor editor,
PsiFile file,
Map<Surrounder, PsiElement[]> surrounders) {
final List<AnAction> applicable = new ArrayList<AnAction>();
boolean hasEnabledSurrounders = false;
Set<Character> usedMnemonicsSet = new HashSet<Character>();
int index = 0;
for (Map.Entry<Surrounder, PsiElement[]> entry : surrounders.entrySet()) {
Surrounder surrounder = entry.getKey();
PsiElement[] elements = entry.getValue();
if (surrounder.isApplicable(elements)) {
char mnemonic;
if (index < 9) {
mnemonic = (char)('0' + index + 1);
}
else if (index == 9) {
mnemonic = '0';
}
else {
mnemonic = (char)('A' + index - 10);
}
index++;
usedMnemonicsSet.add(Character.toUpperCase(mnemonic));
applicable.add(new InvokeSurrounderAction(surrounder, project, editor, elements, mnemonic));
hasEnabledSurrounders = true;
}
}
List<CustomLiveTemplate> customTemplates = TemplateManagerImpl.listApplicableCustomTemplates(editor, file, true);
List<TemplateImpl> templates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, true);
if (!templates.isEmpty() || !customTemplates.isEmpty()) {
applicable.add(new Separator("Live templates"));
}
for (TemplateImpl template : templates) {
applicable.add(new InvokeTemplateAction(template, editor, project, usedMnemonicsSet));
hasEnabledSurrounders = true;
}
for (CustomLiveTemplate customTemplate : customTemplates) {
applicable.add(new WrapWithCustomTemplateAction(customTemplate, editor, file, usedMnemonicsSet));
hasEnabledSurrounders = true;
}
if (!templates.isEmpty() || !customTemplates.isEmpty()) {
applicable.add(Separator.getInstance());
applicable.add(new ConfigureTemplatesAction());
}
return hasEnabledSurrounders ? applicable : null;
}
private static class InvokeSurrounderAction extends AnAction {
private final Surrounder mySurrounder;
private final Project myProject;
private final Editor myEditor;
private final PsiElement[] myElements;
public InvokeSurrounderAction(Surrounder surrounder, Project project, Editor editor, PsiElement[] elements, char mnemonic) {
super(UIUtil.MNEMONIC + String.valueOf(mnemonic) + ". " + surrounder.getTemplateDescription());
mySurrounder = surrounder;
myProject = project;
myEditor = editor;
myElements = elements;
}
@Override
public void actionPerformed(AnActionEvent e) {
new WriteCommandAction(myProject) {
@Override
protected void run(@NotNull Result result) throws Exception {
doSurround(myProject, myEditor, mySurrounder, myElements);
}
}.execute();
}
}
private static class ConfigureTemplatesAction extends AnAction {
private ConfigureTemplatesAction() {
super("Configure Live Templates...");
}
@Override
public void actionPerformed(AnActionEvent e) {
ShowSettingsUtil.getInstance().showSettingsDialog(e.getData(CommonDataKeys.PROJECT), "Live Templates");
}
}
}