mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(editor): fold sql lists and execute selection from gutter
This commit is contained in:
@@ -2212,9 +2212,15 @@ function executeSqlStatementFromGutter(currentView: EditorViewType, line: { from
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Gutter play is always scoped to the statement/command for that line, even
|
||||
// when the main editor execute action would run the full document.
|
||||
// when the main editor execute action would run the full document. An explicit
|
||||
// selection overlapping that statement is more specific, so preserve it; a
|
||||
// selection elsewhere in the document must not hijack the click.
|
||||
const editorViewportRequestId = executionViewportOwnership.beginRequest();
|
||||
emitExecutionRequest({ ...sqlExecutionSnapshotForRange(currentView, statementRange), editorViewportRequestId });
|
||||
const selection = currentView.state.selection.main;
|
||||
const hasSelectedSql = !selection.empty && currentView.state.sliceDoc(selection.from, selection.to).trim().length > 0;
|
||||
const selectionOverlapsStatement = hasSelectedSql && selection.from < statementRange.to && statementRange.from < selection.to;
|
||||
const executionSnapshot = selectionOverlapsStatement ? sqlExecutionSnapshotFromView(currentView) : sqlExecutionSnapshotForRange(currentView, statementRange);
|
||||
emitExecutionRequest({ ...executionSnapshot, editorViewportRequestId });
|
||||
// 不主动聚焦编辑器,否则 CodeMirror 会把屏幕滚回之前的光标位置。
|
||||
// currentView.focus();
|
||||
return true;
|
||||
|
||||
@@ -32,7 +32,10 @@ describe("QueryEditor execution routing", () => {
|
||||
expect(queryEditorSource).toContain("function executeFromContextMenu()");
|
||||
expect(queryEditorSource).toContain("requestExecute();\n focusEditor();");
|
||||
expect(queryEditorSource).toContain("function executeSqlStatementFromGutter");
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest({ ...sqlExecutionSnapshotForRange(currentView, statementRange), editorViewportRequestId })");
|
||||
expect(queryEditorSource).toContain("const hasSelectedSql = !selection.empty && currentView.state.sliceDoc(selection.from, selection.to).trim().length > 0;");
|
||||
expect(queryEditorSource).toContain("const selectionOverlapsStatement = hasSelectedSql && selection.from < statementRange.to && statementRange.from < selection.to;");
|
||||
expect(queryEditorSource).toContain("const executionSnapshot = selectionOverlapsStatement ? sqlExecutionSnapshotFromView(currentView) : sqlExecutionSnapshotForRange(currentView, statementRange);");
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest({ ...executionSnapshot, editorViewportRequestId })");
|
||||
});
|
||||
|
||||
it("routes the new-result-tab shortcut through the same target selection contract", () => {
|
||||
@@ -97,7 +100,9 @@ describe("QueryEditor execution routing", () => {
|
||||
|
||||
it("preserves the source range when executing from the statement gutter", () => {
|
||||
expect(queryEditorSource).toContain("const editorViewportRequestId = executionViewportOwnership.beginRequest()");
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest({ ...sqlExecutionSnapshotForRange(currentView, statementRange), editorViewportRequestId })");
|
||||
expect(queryEditorSource).toContain("sqlExecutionSnapshotFromView(currentView)");
|
||||
expect(queryEditorSource).toContain("sqlExecutionSnapshotForRange(currentView, statementRange)");
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest({ ...executionSnapshot, editorViewportRequestId })");
|
||||
expect(queryEditorSource).not.toContain('emit("execute", statementRange.sql)');
|
||||
});
|
||||
|
||||
|
||||
@@ -221,6 +221,20 @@ LEFT JOIN (
|
||||
expect(foldedTextAtLine(state, 3)).toBe("\n SELECT account_id, SUM(amount) AS total\n FROM fee\n GROUP BY account_id\n");
|
||||
});
|
||||
|
||||
it("folds multiline parenthesized IN lists while keeping the surrounding SQL visible", () => {
|
||||
const sql = `SELECT *
|
||||
FROM account
|
||||
WHERE account_id IN (
|
||||
'7687402546616451098',
|
||||
'7687402818521628718',
|
||||
'7687402206655496235'
|
||||
)
|
||||
AND status = 'active';`;
|
||||
const state = stateFor(sql);
|
||||
|
||||
expect(foldedTextAtLine(state, 3)).toBe("\n '7687402546616451098',\n '7687402818521628718',\n '7687402206655496235'\n");
|
||||
});
|
||||
|
||||
it("folds SELECT branches around UNION ALL at the same query level", () => {
|
||||
const sql = `WITH combined AS (
|
||||
SELECT id, amount
|
||||
|
||||
@@ -15,7 +15,8 @@ interface FoldRange {
|
||||
|
||||
// `@codemirror/lang-sql`'s grammar is deliberately shallow (see its `foldNodeProp`, which only
|
||||
// covers the flat `Statement` and `BlockComment` nodes). This service adds structure-aware folds
|
||||
// for procedural blocks and query expressions without replacing CodeMirror's native folds.
|
||||
// for procedural blocks and parenthesized query expressions without replacing CodeMirror's native
|
||||
// folds.
|
||||
//
|
||||
// Scoped to `BEGIN...END` and `CASE...END` (issue #6574) -- `BEGIN TRY`/`END TRY` and
|
||||
// `BEGIN CATCH`/`END CATCH` fall out of this for free, since `BEGIN` is matched regardless of a
|
||||
@@ -94,16 +95,6 @@ function queryScopeEnd(state: EditorState, scope: SyntaxNode): number {
|
||||
return end;
|
||||
}
|
||||
|
||||
function isQueryParens(state: EditorState, node: SyntaxNode): boolean {
|
||||
for (let child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.name === "(" || child.name === "LineComment" || child.name === "BlockComment") continue;
|
||||
if (child.name !== "Keyword") return false;
|
||||
const keyword = state.sliceDoc(child.from, child.to).toUpperCase();
|
||||
return keyword === "SELECT" || keyword === "WITH";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
interface QueryScopeTokens {
|
||||
scope: SyntaxNode;
|
||||
tokens: Array<{ from: number; keyword: "SELECT" | "UNION" }>;
|
||||
@@ -131,7 +122,10 @@ function addQueryStructureFoldRanges(state: EditorState, tree: Tree, ranges: Map
|
||||
const scopes = new Map<string, QueryScopeTokens>();
|
||||
tree.iterate({
|
||||
enter(node) {
|
||||
if (node.name === "Parens" && isQueryParens(state, node.node)) {
|
||||
// Fold any multiline parenthesized expression, including large `IN (...)`
|
||||
// value lists. The opening line remains visible so the surrounding SQL
|
||||
// structure is still readable while the contents can be collapsed.
|
||||
if (node.name === "Parens") {
|
||||
addMultilineFoldRange(state, ranges, node.from, queryScopeEnd(state, node.node));
|
||||
}
|
||||
if (node.name !== "Keyword") return;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
Reference in New Issue
Block a user