diff --git a/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx b/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx index 6de7d15..6d1a61e 100644 --- a/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx +++ b/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx @@ -50,6 +50,9 @@ vi.mock('./MonacoEditor', () => ({ data-editor="true" data-readonly={String(options?.readOnly)} data-sticky-scroll-enabled={String(options?.stickyScroll?.enabled)} + data-font-size={String(options?.fontSize)} + data-line-height={String(options?.lineHeight)} + data-line-numbers-min-chars={String(options?.lineNumbersMinChars)} > {value} @@ -128,6 +131,9 @@ describe('Object definition viewers Monaco options', () => { const editor = renderer.root.findByProps({ 'data-editor': 'true' }); expect(editor.props['data-sticky-scroll-enabled']).toBe('false'); + expect(editor.props['data-font-size']).toBe('14'); + expect(editor.props['data-line-height']).toBe('24'); + expect(editor.props['data-line-numbers-min-chars']).toBe('4'); }); it('disables sticky scroll for read-only trigger definitions', async () => { diff --git a/frontend/src/components/QueryEditor.results-and-drop.test.tsx b/frontend/src/components/QueryEditor.results-and-drop.test.tsx index 9f22905..144a24b 100644 --- a/frontend/src/components/QueryEditor.results-and-drop.test.tsx +++ b/frontend/src/components/QueryEditor.results-and-drop.test.tsx @@ -151,6 +151,7 @@ const editorState = vi.hoisted(() => { decorationIds: [] as string[], contentHoverCalls: [] as any[], latestOnChange: null as null | ((value?: string) => void), + latestOptions: null as any, }; const offsetAt = (position: { lineNumber: number; column: number }) => { const text = state.value; @@ -295,10 +296,11 @@ vi.mock('../utils/autoFetchVisibility', () => ({ })); vi.mock('@monaco-editor/react', () => ({ - default: ({ defaultValue, onChange, onMount }: any) => { + default: ({ defaultValue, onChange, onMount, options }: any) => { React.useEffect(() => { editorState.value = String(defaultValue || ''); editorState.latestOnChange = onChange; + editorState.latestOptions = options ?? null; onMount?.(editorState.editor, { editor: { setTheme: vi.fn() }, KeyMod: { CtrlCmd: 2048, WinCtrl: 256, Alt: 512, Shift: 1024 }, @@ -706,6 +708,7 @@ describe('QueryEditor external SQL save', () => { editorState.decorationIds = []; editorState.contentHoverCalls = []; editorState.latestOnChange = null; + editorState.latestOptions = null; editorState.editor.getValue.mockClear(); editorState.editor.getModel().getValue.mockClear(); editorState.editor.getModel().getValueLength.mockClear(); @@ -840,6 +843,48 @@ describe('QueryEditor external SQL save', () => { renderer?.unmount(); }); + it('disables sticky scroll for object-edit query tabs', async () => { + storeState.connections[0].config.type = 'oracle'; + storeState.connections[0].config.database = 'ORCLPDB1'; + const plsql = [ + 'CREATE OR REPLACE PROCEDURE cproc_demo AS', + 'BEGIN', + ' NULL;', + 'END cproc_demo;', + '/;', + ].join('\n'); + + let renderer!: ReactTestRenderer; + await act(async () => { + renderer = create(); + }); + + expect(editorState.latestOptions?.stickyScroll?.enabled).toBe(false); + expect(editorState.latestOptions?.fontSize).toBe(14); + expect(editorState.latestOptions?.lineHeight).toBe(24); + expect(editorState.latestOptions?.lineNumbersMinChars).toBe(4); + expect(editorState.editor.updateOptions).toHaveBeenCalledWith(expect.objectContaining({ + fontSize: 14, + lineHeight: 24, + lineNumbersMinChars: 4, + stickyScroll: { enabled: false }, + })); + renderer?.unmount(); + }); + + it('keeps standard query tabs on the default sticky scroll behavior', async () => { + let renderer!: ReactTestRenderer; + await act(async () => { + renderer = create(); + }); + + expect(editorState.latestOptions?.stickyScroll).toBeUndefined(); + expect(editorState.latestOptions?.fontSize).toBeUndefined(); + expect(editorState.latestOptions?.lineHeight).toBeUndefined(); + expect(editorState.editor.updateOptions.mock.calls[0]?.[0]?.stickyScroll).toBeUndefined(); + renderer?.unmount(); + }); + it('runs the preceding Oracle procedure when the cursor is on the SQLPlus slash delimiter', async () => { storeState.connections[0].config.type = 'oracle'; storeState.connections[0].config.database = 'ORCLPDB1'; diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index f874b63..a963538 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -154,6 +154,29 @@ const QUERY_EDITOR_MONACO_FIND_OPTIONS = { } as const; const QUERY_EDITOR_NATIVE_SELECT_CURRENT_LINE_EVENT = 'gonavi:native-select-current-line'; +const buildQueryEditorMonacoOptions = (isObjectEditQueryTab: boolean) => ({ + minimap: { enabled: false }, + automaticLayout: true, + fixedOverflowWidgets: true, + find: QUERY_EDITOR_MONACO_FIND_OPTIONS, + hover: { + enabled: true, + delay: QUERY_EDITOR_HOVER_DELAY_MS, + above: false, + }, + scrollBeyondLastLine: false, + quickSuggestions: { other: true, comments: false, strings: false }, + suggestOnTriggerCharacters: true, + ...(isObjectEditQueryTab + ? { + fontSize: 14, + lineHeight: 24, + lineNumbersMinChars: 4, + stickyScroll: { enabled: false }, + } + : {}), +}); + const QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER = '{SQL}'; const escapeQueryEditorObjectEditSqlLiteral = (value: unknown): string => ( @@ -815,6 +838,10 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc )); const isExternalSQLFileTab = Boolean(String(tab.filePath || '').trim()); const isObjectEditQueryTab = tab.type === 'query' && tab.queryMode === 'object-edit'; + const queryEditorMonacoOptions = useMemo( + () => buildQueryEditorMonacoOptions(isObjectEditQueryTab), + [isObjectEditQueryTab], + ); type ResultSet = QueryEditorResultSet; @@ -2616,15 +2643,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc } lastEditorCursorPositionRef.current = normalizeEditorPosition(editor.getPosition?.()); - editor.updateOptions?.({ - fixedOverflowWidgets: true, - find: QUERY_EDITOR_MONACO_FIND_OPTIONS, - hover: { - enabled: true, - delay: QUERY_EDITOR_HOVER_DELAY_MS, - above: false, - }, - }); + editor.updateOptions?.(buildQueryEditorMonacoOptions(isObjectEditQueryTab)); const applyNavigationHoverStateAtPosition = (targetPosition: { lineNumber: number; column: number } | null) => { if (!ctrlMetaPressedRef.current) { @@ -6258,20 +6277,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc syncQueryDraft(nextValue); }} onMount={handleEditorDidMount} - options={{ - minimap: { enabled: false }, - automaticLayout: true, - fixedOverflowWidgets: true, - find: QUERY_EDITOR_MONACO_FIND_OPTIONS, - hover: { - enabled: true, - delay: QUERY_EDITOR_HOVER_DELAY_MS, - above: false, - }, - scrollBeyondLastLine: false, - quickSuggestions: { other: true, comments: false, strings: false }, - suggestOnTriggerCharacters: true, - }} + options={queryEditorMonacoOptions} />