diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/context-menu/context-menu.html b/src/Apps/NetPad.Apps.App/App/src/core/@application/context-menu/context-menu.html
index badc04bc..f55dfc85 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/context-menu/context-menu.html
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/context-menu/context-menu.html
@@ -18,7 +18,7 @@
innerHTML.bind="item.text">
- ${item.shortcut.keyComboString}
+ ${item.shortcut.keyCombo.asString}
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/editor/editor-setup.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/editor/editor-setup.ts
index de999f36..7386c9cf 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/editor/editor-setup.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/editor/editor-setup.ts
@@ -21,9 +21,14 @@ import {
IRenameProvider,
ISignatureHelpProvider
} from "./providers/interfaces";
+import {KeyCodeNum} from "@common";
+import {IEventBus, Settings, SettingsUpdatedEvent} from "@domain";
+import {ShortcutIds} from "@application/shortcuts/builtin-shortcuts";
export class EditorSetup {
constructor(
+ private readonly settings: Settings,
+ @IEventBus private readonly eventBus: IEventBus,
@all(ICommandProvider) private readonly commandProviders: ICommandProvider[],
@all(IActionProvider) private readonly actionProviders: IActionProvider[],
@all(ICompletionItemProvider) private readonly completionItemProviders: ICompletionItemProvider[],
@@ -50,6 +55,7 @@ export class EditorSetup {
this.registerThemes();
this.registerCommands();
this.registerActions();
+ this.registerKeyboardShortcuts();
this.registerCompletionProviders();
this.registerSemanticTokensProviders();
this.registerDocumentSymbolProviders();
@@ -123,6 +129,68 @@ export class EditorSetup {
});
}
+ private registerKeyboardShortcuts() {
+ // Currently we are only overriding the Command Palette keybinding.
+ let commandPaletteKeybinding: number;
+
+ const addOrUpdateShortcuts = (settings: Settings) => {
+ const commandPaletteShortcutConfig = this.settings.keyboardShortcuts.shortcuts
+ .find(s => s.id === ShortcutIds.openCommandPalette);
+
+ // If the config for this shortcut doesn't exist yet, or did but is now removed.
+ if (!commandPaletteShortcutConfig) {
+ if (commandPaletteKeybinding) {
+ // Disable previous rule
+ monaco.editor.addKeybindingRule({
+ keybinding: commandPaletteKeybinding,
+ command: null,
+ });
+ }
+
+ monaco.editor.addKeybindingRule({
+ keybinding: monaco.KeyCode.F1,
+ command: "editor.action.quickCommand",
+ });
+
+ return;
+ }
+
+ if (commandPaletteKeybinding === undefined) {
+ // If this is first time we are customizing the command palette keybinding,
+ // disable default show command palette
+ monaco.editor.addKeybindingRule({
+ keybinding: monaco.KeyCode.F1,
+ command: null,
+ });
+ } else {
+ // Disable previous rule
+ monaco.editor.addKeybindingRule({
+ keybinding: commandPaletteKeybinding,
+ command: null,
+ });
+ }
+
+ // Add a new rule for the new keybinding
+ const combo: number[] = [];
+ if (commandPaletteShortcutConfig.meta) combo.push(monaco.KeyMod.WinCtrl);
+ if (commandPaletteShortcutConfig.alt) combo.push(monaco.KeyMod.Alt);
+ if (commandPaletteShortcutConfig.ctrl) combo.push(monaco.KeyMod.CtrlCmd);
+ if (commandPaletteShortcutConfig.shift) combo.push(monaco.KeyMod.Shift);
+ if (commandPaletteShortcutConfig.key) combo.push(KeyCodeNum[commandPaletteShortcutConfig.key!.toString() as keyof typeof KeyCodeNum]);
+
+ commandPaletteKeybinding = combo.reduce((a, b) => a | b, 0);
+
+ monaco.editor.addKeybindingRule({
+ keybinding: commandPaletteKeybinding,
+ command: "editor.action.quickCommand",
+ });
+ };
+
+ this.eventBus.subscribeToServer(SettingsUpdatedEvent, event => addOrUpdateShortcuts(event.settings));
+
+ addOrUpdateShortcuts(this.settings);
+ }
+
private registerCompletionProviders() {
for (const completionItemProvider of this.completionItemProviders) {
monaco.languages.registerCompletionItemProvider(completionItemProvider.language, completionItemProvider);
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/index.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/index.ts
index cf983589..dd8be7c8 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/index.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/index.ts
@@ -31,8 +31,10 @@ export * from "./panes/ipane-host-view-state-controller";
export * from "./panes/pane-action"
export * from "./panes/pane";
+export * from "./shortcuts/key-combo";
export * from "./shortcuts/shortcut";
export * from "./shortcuts/shortcut-action-execution-context";
+export * from "./shortcuts/ishortcut-manager";
export * from "./shortcuts/shortcut-manager";
export * from "./shortcuts/builtin-shortcuts";
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/panes/pane-host/pane-host.html b/src/Apps/NetPad.Apps.App/App/src/core/@application/panes/pane-host/pane-host.html
index 052a8633..7f92647b 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/panes/pane-host/pane-host.html
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/panes/pane-host/pane-host.html
@@ -5,7 +5,7 @@
+ title="${pane.name} ${pane.shortcut ? ('(' + pane.shortcut.keyCombo.asString + ')') : ''}">
${pane.name}
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/platforms/electron/services/electron-event-handler-background-service.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/platforms/electron/services/electron-event-handler-background-service.ts
index 0292d3f1..2a69c67e 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/platforms/electron/services/electron-event-handler-background-service.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/platforms/electron/services/electron-event-handler-background-service.ts
@@ -1,11 +1,10 @@
+import {IContainer} from "aurelia";
import {IBackgroundService, WithDisposables} from "@common";
-import {ChannelInfo, Settings} from "@domain";
+import {ChannelInfo} from "@domain";
+import {IShortcutManager, Shortcut} from "@application";
import {ElectronIpcGateway} from "./electron-ipc-gateway";
-import {IShortcutManager} from "@application";
import {IMainMenuService} from "../../../../../windows/main/titlebar/main-menu/main-menu-service";
import {IMenuItem} from "../../../../../windows/main/titlebar/main-menu/imenu-item";
-import {Shortcut} from "@application";
-import {IContainer} from "aurelia";
/**
* Handles top-level IPC events sent by Electron's main process.
@@ -71,7 +70,7 @@ export class ElectronEventHandlerBackgroundService extends WithDisposables imple
return {
name: shortcut.name,
isEnabled: shortcut.isEnabled,
- keyCombo: shortcut.keyComboString.split('+').map(x => x.trim())
+ keyCombo: shortcut.keyCombo.asArray
};
}
}
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/builtin-shortcuts.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/builtin-shortcuts.ts
index 89f1f227..61cd2698 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/builtin-shortcuts.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/builtin-shortcuts.ts
@@ -1,14 +1,27 @@
-import {CreateScriptDto, IScriptService, ISettingService} from "@domain";
import {KeyCode} from "@common";
+import {CreateScriptDto, IScriptService, ISettingsService} from "@domain";
import {Shortcut} from "./shortcut";
-import {EditorUtil} from "../editor/editor-util";
import {ITextEditorService} from "../editor/text-editor-service";
-import {Explorer, NamespacesPane, OutputPane} from "../../../windows/main/panes";
-import {RunScriptEvent, TogglePaneEvent} from "@application"
-import * as monaco from "monaco-editor";
+
+export enum ShortcutIds {
+ openCommandPalette = "shortcut.commandpalette.open",
+ quickOpenDocument = "shortcut.documents.quickopen",
+ openLastActiveDocument = "shortcut.documents.switchtolastactive",
+ newDocument = "shortcut.documents.new",
+ closeDocument = "shortcut.documents.close",
+ saveDocument = "shortcut.documents.save",
+ saveAllDocuments = "shortcut.documents.saveall",
+ runDocument = "shortcut.documents.run",
+ openDocumentProperties = "shortcut.documents.properties",
+ openSettings = "shortcut.settings.open",
+ openOutput = "shortcut.output.open",
+ openExplorer = "shortcut.explorer.open",
+ openNamespaces = "shortcut.namespaces.open",
+ reloadWindow = "shortcut.window.reload",
+}
export const BuiltinShortcuts = [
- new Shortcut("Command Palette")
+ new Shortcut(ShortcutIds.openCommandPalette, "Command Palette")
.withKey(KeyCode.F1)
.hasAction(ctx => {
const editor = ctx.container.get(ITextEditorService).active?.monaco;
@@ -18,63 +31,61 @@ export const BuiltinShortcuts = [
editor.focus();
editor.trigger("", "editor.action.quickCommand", null);
})
- .configurable(false)
+ .captureDefaultKeyCombo()
+ .configurable()
.enabled(),
- new Shortcut("Go to Script")
+ new Shortcut(ShortcutIds.quickOpenDocument, "Go to Script")
.withCtrlKey()
.withKey(KeyCode.KeyT)
.hasAction(ctx => {
- const activeScriptId = ctx.session.active?.script.id;
- if (!activeScriptId) {
- return;
- }
-
- const editors = monaco.editor.getEditors();
- if (!editors.length) {
- return;
- }
-
- let editor = editors.find(e => {
- const model = e.getModel();
- return !model ? false : (EditorUtil.getScriptId(model) === activeScriptId);
- })
+ const editor = ctx.container.get(ITextEditorService).active?.monaco;
- if (!editor) {
- editor = editors.find(e => e.hasTextFocus() || e.hasWidgetFocus()) || editors[0];
- }
+ if (!editor) return;
editor.focus();
- editor.getAction("netpad.action.goToScript")?.run();
+ editor.trigger("", "netpad.action.goToScript", null);
})
- .configurable(false)
+ .captureDefaultKeyCombo()
+ .configurable()
+ .enabled(),
+
+ new Shortcut(ShortcutIds.openLastActiveDocument, "Switch to Last Active Script")
+ .withCtrlKey()
+ .withKey(KeyCode.Tab)
+ .hasAction((ctx) => ctx.session.activateLastActive())
+ .captureDefaultKeyCombo()
+ .configurable()
.enabled(),
- new Shortcut("New")
+ new Shortcut(ShortcutIds.newDocument, "New")
.withCtrlKey()
.withKey(KeyCode.KeyN)
.hasAction((ctx) => ctx.container.get(IScriptService).create(new CreateScriptDto()))
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Close")
+ new Shortcut(ShortcutIds.closeDocument, "Close")
.withCtrlKey()
.withKey(KeyCode.KeyW)
.hasAction((ctx) => {
if (ctx.session.active) ctx.session.close(ctx.session.active.script.id);
})
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Save")
+ new Shortcut(ShortcutIds.saveDocument, "Save")
.withCtrlKey()
.withKey(KeyCode.KeyS)
.hasAction((ctx) => {
if (ctx.session.active) ctx.container.get(IScriptService).save(ctx.session.active.script.id);
})
+ .captureDefaultKeyCombo()
.enabled(),
- new Shortcut("Save All")
+ new Shortcut(ShortcutIds.saveAllDocuments, "Save All")
.withCtrlKey()
.withShiftKey()
.withKey(KeyCode.KeyS)
@@ -84,63 +95,79 @@ export const BuiltinShortcuts = [
await scriptService.save(environment.script.id);
}
})
+ .captureDefaultKeyCombo()
.enabled(),
- new Shortcut("Run")
+ new Shortcut(ShortcutIds.runDocument, "Run")
.withKey(KeyCode.F5)
- .firesEvent(RunScriptEvent)
+ .firesEvent(async () => new (await import("@application/events/action-events")).RunScriptEvent())
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Script Properties")
+ new Shortcut(ShortcutIds.openDocumentProperties, "Script Properties")
.withKey(KeyCode.F4)
.hasAction((ctx) => {
if (ctx.session.active) {
ctx.container.get(IScriptService).openConfigWindow(ctx.session.active.script.id, null);
}
})
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Output")
- .withCtrlKey()
- .withKey(KeyCode.KeyR)
- .firesEvent(() => new TogglePaneEvent(OutputPane))
+ new Shortcut(ShortcutIds.openSettings, "Settings")
+ .withKey(KeyCode.F12)
+ .hasAction((ctx) => ctx.container.get(ISettingsService).openSettingsWindow(null))
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Switch to Last Active Script")
+ new Shortcut(ShortcutIds.openOutput, "Output")
.withCtrlKey()
- .withKey(KeyCode.Tab)
- .hasAction((ctx) => ctx.session.activateLastActive())
- .configurable()
- .enabled(),
+ .withKey(KeyCode.KeyR)
+ .firesEvent(async () => {
+ const TogglePaneEvent = (await import("@application/events/action-events")).TogglePaneEvent;
+ const OutputPane = (await import("../../../windows/main/panes")).OutputPane;
- new Shortcut("Settings")
- .withKey(KeyCode.F12)
- .hasAction((ctx) => ctx.container.get(ISettingService).openSettingsWindow(null))
+ return new TogglePaneEvent(OutputPane);
+ })
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Explorer")
+ new Shortcut(ShortcutIds.openExplorer, "Explorer")
.withAltKey()
.withKey(KeyCode.KeyE)
- .firesEvent(() => new TogglePaneEvent(Explorer))
+ .firesEvent(async () => {
+ const TogglePaneEvent = (await import("@application/events/action-events")).TogglePaneEvent;
+ const Explorer = (await import("../../../windows/main/panes")).Explorer;
+
+ return new TogglePaneEvent(Explorer);
+ })
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Namespaces")
+ new Shortcut(ShortcutIds.openNamespaces, "Namespaces")
.withAltKey()
.withKey(KeyCode.KeyN)
- .firesEvent(() => new TogglePaneEvent(NamespacesPane))
+ .firesEvent(async () => {
+ const TogglePaneEvent = (await import("@application/events/action-events")).TogglePaneEvent;
+ const NamespacesPane = (await import("../../../windows/main/panes")).NamespacesPane;
+
+ return new TogglePaneEvent(NamespacesPane);
+ })
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
- new Shortcut("Reload")
+ new Shortcut(ShortcutIds.reloadWindow, "Reload")
.withCtrlKey()
.withShiftKey()
.withKey(KeyCode.KeyR)
.hasAction(() => window.location.reload())
+ .captureDefaultKeyCombo()
.configurable()
.enabled(),
];
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/ishortcut-manager.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/ishortcut-manager.ts
new file mode 100644
index 00000000..32d1d3b6
--- /dev/null
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/ishortcut-manager.ts
@@ -0,0 +1,41 @@
+import {DI} from "aurelia";
+import {Shortcut} from "./shortcut";
+
+export interface IShortcutManager {
+ /**
+ * Initializes the ShortcutManager and starts listening for keyboard events.
+ */
+ initialize(): void;
+
+ /**
+ * Adds a shortcut to the shortcut registry.
+ * @param shortcut The shortcut to register.
+ */
+ registerShortcut(shortcut: Shortcut): void;
+
+ /**
+ * Removes a shortcut from the shortcut registry.
+ * @param shortcut The shortcut to unregister.
+ */
+ unregisterShortcut(shortcut: Shortcut): void;
+
+ /**
+ * Finds a shortcut by its ID, if one exists.
+ * @param id The id of the shortcut to get.
+ */
+ getShortcut(id: string): Shortcut | undefined;
+
+ /**
+ * Finds a shortcut by its name, if one exists.
+ * @param name The name of the shortcut to get.
+ */
+ getShortcutByName(name: string): Shortcut | undefined;
+
+ /**
+ * Executes a shortcut.
+ * @param shortcut
+ */
+ executeShortcut(shortcut: Shortcut): Promise;
+}
+
+export const IShortcutManager = DI.createInterface();
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/key-combo.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/key-combo.ts
new file mode 100644
index 00000000..7e28766d
--- /dev/null
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/key-combo.ts
@@ -0,0 +1,219 @@
+import {KeyCode} from "@common";
+import {IKeyboardShortcutConfiguration} from "@domain";
+
+/**
+ * A combination of keyboard keys.
+ */
+export class KeyCombo {
+ public meta = false;
+ public alt = false;
+ public ctrl = false;
+ public shift = false;
+ public key?: KeyCode;
+
+ public get hasModifier(): boolean {
+ return this.meta || this.alt || this.ctrl || this.shift;
+ }
+
+ /**
+ * Sets whether META/Super key is required as part of this KeyCombo.
+ */
+ public withMetaKey(required = true): KeyCombo {
+ this.meta = required;
+ return this;
+ }
+
+ /**
+ * Sets whether ALT key is required as part of this KeyCombo.
+ */
+ public withAltKey(required = true): KeyCombo {
+ this.alt = required;
+ return this;
+ }
+
+ /**
+ * Sets whether CTRL key is required as part of this KeyCombo.
+ */
+ public withCtrlKey(required = true): KeyCombo {
+ this.ctrl = required;
+ return this;
+ }
+
+ /**
+ * Sets whether SHIFT key is required as part of this KeyCombo.
+ */
+ public withShiftKey(required = true): KeyCombo {
+ this.shift = required;
+ return this;
+ }
+
+ /**
+ * Sets whether CTRL key is required as part of this KeyCombo.
+ */
+ public withKey(key: KeyCode | undefined): KeyCombo {
+ this.key = key;
+ return this;
+ }
+
+ public updateFrom(config: IKeyboardShortcutConfiguration | KeyCombo) {
+ this.withMetaKey(config.meta)
+ .withAltKey(config.alt)
+ .withCtrlKey(config.ctrl)
+ .withShiftKey(config.shift)
+ .withKey(config instanceof KeyCombo ? config.key : config.key as KeyCode);
+
+ return this;
+ }
+
+ public copyTo(config: IKeyboardShortcutConfiguration) {
+ config.meta = this.meta;
+ config.alt = this.alt;
+ config.ctrl = this.ctrl;
+ config.shift = this.shift;
+ config.key = this.key;
+
+ return this;
+ }
+
+ /**
+ * Creates a deep copy of this KeyCombo instance.
+ */
+ public clone(): KeyCombo {
+ return new KeyCombo().updateFrom(this);
+ }
+
+ /**
+ * Determines if this KeyCombo matches the specified key combination.
+ * @param key Key code.
+ * @param ctrl Whether the ctrl key is pressed.
+ * @param alt Whether the alt key is pressed.
+ * @param shift Whether the shift key is pressed.
+ * @param meta Whether the meta key is pressed.
+ */
+ public matches(
+ key: KeyCode | undefined,
+ ctrl: boolean,
+ alt: boolean,
+ shift: boolean,
+ meta: boolean
+ ): boolean;
+
+ /**
+ * Determines if this KeyCombo matches they key combination in the specified keyboard event.
+ * @param event The keyboard event.
+ */
+ public matches(event: KeyboardEvent): boolean;
+
+ /**
+ * Determines if this KeyCombo has the same key combination as the specified key combo.
+ * @param keyCombo The KeyCombo to compare with.
+ */
+ public matches(keyCombo: KeyCombo): boolean;
+
+ public matches(
+ keyOrEventOrCombo: KeyCode | undefined | KeyboardEvent | KeyCombo,
+ ctrl?: boolean,
+ alt?: boolean,
+ shift?: boolean,
+ meta?: boolean
+ ): boolean {
+ let key: KeyCode | undefined;
+
+ if (keyOrEventOrCombo instanceof KeyboardEvent) {
+ key = keyOrEventOrCombo.code as KeyCode;
+ ctrl = keyOrEventOrCombo.ctrlKey;
+ alt = keyOrEventOrCombo.altKey;
+ shift = keyOrEventOrCombo.shiftKey;
+ meta = keyOrEventOrCombo.metaKey;
+ } else if (keyOrEventOrCombo instanceof KeyCombo) {
+ return (
+ this.key === keyOrEventOrCombo.key &&
+ this.ctrl === keyOrEventOrCombo.ctrl &&
+ this.alt === keyOrEventOrCombo.alt &&
+ this.shift === keyOrEventOrCombo.shift &&
+ this.meta === keyOrEventOrCombo.meta
+ );
+ }
+
+ return this.matchesKeyCombo(key, ctrl ?? false, alt ?? false, shift ?? false, meta ?? false);
+ }
+
+ public matchesKeyCombo(
+ key: KeyCode | undefined | null,
+ ctrl: boolean,
+ alt: boolean,
+ shift: boolean,
+ meta: boolean
+ ): boolean {
+ if (!key) return false;
+
+ if (this.key) {
+ return (
+ this.key === key &&
+ this.ctrl === ctrl &&
+ this.alt === alt &&
+ this.shift === shift &&
+ this.meta === meta
+ );
+ } else
+ return false;
+ }
+
+ public get asArray(): string[] {
+ const combo: string[] = [];
+ if (this.meta) combo.push("Meta");
+ if (this.alt) combo.push("Alt");
+ if (this.ctrl) combo.push("Ctrl");
+ if (this.shift) combo.push("Shift");
+ if (this.key)
+ combo.push(
+ this.key
+ .replace("Key", "")
+ .replace("Digit", "")
+ .replace("Semicolon", ";")
+ .replace("Equal", "=")
+ .replace("Comma", ",")
+ .replace("Minus", "-")
+ .replace("Period", ".")
+ .replace("Slash", "/")
+ .replace("Backquote", "`")
+ .replace("BracketLeft", "[")
+ .replace("Backslash", "\\")
+ .replace("BracketRight", "]")
+ .replace("Quote", "'")
+ );
+
+ return combo;
+ }
+
+ public get asString(): string {
+ return this.asArray.join(" + ");
+ }
+
+ public toString(): string {
+ return this.asString;
+ }
+
+ public static fromKeyboardEvent(event: KeyboardEvent): KeyCombo {
+ const combo = new KeyCombo();
+
+ if (event.metaKey) combo.withMetaKey();
+ if (event.altKey) combo.withAltKey();
+ if (event.ctrlKey) combo.withCtrlKey();
+ if (event.shiftKey) combo.withShiftKey();
+
+ const key = event.key.toUpperCase();
+
+ if (["ALT", "CONTROL", "SHIFT", "META"].indexOf(key) < 0) {
+ const keyCode = KeyCode[event.code as keyof typeof KeyCode];
+ if (!keyCode) throw new Error("Unknown keycode: " + event.code);
+ combo.withKey(keyCode);
+ }
+
+ return combo;
+ }
+
+ public static fromKeyCombo(keyCombo: KeyCombo): KeyCombo {
+ return keyCombo.clone();
+ }
+}
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut-manager.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut-manager.ts
index 4bcef4f5..221b4964 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut-manager.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut-manager.ts
@@ -1,60 +1,67 @@
-import {Constructable, DI, IContainer, ILogger} from "aurelia";
+import {Constructable, IContainer, ILogger} from "aurelia";
+import {IEventBus, Settings, SettingsUpdatedEvent} from "@domain";
import {Shortcut} from "./shortcut";
import {ShortcutActionExecutionContext} from "./shortcut-action-execution-context";
-import {IEventBus} from "@domain";
-
-export interface IShortcutManager {
- /**
- * Initializes the ShortcutManager and starts listening for keyboard events.
- */
- initialize(): void;
-
- /**
- * Adds a shortcut to the shortcut registry.
- * @param shortcut The shortcut to register.
- */
- registerShortcut(shortcut: Shortcut): void;
-
- /**
- * Removes a shortcut from the shortcut registry.
- * @param shortcut The shortcut to unregister.
- */
- unregisterShortcut(shortcut: Shortcut): void;
-
- /**
- * Finds a shortcut by its name, if one exists.
- * @param name The name of the shortcut to get.
- */
- getShortcutByName(name: string): Shortcut | undefined;
-
- /**
- * Executes a shortcut.
- * @param shortcut
- */
- executeShortcut(shortcut: Shortcut): void;
-}
-
-export const IShortcutManager = DI.createInterface();
+import {BuiltinShortcuts} from "./builtin-shortcuts";
+import {IShortcutManager} from "./ishortcut-manager";
export class ShortcutManager implements IShortcutManager {
private readonly registry: Shortcut[] = [];
private readonly logger: ILogger;
constructor(
+ private readonly settings: Settings,
@IEventBus private readonly eventBus: IEventBus,
@IContainer private readonly container: IContainer,
@ILogger logger: ILogger) {
this.logger = logger.scopeTo(nameof(ShortcutManager));
}
- public getShortcutByName(name: string): Shortcut | undefined {
- return this.registry.find(s => s.name === name);
+ public initialize() {
+ this.logger.debug("Initializing");
+
+ const builtInShortcuts = [...BuiltinShortcuts];
+
+ const addOrUpdateShortcuts = (settings: Settings) => {
+ const configs = settings.keyboardShortcuts.shortcuts;
+
+ for (const builtinShortcut of builtInShortcuts) {
+ let shortcut = this.getShortcut(builtinShortcut.id);
+
+ if (!shortcut) {
+ this.registerShortcut(builtinShortcut);
+ shortcut = builtinShortcut;
+ }
+
+ const config = configs.find(x => x.id === shortcut!.id);
+
+ if (config) {
+ shortcut.keyCombo.updateFrom(config);
+ } else {
+ shortcut.resetKeyCombo();
+ }
+ }
+ };
+
+ this.eventBus.subscribeToServer(SettingsUpdatedEvent, event => addOrUpdateShortcuts(event.settings));
+
+ addOrUpdateShortcuts(this.settings);
+
+ // Listen and process keyboard events
+ document.addEventListener("keydown", async (ev) => {
+ const shortcut = this.registry.find((s) => s.isEnabled && s.keyCombo.matches(ev));
+ if (!shortcut) return;
+
+ ev.preventDefault();
+
+ await this.executeShortcut(shortcut);
+ });
}
public registerShortcut(shortcut: Shortcut) {
- this.logger.debug(`Registering shortcut "${shortcut.name}"`);
+ this.logger.debug(`Registering shortcut "${shortcut.name}" (${shortcut.keyCombo.asString})`);
- const existing = this.registry.findIndex((s) => s.matches(shortcut));
+ const existing = this.registry.findIndex((s) => s.keyCombo.matches(shortcut.keyCombo));
if (existing >= 0) {
this.registry[existing] = shortcut;
@@ -63,8 +70,16 @@ export class ShortcutManager implements IShortcutManager {
}
}
+ public getShortcut(id: string): Shortcut | undefined {
+ return this.registry.find(s => s.id === id);
+ }
+
+ public getShortcutByName(name: string): Shortcut | undefined {
+ return this.registry.find(s => s.name === name);
+ }
+
public unregisterShortcut(shortcut: Shortcut) {
- this.logger.debug(`Unregistering shortcut "${shortcut.name}"`);
+ this.logger.debug(`Unregistering shortcut "${shortcut.name}" (${shortcut.keyCombo.asString})`);
const ix = this.registry.indexOf(shortcut);
@@ -73,21 +88,8 @@ export class ShortcutManager implements IShortcutManager {
}
}
- public initialize() {
- this.logger.debug("Initializing");
-
- document.addEventListener("keydown", (ev) => {
- const shortcut = this.registry.find((s) => s.isEnabled && s.matches(ev));
- if (!shortcut) return;
-
- this.executeShortcut(shortcut);
-
- ev.preventDefault();
- });
- }
-
- public executeShortcut(shortcut: Shortcut) {
- this.logger.debug(`Executing shortcut "${shortcut.name}"`);
+ public async executeShortcut(shortcut: Shortcut) {
+ this.logger.debug(`Executing shortcut "${shortcut.name}" (${shortcut.keyCombo.asString})`);
if (shortcut.action) {
const context = new ShortcutActionExecutionContext(this.container);
@@ -95,9 +97,14 @@ export class ShortcutManager implements IShortcutManager {
}
if (shortcut.event) {
- const event = Object.hasOwnProperty.bind(shortcut.event)("prototype")
- ? new (shortcut.event as Constructable)()
- : (shortcut.event as () => Record)();
+ let event: InstanceType;
+
+ if (Object.hasOwnProperty.bind(shortcut.event)("prototype")) {
+ event = new (shortcut.event as Constructable)();
+ } else {
+ const eventOrPromise = (shortcut.event as () => (unknown | Promise))();
+ event = await Promise.resolve(eventOrPromise) as InstanceType;
+ }
this.eventBus.publish(event);
}
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut.ts b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut.ts
index f7e5cbb1..c5e0858c 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@application/shortcuts/shortcut.ts
@@ -1,52 +1,64 @@
import {Constructable} from "@aurelia/kernel/src/interfaces";
import {KeyCode} from "@common";
import {ShortcutActionExecutionContext} from "./shortcut-action-execution-context";
+import {KeyCombo} from "./key-combo";
/**
* A shortcut that executes an action.
*/
export class Shortcut {
- public ctrlKey = false;
- public altKey = false;
- public shiftKey = false;
- public metaKey = false;
- public key?: KeyCode;
- public keyExpression?: (keyCode: KeyCode) => boolean;
+ public id: string;
+ public name: string;
public action?: (context: ShortcutActionExecutionContext) => void;
- public event?: Constructable | (() => unknown);
+ public event?: Constructable | (() => (unknown | Promise));
public isConfigurable = false;
public isEnabled = false;
+ public keyCombo: KeyCombo;
+ public defaultKeyCombo: KeyCombo;
+
+ constructor(id: string, name: string) {
+ this.id = id;
+ this.name = name;
+ this.keyCombo = new KeyCombo();
+ this.defaultKeyCombo = new KeyCombo();
+ }
- constructor(public name: string) {
+ public withCtrlKey(required = true): Shortcut {
+ this.keyCombo.withCtrlKey(required);
+ return this;
}
- public withKey(key: KeyCode): Shortcut {
- this.key = key;
+ public withAltKey(required = true): Shortcut {
+ this.keyCombo.withAltKey(required);
return this;
}
- public withKeyExpression(expression: (keyCode: KeyCode) => boolean): Shortcut {
- this.keyExpression = expression;
+ public withShiftKey(required = true): Shortcut {
+ this.keyCombo.withShiftKey(required);
return this;
}
- public withCtrlKey(mustBePressed = true): Shortcut {
- this.ctrlKey = mustBePressed;
+ public withMetaKey(required = true): Shortcut {
+ this.keyCombo.withMetaKey(required);
return this;
}
- public withAltKey(mustBePressed = true): Shortcut {
- this.altKey = mustBePressed;
+ public withKey(key: KeyCode): Shortcut {
+ this.keyCombo.withKey(key);
return this;
}
- public withShiftKey(mustBePressed = true): Shortcut {
- this.shiftKey = mustBePressed;
+ public captureDefaultKeyCombo(): Shortcut {
+ this.defaultKeyCombo = this.keyCombo.clone();
return this;
}
- public withMetaKey(mustBePressed = true): Shortcut {
- this.metaKey = mustBePressed;
+ public get isDefaultKeyCombo(): boolean {
+ return this.defaultKeyCombo.matches(this.keyCombo);
+ }
+
+ public resetKeyCombo(): Shortcut {
+ this.keyCombo.updateFrom(this.defaultKeyCombo);
return this;
}
@@ -55,10 +67,10 @@ export class Shortcut {
return this;
}
- public firesEvent(eventGetter: () => unknown): Shortcut;
+ public firesEvent(eventGetter: () => (unknown | Promise)): Shortcut;
public firesEvent(eventType: TEventType): Shortcut;
- public firesEvent(eventTypeOrGetter: TEventType | (() => unknown)): Shortcut {
+ public firesEvent(eventTypeOrGetter: TEventType | (() => (unknown | Promise))): Shortcut {
this.event = eventTypeOrGetter;
return this;
}
@@ -73,114 +85,7 @@ export class Shortcut {
return this;
}
- /**
- * Determines if this shortcut matches the specified key combination.
- * @param key Key code.
- * @param ctrl Whether the ctrl key is pressed.
- * @param alt Whether the alt key is pressed.
- * @param shift Whether the shift key is pressed.
- * @param meta Whether the meta key is pressed.
- */
- public matches(
- key: KeyCode | undefined,
- ctrl: boolean,
- alt: boolean,
- shift: boolean,
- meta: boolean
- ): boolean;
-
- /**
- * Determines if this shortcut matches they key combination in the specified keyboard event.
- * @param event The keyboard event.
- */
- public matches(event: KeyboardEvent): boolean;
-
- /**
- * Determines if this shortcut has the same key combination as the specified shortcut.
- * @param shortcut The shortcut to compare with.
- */
- public matches(shortcut: Shortcut): boolean;
-
- public matches(
- keyOrEventOrShortcut: KeyCode | undefined | KeyboardEvent | Shortcut,
- ctrl?: boolean,
- alt?: boolean,
- shift?: boolean,
- meta?: boolean
- ): boolean {
- let key: KeyCode | undefined;
-
- if (keyOrEventOrShortcut instanceof KeyboardEvent) {
- key = keyOrEventOrShortcut.code as KeyCode;
- ctrl = keyOrEventOrShortcut.ctrlKey;
- alt = keyOrEventOrShortcut.altKey;
- shift = keyOrEventOrShortcut.shiftKey;
- meta = keyOrEventOrShortcut.metaKey;
- } else if (keyOrEventOrShortcut instanceof Shortcut) {
- return (
- this.key === keyOrEventOrShortcut.key &&
- this.keyExpression?.toString() === keyOrEventOrShortcut.keyExpression?.toString() &&
- this.ctrlKey === keyOrEventOrShortcut.ctrlKey &&
- this.altKey === keyOrEventOrShortcut.altKey &&
- this.shiftKey === keyOrEventOrShortcut.shiftKey &&
- this.metaKey === keyOrEventOrShortcut.metaKey
- );
- }
-
- return this.matchesKeyCombo(key, ctrl ?? false, alt ?? false, shift ?? false, meta ?? false);
- }
-
- public matchesKeyCombo(
- key: KeyCode | undefined | null,
- ctrl: boolean,
- alt: boolean,
- shift: boolean,
- meta: boolean
- ): boolean {
- if (!key) return false;
-
- if (this.key) {
- return (
- this.key === key &&
- this.ctrlKey === ctrl &&
- this.altKey === alt &&
- this.shiftKey === shift &&
- this.metaKey === meta
- );
- } else if (this.keyExpression) {
- return this.keyExpression(key);
- } else return false;
- }
-
- public get keyComboString(): string {
- const combo: string[] = [];
- if (this.metaKey) combo.push("Meta");
- if (this.altKey) combo.push("Alt");
- if (this.ctrlKey) combo.push("Ctrl");
- if (this.shiftKey) combo.push("Shift");
- if (this.key)
- combo.push(
- this.key
- .replace("Key", "")
- .replace("Digit", "")
- .replace("Semicolon", ";")
- .replace("Equal", "=")
- .replace("Comma", ",")
- .replace("Minus", "-")
- .replace("Period", ".")
- .replace("Slash", "/")
- .replace("Backquote", "`")
- .replace("BracketLeft", "[")
- .replace("Backslash", "\\")
- .replace("BracketRight", "]")
- .replace("Quote", "\"")
- );
- if (this.keyExpression) combo.push("Custom Expression");
-
- return combo.join(" + ").trim();
- }
-
public toString(): string {
- return `${this.name} (${this.keyComboString})`;
+ return `${this.name} (${this.keyCombo.toString()})`;
}
}
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@common/utils/key-codes.ts b/src/Apps/NetPad.Apps.App/App/src/core/@common/utils/key-codes.ts
index 14dde4f2..6acb6df9 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@common/utils/key-codes.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@common/utils/key-codes.ts
@@ -103,3 +103,110 @@ export enum KeyCode {
BracketRight = "BracketRight",
Quote = "Quote",
}
+
+/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
+export enum KeyCodeNum {
+ Backspace = 1,
+ Tab = 2,
+ Enter = 3,
+ ShiftLeft = 4,
+ ShiftRight = 4,
+ ControlLeft = 5,
+ ControlRight = 5,
+ AltLeft = 6,
+ AltRight = 6,
+ Pause = 7,
+ CapsLock = 8,
+ Escape = 9,
+ Space = 10,
+ PageUp = 11,
+ PageDown = 12,
+ End = 13,
+ Home = 14,
+ ArrowLeft = 15,
+ ArrowUp = 16,
+ ArrowRight = 17,
+ ArrowDown = 18,
+ Insert = 19,
+ Delete = 20,
+ Digit0 = 21,
+ Digit1 = 22,
+ Digit2 = 23,
+ Digit3 = 24,
+ Digit4 = 25,
+ Digit5 = 26,
+ Digit6 = 27,
+ Digit7 = 28,
+ Digit8 = 29,
+ Digit9 = 30,
+ KeyA = 31,
+ KeyB = 32,
+ KeyC = 33,
+ KeyD = 34,
+ KeyE = 35,
+ KeyF = 36,
+ KeyG = 37,
+ KeyH = 38,
+ KeyI = 39,
+ KeyJ = 40,
+ KeyK = 41,
+ KeyL = 42,
+ KeyM = 43,
+ KeyN = 44,
+ KeyO = 45,
+ KeyP = 46,
+ KeyQ = 47,
+ KeyR = 48,
+ KeyS = 49,
+ KeyT = 50,
+ KeyU = 51,
+ KeyV = 52,
+ KeyW = 53,
+ KeyX = 54,
+ KeyY = 55,
+ KeyZ = 56,
+ MetaLeft = 57,
+ MetaRight = 57,
+ ContextMenu = 58,
+ Numpad0 = 98,
+ Numpad1 = 99,
+ Numpad2 = 100,
+ Numpad3 = 101,
+ Numpad4 = 102,
+ Numpad5 = 103,
+ Numpad6 = 104,
+ Numpad7 = 105,
+ Numpad8 = 106,
+ Numpad9 = 107,
+ NumpadMultiply = 108,
+ NumpadAdd = 109,
+ NumpadSubtract = 111,
+ NumpadDecimal = 112,
+ NumpadDivide = 113,
+ F1 = 59,
+ F2 = 60,
+ F3 = 61,
+ F4 = 62,
+ F5 = 63,
+ F6 = 64,
+ F7 = 65,
+ F8 = 66,
+ F9 = 67,
+ F10 = 68,
+ F11 = 69,
+ F12 = 70,
+ NumLock = 83,
+ ScrollLock = 84,
+ Semicolon = 85,
+ Equal = 86,
+ Comma = 87,
+ Minus = 88,
+ Period = 89,
+ Slash = 90,
+ Backquote = 91,
+ BracketLeft = 92,
+ Backslash = 93,
+ BracketRight = 94,
+ Quote = 95,
+}
+/* eslint-enable @typescript-eslint/no-duplicate-enum-values */
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts b/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts
index a9f3974f..154b01d1 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts
@@ -4062,6 +4062,7 @@ export class Settings implements ISettings {
appearance!: AppearanceOptions;
editor!: EditorOptions;
results!: ResultsOptions;
+ keyboardShortcuts!: KeyboardShortcutOptions;
omniSharp!: OmniSharpOptions;
constructor(data?: ISettings) {
@@ -4075,6 +4076,7 @@ export class Settings implements ISettings {
this.appearance = new AppearanceOptions();
this.editor = new EditorOptions();
this.results = new ResultsOptions();
+ this.keyboardShortcuts = new KeyboardShortcutOptions();
this.omniSharp = new OmniSharpOptions();
}
}
@@ -4090,6 +4092,7 @@ export class Settings implements ISettings {
this.appearance = _data["appearance"] ? AppearanceOptions.fromJS(_data["appearance"]) : new AppearanceOptions();
this.editor = _data["editor"] ? EditorOptions.fromJS(_data["editor"]) : new EditorOptions();
this.results = _data["results"] ? ResultsOptions.fromJS(_data["results"]) : new ResultsOptions();
+ this.keyboardShortcuts = _data["keyboardShortcuts"] ? KeyboardShortcutOptions.fromJS(_data["keyboardShortcuts"]) : new KeyboardShortcutOptions();
this.omniSharp = _data["omniSharp"] ? OmniSharpOptions.fromJS(_data["omniSharp"]) : new OmniSharpOptions();
}
}
@@ -4112,6 +4115,7 @@ export class Settings implements ISettings {
data["appearance"] = this.appearance ? this.appearance.toJSON() : undefined;
data["editor"] = this.editor ? this.editor.toJSON() : undefined;
data["results"] = this.results ? this.results.toJSON() : undefined;
+ data["keyboardShortcuts"] = this.keyboardShortcuts ? this.keyboardShortcuts.toJSON() : undefined;
data["omniSharp"] = this.omniSharp ? this.omniSharp.toJSON() : undefined;
return data;
}
@@ -4134,6 +4138,7 @@ export interface ISettings {
appearance: AppearanceOptions;
editor: EditorOptions;
results: ResultsOptions;
+ keyboardShortcuts: KeyboardShortcutOptions;
omniSharp: OmniSharpOptions;
}
@@ -4370,6 +4375,125 @@ export interface IResultsOptions {
maxCollectionSerializeLength: number;
}
+export class KeyboardShortcutOptions implements IKeyboardShortcutOptions {
+ shortcuts!: KeyboardShortcutConfiguration[];
+
+ constructor(data?: IKeyboardShortcutOptions) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (this)[property] = (data)[property];
+ }
+ }
+ if (!data) {
+ this.shortcuts = [];
+ }
+ }
+
+ init(_data?: any) {
+ if (_data) {
+ if (Array.isArray(_data["shortcuts"])) {
+ this.shortcuts = [] as any;
+ for (let item of _data["shortcuts"])
+ this.shortcuts!.push(KeyboardShortcutConfiguration.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): KeyboardShortcutOptions {
+ data = typeof data === 'object' ? data : {};
+ let result = new KeyboardShortcutOptions();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (Array.isArray(this.shortcuts)) {
+ data["shortcuts"] = [];
+ for (let item of this.shortcuts)
+ data["shortcuts"].push(item.toJSON());
+ }
+ return data;
+ }
+
+ clone(): KeyboardShortcutOptions {
+ const json = this.toJSON();
+ let result = new KeyboardShortcutOptions();
+ result.init(json);
+ return result;
+ }
+}
+
+export interface IKeyboardShortcutOptions {
+ shortcuts: KeyboardShortcutConfiguration[];
+}
+
+export class KeyboardShortcutConfiguration implements IKeyboardShortcutConfiguration {
+ id!: string;
+ meta!: boolean;
+ alt!: boolean;
+ ctrl!: boolean;
+ shift!: boolean;
+ key?: KeyCode | undefined;
+
+ constructor(data?: IKeyboardShortcutConfiguration) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (this)[property] = (data)[property];
+ }
+ }
+ }
+
+ init(_data?: any) {
+ if (_data) {
+ this.id = _data["id"];
+ this.meta = _data["meta"];
+ this.alt = _data["alt"];
+ this.ctrl = _data["ctrl"];
+ this.shift = _data["shift"];
+ this.key = _data["key"];
+ }
+ }
+
+ static fromJS(data: any): KeyboardShortcutConfiguration {
+ data = typeof data === 'object' ? data : {};
+ let result = new KeyboardShortcutConfiguration();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["id"] = this.id;
+ data["meta"] = this.meta;
+ data["alt"] = this.alt;
+ data["ctrl"] = this.ctrl;
+ data["shift"] = this.shift;
+ data["key"] = this.key;
+ return data;
+ }
+
+ clone(): KeyboardShortcutConfiguration {
+ const json = this.toJSON();
+ let result = new KeyboardShortcutConfiguration();
+ result.init(json);
+ return result;
+ }
+}
+
+export interface IKeyboardShortcutConfiguration {
+ id: string;
+ meta: boolean;
+ alt: boolean;
+ ctrl: boolean;
+ shift: boolean;
+ key?: KeyCode | undefined;
+}
+
+export type KeyCode = "Backspace" | "Tab" | "Enter" | "ShiftLeft" | "ShiftRight" | "ControlLeft" | "ControlRight" | "AltLeft" | "AltRight" | "Pause" | "CapsLock" | "Escape" | "Space" | "PageUp" | "PageDown" | "End" | "Home" | "ArrowLeft" | "ArrowUp" | "ArrowRight" | "ArrowDown" | "PrintScreen" | "Insert" | "Delete" | "Digit0" | "Digit1" | "Digit2" | "Digit3" | "Digit4" | "Digit5" | "Digit6" | "Digit7" | "Digit8" | "Digit9" | "KeyA" | "KeyB" | "KeyC" | "KeyD" | "KeyE" | "KeyF" | "KeyG" | "KeyH" | "KeyI" | "KeyJ" | "KeyK" | "KeyL" | "KeyM" | "KeyN" | "KeyO" | "KeyP" | "KeyQ" | "KeyR" | "KeyS" | "KeyT" | "KeyU" | "KeyV" | "KeyW" | "KeyX" | "KeyY" | "KeyZ" | "MetaLeft" | "MetaRight" | "ContextMenu" | "Numpad0" | "Numpad1" | "Numpad2" | "Numpad3" | "Numpad4" | "Numpad5" | "Numpad6" | "Numpad7" | "Numpad8" | "Numpad9" | "NumpadMultiply" | "NumpadAdd" | "NumpadSubtract" | "NumpadDecimal" | "NumpadDivide" | "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | "F11" | "F12" | "NumLock" | "ScrollLock" | "Semicolon" | "Equal" | "Comma" | "Minus" | "Period" | "Slash" | "Backquote" | "BracketLeft" | "Backslash" | "BracketRight" | "Quote";
+
export class OmniSharpOptions implements IOmniSharpOptions {
enabled!: boolean;
executablePath?: string | undefined;
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@domain/configuration/setting-service.ts b/src/Apps/NetPad.Apps.App/App/src/core/@domain/configuration/settings-service.ts
similarity index 71%
rename from src/Apps/NetPad.Apps.App/App/src/core/@domain/configuration/setting-service.ts
rename to src/Apps/NetPad.Apps.App/App/src/core/@domain/configuration/settings-service.ts
index a781f2cb..9232c949 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@domain/configuration/setting-service.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@domain/configuration/settings-service.ts
@@ -1,13 +1,13 @@
import {DI, IHttpClient} from "aurelia";
import {ISettingsApiClient, Settings, SettingsApiClient} from "@domain";
-export interface ISettingService extends ISettingsApiClient {
+export interface ISettingsService extends ISettingsApiClient {
toggleTheme(): Promise;
}
-export const ISettingService = DI.createInterface();
+export const ISettingsService = DI.createInterface();
-export class SettingService extends SettingsApiClient implements ISettingService {
+export class SettingsService extends SettingsApiClient implements ISettingsService {
constructor(readonly settings: Settings,
baseUrl: string,
@IHttpClient http: IHttpClient) {
diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@domain/index.ts b/src/Apps/NetPad.Apps.App/App/src/core/@domain/index.ts
index 411f4083..9fd35311 100644
--- a/src/Apps/NetPad.Apps.App/App/src/core/@domain/index.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/core/@domain/index.ts
@@ -6,7 +6,7 @@ export * from "./events/ievent-bus";
export * from "./events/iipc-gateway";
export * from "./events/channel-info";
-export * from "./configuration/setting-service";
+export * from "./configuration/settings-service";
export * from "./app/app-service";
export * from "./window/window-service";
export * from "./window/window-state";
diff --git a/src/Apps/NetPad.Apps.App/App/src/main.ts b/src/Apps/NetPad.Apps.App/App/src/main.ts
index a4881a58..5080bd1d 100644
--- a/src/Apps/NetPad.Apps.App/App/src/main.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/main.ts
@@ -11,10 +11,10 @@ import {
IEventBus,
IIpcGateway,
ISession,
- ISettingService,
+ ISettingsService,
Session,
Settings,
- SettingService,
+ SettingsService,
} from "@domain";
import {
ConsoleLogSink,
@@ -50,7 +50,7 @@ const builder = Aurelia.register(
Registration.singleton(IIpcGateway, SignalRIpcGateway),
Registration.singleton(IEventBus, EventBus),
Registration.singleton(ISession, Session),
- Registration.singleton(ISettingService, SettingService),
+ Registration.singleton(ISettingsService, SettingsService),
Registration.singleton(AppMutationObserver, AppMutationObserver),
Registration.singleton(IBackgroundService, SettingsBackgroundService),
LogConfig.register({
@@ -152,7 +152,7 @@ logger.debug(`Configuring platform: ${platform.constructor.name}`);
platform.configure(builder);
// Load app settings
-const settings = await builder.container.get(ISettingService).get();
+const settings = await builder.container.get(ISettingsService).get();
builder.container.get(Settings).init(settings.toJSON());
// Start the app
diff --git a/src/Apps/NetPad.Apps.App/App/src/styles/_common.scss b/src/Apps/NetPad.Apps.App/App/src/styles/_common.scss
index b90edf7f..8c59454d 100644
--- a/src/Apps/NetPad.Apps.App/App/src/styles/_common.scss
+++ b/src/Apps/NetPad.Apps.App/App/src/styles/_common.scss
@@ -47,6 +47,14 @@ kbd {
cursor: pointer;
}
+.text-clickable {
+ @extend %clickable;
+
+ &:hover {
+ @include theme(color, textHoverColor);
+ }
+}
+
.btn.btn-basic {
background: transparent;
}
diff --git a/src/Apps/NetPad.Apps.App/App/src/styles/_icons.scss b/src/Apps/NetPad.Apps.App/App/src/styles/_icons.scss
index 316773f7..09ff02c9 100644
--- a/src/Apps/NetPad.Apps.App/App/src/styles/_icons.scss
+++ b/src/Apps/NetPad.Apps.App/App/src/styles/_icons.scss
@@ -452,6 +452,11 @@ button > .icon-base {
@extend .fa-pencil;
}
+.reset-keyboard-shortcut-icon {
+ @extend %icon-base;
+ @extend .fa-rotate-left;
+}
+
.editor-background-settings-icon {
@extend %icon-base;
@extend .fa-align-left;
diff --git a/src/Apps/NetPad.Apps.App/App/src/styles/themes/_dark.scss b/src/Apps/NetPad.Apps.App/App/src/styles/themes/_dark.scss
index 595320b2..817ab155 100644
--- a/src/Apps/NetPad.Apps.App/App/src/styles/themes/_dark.scss
+++ b/src/Apps/NetPad.Apps.App/App/src/styles/themes/_dark.scss
@@ -1,6 +1,7 @@
$dark: (
textColor: #dcdcdc,
textHighestContrastColor: #fff,
+ textHoverColor: #fff,
textContrastColor: #34314b,
backgroundColor: #222,
backgroundDarkerColor: #1c1c1c,
diff --git a/src/Apps/NetPad.Apps.App/App/src/styles/themes/_light.scss b/src/Apps/NetPad.Apps.App/App/src/styles/themes/_light.scss
index 5a565d23..356ede74 100644
--- a/src/Apps/NetPad.Apps.App/App/src/styles/themes/_light.scss
+++ b/src/Apps/NetPad.Apps.App/App/src/styles/themes/_light.scss
@@ -1,6 +1,7 @@
$light: (
textColor: #34314b,
textHighestContrastColor: #000,
+ textHoverColor: #000,
textContrastColor: #d4d4d4,
backgroundColor: rgb(243, 243, 243),
backgroundDarkerColor: #f3f3f3,
diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/explorer/explorer.ts b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/explorer/explorer.ts
index 0fec27cd..44d0a4b9 100644
--- a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/explorer/explorer.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/explorer/explorer.ts
@@ -1,10 +1,10 @@
import Split from "split.js";
-import {IShortcutManager, Pane} from "@application";
+import {IShortcutManager, Pane, ShortcutIds} from "@application";
export class Explorer extends Pane {
constructor(@IShortcutManager private readonly shortcutManager: IShortcutManager) {
super("Explorer", "explorer-icon");
- this.hasShortcut(shortcutManager.getShortcutByName("Explorer"));
+ this.hasShortcut(shortcutManager.getShortcut(ShortcutIds.openExplorer));
}
public async attached() {
diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/namespaces-pane/namespaces-pane.ts b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/namespaces-pane/namespaces-pane.ts
index 2cbcc5b6..4528df80 100644
--- a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/namespaces-pane/namespaces-pane.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/namespaces-pane/namespaces-pane.ts
@@ -1,7 +1,7 @@
-import {IShortcutManager, Pane} from "@application";
-import {IScriptService, ISession} from "@domain";
import {observable} from "@aurelia/runtime";
import {watch} from "@aurelia/runtime-html";
+import {IShortcutManager, Pane, ShortcutIds} from "@application";
+import {IScriptService, ISession} from "@domain";
import {Util} from "@common";
export class NamespacesPane extends Pane {
@@ -14,7 +14,7 @@ export class NamespacesPane extends Pane {
@IShortcutManager private readonly shortcutManager: IShortcutManager
) {
super("Namespaces", "namespaces-icon");
- this.hasShortcut(shortcutManager.getShortcutByName("Namespaces"));
+ this.hasShortcut(shortcutManager.getShortcut(ShortcutIds.openNamespaces));
}
public override get name() {
diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/output-pane/output-pane.ts b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/output-pane/output-pane.ts
index 7fb71b88..d4328606 100644
--- a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/output-pane/output-pane.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/output-pane/output-pane.ts
@@ -1,4 +1,4 @@
-import {IShortcutManager, Pane, PaneAction} from "@application";
+import {IShortcutManager, Pane, PaneAction, ShortcutIds} from "@application";
import {ISession, IWindowService, Settings} from "@domain";
import {watch} from "@aurelia/runtime-html";
import {IContainer, PLATFORM} from "aurelia";
@@ -17,7 +17,7 @@ export class OutputPane extends Pane {
private readonly settings: Settings) {
super("Output", "output-icon", false);
- this.hasShortcut(shortcutManager.getShortcutByName("Output"));
+ this.hasShortcut(shortcutManager.getShortcut(ShortcutIds.openOutput));
this._actions.push(new PaneAction(
' Pop out',
diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/statusbar/statusbar.ts b/src/Apps/NetPad.Apps.App/App/src/windows/main/statusbar/statusbar.ts
index 918e2f06..f0d9dba7 100644
--- a/src/Apps/NetPad.Apps.App/App/src/windows/main/statusbar/statusbar.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/statusbar/statusbar.ts
@@ -3,7 +3,7 @@ import {
AppStatusMessagePublishedEvent,
IEventBus,
ISession,
- ISettingService,
+ ISettingsService,
ScriptEnvironment,
Settings,
} from "@domain";
@@ -23,7 +23,7 @@ export class Statusbar {
constructor(private readonly workbench: Workbench,
private readonly settings: Settings,
@ISession private readonly session: ISession,
- @ISettingService private readonly settingsService: ISettingService,
+ @ISettingsService private readonly settingsService: ISettingsService,
@IShortcutManager private readonly shortcutManager: IShortcutManager,
private readonly dialogUtil: DialogUtil,
@IEventBus private readonly eventBus: IEventBus) {
diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu-service.ts b/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu-service.ts
index 3b8af3d3..65245d08 100644
--- a/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu-service.ts
+++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu-service.ts
@@ -1,8 +1,8 @@
import {DI} from "aurelia";
import {IMenuItem} from "./imenu-item";
import {System} from "@common";
-import {ISettingService, IWindowService} from "@domain";
-import {IShortcutManager} from "@application";
+import {ISettingsService, IWindowService} from "@domain";
+import {IShortcutManager, ShortcutIds} from "@application";
import {ITextEditorService} from "@application/editor/text-editor-service";
import {AppUpdateDialog} from "@application/dialogs/app-update-dialog/app-update-dialog";
import {DialogUtil} from "@application/dialogs/dialog-util";
@@ -25,7 +25,7 @@ export class MainMenuService implements IMainMenuService {
private readonly _items: IMenuItem[] = [];
constructor(
- @ISettingService private readonly settingsService: ISettingService,
+ @ISettingsService private readonly settingsService: ISettingsService,
@IShortcutManager private readonly shortcutManager: IShortcutManager,
@ITextEditorService private readonly textEditorService: ITextEditorService,
@IWindowService private readonly windowService: IWindowService,
@@ -39,12 +39,12 @@ export class MainMenuService implements IMainMenuService {
id: "file.new",
text: "New",
icon: "add-script-icon",
- shortcut: this.shortcutManager.getShortcutByName("New"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.newDocument),
},
{
id: "file.goToScript",
text: "Go to Script",
- shortcut: this.shortcutManager.getShortcutByName("Go to Script"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.quickOpenDocument),
},
{
isDivider: true
@@ -53,25 +53,25 @@ export class MainMenuService implements IMainMenuService {
id: "file.save",
text: "Save",
icon: "save-icon",
- shortcut: this.shortcutManager.getShortcutByName("Save"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.saveDocument),
},
{
id: "file.saveAll",
text: "Save All",
icon: "save-icon",
- shortcut: this.shortcutManager.getShortcutByName("Save All"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.saveAllDocuments),
},
{
id: "file.properties",
text: "Properties",
icon: "properties-icon",
- shortcut: this.shortcutManager.getShortcutByName("Script Properties"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.openDocumentProperties),
},
{
id: "file.close",
text: "Close",
icon: "close-icon",
- shortcut: this.shortcutManager.getShortcutByName("Close"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.closeDocument),
},
{
isDivider: true
@@ -80,7 +80,7 @@ export class MainMenuService implements IMainMenuService {
id: "file.settings",
text: "Settings",
icon: "settings-icon",
- shortcut: this.shortcutManager.getShortcutByName("Settings"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.openSettings),
},
{
id: "file.exit",
@@ -219,26 +219,26 @@ export class MainMenuService implements IMainMenuService {
id: "view.output",
text: "Output",
icon: "output-icon",
- shortcut: this.shortcutManager.getShortcutByName("Output"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.openOutput),
},
{
id: "view.explorer",
text: "Explorer",
icon: "explorer-icon",
- shortcut: this.shortcutManager.getShortcutByName("Explorer"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.openExplorer),
},
{
id: "view.namespaces",
text: "Namespaces",
icon: "namespaces-icon",
- shortcut: this.shortcutManager.getShortcutByName("Namespaces"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.openNamespaces),
},
{
isDivider: true
},
{
text: "Reload",
- shortcut: this.shortcutManager.getShortcutByName("Reload"),
+ shortcut: this.shortcutManager.getShortcut(ShortcutIds.reloadWindow),
},
{
text: "Toggle Developer Tools",
@@ -328,7 +328,7 @@ export class MainMenuService implements IMainMenuService {
if (menuItem.click) {
await menuItem.click();
} else if (menuItem.shortcut) {
- this.shortcutManager.executeShortcut(menuItem.shortcut);
+ await this.shortcutManager.executeShortcut(menuItem.shortcut);
}
}
diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu.html b/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu.html
index 4ae2964c..eca0b408 100644
--- a/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu.html
+++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/titlebar/main-menu/main-menu.html
@@ -36,7 +36,7 @@
${item.text}