From f3d85abad50d33150dedc6313725130a670b0a76 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Mon, 29 Jul 2024 19:14:20 -0400 Subject: [PATCH] Added support for registering keyboard actions to bld commands. Cleanups. --- .../rife/bld/idea/config/BldBuildCommand.java | 2 +- .../bld/idea/config/BldConfiguration.java | 38 +++++++++++- .../BldNodeDescriptorCommand.java | 25 +++++--- .../execution/BldCommandChooserDialog.java | 2 +- .../execution/BldExecuteListCommands.java | 14 +++-- .../idea/execution/BldRunConfiguration.java | 2 +- .../BldProjectActionAssignShortcut.java | 40 +++++++++++++ .../BldProjectActionExecuteCommand.java | 58 +++++++++++++++++++ .../bld/idea/project/BldProjectWindow.java | 44 ++++++++++++-- .../resources/messages/BldBundle.properties | 1 + 10 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 src/main/java/rife/bld/idea/project/BldProjectActionAssignShortcut.java create mode 100644 src/main/java/rife/bld/idea/project/BldProjectActionExecuteCommand.java diff --git a/src/main/java/rife/bld/idea/config/BldBuildCommand.java b/src/main/java/rife/bld/idea/config/BldBuildCommand.java index bb99d6a..d6bc27b 100644 --- a/src/main/java/rife/bld/idea/config/BldBuildCommand.java +++ b/src/main/java/rife/bld/idea/config/BldBuildCommand.java @@ -4,5 +4,5 @@ */ package rife.bld.idea.config; -public record BldBuildCommand(String name, String displayName, String description) { +public record BldBuildCommand(String name, String description, String actionId) { } diff --git a/src/main/java/rife/bld/idea/config/BldConfiguration.java b/src/main/java/rife/bld/idea/config/BldConfiguration.java index 425b20f..2a6c519 100644 --- a/src/main/java/rife/bld/idea/config/BldConfiguration.java +++ b/src/main/java/rife/bld/idea/config/BldConfiguration.java @@ -4,9 +4,11 @@ */ package rife.bld.idea.config; +import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; @@ -34,6 +36,7 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import rife.bld.idea.console.BldConsoleManager; import rife.bld.idea.console.BldConsoleWindowFactory; import rife.bld.idea.events.ExecuteAfterCompilationEvent; import rife.bld.idea.events.ExecuteBeforeCompilationEvent; @@ -43,6 +46,7 @@ import rife.bld.idea.execution.BldExecution; import rife.bld.idea.execution.BldExecutionFlags; import rife.bld.idea.project.BldProjectWindowFactory; +import rife.bld.idea.project.BldProjectActionExecuteCommand; import rife.bld.idea.utils.BldBundle; import rife.bld.idea.utils.BldConstants; @@ -53,6 +57,8 @@ @State(name = "BldConfiguration", storages = @Storage("bld.xml"), useLoadedStateAsExisting = false) public final class BldConfiguration implements PersistentStateComponent, Disposable { private static final Logger LOG = Logger.getInstance(BldConfiguration.class); + @NonNls public static final String ACTION_ID_PREFIX = "Bld_"; + @NonNls private static final String ELEMENT_EVENTS = "events"; @NonNls private static final String ELEMENT_EXECUTE_ON = "executeOn"; @NonNls private static final String ELEMENT_EVENT = "event"; @@ -67,9 +73,9 @@ public final class BldConfiguration implements PersistentStateComponent private final BldDependencyTree dependencyTree_ = new BldDependencyTree(); private static final Comparator commandComparator = (command1, command2) -> { - final String name1 = command1.displayName(); + final String name1 = command1.name(); if (name1 == null) return -1; - final String name2 = command2.displayName(); + final String name2 = command2.name(); if (name2 == null) return 1; return name1.compareToIgnoreCase(name2); }; @@ -84,6 +90,10 @@ public static BldConfiguration instance(final @NotNull Project project) { return project.getService(BldConfiguration.class); } + public static String getActionIdPrefix(final @NotNull Project project) { + return ACTION_ID_PREFIX + project.getLocationHash(); + } + @Override public void dispose() { // no-op @@ -248,6 +258,27 @@ public void setCommands(List commands) { commandsMap_.clear(); sorted.forEach(cmd -> commandsMap_.put(cmd.name(), cmd)); + ReadAction.run(() -> { + synchronized (this) { + if (!project_.isDisposed()) { + // unregister bld actions + var actionManager = ActionManagerEx.getInstanceEx(); + for (var oldId : actionManager.getActionIdList(getActionIdPrefix(project_))) { + actionManager.unregisterAction(oldId); + } + + // register project actions + for (var command : sorted) { + final var action_id = command.actionId(); + if (action_id != null) { + final var action = new BldProjectActionExecuteCommand(project_, command.name(), command.description()); + actionManager.registerAction(action_id, action); + } + } + } + } + }); + ApplicationManager.getApplication().invokeLater( () -> eventDispatcher_.getMulticaster().configurationChanged(), ModalityState.any() @@ -320,7 +351,7 @@ private boolean runCommandSynchronously(CompileContext compileContext, final Dat if (ExecuteAfterCompilationEvent.TYPE_ID.equals(event.getTypeId()) && compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { compileContext.addMessage( - CompilerMessageCategory.INFORMATION, BldBundle.message("bld.message.skip.command.after.compilation.errors", command.displayName()), null, -1, -1 + CompilerMessageCategory.INFORMATION, BldBundle.message("bld.message.skip.command.after.compilation.errors", command.name()), null, -1, -1 ); return true; } @@ -347,6 +378,7 @@ private static boolean executeCommandSynchronously(final DataContext dataContext else { var task = new Task.Backgroundable(null, BldBundle.message("bld.build.progress.dialog.title"), true) { public void run(@NotNull ProgressIndicator indicator) { + BldConsoleManager.showTaskMessage(BldBundle.message("bld.project.console.commands", command.name()), ConsoleViewContentType.USER_INPUT, project); BldExecution.instance(project).executeCommands(new BldExecutionFlags(), command, state -> { result.set((state == BldBuildListener.FINISHED_SUCCESSFULLY)); command_done.up(); diff --git a/src/main/java/rife/bld/idea/config/explorer/nodeDescriptors/BldNodeDescriptorCommand.java b/src/main/java/rife/bld/idea/config/explorer/nodeDescriptors/BldNodeDescriptorCommand.java index e85a627..815c019 100644 --- a/src/main/java/rife/bld/idea/config/explorer/nodeDescriptors/BldNodeDescriptorCommand.java +++ b/src/main/java/rife/bld/idea/config/explorer/nodeDescriptors/BldNodeDescriptorCommand.java @@ -8,12 +8,14 @@ import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.CellAppearanceEx; import com.intellij.openapi.roots.ui.util.CompositeAppearance; import com.intellij.openapi.util.Comparing; import com.intellij.ui.JBColor; import com.intellij.ui.SimpleColoredComponent; +import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import rife.bld.idea.config.BldBuildCommand; @@ -52,23 +54,32 @@ public boolean update() { final var color = UIUtil.getLabelForeground(); var nameAttributes = new TextAttributes(color, null, null, EffectType.BOXED, Font.PLAIN); - highlightedText_.getEnding().addText(command_.displayName(), nameAttributes); + highlightedText_.getEnding().addText(command_.name(), nameAttributes); + + myName = highlightedText_.getText(); + + addShortcutText(getCommand().actionId()); var configuration = BldConfiguration.instance(myProject); final var added_names = new ArrayList(4); for (final var event : configuration.getEventsForCommand(command_)) { - final String presentableName = event.getPresentableName(); - if (!added_names.contains(presentableName)) { - added_names.add(presentableName); - highlightedText_.getEnding().addText(" (" + presentableName + ')', POSTFIX_ATTRIBUTES); + final String presentable_name = event.getPresentableName(); + if (!added_names.contains(presentable_name)) { + added_names.add(presentable_name); + highlightedText_.getEnding().addText(" (" + presentable_name + ')', POSTFIX_ATTRIBUTES); } } - myName = highlightedText_.getText(); - return !Comparing.equal(highlightedText_, oldText); } + private void addShortcutText(String actionId) { + var shortcut = KeymapUtil.getPrimaryShortcut(actionId); + if (shortcut != null) { + highlightedText_.getEnding().addText(" (" + KeymapUtil.getShortcutText(shortcut) + ")", SimpleTextAttributes.GRAY_ATTRIBUTES); + } + } + public CellAppearanceEx getHighlightedText() { return highlightedText_; } diff --git a/src/main/java/rife/bld/idea/execution/BldCommandChooserDialog.java b/src/main/java/rife/bld/idea/execution/BldCommandChooserDialog.java index e24e88d..dcf720e 100644 --- a/src/main/java/rife/bld/idea/execution/BldCommandChooserDialog.java +++ b/src/main/java/rife/bld/idea/execution/BldCommandChooserDialog.java @@ -90,7 +90,7 @@ private Tree initTree() { TreeSpeedSearch.installOn(tree, false, path -> { final var userObject = ((DefaultMutableTreeNode)path.getLastPathComponent()).getUserObject(); if (userObject instanceof BldBuildCommand command) { - return command.displayName(); + return command.name(); } return null; }); diff --git a/src/main/java/rife/bld/idea/execution/BldExecuteListCommands.java b/src/main/java/rife/bld/idea/execution/BldExecuteListCommands.java index 07f4fe8..9a44abc 100644 --- a/src/main/java/rife/bld/idea/execution/BldExecuteListCommands.java +++ b/src/main/java/rife/bld/idea/execution/BldExecuteListCommands.java @@ -5,6 +5,7 @@ package rife.bld.idea.execution; import com.intellij.execution.ui.ConsoleViewContentType; +import org.jetbrains.annotations.NonNls; import org.json.JSONException; import org.json.JSONObject; import rife.bld.idea.config.BldBuildCommand; @@ -19,12 +20,13 @@ public abstract class BldExecuteListCommands { public static void run(BldExecution execution) { var output = String.join("", execution.executeCommands(new BldExecutionFlags().commands(true), List.of("help", WRAPPER_JSON_ARGUMENT))); + var project = execution.project(); if (output.isEmpty()) { - BldConsoleManager.showTaskMessage("Failed to detect the bld commands.\n", ConsoleViewContentType.ERROR_OUTPUT, execution.project()); + BldConsoleManager.showTaskMessage("Failed to detect the bld commands.\n", ConsoleViewContentType.ERROR_OUTPUT, project); return; } - BldConsoleManager.showTaskMessage("Detected the bld commands\n", ConsoleViewContentType.SYSTEM_OUTPUT, execution.project()); + BldConsoleManager.showTaskMessage("Detected the bld commands\n", ConsoleViewContentType.SYSTEM_OUTPUT, project); var commands = new ArrayList(); @@ -32,12 +34,14 @@ public static void run(BldExecution execution) { var json = new JSONObject(output); var json_commands = json.getJSONObject("commands"); for (var json_command_key : json_commands.keySet()) { - commands.add(new BldBuildCommand(json_command_key, json_command_key, json_commands.getString(json_command_key))); + commands.add(new BldBuildCommand(json_command_key, + json_commands.getString(json_command_key), + BldConfiguration.getActionIdPrefix(project) + '_' + json_command_key)); } } catch (JSONException e) { - BldConsoleManager.showTaskMessage(output + "\n", ConsoleViewContentType.ERROR_OUTPUT, execution.project()); + BldConsoleManager.showTaskMessage(output + "\n", ConsoleViewContentType.ERROR_OUTPUT, project); } - BldConfiguration.instance(execution.project()).setCommands(commands); + BldConfiguration.instance(project).setCommands(commands); } } diff --git a/src/main/java/rife/bld/idea/execution/BldRunConfiguration.java b/src/main/java/rife/bld/idea/execution/BldRunConfiguration.java index 7c79412..4a22cb9 100644 --- a/src/main/java/rife/bld/idea/execution/BldRunConfiguration.java +++ b/src/main/java/rife/bld/idea/execution/BldRunConfiguration.java @@ -55,7 +55,7 @@ public void checkConfiguration() @Override public String suggestedName() { var command = getCommand(); - return command == null ? null : command.displayName(); + return command == null ? null : command.name(); } @NotNull diff --git a/src/main/java/rife/bld/idea/project/BldProjectActionAssignShortcut.java b/src/main/java/rife/bld/idea/project/BldProjectActionAssignShortcut.java new file mode 100644 index 0000000..27ebc5f --- /dev/null +++ b/src/main/java/rife/bld/idea/project/BldProjectActionAssignShortcut.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Geert Bevin (gbevin[remove] at uwyn dot com) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.idea.project; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionUpdateThread; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.keymap.impl.ui.EditKeymapsDialog; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import rife.bld.idea.utils.BldBundle; + +final class BldProjectActionAssignShortcut extends AnAction { + private final Project project_; + private final String actionId_; + + BldProjectActionAssignShortcut(Project project, String actionId) { + super(BldBundle.message("bld.project.assign.shortcut.action.name")); + project_ = project; + actionId_ = actionId; + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + new EditKeymapsDialog(project_, actionId_).show(); + } + + @Override + public void update(@NotNull AnActionEvent e) { + e.getPresentation().setEnabled(actionId_ != null && ActionManager.getInstance().getAction(actionId_) != null); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } +} diff --git a/src/main/java/rife/bld/idea/project/BldProjectActionExecuteCommand.java b/src/main/java/rife/bld/idea/project/BldProjectActionExecuteCommand.java new file mode 100644 index 0000000..9eddb7f --- /dev/null +++ b/src/main/java/rife/bld/idea/project/BldProjectActionExecuteCommand.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Geert Bevin (gbevin[remove] at uwyn dot com) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.idea.project; + +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.NlsActions; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import rife.bld.idea.console.BldConsoleManager; +import rife.bld.idea.execution.BldExecution; +import rife.bld.idea.execution.BldExecutionFlags; +import rife.bld.idea.utils.BldBundle; + +import java.util.List; + +public final class BldProjectActionExecuteCommand extends DumbAwareAction { + private final Project project_; + private final String command_; + private final String debugString_; + + public BldProjectActionExecuteCommand(final @NotNull Project project, + final String command, + final @NlsActions.ActionDescription String description) { + project_ = project; + + var template_presentation = getTemplatePresentation(); + template_presentation.setText("Bld Command: " + command, false); + template_presentation.setDescription(description); + command_ = command; + debugString_ = "Command action: " + command + + "; Project: " + project.getPresentableUrl(); + } + + public @NonNls String toString() { + return debugString_; + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getProject(); + if (project == null) return; + + new Task.Backgroundable(project_, BldBundle.message("bld.project.progress.commands", command_), true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + BldConsoleManager.showTaskMessage(BldBundle.message("bld.project.console.commands", command_), ConsoleViewContentType.USER_INPUT, project_); + BldExecution.instance(project_).executeCommands(new BldExecutionFlags(), List.of(command_)); + } + }.queue(); + } +} \ No newline at end of file diff --git a/src/main/java/rife/bld/idea/project/BldProjectWindow.java b/src/main/java/rife/bld/idea/project/BldProjectWindow.java index c6510fe..30064c3 100644 --- a/src/main/java/rife/bld/idea/project/BldProjectWindow.java +++ b/src/main/java/rife/bld/idea/project/BldProjectWindow.java @@ -12,7 +12,10 @@ import com.intellij.ide.TreeExpander; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManagerListener; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; @@ -26,7 +29,10 @@ import com.intellij.util.EditSourceOnDoubleClickHandler; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.tree.TreeUtil; +import com.intellij.util.xml.DomManager; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import rife.bld.idea.config.BldBuildCommand; import rife.bld.idea.config.BldConfigurationListener; import rife.bld.idea.config.explorer.nodeDescriptors.BldNodeDescriptorCommand; @@ -45,11 +51,11 @@ import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.List; -import java.util.Objects; public final class BldProjectWindow extends SimpleToolWindowPanel implements DataProvider, Disposable { private Project project_; @@ -110,6 +116,36 @@ protected void processDoubleClick(@NotNull MouseEvent e, @NotNull DataContext da } }.installOn(tree_); + tree_.registerKeyboardAction(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + runSelection(DataManager.getInstance().getDataContext(tree_)); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED); + + ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(KeymapManagerListener.TOPIC, new KeymapManagerListener() { + @Override + public void keymapAdded(@NotNull Keymap keymap) { + treeModel_.invalidateAsync(); + } + + @Override + public void keymapRemoved(@NotNull Keymap keymap) { + treeModel_.invalidateAsync(); + } + + @Override + public void activeKeymapChanged(@Nullable Keymap keymap) { + treeModel_.invalidateAsync(); + } + + @Override + public void shortcutChanged(@NotNull Keymap keymap, @NonNls @NotNull String actionId, boolean fromSettings) { + treeModel_.invalidateAsync(); + } + }); + DomManager.getDomManager(project).addDomEventListener(__ -> treeModel_.invalidateAsync(), this); + project.getMessageBus().connect(this).subscribe(RunManagerListener.TOPIC, new RunManagerListener() { @Override public void beforeRunTasksChanged () { @@ -300,9 +336,9 @@ private void popupInvoked(final Component comp, final int x, final int y) { execute_on_group.add(new BldProjectActionExecuteOnEvent(project_, treeModel_, command, ExecuteBeforeCompilationEvent.instance())); execute_on_group.add(new BldProjectActionExecuteOnEvent(project_, treeModel_, command, ExecuteAfterCompilationEvent.instance())); group.add(execute_on_group); + group.add(new BldProjectActionAssignShortcut(project_, command.actionId())); final var popup_menu = ActionManager.getInstance().createActionPopupMenu(BldConstants.BLD_EXPLORER_POPUP, group); popup_menu.getComponent().show(comp, x, y); } - } diff --git a/src/main/resources/messages/BldBundle.properties b/src/main/resources/messages/BldBundle.properties index 554fe8d..815809b 100644 --- a/src/main/resources/messages/BldBundle.properties +++ b/src/main/resources/messages/BldBundle.properties @@ -36,6 +36,7 @@ bld.message.skip.command.after.compilation.errors=Skipping bld command "{0}" bec bld.name=bld bld.progress.text.loading.config=Loading bld configuration... bld.progress.text.running.commands=Running bld commands\u2026 +bld.project.assign.shortcut.action.name=Assign Shortcut... bld.project.commands=Commands bld.project.console.commands=> ./bld {0}\n bld.project.console.refresh=> refresh\n