--- title: Editors sidebar_position: 2 --- # Editor Contributions Extensions can replace Superset's default text editors with custom implementations. This allows you to provide enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. ## Overview Superset uses text editors in various places throughout the application: | Language | Locations | |----------|-----------| | `sql` | SQL Lab, Metric/Filter Popovers | | `json` | Dashboard Properties, Annotation Modal, Theme Modal | | `css` | Dashboard Properties, CSS Template Modal | | `markdown` | Dashboard Markdown component | | `yaml` | Template Params Editor | | `javascript` | Custom JavaScript editor contexts | | `python` | Custom Python editor contexts | | `text` | Plain text editor contexts | By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language. ## Implementing an Editor Your editor component must implement the `EditorProps` interface and expose an `EditorHandle` via `forwardRef`. For the complete interface definitions, see `@apache-superset/core/api/editors.ts`. ### Key EditorProps ```typescript interface EditorProps { /** Controlled value */ value: string; /** Content change handler */ onChange: (value: string) => void; /** Language mode for syntax highlighting */ language: EditorLanguage; /** Keyboard shortcuts to register */ hotkeys?: EditorHotkey[]; /** Callback when editor is ready with imperative handle */ onReady?: (handle: EditorHandle) => void; /** Host-specific context (e.g., database info from SQL Lab) */ metadata?: Record; // ... additional props for styling, annotations, etc. } ``` ### Key EditorHandle Methods ```typescript interface EditorHandle { /** Focus the editor */ focus(): void; /** Get the current editor content */ getValue(): string; /** Get the current cursor position */ getCursorPosition(): Position; /** Move the cursor to a specific position */ moveCursorToPosition(position: Position): void; /** Set the selection range */ setSelection(selection: Range): void; /** Scroll to a specific line */ scrollToLine(line: number): void; // ... additional methods for text manipulation, annotations, etc. } ``` ## Example Implementation Here's an example of a Monaco-based SQL editor implementing the key interfaces shown above: ### MonacoSQLEditor.tsx ```typescript import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react'; import * as monaco from 'monaco-editor'; import type { editors } from '@apache-superset/core'; const MonacoSQLEditor = forwardRef( (props, ref) => { const { value, onChange, hotkeys, onReady } = props; const containerRef = useRef(null); const editorRef = useRef(null); // Implement EditorHandle interface const handle: editors.EditorHandle = { focus: () => editorRef.current?.focus(), getValue: () => editorRef.current?.getValue() ?? '', getCursorPosition: () => { const pos = editorRef.current?.getPosition(); return { line: (pos?.lineNumber ?? 1) - 1, column: (pos?.column ?? 1) - 1 }; }, // ... implement remaining methods }; useImperativeHandle(ref, () => handle, []); useEffect(() => { if (!containerRef.current) return; const editor = monaco.editor.create(containerRef.current, { value, language: 'sql' }); editorRef.current = editor; editor.onDidChangeModelContent(() => onChange(editor.getValue())); // Register hotkeys hotkeys?.forEach(hotkey => { editor.addAction({ id: hotkey.name, label: hotkey.name, run: () => hotkey.exec(handle), }); }); onReady?.(handle); return () => editor.dispose(); }, []); return
; }, ); export default MonacoSQLEditor; ``` ### index.tsx Register the editor at module load time from your extension's entry point: ```typescript import { editors } from '@apache-superset/core'; import MonacoSQLEditor from './MonacoSQLEditor'; editors.registerEditor( { id: 'my-extension.monaco-sql', name: 'Monaco SQL Editor', languages: ['sql'], }, MonacoSQLEditor, ); ``` ## Handling Hotkeys Superset passes keyboard shortcuts via the `hotkeys` prop. Each hotkey includes an `exec` function that receives the `EditorHandle`: ```typescript interface EditorHotkey { name: string; key: string; // e.g., "Ctrl-Enter", "Alt-Shift-F" description?: string; exec: (handle: EditorHandle) => void; } ``` Your editor must register these hotkeys with your editor framework and call `exec(handle)` when triggered. ## Keywords Superset passes static autocomplete suggestions via the `keywords` prop. These include table names, column names, and SQL functions based on the current database context: ```typescript interface EditorKeyword { name: string; value?: string; // Text to insert (defaults to name) meta?: string; // Category like "table", "column", "function" score?: number; // Sorting priority } ``` Your editor should convert these to your framework's completion format and register them for autocomplete. ## Completion Providers For dynamic autocomplete (e.g., fetching suggestions as the user types), implement and register a `CompletionProvider` via the `EditorHandle`: ```typescript const provider: CompletionProvider = { id: 'my-sql-completions', triggerCharacters: ['.', ' '], provideCompletions: async (content, position, context) => { // Use context.metadata for database info // Return array of CompletionItem return [ { label: 'SELECT', insertText: 'SELECT', kind: 'keyword' }, // ... ]; }, }; // Register during editor initialization const disposable = handle.registerCompletionProvider(provider); ``` ## Next Steps - **[SQL Lab Extension Points](./sqllab)** - Learn about other SQL Lab customizations - **[Contribution Types](../contribution-types)** - Explore other contribution types - **[Development](../development)** - Set up your development environment