Skip to content

Commit d5a3d54

Browse files
authored
feat: sql selection (#217)
1 parent 5ac23f2 commit d5a3d54

File tree

3 files changed

+30
-8
lines changed

3 files changed

+30
-8
lines changed

frontend/src/components/tool/SqlEditor.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
22
import { EditorState, Prec } from '@codemirror/state';
33
import { EditorView, keymap, placeholder as placeholderExt } from '@codemirror/view';
44
import { sql } from '@codemirror/lang-sql';
55
import { defaultKeymap } from '@codemirror/commands';
66
import { basicSetup } from 'codemirror';
77

8+
export interface SqlEditorHandle {
9+
getSelectedSql: () => string;
10+
}
11+
812
interface SqlEditorProps {
913
value: string;
1014
onChange?: (value: string) => void;
@@ -14,14 +18,14 @@ interface SqlEditorProps {
1418
placeholder?: string;
1519
}
1620

17-
export function SqlEditor({
21+
export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(function SqlEditor({
1822
value,
1923
onChange,
2024
onRunShortcut,
2125
disabled = false,
2226
readOnly = false,
2327
placeholder = 'Enter SQL statement...',
24-
}: SqlEditorProps) {
28+
}, ref) {
2529
const containerRef = useRef<HTMLDivElement>(null);
2630
const viewRef = useRef<EditorView | null>(null);
2731
const onRunShortcutRef = useRef(onRunShortcut);
@@ -33,6 +37,18 @@ export function SqlEditor({
3337
disabledRef.current = disabled;
3438
}, [onRunShortcut, disabled]);
3539

40+
// Expose method to get selected SQL (or full content if no selection)
41+
useImperativeHandle(ref, () => ({
42+
getSelectedSql: () => {
43+
const view = viewRef.current;
44+
if (!view) return '';
45+
const { from, to } = view.state.selection.main;
46+
return from !== to
47+
? view.state.sliceDoc(from, to)
48+
: view.state.doc.toString();
49+
},
50+
}), []);
51+
3652
useEffect(() => {
3753
if (!containerRef.current) return;
3854

@@ -130,4 +146,4 @@ export function SqlEditor({
130146
className="border border-border rounded-lg bg-background overflow-hidden"
131147
/>
132148
);
133-
}
149+
});

frontend/src/components/tool/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { SqlEditor } from './SqlEditor';
2+
export type { SqlEditorHandle } from './SqlEditor';
23
export { ParameterForm } from './ParameterForm';
34
export { RunButton } from './RunButton';
45
export { ResultsTable } from './ResultsTable';

frontend/src/components/views/ToolDetailView.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { useEffect, useState, useCallback } from 'react';
1+
import { useEffect, useState, useCallback, useRef } from 'react';
22
import { useParams, Navigate, useSearchParams } from 'react-router-dom';
33
import { fetchSource } from '../../api/sources';
44
import { executeTool, type QueryResult } from '../../api/tools';
55
import { ApiError } from '../../api/errors';
66
import type { Tool } from '../../types/datasource';
7-
import { SqlEditor, ParameterForm, RunButton, ResultsTabs, type ResultTab } from '../tool';
7+
import { SqlEditor, ParameterForm, RunButton, ResultsTabs, type ResultTab, type SqlEditorHandle } from '../tool';
88
import LockIcon from '../icons/LockIcon';
99
import CopyIcon from '../icons/CopyIcon';
1010
import CheckIcon from '../icons/CheckIcon';
@@ -16,6 +16,9 @@ export default function ToolDetailView() {
1616
const [isLoading, setIsLoading] = useState(true);
1717
const [error, setError] = useState<ApiError | null>(null);
1818

19+
// Ref to access SqlEditor's selection
20+
const sqlEditorRef = useRef<SqlEditorHandle>(null);
21+
1922
// Query state
2023
const [sql, setSql] = useState(() => {
2124
// Only for execute_sql tools - read from URL on mount
@@ -210,8 +213,9 @@ export default function ToolDetailView() {
210213
let sqlToExecute: string;
211214

212215
if (toolType === 'execute_sql') {
213-
sqlToExecute = sql;
214-
queryResult = await executeTool(toolName, { sql });
216+
// Get selected SQL from editor (returns selection if any, otherwise full content)
217+
sqlToExecute = sqlEditorRef.current?.getSelectedSql() ?? sql;
218+
queryResult = await executeTool(toolName, { sql: sqlToExecute });
215219
} else {
216220
sqlToExecute = getSqlPreview();
217221
queryResult = await executeTool(toolName, params);
@@ -386,6 +390,7 @@ export default function ToolDetailView() {
386390
</button>
387391
</div>
388392
<SqlEditor
393+
ref={sqlEditorRef}
389394
value={toolType === 'execute_sql' ? sql : getSqlPreview()}
390395
onChange={toolType === 'execute_sql' ? setSql : undefined}
391396
onRunShortcut={handleRun}

0 commit comments

Comments
 (0)