You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2026/03/13 12:07:03 UTC

(superset) branch master updated: fix(editor): implement missing methods, fix cursor position clearing (#38603)

This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 18673369076 fix(editor): implement missing methods, fix cursor position clearing (#38603)
18673369076 is described below

commit 186733690761b16d4856e0a7fca09c6f5204b758
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Fri Mar 13 09:06:55 2026 -0300

    fix(editor): implement missing methods, fix cursor position clearing (#38603)
---
 .../packages/superset-core/src/editors/index.ts    | 43 ++++++++++++++++++++++
 .../packages/superset-core/src/sqlLab/index.ts     | 16 ++++++++
 .../src/core/editors/AceEditorProvider.tsx         | 41 +++++++++++++++++++--
 superset-frontend/src/core/sqlLab/index.ts         |  7 ++++
 4 files changed, 103 insertions(+), 4 deletions(-)

diff --git a/superset-frontend/packages/superset-core/src/editors/index.ts b/superset-frontend/packages/superset-core/src/editors/index.ts
index a1ed3ed9653..0fea7c7fe71 100644
--- a/superset-frontend/packages/superset-core/src/editors/index.ts
+++ b/superset-frontend/packages/superset-core/src/editors/index.ts
@@ -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>;
 }
 
 /**
diff --git a/superset-frontend/packages/superset-core/src/sqlLab/index.ts b/superset-frontend/packages/superset-core/src/sqlLab/index.ts
index cae3bc2d4e5..844c25f9ea6 100644
--- a/superset-frontend/packages/superset-core/src/sqlLab/index.ts
+++ b/superset-frontend/packages/superset-core/src/sqlLab/index.ts
@@ -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.
  *
diff --git a/superset-frontend/src/core/editors/AceEditorProvider.tsx b/superset-frontend/src/core/editors/AceEditorProvider.tsx
index abdf76d14f1..3deb345a217 100644
--- a/superset-frontend/src/core/editors/AceEditorProvider.tsx
+++ b/superset-frontend/src/core/editors/AceEditorProvider.tsx
@@ -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);
+    });
+  },
 });
 
 /**
diff --git a/superset-frontend/src/core/sqlLab/index.ts b/superset-frontend/src/core/sqlLab/index.ts
index 8e16a41613a..b14be7efd07 100644
--- a/superset-frontend/src/core/sqlLab/index.ts
+++ b/superset-frontend/src/core/sqlLab/index.ts
@@ -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