Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions frontend/src/components/tool/SqlEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
import { EditorState, Prec } from '@codemirror/state';
import { EditorView, keymap, placeholder as placeholderExt } from '@codemirror/view';
import { sql } from '@codemirror/lang-sql';
import { defaultKeymap } from '@codemirror/commands';
import { basicSetup } from 'codemirror';

export interface SqlEditorHandle {
getSelectedSql: () => string;
}

interface SqlEditorProps {
value: string;
onChange?: (value: string) => void;
Expand All @@ -14,14 +18,14 @@ interface SqlEditorProps {
placeholder?: string;
}

export function SqlEditor({
export const SqlEditor = forwardRef<SqlEditorHandle, SqlEditorProps>(function SqlEditor({
value,
onChange,
onRunShortcut,
disabled = false,
readOnly = false,
placeholder = 'Enter SQL statement...',
}: SqlEditorProps) {
}, ref) {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
const onRunShortcutRef = useRef(onRunShortcut);
Expand All @@ -33,6 +37,18 @@ export function SqlEditor({
disabledRef.current = disabled;
}, [onRunShortcut, disabled]);

// Expose method to get selected SQL (or full content if no selection)
useImperativeHandle(ref, () => ({
getSelectedSql: () => {
const view = viewRef.current;
if (!view) return '';
const { from, to } = view.state.selection.main;
return from !== to
? view.state.sliceDoc(from, to)
: view.state.doc.toString();
},
}), []);

useEffect(() => {
if (!containerRef.current) return;

Expand Down Expand Up @@ -130,4 +146,4 @@ export function SqlEditor({
className="border border-border rounded-lg bg-background overflow-hidden"
/>
);
}
});
1 change: 1 addition & 0 deletions frontend/src/components/tool/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { SqlEditor } from './SqlEditor';
export type { SqlEditorHandle } from './SqlEditor';
export { ParameterForm } from './ParameterForm';
export { RunButton } from './RunButton';
export { ResultsTable } from './ResultsTable';
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/components/views/ToolDetailView.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useEffect, useState, useCallback } from 'react';
import { useEffect, useState, useCallback, useRef } from 'react';
import { useParams, Navigate, useSearchParams } from 'react-router-dom';
import { fetchSource } from '../../api/sources';
import { executeTool, type QueryResult } from '../../api/tools';
import { ApiError } from '../../api/errors';
import type { Tool } from '../../types/datasource';
import { SqlEditor, ParameterForm, RunButton, ResultsTabs, type ResultTab } from '../tool';
import { SqlEditor, ParameterForm, RunButton, ResultsTabs, type ResultTab, type SqlEditorHandle } from '../tool';
import LockIcon from '../icons/LockIcon';
import CopyIcon from '../icons/CopyIcon';
import CheckIcon from '../icons/CheckIcon';
Expand All @@ -16,6 +16,9 @@ export default function ToolDetailView() {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<ApiError | null>(null);

// Ref to access SqlEditor's selection
const sqlEditorRef = useRef<SqlEditorHandle>(null);

// Query state
const [sql, setSql] = useState(() => {
// Only for execute_sql tools - read from URL on mount
Expand Down Expand Up @@ -210,8 +213,9 @@ export default function ToolDetailView() {
let sqlToExecute: string;

if (toolType === 'execute_sql') {
sqlToExecute = sql;
queryResult = await executeTool(toolName, { sql });
// Get selected SQL from editor (returns selection if any, otherwise full content)
sqlToExecute = sqlEditorRef.current?.getSelectedSql() ?? sql;
queryResult = await executeTool(toolName, { sql: sqlToExecute });
} else {
sqlToExecute = getSqlPreview();
queryResult = await executeTool(toolName, params);
Expand Down Expand Up @@ -386,6 +390,7 @@ export default function ToolDetailView() {
</button>
</div>
<SqlEditor
ref={sqlEditorRef}
value={toolType === 'execute_sql' ? sql : getSqlPreview()}
onChange={toolType === 'execute_sql' ? setSql : undefined}
onRunShortcut={handleRun}
Expand Down