/* * Copyright 2012-2015 Sergey Ignatov * * 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.intellij.erlang.quickfixes; import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupAdapter; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.HintUpdateSupply; import com.intellij.util.containers.ContainerUtil; import org.intellij.erlang.psi.*; import org.intellij.erlang.psi.impl.ErlangElementFactory; import org.intellij.erlang.psi.impl.ErlangPsiImplUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.util.List; import java.util.Set; public class ErlangExportFunctionFix extends LocalQuickFixAndIntentionActionOnPsiElement { private static final int MAX_EXPORT_STRING_LENGTH = 80; private final Set<RangeHighlighter> myExportHighlighters = ContainerUtil.newHashSet(); public ErlangExportFunctionFix(ErlangFunction function) { super(function); } @NotNull @Override public String getFamilyName() { return "Export function"; } @NotNull @Override public String getText() { return getFamilyName(); } @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @Nullable Editor editor, @NotNull PsiElement startElement, @Nullable PsiElement endElement) { if (startElement instanceof ErlangFunction) { processFunction(project, (ErlangFunction) startElement, editor); } } private void processFunction(@NotNull final Project project, @NotNull final ErlangFunction function, @Nullable Editor editor) { if (!(function.getContainingFile() instanceof ErlangFile)) return; ErlangFile file = (ErlangFile) function.getContainingFile(); List<ErlangExport> exports = getExportPsiElements(file); if (exports.isEmpty()) { createNewExport(project, file, function); return; } if (exports.size() == 1) { updateExport(project, function, exports.get(0)); return; } if (editor == null || ApplicationManager.getApplication().isUnitTestMode()) { ErlangExport first = ContainerUtil.getFirstItem(exports); assert first != null; updateExport(project, function, first); return; } List<ErlangExport> notEmptyExports = getNotEmptyExports(exports); if (notEmptyExports.size() == 1) { updateExport(project, function, notEmptyExports.get(0)); return; } final JBList exportPopupList = createExportJBList(editor, notEmptyExports); new HintUpdateSupply(exportPopupList) { @Override protected PsiElement getPsiElementForHint(Object selectedValue) { return (PsiElement) selectedValue; } }; JBPopupFactory.getInstance().createListPopupBuilder(exportPopupList) .setTitle("Choose export") .setMovable(false) .setResizable(false) .addListener(new JBPopupAdapter() { @Override public void onClosed(LightweightWindowEvent event) { dropHighlighters(); } }) .setItemChoosenCallback(() -> CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { PsiDocumentManager.getInstance(project).commitAllDocuments(); updateExport(project, function, (ErlangExport) exportPopupList.getSelectedValue()); }), "Export function", null)) .setRequestFocus(true) .createPopup().showInBestPositionFor(editor); } private void dropHighlighters() { for (RangeHighlighter highlight : myExportHighlighters) { highlight.dispose(); } myExportHighlighters.clear(); } private static void updateExport(@NotNull Project project, @NotNull ErlangFunction function, @Nullable ErlangExport oldExport) { if (oldExport == null) return; ErlangExportFunctions exportFunctions = oldExport.getExportFunctions(); if (exportFunctions == null) return; String replace = exportFunctions.getText().replace("[", "").replace("]", ""); String s = replace + (!StringUtil.isEmptyOrSpaces(replace) ? ", " : "") + ErlangPsiImplUtil.createFunctionPresentation(function); ErlangAttribute attribute = PsiTreeUtil.getParentOfType(oldExport, ErlangAttribute.class); if (attribute == null) return; attribute.replace(ErlangElementFactory.createExportFromText(project, s)); } private static void createNewExport(@NotNull Project project, @NotNull ErlangFile file, @NotNull ErlangFunction function) { ErlangCompositeElement elementBefore = ErlangQuickFixBase.getAnchorElement(file); if (elementBefore != null) { file.addBefore(ErlangElementFactory.createExportFromText( project, function.getName() + "/" + function.getArity()), elementBefore); file.addBefore(ErlangElementFactory.createLeafFromText(project, "\n\n"), elementBefore); } } @NotNull public static List<ErlangExport> getExportPsiElements(@NotNull ErlangFile file) { List<ErlangExport> exports = ContainerUtil.newArrayList(); // todo: mode to erlang file for (ErlangAttribute attribute : file.getAttributes()) { if (attribute.getExport() != null) { exports.add(attribute.getExport()); } } return exports; } @NotNull public static List<ErlangExport> getNotEmptyExports(@NotNull List<ErlangExport> exports) { return ContainerUtil.filter(exports, export -> { ErlangExportFunctions functions = export.getExportFunctions(); return functions != null && !functions.getExportFunctionList().isEmpty(); }); } @NotNull private JBList createExportJBList(@NotNull final Editor editor, @NotNull List<ErlangExport> exportList) { DefaultListModel model = new DefaultListModel(); for (ErlangExport export : exportList) { //noinspection unchecked, because of Java 6 model.addElement(export); } final JBList exportPopupList = new JBList(model); exportPopupList.setCellRenderer(new DefaultListCellRenderer() { @NotNull @Override public Component getListCellRendererComponent(@NotNull JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); ErlangExport export = (ErlangExport) value; if (export != null && export.getExportFunctions() != null) { setText(getPrettyPrefix(export.getExportFunctions().getText().replace("[", "").replace("]", ""))); } return rendererComponent; } @NotNull private String getPrettyPrefix(@NotNull String s) { if (s.length() > MAX_EXPORT_STRING_LENGTH) { return s.substring(0, MAX_EXPORT_STRING_LENGTH - 2) + "..."; } return s; } }); exportPopupList.addListSelectionListener(e -> { ErlangExport export = (ErlangExport) exportPopupList.getSelectedValue(); if (export == null) return; dropHighlighters(); MarkupModel markupModel = editor.getMarkupModel(); TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.IDENTIFIER_UNDER_CARET_ATTRIBUTES); ErlangExport selectedExport = (ErlangExport) exportPopupList.getSelectedValue(); myExportHighlighters.add( markupModel.addRangeHighlighter( selectedExport.getTextRange().getStartOffset(), selectedExport.getTextRange().getEndOffset(), HighlighterLayer.SELECTION - 1, attributes, HighlighterTargetArea.EXACT_RANGE)); }); return exportPopupList; } }