244 lines
7.1 KiB
TypeScript
244 lines
7.1 KiB
TypeScript
import { useRef, forwardRef, useImperativeHandle } from 'react'
|
|
import Editor, { Monaco } from '@monaco-editor/react'
|
|
import { useConfig } from '@/components/ui/ConfigProvider'
|
|
import type { editor } from 'monaco-editor'
|
|
|
|
interface SqlEditorProps {
|
|
value: string
|
|
onChange: (value: string | undefined) => void
|
|
onExecute?: () => void
|
|
onSave?: () => void
|
|
readOnly?: boolean
|
|
height?: string
|
|
}
|
|
|
|
export interface SqlEditorRef {
|
|
getSelectedText: () => string
|
|
preserveSelection: () => any
|
|
restoreSelection: (selection: any) => void
|
|
}
|
|
|
|
const SqlEditor = forwardRef<SqlEditorRef, SqlEditorProps>((
|
|
{
|
|
value,
|
|
onChange,
|
|
onExecute,
|
|
onSave,
|
|
readOnly = false,
|
|
}: SqlEditorProps,
|
|
ref
|
|
) => {
|
|
const { mode } = useConfig()
|
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null)
|
|
const monacoRef = useRef<Monaco | null>(null)
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
getSelectedText: () => {
|
|
if (!editorRef.current) return ''
|
|
const selection = editorRef.current.getSelection()
|
|
if (!selection) return ''
|
|
return editorRef.current.getModel()?.getValueInRange(selection) || ''
|
|
},
|
|
preserveSelection: () => {
|
|
if (!editorRef.current) return null
|
|
return editorRef.current.getSelection()
|
|
},
|
|
restoreSelection: (selection: any) => {
|
|
if (!editorRef.current || !selection) return
|
|
editorRef.current.setSelection(selection)
|
|
editorRef.current.revealRangeInCenter(selection)
|
|
},
|
|
}))
|
|
|
|
const handleEditorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
|
editorRef.current = editor
|
|
monacoRef.current = monaco
|
|
|
|
// Add keyboard shortcuts
|
|
editor.addCommand(monaco.KeyCode.F5, () => {
|
|
if (onExecute) onExecute()
|
|
})
|
|
|
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
if (onSave) onSave()
|
|
})
|
|
|
|
// Configure SQL language features
|
|
monaco.languages.registerCompletionItemProvider('sql', {
|
|
provideCompletionItems: (model: any, position: any) => {
|
|
const word = model.getWordUntilPosition(position)
|
|
const range = {
|
|
startLineNumber: position.lineNumber,
|
|
endLineNumber: position.lineNumber,
|
|
startColumn: word.startColumn,
|
|
endColumn: word.endColumn,
|
|
}
|
|
|
|
const suggestions = [
|
|
// SQL Keywords
|
|
{
|
|
label: 'SELECT',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'SELECT ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'FROM',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'FROM ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'WHERE',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'WHERE ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'INSERT INTO',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'INSERT INTO ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'UPDATE',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'UPDATE ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'DELETE FROM',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'DELETE FROM ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'CREATE TABLE',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'CREATE TABLE ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'CREATE PROCEDURE',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'CREATE PROCEDURE ${1:ProcedureName}\nAS\nBEGIN\n\t$0\nEND',
|
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'CREATE FUNCTION',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText:
|
|
'CREATE FUNCTION ${1:FunctionName}()\nRETURNS ${2:ReturnType}\nAS\nBEGIN\n\t$0\nEND',
|
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'CREATE VIEW',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'CREATE VIEW ${1:ViewName}\nAS\n\tSELECT $0',
|
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'JOIN',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'JOIN ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'LEFT JOIN',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'LEFT JOIN ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'RIGHT JOIN',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'RIGHT JOIN ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'INNER JOIN',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'INNER JOIN ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'GROUP BY',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'GROUP BY ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'ORDER BY',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'ORDER BY ',
|
|
range: range,
|
|
},
|
|
{
|
|
label: 'HAVING',
|
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
insertText: 'HAVING ',
|
|
range: range,
|
|
},
|
|
]
|
|
|
|
return { suggestions: suggestions }
|
|
},
|
|
})
|
|
|
|
// Focus the editor
|
|
editor.focus()
|
|
}
|
|
|
|
const editorOptions: editor.IStandaloneEditorConstructionOptions = {
|
|
readOnly: readOnly,
|
|
minimap: { enabled: true },
|
|
fontSize: 14,
|
|
lineNumbers: 'on',
|
|
roundedSelection: false,
|
|
scrollBeyondLastLine: false,
|
|
automaticLayout: true,
|
|
tabSize: 2,
|
|
wordWrap: 'on',
|
|
folding: true,
|
|
glyphMargin: true,
|
|
lineDecorationsWidth: 10,
|
|
lineNumbersMinChars: 3,
|
|
renderLineHighlight: 'all',
|
|
scrollbar: {
|
|
vertical: 'visible',
|
|
horizontal: 'visible',
|
|
useShadows: false,
|
|
verticalHasArrows: false,
|
|
horizontalHasArrows: false,
|
|
verticalScrollbarSize: 10,
|
|
horizontalScrollbarSize: 10,
|
|
},
|
|
suggest: {
|
|
showKeywords: true,
|
|
showSnippets: true,
|
|
},
|
|
}
|
|
|
|
return (
|
|
<div className="h-full w-full relative">
|
|
<div className="absolute inset-0">
|
|
<Editor
|
|
height="100%"
|
|
defaultLanguage="sql"
|
|
value={value}
|
|
onChange={onChange}
|
|
onMount={handleEditorDidMount}
|
|
theme={mode === 'dark' ? 'vs-dark' : 'light'}
|
|
options={editorOptions}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
})
|
|
|
|
SqlEditor.displayName = 'SqlEditor'
|
|
|
|
export default SqlEditor
|