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}
/>