fix(editor): implement missing methods, fix cursor position clearing (#38603)

This commit is contained in:
Michael S. Molina
2026-03-13 09:06:55 -03:00
committed by GitHub
parent f5383263bc
commit 1867336907
4 changed files with 103 additions and 4 deletions

View File

@@ -369,6 +369,28 @@ export interface EditorProps {
theme?: SupersetTheme;
}
/**
* A single text change expressed as an offset-based replacement.
*/
export interface ContentChange {
/** Character offset in the document where the replaced range starts */
rangeOffset: number;
/** Length in characters of the replaced range (0 for pure insertions) */
rangeLength: number;
/** Text inserted at rangeOffset (empty string for pure deletions) */
text: string;
}
/**
* Payload delivered to `onDidChangeContent` listeners.
*/
export interface ContentChangeEvent {
/** Returns the full current content of the editor */
getValue(): string;
/** The individual changes that occurred in this event */
changes: ReadonlyArray<ContentChange>;
}
/**
* Imperative API for controlling the editor programmatically.
*
@@ -492,6 +514,27 @@ export interface EditorHandle {
* - CodeMirror: editor.requestMeasure()
*/
resize(): void;
/**
* Subscribe to content changes in the editor.
*
* The listener receives a {@link ContentChangeEvent} with:
* - `getValue()` — lazy accessor for the full content (call only when needed
* to avoid unnecessary O(n) string allocation on every keystroke)
* - `changes` — the individual edits that occurred, as offset-based replacements
*
* @param listener Called with a ContentChangeEvent on every change
* @param thisArgs Optional `this` context for the listener
* @returns A Disposable that unsubscribes the listener when disposed
*
* @example
* const disposable = editor.onDidChangeContent(e => {
* setStatements(parseStatements(e.getValue()));
* });
* // Later, to unsubscribe:
* disposable.dispose();
*/
onDidChangeContent: Event<ContentChangeEvent>;
}
/**

View File

@@ -252,6 +252,22 @@ export interface QueryResult {
*/
export declare const getActivePanel: () => Panel;
/**
* Switches the active panel in the SQL Lab south pane.
* Built-in panel IDs are 'Results' and 'History'.
* Pinned table panels use the table's ID as their panel ID.
*
* @param panelId The ID of the panel to activate
* @returns Promise that resolves when the panel is activated
*
* @example
* ```typescript
* // Focus the Results panel after running a query
* await setActivePanel('Results');
* ```
*/
export declare function setActivePanel(panelId: string): Promise<void>;
/**
* Gets the currently active tab in SQL Lab.
*

View File

@@ -55,6 +55,8 @@ type Range = editors.Range;
type Selection = editors.Selection;
type EditorAnnotation = editors.EditorAnnotation;
type CompletionProvider = editors.CompletionProvider;
type ContentChange = editors.ContentChange;
type ContentChangeEvent = editors.ContentChangeEvent;
/**
* Maps EditorLanguage to the corresponding Ace editor component.
@@ -117,10 +119,14 @@ const createAceEditorHandle = (
},
moveCursorToPosition: (position: Position) => {
aceEditorRef.current?.editor?.moveCursorToPosition({
row: position.line,
column: position.column,
});
const editor = aceEditorRef.current?.editor;
if (editor) {
editor.clearSelection();
editor.moveCursorToPosition({
row: position.line,
column: position.column,
});
}
},
getSelections: (): Selection[] => {
@@ -186,6 +192,33 @@ const createAceEditorHandle = (
resize: () => {
aceEditorRef.current?.editor?.resize();
},
onDidChangeContent: (listener, thisArgs?) => {
const editor = aceEditorRef.current?.editor;
if (!editor) return new Disposable(() => {});
const bound = (thisArgs ? listener.bind(thisArgs) : listener) as (
e: ContentChangeEvent,
) => void;
const handler = (delta: {
action: 'insert' | 'remove';
start: { row: number; column: number };
lines: string[];
}) => {
const rangeOffset = editor.session.doc.positionToIndex(delta.start);
const changeText = delta.lines.join(
editor.session.doc.getNewLineCharacter(),
);
const change: ContentChange =
delta.action === 'insert'
? { rangeOffset, rangeLength: 0, text: changeText }
: { rangeOffset, rangeLength: changeText.length, text: '' };
bound({ getValue: () => editor.getValue(), changes: [change] });
};
editor.session.on('change', handler);
return new Disposable(() => {
editor.session.off('change', handler);
});
},
});
/**

View File

@@ -694,6 +694,12 @@ const setSchema: typeof sqlLabApi.setSchema = async (schema: string | null) => {
store.dispatch(queryEditorSetSchema(queryEditor ?? null, schema));
};
const setActivePanel: typeof sqlLabApi.setActivePanel = async (
panelId: string,
) => {
store.dispatch({ type: SET_ACTIVE_SOUTHPANE_TAB, tabId: panelId });
};
export const sqlLab: typeof sqlLabApi = {
CTASMethod,
getActivePanel,
@@ -719,6 +725,7 @@ export const sqlLab: typeof sqlLabApi = {
setDatabase,
setCatalog,
setSchema,
setActivePanel,
};
// Export all models