diff --git a/package-lock.json b/package-lock.json
index 4f98fce..2cf1dfd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
"lodash": "^4.17.21",
"marked": "^15.0.8",
"marked-highlight": "^2.2.1",
+ "mitt": "^3.0.1",
"monaco-editor": "^0.52.2",
"normalize.css": "^8.0.1",
"p-debounce": "^4.0.0",
@@ -45,7 +46,8 @@
"svelte-check": "^4.1.5",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
- "vite": "^6.2.6"
+ "vite": "^6.2.6",
+ "vite-plugin-devtools-json": "^0.1.0"
}
},
"node_modules/@agnosticeng/cache": {
@@ -2318,6 +2320,12 @@
"marked": ">=4 <16"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
"node_modules/monaco-editor": {
"version": "0.52.2",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
@@ -2727,6 +2735,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
"node_modules/vite": {
"version": "6.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
@@ -2799,6 +2821,19 @@
}
}
},
+ "node_modules/vite-plugin-devtools-json": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-devtools-json/-/vite-plugin-devtools-json-0.1.0.tgz",
+ "integrity": "sha512-KvdgPBUAAhwnpOgXmJhs6KI4/IPn6xUppLGm20D0Uvp/doZ9TpTYYfzSHX0TgvdsSlTAwzZfzDse+ujGskp68g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "uuid": "^11.1.0"
+ },
+ "peerDependencies": {
+ "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
"node_modules/vitefu": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz",
diff --git a/package.json b/package.json
index a7d30d3..daa79fe 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"lodash": "^4.17.21",
"marked": "^15.0.8",
"marked-highlight": "^2.2.1",
+ "mitt": "^3.0.1",
"monaco-editor": "^0.52.2",
"normalize.css": "^8.0.1",
"p-debounce": "^4.0.0",
@@ -51,6 +52,7 @@
"svelte-check": "^4.1.5",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
- "vite": "^6.2.6"
+ "vite": "^6.2.6",
+ "vite-plugin-devtools-json": "^0.1.0"
}
}
diff --git a/src/lib/components/Datasets/Columns.svelte b/src/lib/components/Datasets/Columns.svelte
index 07ccda6..5d8cbc5 100644
--- a/src/lib/components/Datasets/Columns.svelte
+++ b/src/lib/components/Datasets/Columns.svelte
@@ -1,5 +1,11 @@
diff --git a/src/lib/components/Datasets/Tree.svelte b/src/lib/components/Datasets/Tree.svelte
index 6ee1094..ba24d80 100644
--- a/src/lib/components/Datasets/Tree.svelte
+++ b/src/lib/components/Datasets/Tree.svelte
@@ -3,18 +3,34 @@
import FolderOpen from '$lib/icons/FolderOpen.svelte';
import Table from '$lib/icons/Table.svelte';
import Columns from './Columns.svelte';
+ import { onExpand } from './emitter';
import Tree from './Tree.svelte';
+ import { findNodeInTree, type TreeNode } from './utils';
+
+ interface Props {
+ node: TreeNode;
+ level?: number;
+ expanded?: boolean;
+ }
+
+ let { node, level = 0, expanded: forceExpanded }: Props = $props();
- let { node = {}, level = 0, expanded: forceExpanded = false } = $props();
let expanded = $state(false);
$effect(() => {
- expanded = forceExpanded;
+ if (typeof forceExpanded === 'boolean') expanded = forceExpanded;
});
function toggleExpanded() {
expanded = !expanded;
}
+
+ $effect(() =>
+ onExpand((value) => {
+ if (node.type === 'dataset' && node.value === value) expanded = true;
+ else if (node.type === 'group' && findNodeInTree(node.children, value)) expanded = true;
+ })
+ );
@@ -43,10 +59,12 @@
{/if}
- {#if node.type === 'group' && node.children && expanded}
- {#each node.children as child}
-
- {/each}
+ {#if node.type === 'group'}
+
+ {#each node.children as child}
+
+ {/each}
+
{/if}
{#if node.type === 'dataset' && expanded}
diff --git a/src/lib/components/Datasets/emitter.ts b/src/lib/components/Datasets/emitter.ts
new file mode 100644
index 0000000..28a8c78
--- /dev/null
+++ b/src/lib/components/Datasets/emitter.ts
@@ -0,0 +1,18 @@
+import type { Table } from '$lib/olap-engine';
+import mitt from 'mitt';
+
+type Events = {
+ expand: Table['name'];
+};
+
+const emitter = mitt
();
+
+export function goToDefinition(tableName: string) {
+ emitter.emit('expand', tableName);
+}
+
+export function onExpand(handler: (tableName: string) => void) {
+ emitter.on('expand', handler);
+
+ return () => emitter.off('expand', handler);
+}
diff --git a/src/lib/components/Datasets/index.ts b/src/lib/components/Datasets/index.ts
new file mode 100644
index 0000000..2b18dda
--- /dev/null
+++ b/src/lib/components/Datasets/index.ts
@@ -0,0 +1,2 @@
+export { default } from './Datasets.svelte';
+export * from './emitter';
diff --git a/src/lib/components/Datasets/utils.ts b/src/lib/components/Datasets/utils.ts
index ecef3a8..e7850be 100644
--- a/src/lib/components/Datasets/utils.ts
+++ b/src/lib/components/Datasets/utils.ts
@@ -1,4 +1,4 @@
-import { type ColumnDescriptor, type Table } from '$lib/olap-engine';
+import type { ColumnDescriptor, Table } from '$lib/olap-engine';
import _ from 'lodash';
export function filter(sources: Table[], search: string) {
@@ -12,12 +12,19 @@ export function filter(sources: Table[], search: string) {
);
}
-type TreeNode = {
+export type TreeNode = GroupTreeNode | DatasetTreeNode;
+
+type GroupTreeNode = {
name: string;
- type: 'group' | 'dataset';
- value?: string;
- columns?: ColumnDescriptor[];
- children?: TreeNode[];
+ type: 'group';
+ children: TreeNode[];
+};
+
+type DatasetTreeNode = {
+ name: string;
+ type: 'dataset';
+ value: string;
+ columns: ColumnDescriptor[];
};
export function buildTree(tables: Table[]): TreeNode[] {
@@ -36,14 +43,28 @@ export function buildTree(tables: Table[]): TreeNode[] {
const toArray = (obj: any): TreeNode[] =>
_.map(
obj,
- (value, key): TreeNode => ({
- name: key,
- type: value.children ? 'group' : 'dataset',
- value: value.value,
- columns: value.columns,
- children: value.children ? toArray(value.children) : undefined
- })
+ (value, key) =>
+ ({
+ name: key,
+ type: value.children ? 'group' : 'dataset',
+ value: value.value,
+ columns: value.columns,
+ children: value.children ? toArray(value.children) : undefined
+ }) as TreeNode
);
return toArray(root);
}
+
+export function findNodeInTree(
+ tree: TreeNode[],
+ value: DatasetTreeNode['value']
+): TreeNode | undefined {
+ for (const node of tree) {
+ if (node.type === 'dataset' && node.value === value) return node;
+ else if (node.type === 'group') {
+ const found = findNodeInTree(node.children!, value);
+ if (found) return found;
+ }
+ }
+}
diff --git a/src/lib/components/Editor/Editor.svelte b/src/lib/components/Editor/Editor.svelte
index 3b9dce3..9b67809 100644
--- a/src/lib/components/Editor/Editor.svelte
+++ b/src/lib/components/Editor/Editor.svelte
@@ -1,12 +1,19 @@
@@ -65,5 +131,11 @@
div {
height: 100%;
width: 100%;
+
+ & :global(.to-go-def) {
+ color: #007acc;
+ text-decoration: underline;
+ cursor: pointer;
+ }
}
diff --git a/src/lib/components/Editor/language.ts b/src/lib/components/Editor/language.ts
index 48633d2..2b97cd2 100644
--- a/src/lib/components/Editor/language.ts
+++ b/src/lib/components/Editor/language.ts
@@ -10,6 +10,31 @@ export function setupLanguage(
columns: string[] = []
) {
monaco.languages.register({ id });
+ monaco.languages.setLanguageConfiguration(id, {
+ comments: {
+ lineComment: '--',
+ blockComment: ['/*', '*/']
+ },
+ brackets: [
+ ['{', '}'],
+ ['[', ']'],
+ ['(', ')']
+ ],
+ autoClosingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"' },
+ { open: "'", close: "'" }
+ ],
+ surroundingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"' },
+ { open: "'", close: "'" }
+ ]
+ });
monaco.languages.setMonarchTokensProvider(id, {
ignoreCase: true,
keywords: keywords.map((k) => k.split(' ')[0]),
@@ -19,6 +44,11 @@ export function setupLanguage(
operators: operators,
columns: columns,
+ brackets: [
+ { open: '[', close: ']', token: 'delimiter.square' },
+ { open: '(', close: ')', token: 'delimiter.parenthesis' }
+ ],
+
symbols: /[=> {
const word = model.getWordUntilPosition(position);
const range = {
diff --git a/src/lib/components/SideBar.svelte b/src/lib/components/SideBar.svelte
index f91332e..e494d0f 100644
--- a/src/lib/components/SideBar.svelte
+++ b/src/lib/components/SideBar.svelte
@@ -2,7 +2,8 @@
import type { Table } from '$lib/olap-engine';
import type { HistoryEntry } from '$lib/repositories/history';
import type { Query } from '$lib/repositories/queries';
- import Datasets from './Datasets/Datasets.svelte';
+ import { tick } from 'svelte';
+ import Datasets, { goToDefinition, onExpand as onDatasetExpand } from './Datasets';
import History from './History.svelte';
import ProxySwitch from './ProxySwitch.svelte';
import Queries from './Queries/Queries.svelte';
@@ -14,6 +15,16 @@
tab = next;
}
+ $effect(() =>
+ onDatasetExpand(async (table) => {
+ if (tab !== 'sources') {
+ tab = 'sources';
+ await tick();
+ goToDefinition(table);
+ }
+ })
+ );
+
type Props = {
tables?: Table[];
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 3784227..8f61dfe 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -3,6 +3,7 @@
import type { Log } from '$lib/components/Console.svelte';
import { ContextMenuState } from '$lib/components/ContextMenu';
import ContextMenu from '$lib/components/ContextMenu/ContextMenu.svelte';
+ import { goToDefinition } from '$lib/components/Datasets';
import Drawer from '$lib/components/Drawer.svelte';
import { functions, keywords, operators, types } from '$lib/components/Editor/clickhouse';
import Editor from '$lib/components/Editor/Editor.svelte';
@@ -550,7 +551,11 @@ LIMIT 100;`;
{#each tabs as tab, i (tab.id)}
-
+ t.name)}
+ onCmdClick={goToDefinition}
+ />
{/each}
diff --git a/vite.config.js b/vite.config.js
index 2f207f0..12f88e8 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,9 +1,10 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
+import devtoolsJson from 'vite-plugin-devtools-json';
// https://vitejs.dev/config/
export default defineConfig(async () => ({
- plugins: [sveltekit()],
+ plugins: [sveltekit(), devtoolsJson()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//