From 9cd688528fa0c0447ea29255277f1b9dbe00d31f Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Thu, 30 Apr 2026 07:09:58 -0600 Subject: [PATCH] feat(studio): change SQL editor assistant shortcut to Cmd+Shift+K (#45380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes [FE-3109](https://linear.app/supabase/issue/FE-3109/change-sql-editor-assistant-shortcut-to-cmdshiftk). The SQL editor's "Generate SQL" Monaco action was bound to `Cmd+K`, which conflicted with the global command menu shortcut while the editor was focused. This PR moves the assistant shortcut to `Cmd+Shift+K` and makes `Cmd+K` open the global command menu from inside the editor. ## Changes - `MonacoEditor.tsx` — rebind `generate-sql` to `Cmd+Shift+K`. Add an `editor.addCommand` for `Cmd+K` that opens the global command menu (gated on the user's `COMMAND_MENU_OPEN` shortcut preference). Without this, Monaco swallows `Cmd+K` as a chord prefix and the global hotkey never fires inside the editor. - `SQLEditor.tsx` — update the empty-editor placeholder text from `CMD+K` to `CMD+SHIFT+K`. ## Notes - Monaco's standalone defaults bind `Cmd+Shift+K` to "Delete Line"; registering an `editor.addAction` with the same keybinding overrides it. - The same `Cmd+K` binding still exists in `apps/studio/components/ui/AIEditor/index.tsx` (used by the inline editor panel and edge functions). Out of scope for FE-3109 — happy to file a follow-up. ## Test plan - [x] Focus the SQL editor, press `Cmd+K` → global command menu opens. - [x] Focus the SQL editor, press `Cmd+Shift+K` → Generate SQL widget opens (or "Make an edit" if a diff is already visible). - [x] Disable the command menu shortcut in Account → Preferences → Keyboard shortcuts and confirm `Cmd+K` no longer opens the menu from inside the editor. - [x] Empty SQL snippet placeholder reads "Hit CMD+SHIFT+K to generate query…". ## Summary by CodeRabbit * **Improvements** * Reorganized SQL editor keyboard shortcuts for clearer access * "Generate SQL" shortcut changed to Ctrl/Cmd + Shift + K (was Ctrl/Cmd + K) * Command menu can now be opened with Ctrl/Cmd + K when enabled * Editor UI shortcut hints updated to reflect the new bindings --------- Co-authored-by: Joshen Lim --- .../interfaces/SQLEditor/MonacoEditor.tsx | 20 ++++++++++++++- .../interfaces/SQLEditor/SQLEditor.tsx | 2 +- apps/studio/components/ui/AIEditor/index.tsx | 25 +++++++++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx b/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx index 394743679b7..5192ea0f15e 100644 --- a/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/router' import { MutableRefObject, useEffect, useRef, useState } from 'react' import { cn } from 'ui' import { Admonition } from 'ui-patterns' +import { useSetCommandMenuOpen } from 'ui-patterns/CommandMenu' import type { IStandaloneCodeEditor } from './SQLEditor.types' import { createSqlSnippetSkeletonV2 } from './SQLEditor.utils' @@ -72,6 +73,8 @@ const MonacoEditor = ({ true ) const isAIAssistantHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE) + const isCommandMenuHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.COMMAND_MENU_OPEN) + const setCommandMenuOpen = useSetCommandMenuOpen() // [Joshen] Lodash debounce doesn't seem to be working here, so opting to use useDebounce const [value, setValue] = useState('') @@ -93,6 +96,12 @@ const MonacoEditor = ({ const aiHotkeyEnabledRef = useRef(isAIAssistantHotkeyEnabled) aiHotkeyEnabledRef.current = isAIAssistantHotkeyEnabled + const commandMenuHotkeyEnabledRef = useRef(isCommandMenuHotkeyEnabled) + commandMenuHotkeyEnabledRef.current = isCommandMenuHotkeyEnabled + + const setCommandMenuOpenRef = useRef(setCommandMenuOpen) + setCommandMenuOpenRef.current = setCommandMenuOpen + const handleEditorOnMount: OnMount = async (editor, monaco) => { editorRef.current = editor monacoRef.current = monaco @@ -202,7 +211,7 @@ const MonacoEditor = ({ editor.addAction({ id: 'generate-sql', label: 'Generate SQL', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK], + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyK], run: () => { const selectionParts = getEditorSelectionParts(editor) if (selectionParts) onPrompt(selectionParts) @@ -210,6 +219,15 @@ const MonacoEditor = ({ }) } + // Monaco claims Cmd+K as a chord prefix, which swallows the global command + // menu shortcut while the editor is focused. Intercept it here and open the + // command menu directly so it works the same inside and outside the editor. + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, () => { + if (commandMenuHotkeyEnabledRef.current) { + setCommandMenuOpenRef.current(true) + } + }) + editor.onDidChangeCursorSelection(({ selection }) => { const noSelection = selection.startLineNumber === selection.endLineNumber && diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index 847a3a17785..3ebe382043b 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -937,7 +937,7 @@ export const SQLEditor = () => { placeholder={ !promptState.isOpen && !editorRef.current?.getValue() ? 'Hit ' + - (os === 'macos' ? 'CMD+K' : `CTRL+K`) + + (os === 'macos' ? 'CMD+SHIFT+K' : `CTRL+SHIFT+K`) + ' to generate query or just start typing' : '' } diff --git a/apps/studio/components/ui/AIEditor/index.tsx b/apps/studio/components/ui/AIEditor/index.tsx index 503f5cf459a..63076cff831 100644 --- a/apps/studio/components/ui/AIEditor/index.tsx +++ b/apps/studio/components/ui/AIEditor/index.tsx @@ -4,12 +4,15 @@ import type { editor as monacoEditor } from 'monaco-editor' import { useCallback, useEffect, useRef, useState } from 'react' import { toast } from 'sonner' import { KeyboardShortcut } from 'ui' +import { useSetCommandMenuOpen } from 'ui-patterns' import { DiffEditor } from '../DiffEditor' import ResizableAIWidget from './ResizableAIWidget' import { getEditorSelectionParts } from './utils' import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' import { constructHeaders } from '@/data/fetchers' +import { SHORTCUT_IDS } from '@/state/shortcuts/registry' +import { useIsShortcutEnabled } from '@/state/shortcuts/useIsShortcutEnabled' import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' interface AIEditorProps { @@ -65,9 +68,18 @@ export const AIEditor = ({ const monacoRef = useRef(null) const closeActionDisposableRef = useRef<{ dispose: () => void } | null>(null) + const isCommandMenuHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.COMMAND_MENU_OPEN) + const setCommandMenuOpen = useSetCommandMenuOpen() + const executeQueryRef = useRef(executeQuery) executeQueryRef.current = executeQuery + const commandMenuHotkeyEnabledRef = useRef(isCommandMenuHotkeyEnabled) + commandMenuHotkeyEnabledRef.current = isCommandMenuHotkeyEnabled + + const setCommandMenuOpenRef = useRef(setCommandMenuOpen) + setCommandMenuOpenRef.current = setCommandMenuOpen + const [currentValue, setCurrentValue] = useState(value || defaultValue) const [isDiffMode, setIsDiffMode] = useState(false) const [isDiffEditorMounted, setIsDiffEditorMounted] = useState(false) @@ -251,7 +263,7 @@ export const AIEditor = ({ editor.addAction({ id: 'generate-ai', label: 'Generate with AI', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK], + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyK], run: () => { const selectionParts = getEditorSelectionParts(editor) if (!selectionParts) return @@ -259,6 +271,15 @@ export const AIEditor = ({ }, }) + // Monaco claims Cmd+K as a chord prefix, which swallows the global command + // menu shortcut while the editor is focused. Intercept it here and open the + // command menu directly so it works the same inside and outside the editor. + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, () => { + if (commandMenuHotkeyEnabledRef.current) { + setCommandMenuOpenRef.current(true) + } + }) + if (autoFocus) { if (editor.getValue().length === 1) editor.setPosition({ lineNumber: 1, column: 2 }) editor.focus() @@ -435,7 +456,7 @@ export const AIEditor = ({ > Hit{' '} {' '}