| /* |
| * 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.ide.actions; |
| |
| import com.intellij.ide.util.gotoByName.ChooseByNameFilter; |
| import com.intellij.ide.util.gotoByName.ChooseByNameModel; |
| import com.intellij.ide.util.gotoByName.ChooseByNamePopup; |
| import com.intellij.ide.util.gotoByName.ChooseByNamePopupComponent; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; |
| import com.intellij.openapi.keymap.KeymapUtil; |
| import com.intellij.openapi.project.DumbAwareAction; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.ui.DocumentAdapter; |
| import com.intellij.ui.speedSearch.SpeedSearchSupply; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.DocumentEvent; |
| import java.awt.*; |
| import java.awt.event.InputEvent; |
| import java.awt.event.KeyEvent; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Author: msk |
| */ |
| public abstract class GotoActionBase extends AnAction { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.GotoActionBase"); |
| |
| protected static Class myInAction = null; |
| private static final Map<Class, Pair<String, Integer>> ourLastStrings = ContainerUtil.newHashMap(); |
| private static final Map<Class, List<String>> ourHistory = ContainerUtil.newHashMap(); |
| private int myHistoryIndex = 0; |
| |
| @Override |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| LOG.assertTrue(!getClass().equals(myInAction)); |
| try { |
| myInAction = getClass(); |
| List<String> strings = ourHistory.get(myInAction); |
| myHistoryIndex = strings == null || strings.size() <= 1 || !ourLastStrings.containsKey(myInAction) ? 0 : 1; |
| gotoActionPerformed(e); |
| } |
| catch (Throwable t) { |
| LOG.error(t); |
| myInAction = null; |
| } |
| } |
| |
| protected abstract void gotoActionPerformed(AnActionEvent e); |
| |
| @Override |
| public void update(@NotNull final AnActionEvent event) { |
| final Presentation presentation = event.getPresentation(); |
| final DataContext dataContext = event.getDataContext(); |
| final Project project = CommonDataKeys.PROJECT.getData(dataContext); |
| presentation.setEnabled(!getClass().equals (myInAction) && (!requiresProject() || project != null) && hasContributors(dataContext)); |
| presentation.setVisible(hasContributors(dataContext)); |
| } |
| |
| protected boolean hasContributors(final DataContext dataContext) { |
| return true; |
| } |
| |
| protected boolean requiresProject() { |
| return true; |
| } |
| |
| @Nullable |
| public static PsiElement getPsiContext(final AnActionEvent e) { |
| PsiFile file = e.getData(CommonDataKeys.PSI_FILE); |
| if (file != null) return file; |
| Project project = e.getData(CommonDataKeys.PROJECT); |
| return getPsiContext(project); |
| } |
| |
| @Nullable |
| public static PsiElement getPsiContext(final Project project) { |
| if (project == null) return null; |
| Editor selectedEditor = FileEditorManager.getInstance(project).getSelectedTextEditor(); |
| if (selectedEditor == null) return null; |
| Document document = selectedEditor.getDocument(); |
| return PsiDocumentManager.getInstance(project).getPsiFile(document); |
| } |
| |
| protected abstract static class GotoActionCallback<T> { |
| @Nullable |
| protected ChooseByNameFilter<T> createFilter(@NotNull ChooseByNamePopup popup) { |
| return null; |
| } |
| |
| public abstract void elementChosen(ChooseByNamePopup popup, Object element); |
| } |
| |
| protected static Pair<String, Integer> getInitialText(boolean useEditorSelection, AnActionEvent e) { |
| final String predefined = e.getData(PlatformDataKeys.PREDEFINED_TEXT); |
| if (!StringUtil.isEmpty(predefined)) { |
| return Pair.create(predefined, 0); |
| } |
| if (useEditorSelection) { |
| final Editor editor = e.getData(CommonDataKeys.EDITOR); |
| if (editor != null) { |
| final String selectedText = editor.getSelectionModel().getSelectedText(); |
| if (selectedText != null && !selectedText.contains("\n")) { |
| return Pair.create(selectedText, 0); |
| } |
| } |
| } |
| |
| final String query = e.getData(SpeedSearchSupply.SPEED_SEARCH_CURRENT_QUERY); |
| if (!StringUtil.isEmpty(query)) { |
| return Pair.create(query, 0); |
| } |
| |
| final Component focusOwner = IdeFocusManager.getInstance(getEventProject(e)).getFocusOwner(); |
| if (focusOwner instanceof JComponent) { |
| final SpeedSearchSupply supply = SpeedSearchSupply.getSupply((JComponent)focusOwner); |
| if (supply != null) { |
| return Pair.create(supply.getEnteredPrefix(), 0); |
| } |
| } |
| |
| if (myInAction != null) { |
| final Pair<String, Integer> lastString = ourLastStrings.get(myInAction); |
| if (lastString != null) { |
| return lastString; |
| } |
| } |
| |
| return Pair.create("", 0); |
| } |
| |
| protected <T> void showNavigationPopup(AnActionEvent e, ChooseByNameModel model, final GotoActionCallback<T> callback) { |
| showNavigationPopup(e, model, callback, true); |
| } |
| |
| protected <T> void showNavigationPopup(AnActionEvent e, |
| ChooseByNameModel model, |
| final GotoActionCallback<T> callback, |
| final boolean allowMultipleSelection) { |
| showNavigationPopup(e, model, callback, null, true, allowMultipleSelection); |
| } |
| |
| protected <T> void showNavigationPopup(AnActionEvent e, |
| ChooseByNameModel model, |
| final GotoActionCallback<T> callback, |
| @Nullable final String findUsagesTitle, |
| boolean useSelectionFromEditor) { |
| showNavigationPopup(e, model, callback, findUsagesTitle, useSelectionFromEditor, true); |
| } |
| |
| protected <T> void showNavigationPopup(AnActionEvent e, |
| ChooseByNameModel model, |
| final GotoActionCallback<T> callback, |
| @Nullable final String findUsagesTitle, |
| boolean useSelectionFromEditor, |
| final boolean allowMultipleSelection) { |
| final Project project = e.getData(CommonDataKeys.PROJECT); |
| boolean mayRequestOpenInCurrentWindow = model.willOpenEditor() && FileEditorManagerEx.getInstanceEx(project).hasSplitOrUndockedWindows(); |
| Pair<String, Integer> start = getInitialText(useSelectionFromEditor, e); |
| showNavigationPopup(callback, findUsagesTitle, |
| ChooseByNamePopup.createPopup(project, model, getPsiContext(e), start.first, |
| mayRequestOpenInCurrentWindow, start.second), allowMultipleSelection); |
| } |
| |
| protected <T> void showNavigationPopup(final GotoActionCallback<T> callback, |
| @Nullable final String findUsagesTitle, |
| final ChooseByNamePopup popup) { |
| showNavigationPopup(callback, findUsagesTitle, popup, true); |
| } |
| |
| protected <T> void showNavigationPopup(final GotoActionCallback<T> callback, |
| @Nullable final String findUsagesTitle, |
| final ChooseByNamePopup popup, |
| final boolean allowMultipleSelection) { |
| |
| final Class startedAction = myInAction; |
| LOG.assertTrue(startedAction != null); |
| |
| popup.setCheckBoxShortcut(getShortcutSet()); |
| popup.setFindUsagesTitle(findUsagesTitle); |
| final ChooseByNameFilter<T> filter = callback.createFilter(popup); |
| |
| if (historyEnabled() && popup.getAdText() == null) { |
| popup.setAdText("Press " + |
| KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_MASK)) + " or " + |
| KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_MASK)) + |
| " to navigate through the history"); |
| } |
| |
| popup.invoke(new ChooseByNamePopupComponent.Callback() { |
| @Override |
| public void onClose() { |
| //noinspection ConstantConditions |
| if (startedAction != null && startedAction.equals(myInAction)) { |
| String text = popup.getEnteredText(); |
| ourLastStrings.put(myInAction, Pair.create(text, popup.getSelectedIndex())); |
| updateHistory(text); |
| myInAction = null; |
| } |
| if (filter != null) { |
| filter.close(); |
| } |
| } |
| |
| private void updateHistory(@Nullable String text) { |
| if (!StringUtil.isEmptyOrSpaces(text)) { |
| List<String> history = ourHistory.get(myInAction); |
| if (history == null) history = ContainerUtil.newArrayList(); |
| if (!text.equals(ContainerUtil.getFirstItem(history))) { |
| history.add(0, text); |
| } |
| ourHistory.put(myInAction, history); |
| } |
| } |
| |
| @Override |
| public void elementChosen(Object element) { |
| callback.elementChosen(popup, element); |
| } |
| }, ModalityState.current(), allowMultipleSelection); |
| |
| final JTextField editor = popup.getTextField(); |
| |
| final DocumentAdapter historyResetListener = new DocumentAdapter() { |
| @Override |
| protected void textChanged(DocumentEvent e) { |
| myHistoryIndex = 0; |
| } |
| }; |
| |
| abstract class HistoryAction extends DumbAwareAction { |
| @Override |
| public void update(@NotNull AnActionEvent e) { |
| e.getPresentation().setEnabled(historyEnabled()); |
| } |
| |
| void setText(@NotNull List<String> strings) { |
| javax.swing.text.Document document = editor.getDocument(); |
| document.removeDocumentListener(historyResetListener); |
| editor.setText(strings.get(myHistoryIndex)); |
| document.addDocumentListener(historyResetListener); |
| editor.selectAll(); |
| } |
| } |
| |
| editor.getDocument().addDocumentListener(historyResetListener); |
| |
| new HistoryAction() { |
| @Override |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| List<String> strings = ourHistory.get(myInAction); |
| setText(strings); |
| myHistoryIndex = myHistoryIndex >= strings.size() - 1 ? 0 : myHistoryIndex + 1; |
| } |
| |
| }.registerCustomShortcutSet(CustomShortcutSet.fromString("ctrl UP"), editor); |
| |
| new HistoryAction() { |
| @Override |
| public void actionPerformed(@NotNull AnActionEvent e) { |
| List<String> strings = ourHistory.get(myInAction); |
| setText(strings); |
| myHistoryIndex = myHistoryIndex <= 0 ? strings.size() - 1 : myHistoryIndex - 1; |
| } |
| }.registerCustomShortcutSet(CustomShortcutSet.fromString("ctrl DOWN"), editor); |
| } |
| |
| private static boolean historyEnabled() { |
| return !ContainerUtil.isEmpty(ourHistory.get(myInAction)); |
| } |
| } |