mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-03 12:18:48 +08:00
🐛 fix(query-editor): 修复 SQL 片段补全与插入交互
- 插入 SQL 片段后主动关闭片段弹窗,保持右键插入行为一致 - 提升 Monaco 补全详情区最小高度,放大片段说明展示区域 - 列补全解析改为基于当前语句引用上下文,修复当前语句前半段补全丢列问题 - 为 SQL 草稿持久化补齐 window 定时器兜底,避免测试环境 clearTimeout 缺失 - 更新 SQL 片段弹窗关闭和补全说明高度相关回归测试
This commit is contained in:
@@ -3642,7 +3642,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
})],
|
||||
);
|
||||
expect(editorState.value).toBe('SELECT id FROM user_table;');
|
||||
expect(renderer.root.findByProps({ 'data-query-editor-snippet-picker': 'true' })).toBeTruthy();
|
||||
expect(renderer.root.findAllByProps({ 'data-query-editor-snippet-picker': 'true' })).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('prefers Monaco snippet controller insertion when the controller is available', async () => {
|
||||
@@ -3695,7 +3695,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
);
|
||||
expect(editorState.editor.executeEdits).not.toHaveBeenCalled();
|
||||
expect(editorState.value).toBe('ALTER TABLE demo_table\nADD COLUMN user_name VARCHAR(255);');
|
||||
expect(renderer.root.findByProps({ 'data-query-editor-snippet-picker': 'true' })).toBeTruthy();
|
||||
expect(renderer.root.findAllByProps({ 'data-query-editor-snippet-picker': 'true' })).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps the SQL snippet picker modal non-mask-closable to avoid immediate close after context-menu click', () => {
|
||||
@@ -8892,6 +8892,24 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(css).not.toContain('body[data-ui-version="v2"] .gn-v2-query-monaco-stage .monaco-editor .find-widget {');
|
||||
});
|
||||
|
||||
it('raises QueryEditor suggest docs height for SQL snippet completion without widening global Monaco defaults', () => {
|
||||
const appCss = readFileSync(new URL('../App.css', import.meta.url), 'utf8');
|
||||
|
||||
expect(queryEditorSource).toContain('QUERY_EDITOR_SQL_SNIPPET_SUGGEST_DETAIL_MIN_HEIGHT = 260');
|
||||
expect(queryEditorSource).toContain("editor.getContribution?.('editor.contrib.suggestController')");
|
||||
expect(queryEditorSource).toContain('const originalSuggestDetailsLayout = suggestDetailsWidget.layout.bind(suggestDetailsWidget);');
|
||||
expect(queryEditorSource).toContain('suggestDetailsWidget.layout = (width: number, height: number) => {');
|
||||
expect(queryEditorSource).toContain('Math.max(height, QUERY_EDITOR_SQL_SNIPPET_SUGGEST_DETAIL_MIN_HEIGHT)');
|
||||
expect(queryEditorSource).toContain("className={isV2Ui ? 'gn-v2-query-monaco-stage gn-query-monaco-stage' : 'gn-query-monaco-stage'}");
|
||||
expect(appCss).toContain('.gn-query-monaco-stage .monaco-editor .suggest-details-container {');
|
||||
expect(appCss).toContain('min-height: 260px;');
|
||||
expect(appCss).toContain('.gn-query-monaco-stage .monaco-editor .suggest-details {');
|
||||
expect(appCss).toContain('min-height: 260px;');
|
||||
expect(appCss).not.toContain('.gn-query-monaco-stage .monaco-editor .suggest-widget {');
|
||||
expect(appCss).not.toContain('width: 680px;');
|
||||
expect(appCss).not.toContain('min-width: 560px;');
|
||||
});
|
||||
|
||||
it('keeps the v2 query editor toolbar grouped and compact', () => {
|
||||
const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
|
||||
const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');
|
||||
|
||||
@@ -742,6 +742,7 @@ const clearRecord = (record: Record<string, unknown>) => {
|
||||
delete record[key];
|
||||
});
|
||||
};
|
||||
const QUERY_EDITOR_SQL_SNIPPET_SUGGEST_DETAIL_MIN_HEIGHT = 260;
|
||||
|
||||
const buildSqlSnippetVariableMap = (now: Date): Record<string, string> => {
|
||||
const pad = (value: number) => String(value).padStart(2, '0');
|
||||
@@ -1197,6 +1198,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
if (typeof nextValue === 'string') {
|
||||
applyQueryState(nextValue);
|
||||
}
|
||||
handleCloseSqlSnippetPicker();
|
||||
editor.focus?.();
|
||||
return;
|
||||
}
|
||||
@@ -1246,8 +1248,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
if (typeof nextValue === 'string') {
|
||||
applyQueryState(nextValue);
|
||||
}
|
||||
handleCloseSqlSnippetPicker();
|
||||
editor.focus?.();
|
||||
}, [applyQueryState]);
|
||||
}, [applyQueryState, handleCloseSqlSnippetPicker]);
|
||||
|
||||
useEffect(() => {
|
||||
persistQueryTabDraftSnapshot(draftSnapshotTab, query, {
|
||||
@@ -2600,6 +2603,17 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
const handleEditorDidMount: OnMount = (editor, monaco) => {
|
||||
editorRef.current = editor;
|
||||
monacoRef.current = monaco;
|
||||
|
||||
const suggestController = editor.getContribution?.('editor.contrib.suggestController') as {
|
||||
widget?: { value?: { _details?: { widget?: { layout?: (width: number, height: number) => void } } } };
|
||||
} | null;
|
||||
const suggestDetailsWidget = suggestController?.widget?.value?._details?.widget;
|
||||
if (suggestDetailsWidget?.layout) {
|
||||
const originalSuggestDetailsLayout = suggestDetailsWidget.layout.bind(suggestDetailsWidget);
|
||||
suggestDetailsWidget.layout = (width: number, height: number) => {
|
||||
originalSuggestDetailsLayout(width, Math.max(height, QUERY_EDITOR_SQL_SNIPPET_SUGGEST_DETAIL_MIN_HEIGHT));
|
||||
};
|
||||
}
|
||||
lastEditorCursorPositionRef.current = normalizeEditorPosition(editor.getPosition?.());
|
||||
|
||||
editor.updateOptions?.({
|
||||
@@ -3415,6 +3429,8 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
? fullText.slice(currentStatementRange.start, cursorOffset)
|
||||
: fullText.slice(0, cursorOffset);
|
||||
const completionScopeText = currentStatementPrefix || linePrefix;
|
||||
const currentStatementText = currentStatementRange?.text || '';
|
||||
const completionReferenceText = currentStatementText || completionScopeText;
|
||||
|
||||
// 0) 三段式 db.table.column 格式:当输入 db.table. 时提示列
|
||||
const threePartMatch = linePrefix.match(QUERY_EDITOR_SQL_THREE_PART_COMPLETION_REGEX);
|
||||
@@ -3552,7 +3568,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
}
|
||||
|
||||
// 否则检查是否是表别名或表名,提示列
|
||||
const aliasMap = buildQueryEditorAliasMap(completionScopeText, sharedCurrentDb || '');
|
||||
const aliasMap = buildQueryEditorAliasMap(completionReferenceText, sharedCurrentDb || '');
|
||||
|
||||
const tableInfo = aliasMap[qualifier.toLowerCase()];
|
||||
if (tableInfo) {
|
||||
@@ -3586,7 +3602,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
tableRegex.lastIndex = 0;
|
||||
const foundTables = new Set<string>();
|
||||
let match;
|
||||
while ((match = tableRegex.exec(completionScopeText)) !== null) {
|
||||
while ((match = tableRegex.exec(completionReferenceText)) !== null) {
|
||||
const t = normalizeQualifiedName(match[1] || '');
|
||||
if (!t) continue;
|
||||
// 存储完整标识 db.table 或 table
|
||||
@@ -3685,7 +3701,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
|
||||
const referencedColumns: CompletionColumnMeta[] = [];
|
||||
if (!expectsTableName) {
|
||||
const aliasMapForReferencedTables = buildQueryEditorAliasMap(completionScopeText, currentDatabase);
|
||||
const aliasMapForReferencedTables = buildQueryEditorAliasMap(completionReferenceText, currentDatabase);
|
||||
const seenReferencedTables = new Set<string>();
|
||||
for (const tableInfo of Object.values(aliasMapForReferencedTables)) {
|
||||
const key = `${String(tableInfo.dbName || '').toLowerCase()}.${String(tableInfo.tableName || '').toLowerCase()}`;
|
||||
@@ -6223,7 +6239,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
|
||||
<div
|
||||
ref={editorStageRef}
|
||||
className={isV2Ui ? 'gn-v2-query-monaco-stage' : undefined}
|
||||
className={isV2Ui ? 'gn-v2-query-monaco-stage gn-query-monaco-stage' : 'gn-query-monaco-stage'}
|
||||
style={resolvedQueryEditorStageStyle}
|
||||
>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user