Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/desktop/src-electron/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const nativeMenuCommands = new Set([
"editor:heading-6",
"editor:heading-0",
"editor:toggle-live-preview",
"developer:new-terminal-tab",
"layout:toggle-sidebar",
"layout:toggle-right-panel",
"nav:command-palette",
Expand Down Expand Up @@ -180,6 +181,11 @@ function buildApplicationMenu() {
submenu: [
commandItem("vault:new-note", "New Note", "CommandOrControl+N"),
commandItem("editor:new-tab", "New Tab", "CommandOrControl+T"),
commandItem(
"developer:new-terminal-tab",
"New Terminal",
"CommandOrControl+R",
),
commandItem("vault:open", "Open Vault...", "Shift+CommandOrControl+O"),
separator(),
commandItem("editor:close-tab", "Close Tab", "CommandOrControl+W"),
Expand Down
64 changes: 64 additions & 0 deletions apps/desktop/src/App.noteWindow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useCommandStore } from "./features/command-palette/store/commandStore";
import { isTerminalTab, useEditorStore } from "./app/store/editorStore";
import { useSettingsStore } from "./app/store/settingsStore";
import { useVaultStore } from "./app/store/vaultStore";
import { getDesktopPlatform } from "./app/utils/platform";
import {
resetTerminalRuntimeStoreForTests,
useTerminalRuntimeStore,
Expand Down Expand Up @@ -209,6 +210,69 @@ describe("App note window", () => {
expect(activeTab && isTerminalTab(activeTab)).toBe(true);
});

it("opens workspace terminals from the developer terminal shortcut", async () => {
detachedWindowMock.label = "main";
detachedWindowMock.mode = "main";
window.history.replaceState({}, "", "/");
useSettingsStore.setState({
developerModeEnabled: true,
developerTerminalEnabled: true,
});

renderComponent(<App />);
await flushPromises();

const platform = getDesktopPlatform();

await act(async () => {
window.dispatchEvent(
new KeyboardEvent("keydown", {
key: "r",
metaKey: platform === "macos",
ctrlKey: platform !== "macos",
}),
);
await Promise.resolve();
});
await flushPromises();

const activeTab = useEditorStore
.getState()
.tabs.find(
(tab) => tab.id === useEditorStore.getState().activeTabId,
);
expect(activeTab && isTerminalTab(activeTab)).toBe(true);
});

it("does not open workspace terminals from the shortcut when disabled", async () => {
detachedWindowMock.label = "main";
detachedWindowMock.mode = "main";
window.history.replaceState({}, "", "/");
useSettingsStore.setState({
developerModeEnabled: true,
developerTerminalEnabled: false,
});

renderComponent(<App />);
await flushPromises();

const platform = getDesktopPlatform();

await act(async () => {
window.dispatchEvent(
new KeyboardEvent("keydown", {
key: "r",
metaKey: platform === "macos",
ctrlKey: platform !== "macos",
}),
);
await Promise.resolve();
});
await flushPromises();

expect(useEditorStore.getState().tabs.some(isTerminalTab)).toBe(false);
});

it("starts workspace terminal runtimes inside detached note windows", async () => {
mockInvoke().mockResolvedValue({
sessionId: "devterm-note-1",
Expand Down
12 changes: 10 additions & 2 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ function useRegisterCommands(
const openVaultShortcut = getShortcutDefinition("open_vault");
const newNoteShortcut = getShortcutDefinition("new_note");
const newAgentShortcut = getShortcutDefinition("new_agent");
const newTerminalShortcut = getShortcutDefinition("new_terminal");
const closeTabShortcut = getShortcutDefinition("close_tab");
const newTabShortcut = getShortcutDefinition("new_tab");
const reopenClosedTabShortcut =
Expand Down Expand Up @@ -934,8 +935,9 @@ function useRegisterCommands(

register({
id: "developer:new-terminal-tab",
label: "New Terminal",
category: "Developer",
label: newTerminalShortcut.label,
shortcut: formatShortcutAction(newTerminalShortcut.id, platform),
category: newTerminalShortcut.category,
when: developerModeEnabled,
execute: () => {
useEditorStore.getState().openTerminal();
Expand Down Expand Up @@ -1042,6 +1044,12 @@ function useGlobalShortcuts(openSettings: () => void) {
return;
}

if (matchesShortcutAction(e, "new_terminal", platform)) {
e.preventDefault();
useCommandStore.getState().execute("developer:new-terminal-tab");
return;
}

if (matchesShortcutAction(e, "close_tab", platform)) {
e.preventDefault();
useCommandStore.getState().execute("editor:close-tab");
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/app/shortcuts/format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ describe("shortcut registry formatting", () => {
expect(formatShortcutAction("new_agent", "windows")).toBe(
"Ctrl+Shift+N",
);
expect(formatShortcutAction("new_terminal", "macos")).toBe("⌘R");
expect(formatShortcutAction("new_terminal", "windows")).toBe("Ctrl+R");
expect(formatShortcutAction("zoom_in", "macos")).toBe("⌘=");
expect(formatShortcutAction("zoom_out", "windows")).toBe("Ctrl+-");
expect(formatShortcutAction("reset_zoom", "macos")).toBe("⌘0");
Expand Down Expand Up @@ -59,6 +61,13 @@ describe("shortcut registry formatting", () => {
shortcut: "Ctrl+Shift+N",
},
);
expect(
entries.find((entry) => entry.id === "new_terminal"),
).toMatchObject({
label: "New Terminal",
category: "Developer",
shortcut: "Ctrl+R",
});
expect(entries.find((entry) => entry.id === "zoom_in")).toMatchObject({
label: "Zoom In",
category: "View",
Expand Down Expand Up @@ -242,6 +251,24 @@ describe("shortcut registry matching", () => {
).toBe(true);
});

it("matches new terminal on both platforms", () => {
const macEvent = new KeyboardEvent("keydown", {
key: "R",
metaKey: true,
});
const windowsEvent = new KeyboardEvent("keydown", {
key: "r",
ctrlKey: true,
});

expect(matchesShortcutAction(macEvent, "new_terminal", "macos")).toBe(
true,
);
expect(
matchesShortcutAction(windowsEvent, "new_terminal", "windows"),
).toBe(true);
});

it("matches zoom in aliases on both platforms", () => {
const macEvent = new KeyboardEvent("keydown", {
key: "+",
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/app/shortcuts/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ShortcutActionId =
| "open_vault"
| "new_note"
| "new_agent"
| "new_terminal"
| "new_tab"
| "close_tab"
| "reopen_closed_tab"
Expand Down Expand Up @@ -121,6 +122,15 @@ const shortcutDefinitions = [
windows: [{ key: "n", modifiers: ["ctrl", "shift"] }],
},
},
{
id: "new_terminal",
label: "New Terminal",
category: "Developer",
bindings: {
macos: [{ key: "r", modifiers: ["meta"] }],
windows: [{ key: "r", modifiers: ["ctrl"] }],
},
},
{
id: "new_tab",
label: "New Tab",
Expand Down Expand Up @@ -408,6 +418,7 @@ export const SHORTCUT_SETTINGS_ORDER: ShortcutActionId[] = [
"open_vault",
"new_note",
"new_agent",
"new_terminal",
"new_tab",
"reopen_closed_tab",
"find_in_note",
Expand Down
Loading