diff --git a/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java b/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java index cf07ac1f46b8..6832c1a79ce6 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java @@ -81,7 +81,7 @@ public class RecentLocationsAction extends AnAction { private static final int MINIMUM_WIDTH = JBUI.scale(200); private static final int MINIMUM_HEIGHT = JBUI.scale(100); private static final Color SHORTCUT_FOREGROUND_COLOR = UIUtil.getContextHelpForeground(); - private static final String SHORTCUT_HEX_COLOR = String.format("#%02x%02x%02x", + public static final String SHORTCUT_HEX_COLOR = String.format("#%02x%02x%02x", SHORTCUT_FOREGROUND_COLOR.getRed(), SHORTCUT_FOREGROUND_COLOR.getGreen(), SHORTCUT_FOREGROUND_COLOR.getBlue()); diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentFilesAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentFilesAction.java index 003581b0c8e9..91180e3532a6 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentFilesAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentFilesAction.java @@ -22,6 +22,7 @@ package com.intellij.ide.actions; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.IdeBundle; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.project.DumbAwareAction; import org.jetbrains.annotations.NotNull; @@ -32,7 +33,7 @@ public class ShowRecentFilesAction extends DumbAwareAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.recent.files"); - Switcher.createAndShowSwitcher(e, IdeBundle.message("title.popup.recent.files"), true, null); + Switcher.createAndShowSwitcher(e, IdeBundle.message("title.popup.recent.files"), IdeActions.ACTION_RECENT_FILES,false, true); } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentlyEditedFilesAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentlyEditedFilesAction.java index 76fce0205c1c..b5afc71a4216 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentlyEditedFilesAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/ShowRecentlyEditedFilesAction.java @@ -20,7 +20,6 @@ package com.intellij.ide.actions; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; @@ -30,7 +29,7 @@ public class ShowRecentlyEditedFilesAction extends DumbAwareAction { public void actionPerformed(@NotNull AnActionEvent e) { final Project project = e.getProject(); if (project != null) { - Switcher.createAndShowSwitcher(e, "Recently Edited Files", true, IdeDocumentHistory.getInstance(project).getChangedFiles()); + Switcher.createAndShowSwitcher(e, "Recently Edited Files", "RecentChangedFiles", true, true); } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java b/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java index 5f2b49eb252e..5166a0ef2e14 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java @@ -3,18 +3,21 @@ package com.intellij.ide.actions; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.ide.DataManager; +import com.intellij.ide.IdeBundle; import com.intellij.ide.IdeEventQueue; import com.intellij.ide.ui.UISettings; import com.intellij.ide.ui.UISettingsState; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.impl.PresentationFactory; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Experiments; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; import com.intellij.openapi.fileEditor.impl.EditorTabPresentationUtil; import com.intellij.openapi.fileEditor.impl.EditorWindow; @@ -43,6 +46,8 @@ import com.intellij.openapi.wm.impl.ToolWindowManagerImpl; import com.intellij.problems.WolfTheProblemSolver; import com.intellij.ui.*; import com.intellij.ui.border.CustomLineBorder; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBList; import com.intellij.ui.speedSearch.NameFilteringListModel; import com.intellij.ui.speedSearch.SpeedSearchUtil; @@ -66,6 +71,7 @@ import java.io.File; import java.util.List; import java.util.*; +import static com.intellij.ide.actions.RecentLocationsAction.SHORTCUT_HEX_COLOR; import static com.intellij.openapi.keymap.KeymapUtil.getActiveKeymapShortcuts; import static java.awt.event.InputEvent.CTRL_DOWN_MASK; import static java.awt.event.KeyEvent.*; @@ -146,7 +152,8 @@ public class Switcher extends AnAction implements DumbAware { } if (SWITCHER == null) { isNewSwitcher = true; - SWITCHER = createAndShowSwitcher(e, SWITCHER_TITLE, false, null); + // Assigns SWITCHER field + createAndShowSwitcher(project, SWITCHER_TITLE, IdeActions.ACTION_SWITCHER, false, false); FeatureUsageTracker.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID); } } @@ -167,32 +174,47 @@ public class Switcher extends AnAction implements DumbAware { } } + /** + * @deprecated Please use {@link Switcher#createAndShowSwitcher(AnActionEvent, String, boolean, boolean)} + */ + @Deprecated @Nullable public static SwitcherPanel createAndShowSwitcher(@NotNull AnActionEvent e, @NotNull String title, boolean pinned, @Nullable final VirtualFile[] vFiles) { - Project project = getEventProject(e); - if (SWITCHER != null && Comparing.equal(SWITCHER.myTitle, title)) { - SWITCHER.goForward(); - return null; + return createAndShowSwitcher(e, title, "RecentFiles", pinned, vFiles != null); + } + + public static SwitcherPanel createAndShowSwitcher(@NotNull AnActionEvent e, @NotNull String title, @NotNull String actionId, boolean onlyEdited, boolean pinned) { + Project project = e.getProject(); + if (SWITCHER != null) { + final boolean sameShortcut = Comparing.equal(SWITCHER.myTitle, title); + if (SWITCHER.isCheckboxMode()) { + if (sameShortcut) { + SWITCHER.toggleShowEditedFiles(); + } + else { + SWITCHER.setShowOnlyEditedFiles(onlyEdited); + } + return null; + } + else if (sameShortcut) { + SWITCHER.goForward(); + return null; + } } - return project == null ? null : createAndShowSwitcher(project, title, pinned, vFiles == null ? null : Arrays.asList(vFiles)); + return project == null ? null : createAndShowSwitcher(project, title, actionId, onlyEdited, pinned); } @Nullable private static SwitcherPanel createAndShowSwitcher(@NotNull Project project, @NotNull String title, - boolean pinned, - @Nullable final List vFiles) { + @NotNull String actionId, + boolean onlyEdited, + boolean pinned) { synchronized (Switcher.class) { if (SWITCHER != null) { SWITCHER.cancel(); } - SWITCHER = new SwitcherPanel(project, title, pinned) { - @NotNull - @Override - protected List getFiles(@NotNull Project project) { - return vFiles != null ? vFiles : super.getFiles(project); - } - }; + SWITCHER = new SwitcherPanel(project, title, actionId, onlyEdited, pinned); return SWITCHER; } } @@ -203,6 +225,7 @@ public class Switcher extends AnAction implements DumbAware { final JBList files; final JPanel separator; final ToolWindowManager twManager; + final JBCheckBox myShowOnlyEditedFilesCheckBox; final JLabel pathLabel = new JLabel(" "); final JPanel descriptions; final Project project; @@ -211,6 +234,7 @@ public class Switcher extends AnAction implements DumbAware { final Alarm myAlarm; final SwitcherSpeedSearch mySpeedSearch; final String myTitle; + final String myActionId; @Nullable @Override @@ -301,10 +325,11 @@ public class Switcher extends AnAction implements DumbAware { }; @SuppressWarnings({"ConstantConditions"}) - SwitcherPanel(@NotNull final Project project, @NotNull String title, boolean pinned) { + SwitcherPanel(@NotNull final Project project, @NotNull String title, @NotNull String actionId, boolean onlyEdited, boolean pinned) { setLayout(new SwitcherLayouter()); this.project = project; myTitle = title; + myActionId = actionId; myPinned = pinned; mySpeedSearch = pinned ? new SwitcherSpeedSearch() : null; @@ -327,7 +352,7 @@ public class Switcher extends AnAction implements DumbAware { descriptions.setBorder(new CustomLineBorder(JBUI.CurrentTheme.Advertiser.borderColor(), JBUI.insetsTop(1))); descriptions.add(pathLabel, BorderLayout.CENTER); twManager = ToolWindowManager.getInstance(project); - DefaultListModel twModel = new DefaultListModel(); + CollectionListModel twModel = new CollectionListModel(); List actions = ToolWindowsGroup.getToolWindowActions(project, true); List windows = ContainerUtil.newArrayList(); for (ActivateToolWindowAction action : actions) { @@ -340,7 +365,7 @@ public class Switcher extends AnAction implements DumbAware { final Map map = ContainerUtil.reverseMap(twShortcuts); Collections.sort(windows, (o1, o2) -> StringUtil.compare(map.get(o1), map.get(o2), false)); for (ToolWindow window : windows) { - twModel.addElement(window); + twModel.add(window); } toolWindows = new JBList(twModel); @@ -390,66 +415,12 @@ public class Switcher extends AnAction implements DumbAware { separator.setPreferredSize(JBUI.size(9, 10)); separator.setBackground(toolWindows.getBackground()); - int selectionIndex = -1; - final FileEditorManagerImpl editorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project); - final ArrayList filesData = new ArrayList<>(); - final ArrayList editors = new ArrayList<>(); - if (!pinned) { - if (UISettings.getInstance().getEditorTabPlacement() != UISettings.TABS_NONE) { - for (Pair pair : editorManager.getSelectionHistory()) { - editors.add(new FileInfo(pair.first, pair.second, project)); - } - } - } - if (editors.size() < 2 || isPinnedMode()) { - if (isPinnedMode() && editors.size() > 1) { - filesData.addAll(editors); - } - final List recentFiles = getFiles(project); - final int maxFiles = Math.max(editors.size(), recentFiles.size()); - final int minIndex = isPinnedMode() ? 0 : (recentFiles.size() - Math.min(toolWindows.getModel().getSize(), maxFiles)); - boolean firstRecentMarked = false; - final List selectedFiles = Arrays.asList(editorManager.getSelectedFiles()); - for (int i = recentFiles.size() - 1; i >= minIndex; i--) { - if (isPinnedMode() - && selectedFiles.contains(recentFiles.get(i)) - && UISettings.getInstance().getEditorTabPlacement() != UISettings.TABS_NONE) { - continue; - } - - final FileInfo info = new FileInfo(recentFiles.get(i), null, project); - boolean add = true; - if (isPinnedMode()) { - for (FileInfo fileInfo : filesData) { - if (fileInfo.first.equals(info.first)) { - add = false; - break; - } - } - } - if (add) { - filesData.add(info); - if (!firstRecentMarked) { - selectionIndex = filesData.size() - 1; - if (selectionIndex != 0 || UISettings.getInstance().getEditorTabPlacement() != UISettings.TABS_NONE || !isPinnedMode() || selectedFiles.isEmpty()) { - firstRecentMarked = true; - } - } - } - } - //if (editors.size() == 1) selectionIndex++; - if (editors.size() == 1 && (filesData.isEmpty() || !editors.get(0).getFirst().equals(filesData.get(0).getFirst()))) { - filesData.add(0, editors.get(0)); - } - } else { - for (int i = 0; i < Math.min(30, editors.size()); i++) { - filesData.add(editors.get(i)); - } - } - - final DefaultListModel filesModel = new DefaultListModel(); - for (FileInfo editor : filesData) { - filesModel.addElement(editor); + final Pair, Integer> filesAndSelection = getFilesToShowAndSelectionIndex(project, collectFiles(project, onlyEdited), + toolWindows.getModel().getSize(), pinned); + final int selectionIndex = filesAndSelection.getSecond(); + final CollectionListModel filesModel = new CollectionListModel(); + for (FileInfo editor : filesAndSelection.getFirst()) { + filesModel.add(editor); } final VirtualFilesRenderer filesRenderer = new VirtualFilesRenderer(this) { @@ -557,7 +528,7 @@ public class Switcher extends AnAction implements DumbAware { ScrollingUtil.ensureSelectionExists(files); this.add(toolWindows, BorderLayout.WEST); - if (filesModel.size() > 0) { + if (filesModel.getSize() > 0) { files.setAlignmentY(1f); final JScrollPane pane = ScrollPaneFactory.createScrollPane(files, true); pane.setPreferredSize(new Dimension(files.getPreferredSize().width, 20 * 20)); @@ -580,13 +551,31 @@ public class Switcher extends AnAction implements DumbAware { KeymapUtil.reassignAction(files, getKeyStroke(VK_UP, 0), getKeyStroke(VK_UP, CTRL_DOWN_MASK), WHEN_FOCUSED, false); KeymapUtil.reassignAction(files, getKeyStroke(VK_DOWN, 0), getKeyStroke(VK_DOWN, CTRL_DOWN_MASK), WHEN_FOCUSED, false); + myShowOnlyEditedFilesCheckBox = new MyCheckBox(actionId, onlyEdited); + JPanel topPanel = createTopPanel(myShowOnlyEditedFilesCheckBox, isCheckboxMode() ? IdeBundle.message("title.popup.recent.files") : title); + this.add(topPanel, BorderLayout.NORTH); + + if (isCheckboxMode()) { + myShowOnlyEditedFilesCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setShowOnlyEditedFiles(myShowOnlyEditedFilesCheckBox.isSelected()); + } + }); + } + else { + myShowOnlyEditedFilesCheckBox.setEnabled(false); + myShowOnlyEditedFilesCheckBox.setVisible(false); + } + + + myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(this, filesModel.getSize() > 0 ? files : toolWindows) .setResizable(pinned) .setModalContext(false) .setFocusable(true) .setRequestFocus(true) - .setTitle(title) .setCancelOnWindowDeactivation(true) .setCancelOnOtherWindowOpen(true) .setMovable(pinned) @@ -637,7 +626,6 @@ public class Switcher extends AnAction implements DumbAware { addFocusTraversalKeys(popupFocusAncestor, KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, "LEFT"); addFocusTraversalKeys(popupFocusAncestor, KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, "control RIGHT"); addFocusTraversalKeys(popupFocusAncestor, KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, "control LEFT"); - } private Container getPopupFocusAncestor() { @@ -645,6 +633,92 @@ public class Switcher extends AnAction implements DumbAware { return content == null ? null : content.getFocusCycleRootAncestor(); } + @NotNull + private static List collectFiles(@NotNull Project project, boolean onlyEdited) { + return onlyEdited ? Arrays.asList(IdeDocumentHistory.getInstance(project).getChangedFiles()) + : EditorHistoryManager.getInstance(project).getFileList(); + } + + @NotNull + private static Pair, Integer> getFilesToShowAndSelectionIndex(@NotNull Project project, + @NotNull List filesForInit, + int toolWindowsCount, + boolean pinned) { + int selectionIndex = -1; + final FileEditorManagerImpl editorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project); + final ArrayList filesData = new ArrayList<>(); + final ArrayList editors = new ArrayList<>(); + if (!pinned) { + if (UISettings.getInstance().getEditorTabPlacement() != UISettings.TABS_NONE) { + for (Pair pair : editorManager.getSelectionHistory()) { + editors.add(new FileInfo(pair.first, pair.second, project)); + } + } + } + if (editors.size() < 2 || pinned) { + if (pinned && editors.size() > 1) { + filesData.addAll(editors); + } + final List recentFiles = filesForInit; + final int maxFiles = Math.max(editors.size(), recentFiles.size()); + final int minIndex = pinned ? 0 : (recentFiles.size() - Math.min(toolWindowsCount, maxFiles)); + boolean firstRecentMarked = false; + final List selectedFiles = Arrays.asList(editorManager.getSelectedFiles()); + for (int i = recentFiles.size() - 1; i >= minIndex; i--) { + if (pinned + && selectedFiles.contains(recentFiles.get(i)) + && UISettings.getInstance().getEditorTabPlacement() != UISettings.TABS_NONE) { + continue; + } + + final FileInfo info = new FileInfo(recentFiles.get(i), null, project); + boolean add = true; + if (pinned) { + for (FileInfo fileInfo : filesData) { + if (fileInfo.first.equals(info.first)) { + add = false; + break; + } + } + } + if (add) { + filesData.add(info); + if (!firstRecentMarked) { + selectionIndex = filesData.size() - 1; + if (selectionIndex != 0 || UISettings.getInstance().getEditorTabPlacement() != UISettings.TABS_NONE || !pinned || selectedFiles.isEmpty()) { + firstRecentMarked = true; + } + } + } + } + //if (editors.size() == 1) selectionIndex++; + if (editors.size() == 1 && (filesData.isEmpty() || !editors.get(0).getFirst().equals(filesData.get(0).getFirst()))) { + filesData.add(0, editors.get(0)); + } + } else { + for (int i = 0; i < Math.min(30, editors.size()); i++) { + filesData.add(editors.get(i)); + } + } + + return Pair.create(filesData, selectionIndex); + } + + @NotNull + private static JPanel createTopPanel(JBCheckBox showOnlyEditedFilesCheckBox, @NotNull String title) { + JPanel topPanel = new CaptionPanel(); + JBLabel titleLabel = new JBLabel(title); + titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD)); + topPanel.add(titleLabel, BorderLayout.WEST); + topPanel.add(showOnlyEditedFilesCheckBox, BorderLayout.EAST); + + Dimension size = topPanel.getPreferredSize(); + size.height = JBUI.scale(29); + topPanel.setPreferredSize(size); + topPanel.setBorder(JBUI.Borders.empty(5, 8)); + return topPanel; + } + private static void addFocusTraversalKeys (Container focusCycleRoot, int focusTraversalType, String keyStroke) { Set focusTraversalKeySet = focusCycleRoot.getFocusTraversalKeys(focusTraversalType); @@ -653,9 +727,10 @@ public class Switcher extends AnAction implements DumbAware { focusCycleRoot.setFocusTraversalKeys(focusTraversalType, set); } + @Deprecated @NotNull protected List getFiles(@NotNull Project project) { - return EditorHistoryManager.getInstance(project).getFileList(); + throw new UnsupportedOperationException("deprecated"); } @NotNull @@ -817,8 +892,8 @@ public class Switcher extends AnAction implements DumbAware { private static void removeElementAt(@NotNull JList jList, int index) { final ListModel model = jList.getModel(); - if (model instanceof DefaultListModel) { - ((DefaultListModel)model).removeElementAt(index); + if (model instanceof CollectionListModel) { + ((CollectionListModel)model).remove(index); } else if (model instanceof NameFilteringListModel) { ((NameFilteringListModel)model).remove(index); @@ -891,6 +966,40 @@ public class Switcher extends AnAction implements DumbAware { return files.hasFocus() ? files : toolWindows.hasFocus() ? toolWindows : preferable; } + boolean isCheckboxMode() { + return isPinnedMode() && Experiments.isFeatureEnabled("recent.and.edited.files.together"); + } + + void toggleShowEditedFiles() { + myShowOnlyEditedFilesCheckBox.doClick(); + } + + void setShowOnlyEditedFiles(boolean onlyEdited) { + if (myShowOnlyEditedFilesCheckBox.isSelected() != onlyEdited) { + myShowOnlyEditedFilesCheckBox.setSelected(onlyEdited); + } + + final boolean listWasSelected = files.getSelectedIndex() != -1; + + final Pair, Integer> filesAndSelection = getFilesToShowAndSelectionIndex( + project, collectFiles(project, onlyEdited), toolWindows.getModel().getSize(), isPinnedMode()); + final int selectionIndex = filesAndSelection.getSecond(); + + final ListModel model = files.getModel(); + if (model instanceof CollectionListModel) { + ((CollectionListModel)model).replaceAll(filesAndSelection.getFirst()); + } + else if (model instanceof NameFilteringListModel) { + ((NameFilteringListModel)model).replaceAll(filesAndSelection.getFirst()); + } + + if (selectionIndex > -1 && listWasSelected) { + files.setSelectedIndex(selectionIndex); + } + files.revalidate(); + files.repaint(); + } + void navigate(final InputEvent e) { final boolean openInNewWindow = e != null && e.isShiftDown() && e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == VK_ENTER; final Object[] values = getSelectedList().getSelectedValues(); @@ -1178,6 +1287,23 @@ public class Switcher extends AnAction implements DumbAware { } } + private static class MyCheckBox extends JBCheckBox { + private MyCheckBox(@NotNull String actionId, boolean selected) { + super(layoutText(actionId), selected); + setOpaque(false); + setFocusable(false); + } + + private static String layoutText(@NotNull String actionId) { + ShortcutSet shortcuts = KeymapUtil.getActiveKeymapShortcuts(actionId); + return "" + + IdeBundle.message("recent.files.checkbox.label") + + " " + + KeymapUtil.getShortcutsText(shortcuts.getShortcuts()) + "" + + ""; + } + } + private static class VirtualFilesRenderer extends ColoredListCellRenderer { private final SwitcherPanel mySwitcherPanel; boolean open; diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index d8e35f5167c5..c9f46ea96f22 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -473,6 +473,7 @@ command.select.all=Select All message.no.targets.available=No targets available in this context title.popup.select.target=Select In title.popup.recent.files=Recent Files +recent.files.checkbox.label=Show changed only recent.locations.popup.title=Recent Locations recent.locations.changed.locations=Recent Changed Locations recent.locations.popup.empty.text=No recent locations found diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index ac63d8ae7bd1..0215925b4d37 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -594,6 +594,10 @@ Intergation with global menu in Linux + + Second Cmd/Ctrl+E switches to Recently Edited Files + +