🐛 fix(query-editor): 限制 macOS 搜索仅响应 Cmd+F

- 拦截 Monaco 内置 Cmd+E find-with-selection 行为
- 保留默认 Cmd+E 当前语句选择逻辑,禁用或改键时不触发搜索
- 增加 SQL 编辑器快捷键回归测试并加固 Monaco 测试包装
This commit is contained in:
Syngnat
2026-07-08 20:30:31 +08:00
parent dca1e8a251
commit f74ad02b9d
3 changed files with 136 additions and 12 deletions

View File

@@ -168,7 +168,8 @@ const installOceanBaseOracleNavigationFallback = (editor: any) => {
};
const patchQueryEditorAiInlineRightArrowFallback = (editor: any, monaco: any) => {
const originalAddCommand = editor?.addCommand?.bind?.(editor);
const rawAddCommand = editor?.addCommand;
const originalAddCommand = rawAddCommand?.bind?.(editor);
if (!originalAddCommand || !monaco?.KeyCode?.RightArrow) {
return;
}
@@ -180,7 +181,7 @@ const patchQueryEditorAiInlineRightArrowFallback = (editor: any, monaco: any) =>
configurable: true,
});
editor.addCommand = (keybinding: any, handler: any, context: any) => {
const patchedAddCommand = (keybinding: any, handler: any, context: any) => {
if (
keybinding === monaco.KeyCode.RightArrow
&& context === QUERY_EDITOR_AI_INLINE_CONTEXT_KEY
@@ -200,6 +201,32 @@ const patchQueryEditorAiInlineRightArrowFallback = (editor: any, monaco: any) =>
}
return originalAddCommand(keybinding, handler, context);
};
if (rawAddCommand?.mock) {
for (const propertyName of [
'mock',
'mockClear',
'mockReset',
'mockRestore',
'mockImplementation',
'mockImplementationOnce',
'mockName',
'getMockName',
]) {
if (!(propertyName in rawAddCommand)) {
continue;
}
Object.defineProperty(patchedAddCommand, propertyName, {
configurable: true,
get: () => {
const value = rawAddCommand[propertyName];
return typeof value === 'function' ? value.bind(rawAddCommand) : value;
},
});
}
}
editor.addCommand = patchedAddCommand;
};
const installPrintableInputFallback = (editor: any, monaco: any) => {
@@ -207,8 +234,9 @@ const installPrintableInputFallback = (editor: any, monaco: any) => {
if (!editorDomNode || editor.__gonaviPrintableInputFallbackInstalled) {
return;
}
const TextAreaElement = typeof HTMLTextAreaElement === 'undefined' ? null : HTMLTextAreaElement;
const input = editorDomNode.querySelector?.('textarea.inputarea, .inputarea textarea, textarea') as HTMLTextAreaElement | null;
if (!(input instanceof HTMLTextAreaElement)) {
if (!TextAreaElement || !(input instanceof TextAreaElement)) {
return;
}
Object.defineProperty(editor, '__gonaviPrintableInputFallbackInstalled', {

View File

@@ -4481,6 +4481,39 @@ describe('QueryEditor external SQL save', () => {
).not.toContain('gonavi:find-active-query');
});
it('keeps SQL editor search on Cmd+F only and suppresses Monaco Cmd+E find-with-selection', async () => {
storeState.shortcutOptions.selectCurrentStatement.mac = { enabled: false, combo: '' };
storeState.shortcutOptions.selectCurrentStatement.windows = { enabled: false, combo: '' };
await act(async () => {
create(<QueryEditor tab={createTab({
query: 'SELECT 1;\nSELECT 2 AS two;\nSELECT 3;',
readOnly: true,
})} />);
});
(window.dispatchEvent as any).mockClear();
(document.execCommand as any).mockClear();
expect(findEditorAction('gonavi.findInEditor')).toMatchObject({
keybindings: [2048 | 70],
});
const suppressMacFindAction = findEditorAction('gonavi.suppressMacFindWithSelection');
expect(suppressMacFindAction).toMatchObject({
keybindings: [2048 | 69],
});
await act(async () => {
suppressMacFindAction.run();
await Promise.resolve();
});
expect(
(window.dispatchEvent as any).mock.calls.map((call: any[]) => call[0]?.type),
).not.toContain('gonavi:find-active-query');
expect(document.execCommand).not.toHaveBeenCalled();
});
it('intercepts Ctrl/Cmd+D at window level and duplicates the current line below', async () => {
storeState.shortcutOptions.duplicateCurrentLine.mac = { enabled: true, combo: 'Meta+D' };
storeState.shortcutOptions.duplicateCurrentLine.windows = { enabled: true, combo: 'Ctrl+D' };

View File

@@ -170,6 +170,8 @@ const QUERY_EDITOR_MONACO_FIND_OPTIONS = {
addExtraSpaceOnTop: true,
} as const;
const QUERY_EDITOR_NATIVE_SELECT_CURRENT_LINE_EVENT = 'gonavi:native-select-current-line';
const QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO = 'Meta+E';
const QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID = 'gonavi.suppressMacFindWithSelection';
const QUERY_EDITOR_AI_INLINE_DEBOUNCE_MS = 90;
const QUERY_EDITOR_AI_INLINE_CONTEXT_KEY = 'gonaviAiInlineSuggestionVisible';
@@ -1131,6 +1133,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const monacoRef = useRef<any>(null);
const runQueryActionRef = useRef<any>(null);
const selectCurrentStatementActionRef = useRef<any>(null);
const macFindWithSelectionGuardActionRef = useRef<any>(null);
const duplicateCurrentLineActionRef = useRef<any>(null);
const saveQueryActionRef = useRef<any>(null);
const findInEditorActionRef = useRef<any>(null);
@@ -4308,6 +4311,8 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
objectHoverActionRef.current = null;
triggerSqlAiCompletionActionRef.current?.dispose?.();
triggerSqlAiCompletionActionRef.current = null;
macFindWithSelectionGuardActionRef.current?.dispose?.();
macFindWithSelectionGuardActionRef.current = null;
triggerSqlAiCompletionKeydownDisposableRef.current?.dispose?.();
triggerSqlAiCompletionKeydownDisposableRef.current = null;
triggerAiInlineCompletionRef.current = null;
@@ -4369,6 +4374,30 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
}
}
const macFindWithSelectionGuardKeyBinding = activeShortcutPlatform === 'mac'
? comboToMonacoKeyBinding(
QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO, monaco.KeyMod, monaco.KeyCode,
)
: null;
if (macFindWithSelectionGuardKeyBinding) {
macFindWithSelectionGuardActionRef.current = editor.addAction({
id: QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID,
label: 'GoNavi: Suppress macOS Cmd+E Find with Selection',
keybindings: [
macFindWithSelectionGuardKeyBinding.keyMod
| macFindWithSelectionGuardKeyBinding.keyCode,
],
run: () => {
if (
selectStatementBinding?.enabled
&& normalizeShortcutCombo(selectStatementBinding.combo) === QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO
) {
void handleSelectCurrentStatement();
}
},
});
}
const duplicateLineBinding = duplicateCurrentLineShortcutBinding;
if (duplicateLineBinding?.enabled && duplicateLineBinding.combo) {
const keyBinding = comboToMonacoKeyBinding(
@@ -6734,21 +6763,51 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
selectCurrentStatementActionRef.current.dispose();
selectCurrentStatementActionRef.current = null;
}
if (macFindWithSelectionGuardActionRef.current) {
macFindWithSelectionGuardActionRef.current.dispose();
macFindWithSelectionGuardActionRef.current = null;
}
const editor = editorRef.current;
const monaco = monacoRef.current;
if (!editor || !monaco) return;
const binding = selectCurrentStatementShortcutBinding;
if (!binding?.enabled || !binding.combo) return;
if (binding?.enabled && binding.combo) {
const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
if (keyBinding) {
selectCurrentStatementActionRef.current = editor.addAction({
id: 'gonavi.selectCurrentStatement',
label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.selectCurrentStatement.label'),
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
run: handleSelectCurrentStatement,
});
}
}
const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
if (keyBinding) {
selectCurrentStatementActionRef.current = editor.addAction({
id: 'gonavi.selectCurrentStatement',
label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.selectCurrentStatement.label'),
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
run: handleSelectCurrentStatement,
const macFindWithSelectionGuardKeyBinding = activeShortcutPlatform === 'mac'
? comboToMonacoKeyBinding(
QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO,
monaco.KeyMod,
monaco.KeyCode,
)
: null;
if (macFindWithSelectionGuardKeyBinding) {
macFindWithSelectionGuardActionRef.current = editor.addAction({
id: QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID,
label: 'GoNavi: Suppress macOS Cmd+E Find with Selection',
keybindings: [
macFindWithSelectionGuardKeyBinding.keyMod
| macFindWithSelectionGuardKeyBinding.keyCode,
],
run: () => {
if (
binding?.enabled
&& normalizeShortcutCombo(binding.combo) === QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO
) {
void handleSelectCurrentStatement();
}
},
});
}
@@ -6757,8 +6816,12 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
selectCurrentStatementActionRef.current.dispose();
selectCurrentStatementActionRef.current = null;
}
if (macFindWithSelectionGuardActionRef.current) {
macFindWithSelectionGuardActionRef.current.dispose();
macFindWithSelectionGuardActionRef.current = null;
}
};
}, [languagePreference, selectCurrentStatementShortcutBinding, handleSelectCurrentStatement]);
}, [activeShortcutPlatform, languagePreference, selectCurrentStatementShortcutBinding, handleSelectCurrentStatement]);
useEffect(() => {
if (duplicateCurrentLineActionRef.current) {