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{' '} {' '}