diff --git a/.gitignore b/.gitignore index 2f4ea73..fc011be 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ typings/ # Electron-Forge out/ src/build.json + +# Claude Code worktrees +.claude/worktrees/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5beb55d..1abd12d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.0.58 + +- Feat: ⌃+⌘+T global shortcut for Terminal tab (customizable in Settings) +- Fix: Cmd+←/→ in xterm (beginning/end of line) + ## 1.0.57 - Fix: menubar Keyboard Shortcuts submenu now reflects custom shortcuts diff --git a/CLAUDE.md b/CLAUDE.md index 68ed2a0..bee3673 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,10 @@ # CodeV Development Guide +## Git + +- **Default branch: `main`** (changed from `develop` — PRs should target `main`) +- `develop` branch is legacy and no longer used + ## Build Commands - Electron: `yarn start` (dev), `yarn make` (build), `yarn dev` (with server) diff --git a/README.md b/README.md index 70e9fb4..e64ccbf 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ For the full same-cwd accuracy matrix (detection + switch by launch method and t ### Embedded Terminal -CodeV includes a built-in terminal tab (powered by xterm.js + node-pty, same technology as VS Code's integrated terminal). Press `⌘+3` or click the **Term** tab to open it. +CodeV includes a built-in terminal tab (powered by xterm.js + node-pty, same technology as VS Code's integrated terminal). Press `⌃+⌘+T` from anywhere (global shortcut) or `⌘+3` when CodeV is in foreground to open it. - Pre-spawned on app start for instant access - Default working directory: Settings → Working Directory (fallback to home) diff --git a/package.json b/package.json index e039e07..b1368eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "CodeV", "productName": "CodeV", - "version": "1.0.57", + "version": "1.0.58", "description": "Quick switcher for VS Code, Cursor, and Claude Code sessions", "repository": { "type": "git", diff --git a/src/TrayGenerator.ts b/src/TrayGenerator.ts index fa9f271..2dfdd67 100644 --- a/src/TrayGenerator.ts +++ b/src/TrayGenerator.ts @@ -14,6 +14,7 @@ export interface ShortcutSettings { quickSwitcher: string; aiInsight: string; aiChat: string; + terminal: string; } export class TrayGenerator { @@ -25,6 +26,7 @@ export class TrayGenerator { quickSwitcher: 'Command+Control+R', aiInsight: 'Command+Control+E', aiChat: 'Command+Control+C', + terminal: 'Command+Control+T', }; constructor( @@ -91,6 +93,7 @@ export class TrayGenerator { label: 'Keyboard Shortcuts', submenu: [ { label: `${this.acceleratorToMenuLabel(this.shortcuts.quickSwitcher)}: Open CodeV Quick Switcher`, enabled: false }, + { label: `${this.acceleratorToMenuLabel(this.shortcuts.terminal)}: Terminal`, enabled: false }, { label: 'Tab: Switch Projects / Sessions', enabled: false }, { label: `${this.acceleratorToMenuLabel(this.shortcuts.aiInsight)}: AI Assistant Insight`, enabled: false }, { label: `${this.acceleratorToMenuLabel(this.shortcuts.aiChat)}: AI Assistant Smart Chat`, enabled: false }, diff --git a/src/electron-api.d.ts b/src/electron-api.d.ts index fc4925a..0c95feb 100644 --- a/src/electron-api.d.ts +++ b/src/electron-api.d.ts @@ -63,6 +63,8 @@ interface IElectronAPI { onFolderSelected: (callback: IpcCallback) => void; onWorkingFolderIterated: (callback: IpcCallback) => void; onFocusWindow: (callback: IpcCallback) => void; + onSwitchToTerminal: (callback: IpcCallback) => void; + onCheckTerminalAndHide: (callback: IpcCallback) => void; onXWinNotFound: (callback: IpcCallback) => void; // AI Assistant insight events @@ -108,9 +110,9 @@ interface IElectronAPI { searchConversations: (searchTerm: string) => Promise; // Keyboard shortcuts - getShortcuts: () => Promise<{ quickSwitcher: string; aiInsight: string; aiChat: string }>; + getShortcuts: () => Promise<{ quickSwitcher: string; aiInsight: string; aiChat: string; terminal: string }>; setShortcut: (key: string, accelerator: string) => Promise<{ success: boolean; error?: string }>; - resetShortcuts: () => Promise<{ quickSwitcher: string; aiInsight: string; aiChat: string }>; + resetShortcuts: () => Promise<{ quickSwitcher: string; aiInsight: string; aiChat: string; terminal: string }>; pauseShortcut: (key: string) => Promise; resumeShortcut: (key: string) => Promise; diff --git a/src/main.ts b/src/main.ts index 7b4a223..1e4f5e6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1205,6 +1205,7 @@ const trayToggleEvtHandler = async () => { quickSwitcher: 'Command+Control+R', aiInsight: 'Command+Control+E', aiChat: 'Command+Control+C', + terminal: 'Command+Control+T', }; // Shortcut callback: Quick Switcher @@ -1516,11 +1517,27 @@ const trayToggleEvtHandler = async () => { // End of the new implementation }; + // Shortcut callback: Terminal (Ctrl+Cmd+T) — toggle behavior like Quick Switcher + const terminalCallback = () => { + if (BrowserWindow.getAllWindows().length === 0) { + switcherWindow = createSwitcherWindow(); + } + const window = getSwitcherWindow(); + if (window && window.isVisible()) { + // If already showing Terminal tab, hide; otherwise switch to Terminal + window.webContents.send('check-terminal-and-hide'); + } else if (window) { + window.webContents.send('switch-to-terminal'); + showSwitcherWindow(); + } + }; + // Map shortcut keys to their callbacks const shortcutCallbacks: Record void> = { quickSwitcher: quickSwitcherCallback, aiInsight: aiInsightCallback, aiChat: aiChatCallback, + terminal: terminalCallback, }; // Register all shortcuts from settings (or defaults) @@ -1542,6 +1559,7 @@ const trayToggleEvtHandler = async () => { quickSwitcher: ((await settings.get('shortcut-quickSwitcher')) as string) || DEFAULT_SHORTCUTS.quickSwitcher, aiInsight: ((await settings.get('shortcut-aiInsight')) as string) || DEFAULT_SHORTCUTS.aiInsight, aiChat: ((await settings.get('shortcut-aiChat')) as string) || DEFAULT_SHORTCUTS.aiChat, + terminal: ((await settings.get('shortcut-terminal')) as string) || DEFAULT_SHORTCUTS.terminal, }); const syncTrayShortcuts = async () => { diff --git a/src/popup.tsx b/src/popup.tsx index 15963a6..acf2c8c 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -68,6 +68,7 @@ const PopupDefaultExample = ({ quickSwitcher: 'Command+Control+R', aiInsight: 'Command+Control+E', aiChat: 'Command+Control+C', + terminal: 'Command+Control+T', }); const [editingShortcut, setEditingShortcut] = useState(null); const [shortcutError, setShortcutError] = useState(''); @@ -214,6 +215,7 @@ const PopupDefaultExample = ({ quickSwitcher: defaults.quickSwitcher, aiInsight: defaults.aiInsight, aiChat: defaults.aiChat, + terminal: defaults.terminal, }); } setEditingShortcut(null); @@ -232,6 +234,7 @@ const PopupDefaultExample = ({ { key: 'quickSwitcher', label: 'Quick Switcher' }, { key: 'aiInsight', label: 'AI Insight' }, { key: 'aiChat', label: 'AI Chat' }, + { key: 'terminal', label: 'Terminal' }, ]; return ( diff --git a/src/preload.ts b/src/preload.ts index 7b3c08a..ec3e8e3 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -71,6 +71,8 @@ contextBridge.exposeInMainWorld('electronAPI', { ipcRenderer.on('working-folder-iterated', callback), openExternal: (url: string) => ipcRenderer.send('open-external', url), onFocusWindow: (callback: any) => ipcRenderer.on('window-focus', callback), + onSwitchToTerminal: (callback: any) => ipcRenderer.on('switch-to-terminal', callback), + onCheckTerminalAndHide: (callback: any) => ipcRenderer.on('check-terminal-and-hide', callback), onXWinNotFound: (callback: any) => ipcRenderer.on('xwin-not-found', callback), // Listen for code to explain in the ai assistant window diff --git a/src/switcher-ui.tsx b/src/switcher-ui.tsx index 3028709..ad2b896 100644 --- a/src/switcher-ui.tsx +++ b/src/switcher-ui.tsx @@ -491,6 +491,20 @@ function SwitcherApp() { forceFocusOnInput(); }); + window.electronAPI.onSwitchToTerminal(() => { + modeRef.current = 'terminal'; + setMode('terminal'); + }); + + window.electronAPI.onCheckTerminalAndHide(() => { + if (modeRef.current === 'terminal') { + window.electronAPI.hideApp(); + } else { + modeRef.current = 'terminal'; + setMode('terminal'); + } + }); + // Data refresh on window focus: // - Projects: always refetch (complements tab-switch which doesn't refetch projects) // - Sessions: only refetch if sessions tab is active (tab-switch already fetches on entry) diff --git a/src/terminal-tab.tsx b/src/terminal-tab.tsx index 87eb332..51a5c2d 100644 --- a/src/terminal-tab.tsx +++ b/src/terminal-tab.tsx @@ -36,6 +36,9 @@ const TerminalTab = ({ visible }: { visible: boolean }) => { term.attachCustomKeyEventHandler((e) => { if (e.metaKey && ['1', '2', '3', '[', ']'].includes(e.key)) return false; if (e.ctrlKey && e.key === 'Tab') return false; + // Cmd+←/→ → beginning/end of line + if (e.type === 'keydown' && e.metaKey && e.key === 'ArrowLeft') { term.input('\x01'); return false; } + if (e.type === 'keydown' && e.metaKey && e.key === 'ArrowRight') { term.input('\x05'); return false; } return true; });