mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
## Summary Step 2 of the SQL Editor testability plan. `SQLEditorContext` already wrapped the Monaco refs and exposed a few semantic imperative helpers (`getEditorSql`, `clearHighlights`, `applyErrorHighlight`, `refocusEditor`, …). This finishes that abstraction so no hook or controller touches `editorRef.current`/`diffEditorRef.current` directly anymore — they only call the port. The port is what will let Step 3's test harness inject a real in-memory editor adapter instead of mocking Monaco; production wires it to the real Monaco refs, unchanged. - Extends the context value with two semantic controllers, backed by the existing refs: - `editor: EditorController` — `isReady`, `getValue`, `getSelectionStartLine`, `getSql` (today's `getEditorSql`), `replaceAll` (wraps the repeated `executeEdits(...)` pattern), `focus`, `revealLineInCenter`, `highlightErrorLine` (today's `applyErrorHighlight`), `clearHighlights`. - `diff: DiffController` — `isMounted`, `getModifiedValue`, `setDiff` (the diff-sync effect body), `attach` (today's `handleDiffEditorMount`). - Migrates every touch point off raw refs onto the port: `useSqlEditorExecution`, `usePrettifyQuery`, `useSqlEditorShortcuts`, `SQLEditorControllers`' `readEditorSql`, and `useSqlEditorAi`'s `acceptAiHandler`/`drainDiffRequest`/`handleDiffEditorMount`/diff-sync effect. - `SQLEditorEditorPanel.tsx` is intentionally left untouched — it wires the raw refs into the real Monaco/DiffEditor React components for rendering, which isn't decision logic to abstract. Behavior-preserving. ## Test plan - [x] `pnpm --filter studio typecheck` - [x] `pnpm test:studio -- SQLEditor` (265 tests passing) - [x] `pnpm --filter studio run lint:ratchet`
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
import { useParams } from 'common'
|
|
import { useRouter } from 'next/router'
|
|
import { useCallback, useEffect } from 'react'
|
|
|
|
import { resolveDiffKeyAction } from './SQLEditor.utils'
|
|
import { useSQLEditorContext } from './SQLEditorContext'
|
|
import { detectOS } from '@/lib/helpers'
|
|
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
|
|
|
type UseSqlEditorShortcutsArgs = {
|
|
isDiffOpen: boolean
|
|
isPromptOpen: boolean
|
|
prettifyQuery: () => void
|
|
acceptAiHandler: () => void
|
|
discardAiHandler: () => void
|
|
resetPrompt: () => void
|
|
}
|
|
|
|
/**
|
|
* Registers the SQL editor's keyboard shortcuts (focus editor, new snippet,
|
|
* format) plus the window keydown that accepts/discards an open AI diff
|
|
* or dismisses the prompt.
|
|
*/
|
|
export function useSqlEditorShortcuts({
|
|
isDiffOpen,
|
|
isPromptOpen,
|
|
prettifyQuery,
|
|
acceptAiHandler,
|
|
discardAiHandler,
|
|
resetPrompt,
|
|
}: UseSqlEditorShortcutsArgs) {
|
|
const os = detectOS()
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
const { editor, refocusEditor } = useSQLEditorContext()
|
|
|
|
const openNewSnippet = useCallback(() => {
|
|
if (!ref) return
|
|
// skip=true bypasses the "load last visited snippet" redirect on /sql/new.
|
|
// Without it, the effect in pages/project/[ref]/sql/[id].tsx bounces back
|
|
// to the previous snippet.
|
|
router.push(`/project/${ref}/sql/new?skip=true`)
|
|
}, [ref, router])
|
|
|
|
useShortcut(SHORTCUT_IDS.SQL_EDITOR_FOCUS_EDITOR, refocusEditor, {
|
|
registerInCommandMenu: true,
|
|
})
|
|
|
|
useShortcut(SHORTCUT_IDS.SQL_EDITOR_NEW_SNIPPET, openNewSnippet, {
|
|
registerInCommandMenu: true,
|
|
})
|
|
|
|
useShortcut(SHORTCUT_IDS.SQL_EDITOR_FORMAT, prettifyQuery, {
|
|
registerInCommandMenu: true,
|
|
})
|
|
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
const action = resolveDiffKeyAction(e, { isDiffOpen, isPromptOpen, os })
|
|
|
|
switch (action.type) {
|
|
case 'accept':
|
|
acceptAiHandler()
|
|
resetPrompt()
|
|
return
|
|
case 'escape':
|
|
if (action.shouldDiscard) discardAiHandler()
|
|
resetPrompt()
|
|
editor.focus()
|
|
return
|
|
case 'none':
|
|
return
|
|
}
|
|
}
|
|
window.addEventListener('keydown', handler)
|
|
return () => window.removeEventListener('keydown', handler)
|
|
}, [editor, os, isDiffOpen, isPromptOpen, acceptAiHandler, discardAiHandler, resetPrompt])
|
|
}
|