From 21dc4f14cbabc3702a7a8398db2ba300b3b6ed91 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 15 Apr 2024 10:57:48 +0300 Subject: [PATCH 01/32] KXI-38369 --- package.json | 111 ++++++++++++++----- resources/dark/plus.svg | 3 + resources/light/plus.svg | 3 + src/commands/scratchpadCommand.ts | 100 ++++++++++++++++++ src/extension.ts | 100 +++++++++++++++++- src/extensionVariables.ts | 11 +- src/services/dataSourceEditorProvider.ts | 129 +++++++++++++++++++++++ src/services/workspaceTreeProvider.ts | 110 +++++++++++++++++++ 8 files changed, 540 insertions(+), 27 deletions(-) create mode 100644 resources/dark/plus.svg create mode 100644 resources/light/plus.svg create mode 100644 src/commands/scratchpadCommand.ts create mode 100644 src/services/dataSourceEditorProvider.ts create mode 100644 src/services/workspaceTreeProvider.ts diff --git a/package.json b/package.json index 0d834efd..7eed31db 100644 --- a/package.json +++ b/package.json @@ -31,30 +31,7 @@ "kdb Insights" ], "activationEvents": [ - "onCommand:kdb.installTools", - "onCommand:kdb.addConnection", - "onCommand:kdb.newConnection.createNewInsightConnection", - "onCommand:kdb.newConnection.createNewConnection", - "onCommand:kdb.newConnection.createNewBundledConnection", - "onCommand:kdb.removeConnection", - "onCommand:kdb.connect", - "onCommand:kdb.disconnect", - "onCommand:kdb.addAuthentication", - "onCommand:kdb.enableTLS", - "onCommand:kdb.dataSource.addDataSource", - "onCommand:kdb.dataSource.saveDataSource", - "onCommand:kdb.dataSource.runDataSource", - "onCommand:kdb.dataSource.renameDataSource", - "onCommand:kdb.dataSource.deleteDataSource", - "onCommand:kdb.dataSource.openDataSource", - "onCommand:kdb.resultsPanel.update", - "onCommand:kdb.resultsPanel.clear", - "onCommand:kdb.resultsPanel.export.csv", - "onView:kdb-servers", - "onView:kdb-query-history", - "onView:kdb-datasources-explorer", - "onView:kdb-results", - "onTerminalProfile:kdb.q-terminal" + "onStartupFinished" ], "main": "./out/extension.js", "contributes": { @@ -187,6 +164,12 @@ "type": "boolean", "description": "Enable linting for q files", "default": true + }, + "kdb.scratchpads": { + "type": "object", + "description": "Scratchpads", + "default": {}, + "scope": "resource" } } }, @@ -199,6 +182,42 @@ "dark": "resources/dark/refresh.svg" } }, + { + "command": "kdb.createDataSource", + "title": "Add a new data source", + "icon": { + "light": "resources/light/plus.svg", + "dark": "resources/dark/plus.svg" + } + }, + { + "command": "kdb.refreshDataSourceExplorer", + "title": "Refresh data sources", + "icon": { + "light": "resources/light/refresh.svg", + "dark": "resources/dark/refresh.svg" + } + }, + { + "command": "kdb.createScratchpad", + "title": "Add a new scratchpad", + "icon": { + "light": "resources/light/plus.svg", + "dark": "resources/dark/plus.svg" + } + }, + { + "command": "kdb.refreshScratchpadExplorer", + "title": "Refresh scratchpads", + "icon": { + "light": "resources/light/refresh.svg", + "dark": "resources/dark/refresh.svg" + } + }, + { + "command": "kdb.runScratchpad", + "title": "Run scratchpad" + }, { "command": "kdb.startLocalProcess", "title": "Start q process" @@ -409,6 +428,16 @@ "name": "Connections", "icon": "resources/server.svg" }, + { + "id": "kdb-datasource-explorer", + "name": "Datasources", + "icon": "resources/server.svg" + }, + { + "id": "kdb-scratchpad-explorer", + "name": "Scratchpads", + "icon": "resources/server.svg" + }, { "id": "kdb-query-history", "name": "Query History", @@ -524,6 +553,26 @@ "command": "kdb.resultsPanel.export.csv", "when": "view == kdb-results", "group": "resultsPanel" + }, + { + "command": "kdb.createDataSource", + "when": "view == kdb-datasource-explorer", + "group": "navigation" + }, + { + "command": "kdb.refreshDataSourceExplorer", + "when": "view == kdb-datasource-explorer", + "group": "navigation" + }, + { + "command": "kdb.createScratchpad", + "when": "view == kdb-scratchpad-explorer", + "group": "navigation" + }, + { + "command": "kdb.refreshScratchpadExplorer", + "when": "view == kdb-scratchpad-explorer", + "group": "navigation" } ], "view/item/context": [ @@ -644,7 +693,19 @@ "when": "(resourceExtname == .q && (kdb.connected || kdb.insightsConnected)) || (resourceExtname == .py && kdb.insightsConnected)" } ] - } + }, + "customEditors": [ + { + "viewType": "kdb.dataSourceEditor", + "displayName": "Data Source Editor", + "selector": [ + { + "filenamePattern": "*.kdb.json" + } + ], + "priority": "default" + } + ] }, "license": "MIT", "bugs": { diff --git a/resources/dark/plus.svg b/resources/dark/plus.svg new file mode 100644 index 00000000..027d67c6 --- /dev/null +++ b/resources/dark/plus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/light/plus.svg b/resources/light/plus.svg new file mode 100644 index 00000000..14d0bdb4 --- /dev/null +++ b/resources/light/plus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts new file mode 100644 index 00000000..4eb22632 --- /dev/null +++ b/src/commands/scratchpadCommand.ts @@ -0,0 +1,100 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { TextEditor, window, workspace } from "vscode"; +import { ext } from "../extensionVariables"; + +function setRunScratchpadItemText(text: string) { + ext.runScratchpadItem.text = `$(notebook-execute) ${text}`; +} + +export function activeEditorChanged(editor?: TextEditor) { + const item = ext.runScratchpadItem; + if (editor) { + const uri = editor.document.uri; + const path = uri.path; + if (path.endsWith("kdb.q") || path.endsWith("kdb.py")) { + const conf = workspace.getConfiguration("kdb", uri); + const map = conf.get<{ [key: string]: string }>("scratchpads", {}); + + if (path in map) { + const servers = conf.get<{ [key: string]: { serverName: string } }>( + "servers", + {}, + ); + setRunScratchpadItemText(servers[map[path]].serverName); + } else { + setRunScratchpadItemText("Run"); + } + item.show(); + } else { + item.hide(); + } + } else { + item.hide(); + } +} + +export async function runScratchpad(editor?: TextEditor) { + if (!editor) { + return; + } + + const uri = editor.document.uri; + const path = uri.path; + + if (!path.endsWith("kdb.q") && !path.endsWith("kdb.py")) { + return; + } + + const conf = workspace.getConfiguration("kdb", uri); + const map = conf.get<{ [key: string]: string }>("scratchpads", {}); + + let server = ""; + + if (path in map) { + server = map[path]; + } else { + const servers = conf.get<{ [key: string]: { serverName: string } }>( + "servers", + {}, + ); + + const picked = await window.showQuickPick( + Object.keys(servers).map((key) => servers[key].serverName), + { + title: "Choose a connection", + }, + ); + + if (picked) { + const key = Object.keys(servers).find( + (key) => servers[key].serverName === picked, + ); + + if (key) { + setRunScratchpadItemText(picked); + server = key; + map[path] = server; + await conf.update("scratchpads", map); + } + } + } + + if (server) { + // TODO + window.showInformationMessage( + `Scratchpad is sent for running on associated connection (${server})`, + ); + } +} diff --git a/src/extension.ts b/src/extension.ts index 7241a801..5e45c5d1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,7 @@ import { env } from "node:process"; import path from "path"; import { CancellationToken, + Command, commands, CompletionItem, CompletionItemKind, @@ -22,11 +23,16 @@ import { ExtensionContext, languages, Position, + Range, + StatusBarAlignment, TextDocument, TextDocumentContentProvider, + TextEditor, + ThemeColor, Uri, window, workspace, + WorkspaceEdit, } from "vscode"; import { LanguageClient, @@ -50,7 +56,6 @@ import { stopLocalProcessByServerName, } from "./commands/installTools"; import { - addAuthConnection, addInsightsConnection, addKdbConnection, addNewConnection, @@ -94,6 +99,20 @@ import { import { runQFileTerminal } from "./utils/execution"; import AuthSettings from "./utils/secretStorage"; import { Telemetry } from "./utils/telemetryClient"; +import { + DataSourceEditorProvider, + updateJsonDocument, +} from "./services/dataSourceEditorProvider"; +import { + addWorkspaceFile, + FileTreeItem, + WorkspaceTreeProvider, +} from "./services/workspaceTreeProvider"; +import { + activeEditorChanged, + runScratchpad, +} from "./commands/scratchpadCommand"; +import { createDefaultDataSourceFile } from "./models/dataSource"; let client: LanguageClient; @@ -112,6 +131,8 @@ export async function activate(context: ExtensionContext) { ext.resultsViewProvider = new KdbResultsViewProvider( ext.context.extensionUri, ); + ext.scratchpadTreeProvider = new WorkspaceTreeProvider("**/*.kdb.{q,py}"); + ext.dataSourceTreeProvider = new WorkspaceTreeProvider("**/*.kdb.json"); commands.executeCommand("setContext", "kdb.QHOME", env.QHOME); @@ -124,6 +145,14 @@ export async function activate(context: ExtensionContext) { "kdb-query-history", ext.queryHistoryProvider, ); + window.registerTreeDataProvider( + "kdb-scratchpad-explorer", + ext.scratchpadTreeProvider, + ); + window.registerTreeDataProvider( + "kdb-datasource-explorer", + ext.dataSourceTreeProvider, + ); // initialize local servers if (servers !== undefined) { @@ -331,6 +360,50 @@ export async function activate(context: ExtensionContext) { await executeQuery(query, undefined, isPython); } }), + commands.registerCommand( + "kdb.createDataSource", + async (item: FileTreeItem) => { + const uri = await addWorkspaceFile(item, "datasource", ".kdb.json"); + + if (uri) { + const edit = new WorkspaceEdit(); + + edit.replace( + uri, + new Range(0, 0, 1, 0), + JSON.stringify(createDefaultDataSourceFile(), null, 2), + ); + + workspace.applyEdit(edit); + + await commands.executeCommand( + "vscode.openWith", + uri, + DataSourceEditorProvider.viewType, + ); + } + }, + ), + commands.registerCommand("kdb.refreshDataSourceExplorer", () => { + ext.dataSourceTreeProvider.refresh(); + }), + commands.registerCommand( + "kdb.createScratchpad", + async (item: FileTreeItem) => { + const uri = await addWorkspaceFile(item, "scratchpad", ".kdb.q"); + if (uri) { + await window.showTextDocument(uri); + } + }, + ), + commands.registerCommand("kdb.refreshScratchpadExplorer", () => { + ext.scratchpadTreeProvider.refresh(); + }), + commands.registerCommand("kdb.runScratchpad", async () => { + await runScratchpad(window.activeTextEditor); + }), + + DataSourceEditorProvider.register(context), ); const lastResult: QueryResult | undefined = undefined; @@ -404,6 +477,31 @@ export async function activate(context: ExtensionContext) { }), ); + workspace.onDidChangeWorkspaceFolders(() => { + ext.dataSourceTreeProvider.refresh(); + ext.scratchpadTreeProvider.refresh(); + }); + + ext.runScratchpadItem = window.createStatusBarItem( + StatusBarAlignment.Right, + 10000, + ); + + ext.runScratchpadItem.backgroundColor = new ThemeColor( + "statusBarItem.warningBackground", + ); + ext.runScratchpadItem.command = { + title: "", + command: "kdb.runScratchpad", + arguments: [], + }; + + window.onDidChangeActiveTextEditor((editor?: TextEditor) => { + activeEditorChanged(editor); + }); + + activeEditorChanged(window.activeTextEditor); + //q language server const serverModule = path.join(context.extensionPath, "out", "server.js"); const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 66aebcc0..5da97f93 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -11,7 +11,12 @@ * specific language governing permissions and limitations under the License. */ -import { ExtensionContext, extensions, OutputChannel } from "vscode"; +import { + ExtensionContext, + extensions, + OutputChannel, + StatusBarItem, +} from "vscode"; import { LanguageClient } from "vscode-languageclient/node"; import { Connection } from "./models/connection"; import { LocalProcess } from "./models/localProcess"; @@ -27,6 +32,7 @@ import { import { QueryHistoryProvider } from "./services/queryHistoryProvider"; import { KdbResultsViewProvider } from "./services/resultsPanelProvider"; import AuthSettings from "./utils/secretStorage"; +import { WorkspaceTreeProvider } from "./services/workspaceTreeProvider"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { @@ -37,6 +43,9 @@ export namespace ext { export let dataSourceProvider: KdbDataSourceProvider; export let queryHistoryProvider: QueryHistoryProvider; export let resultsViewProvider: KdbResultsViewProvider; + export let scratchpadTreeProvider: WorkspaceTreeProvider; + export let dataSourceTreeProvider: WorkspaceTreeProvider; + export let runScratchpadItem: StatusBarItem; export let serverObjects: ServerObject; export let openSslVersion: string | null; export let resultPanelCSV: string; diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts new file mode 100644 index 00000000..ec3aaf10 --- /dev/null +++ b/src/services/dataSourceEditorProvider.ts @@ -0,0 +1,129 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { + CustomTextEditorProvider, + Disposable, + ExtensionContext, + Range, + TextDocument, + Webview, + WebviewPanel, + WorkspaceEdit, + window, + workspace, +} from "vscode"; +import { getUri } from "../utils/getUri"; +import { getNonce } from "../utils/getNonce"; + +export class DataSourceEditorProvider implements CustomTextEditorProvider { + static readonly viewType = "kdb.dataSourceEditor"; + + public static register(context: ExtensionContext): Disposable { + const provider = new DataSourceEditorProvider(context); + return window.registerCustomEditorProvider( + DataSourceEditorProvider.viewType, + provider, + ); + } + + constructor(private readonly context: ExtensionContext) {} + + resolveCustomTextEditor( + document: TextDocument, + webviewPanel: WebviewPanel, + ): void | Thenable { + const webview = webviewPanel.webview; + webview.options = { enableScripts: true }; + webview.html = this.getWebviewContent(webview); + + function updateWebview() { + webview.postMessage({ + type: "update", + text: document.getText(), + }); + } + + const changeDocumentSubscription = workspace.onDidChangeTextDocument( + (event) => { + if (event.document.uri.toString() === document.uri.toString()) { + updateWebview(); + } + }, + ); + + webviewPanel.onDidDispose(() => { + changeDocumentSubscription.dispose(); + }); + + webview.onDidReceiveMessage((event) => { + switch (event.type) { + case "add": + break; + + case "delete": + break; + } + }); + + updateWebview(); + } + + private getDocumentAsJson(document: TextDocument) { + const text = document.getText(); + if (text.trim().length === 0) { + return {}; + } + + try { + return JSON.parse(text); + } catch (error) { + throw error; + } + } + + private updateTextDocument(document: TextDocument, json: unknown) { + const edit = new WorkspaceEdit(); + + edit.replace( + document.uri, + new Range(0, 0, document.lineCount, 0), + JSON.stringify(json, null, 2), + ); + + return workspace.applyEdit(edit); + } + + private getWebviewContent(webview: Webview) { + const webviewUri = getUri(webview, this.context.extensionUri, [ + "out", + "webview.js", + ]); + const nonce = getNonce(); + + return /* html */ ` + + + + + + DataSource + + + + + + + `; + } +} diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts new file mode 100644 index 00000000..a84fc6d8 --- /dev/null +++ b/src/services/workspaceTreeProvider.ts @@ -0,0 +1,110 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { + Command, + EventEmitter, + RelativePattern, + TreeDataProvider, + TreeItem, + TreeItemCollapsibleState, + Uri, + workspace, +} from "vscode"; + +export class WorkspaceTreeProvider implements TreeDataProvider { + private _onDidChangeTreeData = new EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + constructor(private readonly glob: string) {} + + refresh() { + this._onDidChangeTreeData.fire(undefined); + } + + getChildren(element?: FileTreeItem) { + if (element) { + return element.getChildren(); + } + const folders = workspace.workspaceFolders; + if (folders) { + return Promise.resolve( + folders.map((folder) => new FileTreeItem(folder.uri, this.glob)), + ); + } + return Promise.resolve([]); + } + + getTreeItem(element: FileTreeItem) { + return element; + } +} + +export class FileTreeItem extends TreeItem { + private declare pattern?: RelativePattern; + + constructor(resourceUri: Uri, glob?: string) { + super(resourceUri); + this.id = resourceUri.toString(); + if (glob) { + const folder = workspace.getWorkspaceFolder(resourceUri); + if (folder) { + this.pattern = new RelativePattern(folder, glob); + this.collapsibleState = TreeItemCollapsibleState.Collapsed; + } + } else { + this.command = { + title: "", + command: "vscode.open", + arguments: [resourceUri], + }; + } + } + + async getChildren(): Promise { + if (this.pattern) { + const files = await workspace.findFiles(this.pattern); + return files.map((file) => new FileTreeItem(file)); + } + return []; + } +} + +export async function addWorkspaceFile( + item: FileTreeItem, + name: string, + ext: string, +) { + const folders = workspace.workspaceFolders; + if (folders) { + const ws = + item && item.resourceUri + ? workspace.getWorkspaceFolder(item.resourceUri) + : folders[0]; + if (ws) { + let i = 1; + while (true) { + const files = await workspace.findFiles(`${name}-${i}${ext}`); + if (files.length === 0) { + break; + } + i++; + } + const uri = Uri.joinPath(ws.uri, `${name}-${i}${ext}`).with({ + scheme: "untitled", + }); + await workspace.openTextDocument(uri); + return uri; + } + } +} From 6e2dff7e915d6bff1e307ac8fc4b2f326a127174 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 15 Apr 2024 14:41:38 +0300 Subject: [PATCH 02/32] fixed test --- src/extension.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 5e45c5d1..75e7ff58 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -99,10 +99,7 @@ import { import { runQFileTerminal } from "./utils/execution"; import AuthSettings from "./utils/secretStorage"; import { Telemetry } from "./utils/telemetryClient"; -import { - DataSourceEditorProvider, - updateJsonDocument, -} from "./services/dataSourceEditorProvider"; +import { DataSourceEditorProvider } from "./services/dataSourceEditorProvider"; import { addWorkspaceFile, FileTreeItem, From 400b01ce4cc37c07306f8ef4f36ec316e029900b Mon Sep 17 00:00:00 2001 From: ecmel Date: Tue, 16 Apr 2024 18:34:30 +0300 Subject: [PATCH 03/32] add watcher and sort --- src/commands/scratchpadCommand.ts | 7 ++++++- src/extension.ts | 16 ++++++++-------- src/services/workspaceTreeProvider.ts | 4 +++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index 4eb22632..b032da8a 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -18,7 +18,12 @@ function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(notebook-execute) ${text}`; } -export function activeEditorChanged(editor?: TextEditor) { +export function workspaceFoldersChanged() { + ext.dataSourceTreeProvider.refresh(); + ext.scratchpadTreeProvider.refresh(); +} + +export function activeEditorChanged(editor?: TextEditor | undefined) { const item = ext.runScratchpadItem; if (editor) { const uri = editor.document.uri; diff --git a/src/extension.ts b/src/extension.ts index 75e7ff58..2afc8db5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -27,7 +27,6 @@ import { StatusBarAlignment, TextDocument, TextDocumentContentProvider, - TextEditor, ThemeColor, Uri, window, @@ -108,6 +107,7 @@ import { import { activeEditorChanged, runScratchpad, + workspaceFoldersChanged, } from "./commands/scratchpadCommand"; import { createDefaultDataSourceFile } from "./models/dataSource"; @@ -474,16 +474,18 @@ export async function activate(context: ExtensionContext) { }), ); - workspace.onDidChangeWorkspaceFolders(() => { - ext.dataSourceTreeProvider.refresh(); - ext.scratchpadTreeProvider.refresh(); - }); + const watcher = workspace.createFileSystemWatcher("**/*.kdb.{json,q,py}"); + watcher.onDidCreate(workspaceFoldersChanged); + watcher.onDidDelete(workspaceFoldersChanged); + workspace.onDidChangeWorkspaceFolders(workspaceFoldersChanged); ext.runScratchpadItem = window.createStatusBarItem( StatusBarAlignment.Right, 10000, ); + ext.runScratchpadItem.tooltip = "Run scratchpad"; + ext.runScratchpadItem.backgroundColor = new ThemeColor( "statusBarItem.warningBackground", ); @@ -493,9 +495,7 @@ export async function activate(context: ExtensionContext) { arguments: [], }; - window.onDidChangeActiveTextEditor((editor?: TextEditor) => { - activeEditorChanged(editor); - }); + window.onDidChangeActiveTextEditor(activeEditorChanged); activeEditorChanged(window.activeTextEditor); diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index a84fc6d8..74d90926 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -74,7 +74,9 @@ export class FileTreeItem extends TreeItem { async getChildren(): Promise { if (this.pattern) { const files = await workspace.findFiles(this.pattern); - return files.map((file) => new FileTreeItem(file)); + return files + .sort((a, b) => a.path.localeCompare(b.path)) + .map((file) => new FileTreeItem(file)); } return []; } From 0a9e4b33aaed6ab47d80a040ee3ea052f42b6ea3 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 17 Apr 2024 18:32:02 +0300 Subject: [PATCH 04/32] implemented code lens --- package.json | 4 + src/commands/scratchpadCommand.ts | 134 +++++++++++++++----------- src/extension.ts | 28 ++++-- src/services/workspaceTreeProvider.ts | 6 +- 4 files changed, 107 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 7eed31db..513a0e3b 100644 --- a/package.json +++ b/package.json @@ -218,6 +218,10 @@ "command": "kdb.runScratchpad", "title": "Run scratchpad" }, + { + "command": "kdb.pickConnection", + "title": "Pick connection" + }, { "command": "kdb.startLocalProcess", "title": "Start q process" diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index b032da8a..ba54b012 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -11,13 +11,51 @@ * specific language governing permissions and limitations under the License. */ -import { TextEditor, window, workspace } from "vscode"; +import { + CodeLens, + CodeLensProvider, + ProviderResult, + Range, + TextDocument, + TextEditor, + Uri, + window, + workspace, +} from "vscode"; import { ext } from "../extensionVariables"; function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(notebook-execute) ${text}`; } +function getServers() { + const conf = workspace.getConfiguration("kdb"); + const servers = conf.get<{ [key: string]: { serverName: string } }>( + "servers", + {}, + ); + return Object.keys(servers).map((key) => servers[key].serverName); +} + +function getServerForScratchpad(uri: Uri) { + const conf = workspace.getConfiguration("kdb", uri); + const scratchpads = conf.get<{ [key: string]: string | undefined }>( + "scratchpads", + {}, + ); + return scratchpads[uri.path]; +} + +async function setServerForScratchpad(uri: Uri, server: string | undefined) { + const conf = workspace.getConfiguration("kdb", uri); + const scratchpads = conf.get<{ [key: string]: string | undefined }>( + "scratchpads", + {}, + ); + scratchpads[uri.path] = server; + await conf.update("scratchpads", scratchpads); +} + export function workspaceFoldersChanged() { ext.dataSourceTreeProvider.refresh(); ext.scratchpadTreeProvider.refresh(); @@ -29,18 +67,8 @@ export function activeEditorChanged(editor?: TextEditor | undefined) { const uri = editor.document.uri; const path = uri.path; if (path.endsWith("kdb.q") || path.endsWith("kdb.py")) { - const conf = workspace.getConfiguration("kdb", uri); - const map = conf.get<{ [key: string]: string }>("scratchpads", {}); - - if (path in map) { - const servers = conf.get<{ [key: string]: { serverName: string } }>( - "servers", - {}, - ); - setRunScratchpadItemText(servers[map[path]].serverName); - } else { - setRunScratchpadItemText("Run"); - } + const server = getServerForScratchpad(uri); + setRunScratchpadItemText(server || "Run"); item.show(); } else { item.hide(); @@ -50,56 +78,52 @@ export function activeEditorChanged(editor?: TextEditor | undefined) { } } -export async function runScratchpad(editor?: TextEditor) { - if (!editor) { - return; - } +export async function pickConnection(uri: Uri) { + const server = getServerForScratchpad(uri); - const uri = editor.document.uri; - const path = uri.path; + let picked = await window.showQuickPick(["(none)", ...getServers()], { + title: "Choose a connection", + placeHolder: server, + }); - if (!path.endsWith("kdb.q") && !path.endsWith("kdb.py")) { - return; + if (picked) { + if (picked === "(none)") { + picked = undefined; + } + await setServerForScratchpad(uri, picked); + setRunScratchpadItemText(picked || "Run"); } - const conf = workspace.getConfiguration("kdb", uri); - const map = conf.get<{ [key: string]: string }>("scratchpads", {}); - - let server = ""; - - if (path in map) { - server = map[path]; - } else { - const servers = conf.get<{ [key: string]: { serverName: string } }>( - "servers", - {}, - ); - - const picked = await window.showQuickPick( - Object.keys(servers).map((key) => servers[key].serverName), - { - title: "Choose a connection", - }, - ); + return picked; +} - if (picked) { - const key = Object.keys(servers).find( - (key) => servers[key].serverName === picked, - ); +export async function runScratchpad(uri: Uri) { + let server = getServerForScratchpad(uri); - if (key) { - setRunScratchpadItemText(picked); - server = key; - map[path] = server; - await conf.update("scratchpads", map); - } - } + if (!server) { + server = await pickConnection(uri); } if (server) { - // TODO - window.showInformationMessage( - `Scratchpad is sent for running on associated connection (${server})`, - ); + window.showInformationMessage(`Running scratchpad on ${server}`); + } +} + +export class ConnectionLensProvider implements CodeLensProvider { + provideCodeLenses(document: TextDocument): ProviderResult { + const uri = document.uri; + const server = getServerForScratchpad(uri); + const top = new Range(0, 0, 0, 0); + const runScratchpad = new CodeLens(top, { + command: "kdb.runScratchpad", + title: server ? `Run on ${server}` : "Run", + arguments: [uri], + }); + const pickConnection = new CodeLens(top, { + command: "kdb.pickConnection", + title: "Choose Connection", + arguments: [uri], + }); + return [runScratchpad, pickConnection]; } } diff --git a/src/extension.ts b/src/extension.ts index 2afc8db5..1193b11a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -106,6 +106,8 @@ import { } from "./services/workspaceTreeProvider"; import { activeEditorChanged, + ConnectionLensProvider, + pickConnection, runScratchpad, workspaceFoldersChanged, } from "./commands/scratchpadCommand"; @@ -396,11 +398,25 @@ export async function activate(context: ExtensionContext) { commands.registerCommand("kdb.refreshScratchpadExplorer", () => { ext.scratchpadTreeProvider.refresh(); }), + commands.registerCommand("kdb.pickConnection", async () => { + const editor = window.activeTextEditor; + if (editor) { + await pickConnection(editor.document.uri); + } + }), commands.registerCommand("kdb.runScratchpad", async () => { - await runScratchpad(window.activeTextEditor); + const editor = window.activeTextEditor; + if (editor) { + await runScratchpad(editor.document.uri); + } }), DataSourceEditorProvider.register(context), + + languages.registerCodeLensProvider( + { pattern: "**/*.kdb.{q,py}" }, + new ConnectionLensProvider(), + ), ); const lastResult: QueryResult | undefined = undefined; @@ -474,11 +490,6 @@ export async function activate(context: ExtensionContext) { }), ); - const watcher = workspace.createFileSystemWatcher("**/*.kdb.{json,q,py}"); - watcher.onDidCreate(workspaceFoldersChanged); - watcher.onDidDelete(workspaceFoldersChanged); - workspace.onDidChangeWorkspaceFolders(workspaceFoldersChanged); - ext.runScratchpadItem = window.createStatusBarItem( StatusBarAlignment.Right, 10000, @@ -495,8 +506,11 @@ export async function activate(context: ExtensionContext) { arguments: [], }; + const watcher = workspace.createFileSystemWatcher("**/*.kdb.{json,q,py}"); + watcher.onDidCreate(workspaceFoldersChanged); + watcher.onDidDelete(workspaceFoldersChanged); + workspace.onDidChangeWorkspaceFolders(workspaceFoldersChanged); window.onDidChangeActiveTextEditor(activeEditorChanged); - activeEditorChanged(window.activeTextEditor); //q language server diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 74d90926..6a2dc45c 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -23,13 +23,13 @@ import { } from "vscode"; export class WorkspaceTreeProvider implements TreeDataProvider { - private _onDidChangeTreeData = new EventEmitter(); + private _onDidChangeTreeData = new EventEmitter(); readonly onDidChangeTreeData = this._onDidChangeTreeData.event; constructor(private readonly glob: string) {} refresh() { - this._onDidChangeTreeData.fire(undefined); + this._onDidChangeTreeData.fire(); } getChildren(element?: FileTreeItem) { @@ -60,7 +60,7 @@ export class FileTreeItem extends TreeItem { const folder = workspace.getWorkspaceFolder(resourceUri); if (folder) { this.pattern = new RelativePattern(folder, glob); - this.collapsibleState = TreeItemCollapsibleState.Collapsed; + this.collapsibleState = TreeItemCollapsibleState.Expanded; } } else { this.command = { From a88f16d64a2f73aea596d17099ea162aefefe819 Mon Sep 17 00:00:00 2001 From: ecmel Date: Sat, 20 Apr 2024 17:16:43 +0300 Subject: [PATCH 05/32] use vscode icons --- package.json | 20 ++++---------------- resources/dark/plus.svg | 3 --- src/commands/scratchpadCommand.ts | 7 ++----- 3 files changed, 6 insertions(+), 24 deletions(-) delete mode 100644 resources/dark/plus.svg diff --git a/package.json b/package.json index 513a0e3b..33b79082 100644 --- a/package.json +++ b/package.json @@ -185,34 +185,22 @@ { "command": "kdb.createDataSource", "title": "Add a new data source", - "icon": { - "light": "resources/light/plus.svg", - "dark": "resources/dark/plus.svg" - } + "icon": "$(add)" }, { "command": "kdb.refreshDataSourceExplorer", "title": "Refresh data sources", - "icon": { - "light": "resources/light/refresh.svg", - "dark": "resources/dark/refresh.svg" - } + "icon": "$(refresh)" }, { "command": "kdb.createScratchpad", "title": "Add a new scratchpad", - "icon": { - "light": "resources/light/plus.svg", - "dark": "resources/dark/plus.svg" - } + "icon": "$(add)" }, { "command": "kdb.refreshScratchpadExplorer", "title": "Refresh scratchpads", - "icon": { - "light": "resources/light/refresh.svg", - "dark": "resources/dark/refresh.svg" - } + "icon": "$(refresh)" }, { "command": "kdb.runScratchpad", diff --git a/resources/dark/plus.svg b/resources/dark/plus.svg deleted file mode 100644 index 027d67c6..00000000 --- a/resources/dark/plus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index ba54b012..c8cc1b1b 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -25,7 +25,7 @@ import { import { ext } from "../extensionVariables"; function setRunScratchpadItemText(text: string) { - ext.runScratchpadItem.text = `$(notebook-execute) ${text}`; + ext.runScratchpadItem.text = `$(run) ${text}`; } function getServers() { @@ -111,18 +111,15 @@ export async function runScratchpad(uri: Uri) { export class ConnectionLensProvider implements CodeLensProvider { provideCodeLenses(document: TextDocument): ProviderResult { - const uri = document.uri; - const server = getServerForScratchpad(uri); + const server = getServerForScratchpad(document.uri); const top = new Range(0, 0, 0, 0); const runScratchpad = new CodeLens(top, { command: "kdb.runScratchpad", title: server ? `Run on ${server}` : "Run", - arguments: [uri], }); const pickConnection = new CodeLens(top, { command: "kdb.pickConnection", title: "Choose Connection", - arguments: [uri], }); return [runScratchpad, pickConnection]; } From 8c4cf8525383800ec59628d3465e3076a8b60e73 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Thu, 25 Apr 2024 13:36:07 +0100 Subject: [PATCH 06/32] scratchpad status icon and status methods --- resources/scratchpad-active.svg | 9 +++++++++ resources/scratchpad-connected.svg | 9 +++++++++ resources/scratchpad.svg | 9 +++++++++ src/extensionVariables.ts | 3 +++ src/models/scratchpad.ts | 19 ++++++++++++++++++ src/utils/core.ts | 14 +++++++++++++ test/suite/utils.test.ts | 32 ++++++++++++++++++++++++++++++ 7 files changed, 95 insertions(+) create mode 100644 resources/scratchpad-active.svg create mode 100644 resources/scratchpad-connected.svg create mode 100644 resources/scratchpad.svg create mode 100644 src/models/scratchpad.ts diff --git a/resources/scratchpad-active.svg b/resources/scratchpad-active.svg new file mode 100644 index 00000000..2c888c61 --- /dev/null +++ b/resources/scratchpad-active.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/resources/scratchpad-connected.svg b/resources/scratchpad-connected.svg new file mode 100644 index 00000000..68346b6c --- /dev/null +++ b/resources/scratchpad-connected.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/resources/scratchpad.svg b/resources/scratchpad.svg new file mode 100644 index 00000000..e03cc1a4 --- /dev/null +++ b/resources/scratchpad.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 5da97f93..bf17ecca 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -33,6 +33,7 @@ import { QueryHistoryProvider } from "./services/queryHistoryProvider"; import { KdbResultsViewProvider } from "./services/resultsPanelProvider"; import AuthSettings from "./utils/secretStorage"; import { WorkspaceTreeProvider } from "./services/workspaceTreeProvider"; +import { ScratchpadFile } from "./models/scratchpad"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { @@ -46,6 +47,8 @@ export namespace ext { export let scratchpadTreeProvider: WorkspaceTreeProvider; export let dataSourceTreeProvider: WorkspaceTreeProvider; export let runScratchpadItem: StatusBarItem; + export const activeScratchPadList: Array = []; + export const connectedScratchPadList: Array = []; export let serverObjects: ServerObject; export let openSslVersion: string | null; export let resultPanelCSV: string; diff --git a/src/models/scratchpad.ts b/src/models/scratchpad.ts new file mode 100644 index 00000000..be2fb455 --- /dev/null +++ b/src/models/scratchpad.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +export interface ScratchpadFile { + name: string; + originalName?: string; + activeConnection?: string; + code: string; +} diff --git a/src/utils/core.ts b/src/utils/core.ts index 4b1f2106..61e7f780 100644 --- a/src/utils/core.ts +++ b/src/utils/core.ts @@ -259,6 +259,20 @@ export function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } +export function getScratchpadStatusIcon(label: string) { + if ( + ext.activeScratchPadList?.some((scratchpad) => scratchpad.name === label) + ) { + return "-active"; + } else if ( + ext.connectedScratchPadList?.some((scratchpad) => scratchpad.name === label) + ) { + return "-connected"; + } else { + return ""; + } +} + export async function checkLocalInstall(): Promise { const QHOME = workspace.getConfiguration().get("kdb.qHomeDirectory"); if (QHOME || env.QHOME) { diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index c703e52d..8867fef4 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -49,6 +49,7 @@ import { import { DataSourceTypes } from "../../src/models/dataSource"; import { InsightDetails } from "../../src/models/insights"; import { Parse } from "../../src/ipc/parse.qlist"; +import { ScratchpadFile } from "../../src/models/scratchpad"; interface ITestItem extends vscode.QuickPickItem { id: number; @@ -175,6 +176,37 @@ describe("Utils", () => { assert.strictEqual(ext.hideDetailedConsoleQueryOutput, false); }); }); + + describe("getScratchpadStatusIcon", () => { + const scratchpadDummy: ScratchpadFile = { + name: "test", + code: "", + }; + beforeEach(() => { + ext.activeScratchPadList.length = 0; + ext.connectedScratchPadList.length = 0; + }); + afterEach(() => { + ext.activeScratchPadList.length = 0; + ext.connectedScratchPadList.length = 0; + }); + it("should return active if scratchpad label is on the active list", () => { + ext.activeScratchPadList.push(scratchpadDummy); + ext.connectedScratchPadList.push(scratchpadDummy); + const result = coreUtils.getScratchpadStatusIcon("test"); + assert.strictEqual(result, "-active"); + }); + it("should return connected if scratchpad label is on the connected list", () => { + ext.connectedScratchPadList.push(scratchpadDummy); + const result = coreUtils.getScratchpadStatusIcon("test"); + assert.strictEqual(result, "-connected"); + }); + + it("should return empty string if scratchpad label is not on the active or connected list", () => { + const result = coreUtils.getScratchpadStatusIcon("test"); + assert.strictEqual(result, ""); + }); + }); }); describe("dataSource", () => { From 353ade461619e74263a9b67fb8f01b8cc5552855 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 29 Apr 2024 12:26:16 +0300 Subject: [PATCH 07/32] cleanup activationEvents --- package.json | 36 +++++++++++++++--------------------- src/extension.ts | 3 --- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 1daf2c15..6594aa70 100644 --- a/package.json +++ b/package.json @@ -32,30 +32,11 @@ ], "activationEvents": [ "onCommand:kdb.installTools", - "onCommand:kdb.addConnection", "onCommand:kdb.newConnection.createNewInsightConnection", "onCommand:kdb.newConnection.createNewConnection", "onCommand:kdb.newConnection.createNewBundledConnection", - "onCommand:kdb.removeConnection", - "onCommand:kdb.connect", - "onCommand:kdb.active.connection", - "onCommand:kdb.disconnect", - "onCommand:kdb.addAuthentication", - "onCommand:kdb.enableTLS", - "onCommand:kdb.dataSource.addDataSource", - "onCommand:kdb.dataSource.saveDataSource", - "onCommand:kdb.dataSource.runDataSource", - "onCommand:kdb.dataSource.renameDataSource", - "onCommand:kdb.dataSource.deleteDataSource", - "onCommand:kdb.dataSource.openDataSource", - "onCommand:kdb.resultsPanel.update", - "onCommand:kdb.resultsPanel.clear", - "onCommand:kdb.resultsPanel.export.csv", - "onView:kdb-servers", - "onView:kdb-query-history", - "onView:kdb-datasources-explorer", - "onView:kdb-results", - "onTerminalProfile:kdb.q-terminal" + "onTerminalProfile:kdb.q-terminal", + "onLanguage:python" ], "main": "./out/extension.js", "contributes": { @@ -214,53 +195,64 @@ "icon": "$(add)" }, { + "category": "KX", "command": "kdb.refreshDataSourceExplorer", "title": "Refresh data sources", "icon": "$(refresh)" }, { + "category": "KX", "command": "kdb.createScratchpad", "title": "Add a new scratchpad", "icon": "$(add)" }, { + "category": "KX", "command": "kdb.refreshScratchpadExplorer", "title": "Refresh scratchpads", "icon": "$(refresh)" }, { + "category": "KX", "command": "kdb.runScratchpad", "title": "Run scratchpad" }, { + "category": "KX", "command": "kdb.pickConnection", "title": "Pick connection" }, { + "category": "KX", "command": "kdb.createDataSource", "title": "Add a new data source", "icon": "$(add)" }, { + "category": "KX", "command": "kdb.refreshDataSourceExplorer", "title": "Refresh data sources", "icon": "$(refresh)" }, { + "category": "KX", "command": "kdb.createScratchpad", "title": "Add a new scratchpad", "icon": "$(add)" }, { + "category": "KX", "command": "kdb.refreshScratchpadExplorer", "title": "Refresh scratchpads", "icon": "$(refresh)" }, { + "category": "KX", "command": "kdb.runScratchpad", "title": "Run scratchpad" }, { + "category": "KX", "command": "kdb.pickConnection", "title": "Pick connection" }, @@ -290,10 +282,12 @@ "title": "Connect server" }, { + "category": "KX", "command": "kdb.active.connection", "title": "Active connection" }, { + "category": "KX", "command": "kdb.addAuthentication", "title": "Add Authentication", "position": "end" diff --git a/src/extension.ts b/src/extension.ts index 669e1102..30181ee5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,16 +14,13 @@ import { env } from "node:process"; import path from "path"; import { - CancellationToken, Command, commands, EventEmitter, ExtensionContext, languages, - Position, Range, StatusBarAlignment, - TextDocument, TextDocumentContentProvider, ThemeColor, Uri, From fb6336d86d74e724dae33d6c24657f22860651bd Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 29 Apr 2024 23:59:01 +0300 Subject: [PATCH 08/32] fixes KXI-38122, KXI-36434, KXI-37785, KXI-41647 --- package.json | 70 +++++++++++++++---------------- src/classes/localConnection.ts | 1 + src/commands/scratchpadCommand.ts | 69 +++++++++++++++++++++++++++--- src/commands/serverCommand.ts | 38 ++++++++++------- src/extension.ts | 13 +++--- src/extensionVariables.ts | 2 + 6 files changed, 131 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index 6594aa70..0faf3c64 100644 --- a/package.json +++ b/package.json @@ -183,10 +183,7 @@ "category": "KX", "command": "kdb.refreshServerObjects", "title": "Refresh server objects", - "icon": { - "light": "resources/light/refresh.svg", - "dark": "resources/dark/refresh.svg" - } + "icon": "$(refresh)" }, { "category": "KX", @@ -269,7 +266,8 @@ { "category": "KX", "command": "kdb.addConnection", - "title": "Add new connection" + "title": "Add new connection", + "icon": "$(add)" }, { "category": "KX", @@ -320,7 +318,8 @@ { "category": "KX", "command": "kdb.queryHistory.clear", - "title": "Clear query history" + "title": "Clear query history", + "icon": "$(clear-all)" }, { "category": "KX", @@ -394,7 +393,7 @@ "command": "kdb.execute.fileQuery", "title": "Execute Entire File", "icon": "$(run)", - "enablement": "kdb.connected && editorFocus" + "enablement": "kdb.connected" }, { "category": "KX", @@ -436,7 +435,7 @@ "command": "kdb.execute.pythonScratchpadQuery", "key": "ctrl+d", "mac": "cmd+d", - "when": "editorLangId == python && kdb.insightsConnected" + "when": "editorLangId == python && kdb.insightsConnected" }, { "command": "kdb.execute.pythonFileScratchpadQuery", @@ -609,52 +608,53 @@ "view/title": [ { "command": "kdb.addConnection", - "when": "view == kdb-servers" + "when": "view == kdb-servers", + "group": "navigation@1" }, { "command": "kdb.refreshServerObjects", "when": "view == kdb-servers", - "group": "navigation" - }, - { - "command": "kdb.dataSource.addDataSource", - "when": "view == kdb-datasources-explorer", - "group": "dataSourceFiles" - }, - { - "command": "kdb.resultsPanel.clear", - "when": "view == kdb-results", - "group": "resultsPanel" - }, - { - "command": "kdb.queryHistory.clear", - "when": "view == kdb-query-history", - "group": "queryHistory" - }, - { - "command": "kdb.resultsPanel.export.csv", - "when": "view == kdb-results", - "group": "resultsPanel" + "group": "navigation@2" }, { "command": "kdb.createDataSource", "when": "view == kdb-datasource-explorer", - "group": "navigation" + "group": "navigation@1" }, { "command": "kdb.refreshDataSourceExplorer", "when": "view == kdb-datasource-explorer", - "group": "navigation" + "group": "navigation@2" }, { "command": "kdb.createScratchpad", "when": "view == kdb-scratchpad-explorer", - "group": "navigation" + "group": "navigation@1" }, { "command": "kdb.refreshScratchpadExplorer", "when": "view == kdb-scratchpad-explorer", - "group": "navigation" + "group": "navigation@2" + }, + { + "command": "kdb.queryHistory.clear", + "when": "view == kdb-query-history", + "group": "navigation@1" + }, + { + "command": "kdb.resultsPanel.clear", + "when": "view == kdb-results", + "group": "resultsPanel" + }, + { + "command": "kdb.resultsPanel.export.csv", + "when": "view == kdb-results", + "group": "resultsPanel" + }, + { + "command": "kdb.dataSource.addDataSource", + "when": "view == kdb-datasources-explorer", + "group": "dataSourceFiles" } ], "view/item/context": [ @@ -760,7 +760,7 @@ { "command": "kdb.execute.fileQuery", "group": "q", - "when": "editorLangId == q && kdb.connected" + "when": "editorLangId == q && kdb.connected" }, { "command": "kdb.execute.pythonScratchpadQuery", diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index 4fb9e608..b4dabc16 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -69,6 +69,7 @@ export class LocalConnection { return this.connection; } + // TODO MS This function is not async public async connect( callback: nodeq.AsyncValueCallback, ): Promise { diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index c8cc1b1b..bacb8cd1 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -23,6 +23,10 @@ import { workspace, } from "vscode"; import { ext } from "../extensionVariables"; +import { ConnectionManagementService } from "../services/connectionManagerService"; +import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; +import { activeConnection, connect, runQuery } from "./serverCommand"; +import { ExecutionTypes } from "../models/execution"; function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(run) ${text}`; @@ -30,11 +34,19 @@ function setRunScratchpadItemText(text: string) { function getServers() { const conf = workspace.getConfiguration("kdb"); - const servers = conf.get<{ [key: string]: { serverName: string } }>( + const servers = conf.get<{ [key: string]: { serverAlias: string } }>( "servers", {}, ); - return Object.keys(servers).map((key) => servers[key].serverName); + const insights = conf.get<{ [key: string]: { alias: string } }>( + "insightsEnterpriseConnections", + {}, + ); + + return [ + ...Object.keys(servers).map((key) => servers[key].serverAlias), + ...Object.keys(insights).map((key) => insights[key].alias), + ]; } function getServerForScratchpad(uri: Uri) { @@ -61,10 +73,22 @@ export function workspaceFoldersChanged() { ext.scratchpadTreeProvider.refresh(); } +function setRealActiveTextEditor(editor?: TextEditor | undefined) { + if (editor) { + const scheme = editor.document.uri.scheme; + if (scheme === "file") { + ext.activeTextEditor = editor; + } + } else { + ext.activeTextEditor = undefined; + } +} + export function activeEditorChanged(editor?: TextEditor | undefined) { + setRealActiveTextEditor(editor); const item = ext.runScratchpadItem; - if (editor) { - const uri = editor.document.uri; + if (ext.activeTextEditor) { + const uri = ext.activeTextEditor.document.uri; const path = uri.path; if (path.endsWith("kdb.q") || path.endsWith("kdb.py")) { const server = getServerForScratchpad(uri); @@ -105,7 +129,42 @@ export async function runScratchpad(uri: Uri) { } if (server) { - window.showInformationMessage(`Running scratchpad on ${server}`); + const servers = await ext.serverProvider.getChildren(); + const found = servers.find((item) => { + if (item instanceof InsightsNode) { + return item.details.alias === server; + } else if (item instanceof KdbNode) { + return item.details.serverAlias === server; + } + return false; + }); + + const node = found as KdbNode; + if (found) { + const cms = new ConnectionManagementService(); + if (!cms.isConnected(node.label)) { + const action = await window.showWarningMessage( + `${node.label} not connected`, + "Connect", + ); + if (action === "Connect") { + await connect(node); + } else { + return; + } + } + + activeConnection(node); + + const isPython = uri.path.endsWith(".py"); + const type = isPython + ? ExecutionTypes.PythonQueryFile + : ExecutionTypes.QueryFile; + await runQuery(type); + ext.activeConnection?.update(); + } else { + window.showErrorMessage(`${node.label} not found`); + } } } diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 9243988b..817621b5 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -16,7 +16,14 @@ import { readFileSync } from "fs-extra"; import { jwtDecode } from "jwt-decode"; import { join } from "path"; import * as url from "url"; -import { Position, ProgressLocation, Range, commands, window } from "vscode"; +import { + Position, + ProgressLocation, + Range, + commands, + window, + workspace, +} from "vscode"; import { ext } from "../extensionVariables"; import { isCompressed, uncompress } from "../ipc/c"; import { GetDataObjectPayload } from "../models/data"; @@ -272,7 +279,7 @@ export async function removeConnection(viewItem: KdbNode | InsightsNode) { export async function connect(viewItem: KdbNode | InsightsNode): Promise { const connMngService = new ConnectionManagementService(); - commands.executeCommand("kdb-results.focus"); + await commands.executeCommand("kdb-results.focus"); ExecutionConsole.start(); // handle cleaning up existing connection if (ext.activeConnection !== undefined) { @@ -284,17 +291,14 @@ export async function connect(viewItem: KdbNode | InsightsNode): Promise { // check for TLS support if (viewItem.details.tls) { if (!(await checkOpenSslInstalled())) { - window - .showInformationMessage( - "TLS support requires OpenSSL to be installed.", - "More Info", - "Cancel", - ) - .then(async (result) => { - if (result === "More Info") { - await openUrl("https://code.kx.com/q/kb/ssl/"); - } - }); + const result = await window.showInformationMessage( + "TLS support requires OpenSSL to be installed.", + "More Info", + "Cancel", + ); + if (result === "More Info") { + openUrl("https://code.kx.com/q/kb/ssl/"); + } } } } @@ -328,6 +332,7 @@ export async function disconnect(connLabel: string): Promise { } } +// TODO MS kdb.execute.entireFile -> kdb.execute.fileQuery export async function executeQuery( query: string, context?: string, @@ -419,8 +424,9 @@ export function getConextForRerunQuery(query: string): string { return context; } -export function runQuery(type: ExecutionTypes, rerunQuery?: string) { - const editor = window.activeTextEditor; +// TODO MS +export async function runQuery(type: ExecutionTypes, rerunQuery?: string) { + const editor = ext.activeTextEditor; if (!editor) { return false; } @@ -457,7 +463,7 @@ export function runQuery(type: ExecutionTypes, rerunQuery?: string) { } break; } - executeQuery(query, context, isPython); + await executeQuery(query, context, isPython); } export function rerunQuery(rerunQueryElement: QueryHistory) { diff --git a/src/extension.ts b/src/extension.ts index 30181ee5..9804eca7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -334,24 +334,25 @@ export async function activate(context: ExtensionContext) { } }), commands.registerCommand("kdb.execute.selectedQuery", async () => { - runQuery(ExecutionTypes.QuerySelection); + await runQuery(ExecutionTypes.QuerySelection); ext.activeConnection?.update(); }), commands.registerCommand("kdb.execute.fileQuery", async () => { - runQuery(ExecutionTypes.QueryFile); + await runQuery(ExecutionTypes.QueryFile); ext.activeConnection?.update(); }), commands.registerCommand("kdb.execute.pythonScratchpadQuery", async () => { - runQuery(ExecutionTypes.PythonQuerySelection); + await runQuery(ExecutionTypes.PythonQuerySelection); ext.activeConnection?.update(); }), commands.registerCommand( "kdb.execute.pythonFileScratchpadQuery", async () => { - runQuery(ExecutionTypes.PythonQueryFile); + await runQuery(ExecutionTypes.PythonQueryFile); ext.activeConnection?.update(); }, ), + // TODO MS REMOVE commands.registerCommand("kdb.execute.entireFile", async (uri: Uri) => { if (!uri) { return; @@ -403,13 +404,13 @@ export async function activate(context: ExtensionContext) { ext.scratchpadTreeProvider.refresh(); }), commands.registerCommand("kdb.pickConnection", async () => { - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; if (editor) { await pickConnection(editor.document.uri); } }), commands.registerCommand("kdb.runScratchpad", async () => { - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; if (editor) { await runScratchpad(editor.document.uri); } diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index c870b238..c43d4c90 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -17,6 +17,7 @@ import { languages, OutputChannel, StatusBarItem, + TextEditor, } from "vscode"; import { LanguageClient } from "vscode-languageclient/node"; import { LocalProcess } from "./models/localProcess"; @@ -39,6 +40,7 @@ import { InsightsConnection } from "./classes/insightsConnection"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { + export let activeTextEditor: TextEditor | undefined; export let context: ExtensionContext; export let outputChannel: OutputChannel; export let consolePanel: OutputChannel; From fae17e0828c1c63c7cf28866866d3974d188af3f Mon Sep 17 00:00:00 2001 From: ecmel Date: Tue, 30 Apr 2024 10:34:04 +0300 Subject: [PATCH 09/32] working draft --- src/commands/scratchpadCommand.ts | 29 +++++++++++++++++++++++++---- src/extension.ts | 3 --- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index bacb8cd1..6d7866a1 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -28,6 +28,8 @@ import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; import { activeConnection, connect, runQuery } from "./serverCommand"; import { ExecutionTypes } from "../models/execution"; +const connectionService = new ConnectionManagementService(); + function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(run) ${text}`; } @@ -68,6 +70,27 @@ async function setServerForScratchpad(uri: Uri, server: string | undefined) { await conf.update("scratchpads", scratchpads); } +async function waitForConnection(name: string) { + return new Promise((resolve, reject) => { + let count = 0; + const retry = () => { + count++; + setTimeout(() => { + if (connectionService.isConnected(name)) { + resolve(); + } else { + if (count < 5) { + retry(); + } else { + reject(`Can not connect to ${name}`); + } + } + }, 50); + }; + retry(); + }); +} + export function workspaceFoldersChanged() { ext.dataSourceTreeProvider.refresh(); ext.scratchpadTreeProvider.refresh(); @@ -141,19 +164,18 @@ export async function runScratchpad(uri: Uri) { const node = found as KdbNode; if (found) { - const cms = new ConnectionManagementService(); - if (!cms.isConnected(node.label)) { + if (!connectionService.isConnected(node.label)) { const action = await window.showWarningMessage( `${node.label} not connected`, "Connect", ); if (action === "Connect") { await connect(node); + await waitForConnection(node.label); } else { return; } } - activeConnection(node); const isPython = uri.path.endsWith(".py"); @@ -161,7 +183,6 @@ export async function runScratchpad(uri: Uri) { ? ExecutionTypes.PythonQueryFile : ExecutionTypes.QueryFile; await runQuery(type); - ext.activeConnection?.update(); } else { window.showErrorMessage(`${node.label} not found`); } diff --git a/src/extension.ts b/src/extension.ts index 9804eca7..a3838eb2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -463,9 +463,6 @@ export async function activate(context: ExtensionContext) { StatusBarAlignment.Right, 10000, ); - - ext.runScratchpadItem.tooltip = "Run scratchpad"; - ext.runScratchpadItem.backgroundColor = new ThemeColor( "statusBarItem.warningBackground", ); From b68ac676d81a4fd3875f993d1aa6952a40e4ad83 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 1 May 2024 09:37:34 +0300 Subject: [PATCH 10/32] use more of connection service --- src/commands/scratchpadCommand.ts | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index 6d7866a1..882edbf3 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -25,7 +25,7 @@ import { import { ext } from "../extensionVariables"; import { ConnectionManagementService } from "../services/connectionManagerService"; import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; -import { activeConnection, connect, runQuery } from "./serverCommand"; +import { runQuery } from "./serverCommand"; import { ExecutionTypes } from "../models/execution"; const connectionService = new ConnectionManagementService(); @@ -78,12 +78,10 @@ async function waitForConnection(name: string) { setTimeout(() => { if (connectionService.isConnected(name)) { resolve(); + } else if (count < 5) { + retry(); } else { - if (count < 5) { - retry(); - } else { - reject(`Can not connect to ${name}`); - } + reject(`Can not connect to ${name}`); } }, 50); }; @@ -153,30 +151,30 @@ export async function runScratchpad(uri: Uri) { if (server) { const servers = await ext.serverProvider.getChildren(); - const found = servers.find((item) => { + // ext.connectionsList is not used, we can hit from workspace + const connection = servers.find((item) => { if (item instanceof InsightsNode) { return item.details.alias === server; } else if (item instanceof KdbNode) { return item.details.serverAlias === server; } return false; - }); + }) as KdbNode | InsightsNode; - const node = found as KdbNode; - if (found) { - if (!connectionService.isConnected(node.label)) { + if (connection) { + if (!connectionService.isConnected(connection.label)) { const action = await window.showWarningMessage( - `${node.label} not connected`, + `${connection.label} not connected`, "Connect", ); if (action === "Connect") { - await connect(node); - await waitForConnection(node.label); + await connectionService.connect(connection.label); + await waitForConnection(connection.label); } else { return; } } - activeConnection(node); + connectionService.setActiveConnection(connection); const isPython = uri.path.endsWith(".py"); const type = isPython @@ -184,7 +182,7 @@ export async function runScratchpad(uri: Uri) { : ExecutionTypes.QueryFile; await runQuery(type); } else { - window.showErrorMessage(`${node.label} not found`); + window.showErrorMessage(`${server} not found`); } } } From 91204571951d9ff113ae8f6faff85faf05b62c75 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 1 May 2024 14:33:20 +0300 Subject: [PATCH 11/32] added icons --- src/classes/localConnection.ts | 2 + src/commands/scratchpadCommand.ts | 86 ++++++++++++++---------- src/commands/serverCommand.ts | 6 ++ src/extension.ts | 16 +++-- src/services/connectionManagerService.ts | 6 ++ src/services/workspaceTreeProvider.ts | 42 ++++++++++-- 6 files changed, 115 insertions(+), 43 deletions(-) diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index b4dabc16..9fccb0d9 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -76,6 +76,8 @@ export class LocalConnection { nodeq.connect(this.options, (err, conn) => { if (err || !conn) { ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); window.showErrorMessage( `Connection to server ${this.options.host}:${this.options.port} failed! Details: ${err?.message}`, diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index 882edbf3..e887679f 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -51,15 +51,6 @@ function getServers() { ]; } -function getServerForScratchpad(uri: Uri) { - const conf = workspace.getConfiguration("kdb", uri); - const scratchpads = conf.get<{ [key: string]: string | undefined }>( - "scratchpads", - {}, - ); - return scratchpads[uri.path]; -} - async function setServerForScratchpad(uri: Uri, server: string | undefined) { const conf = workspace.getConfiguration("kdb", uri); const scratchpads = conf.get<{ [key: string]: string | undefined }>( @@ -89,9 +80,46 @@ async function waitForConnection(name: string) { }); } +async function getConnectionForServer(server: string) { + if (server) { + const servers = await ext.serverProvider.getChildren(); + return servers.find((item) => { + if (item instanceof InsightsNode) { + return item.details.alias === server; + } else if (item instanceof KdbNode) { + return item.details.serverAlias === server; + } + return false; + }) as KdbNode | InsightsNode; + } +} + +export function getServerForUri(uri: Uri) { + const conf = workspace.getConfiguration("kdb", uri); + const scratchpads = conf.get<{ [key: string]: string | undefined }>( + "scratchpads", + {}, + ); + return scratchpads[uri.path]; +} + +export function getConnectionForUri(uri: Uri) { + const server = getServerForUri(uri); + if (server) { + return ext.connectionsList.find((item) => { + if (item instanceof InsightsNode) { + return item.details.alias === server; + } else if (item instanceof KdbNode) { + return item.details.serverAlias === server; + } + return false; + }) as KdbNode | InsightsNode; + } +} + export function workspaceFoldersChanged() { - ext.dataSourceTreeProvider.refresh(); - ext.scratchpadTreeProvider.refresh(); + ext.dataSourceTreeProvider.reload(); + ext.scratchpadTreeProvider.reload(); } function setRealActiveTextEditor(editor?: TextEditor | undefined) { @@ -111,8 +139,8 @@ export function activeEditorChanged(editor?: TextEditor | undefined) { if (ext.activeTextEditor) { const uri = ext.activeTextEditor.document.uri; const path = uri.path; - if (path.endsWith("kdb.q") || path.endsWith("kdb.py")) { - const server = getServerForScratchpad(uri); + if (path.endsWith(".kdb.q") || path.endsWith(".kdb.py")) { + const server = getServerForUri(uri); setRunScratchpadItemText(server || "Run"); item.show(); } else { @@ -124,7 +152,7 @@ export function activeEditorChanged(editor?: TextEditor | undefined) { } export async function pickConnection(uri: Uri) { - const server = getServerForScratchpad(uri); + const server = getServerForUri(uri); let picked = await window.showQuickPick(["(none)", ...getServers()], { title: "Choose a connection", @@ -143,28 +171,18 @@ export async function pickConnection(uri: Uri) { } export async function runScratchpad(uri: Uri) { - let server = getServerForScratchpad(uri); + let server = getServerForUri(uri); if (!server) { server = await pickConnection(uri); } if (server) { - const servers = await ext.serverProvider.getChildren(); - // ext.connectionsList is not used, we can hit from workspace - const connection = servers.find((item) => { - if (item instanceof InsightsNode) { - return item.details.alias === server; - } else if (item instanceof KdbNode) { - return item.details.serverAlias === server; - } - return false; - }) as KdbNode | InsightsNode; - + const connection = await getConnectionForServer(server); if (connection) { if (!connectionService.isConnected(connection.label)) { const action = await window.showWarningMessage( - `${connection.label} not connected`, + `${connection.label} is not connected`, "Connect", ); if (action === "Connect") { @@ -176,20 +194,20 @@ export async function runScratchpad(uri: Uri) { } connectionService.setActiveConnection(connection); - const isPython = uri.path.endsWith(".py"); - const type = isPython - ? ExecutionTypes.PythonQueryFile - : ExecutionTypes.QueryFile; - await runQuery(type); + await runQuery( + uri.path.endsWith(".py") + ? ExecutionTypes.PythonQueryFile + : ExecutionTypes.QueryFile, + ); } else { - window.showErrorMessage(`${server} not found`); + window.showErrorMessage(`${server} is not found`); } } } export class ConnectionLensProvider implements CodeLensProvider { provideCodeLenses(document: TextDocument): ProviderResult { - const server = getServerForScratchpad(document.uri); + const server = getServerForUri(document.uri); const top = new Range(0, 0, 0, 0); const runScratchpad = new CodeLens(top, { command: "kdb.runScratchpad", diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 817621b5..f05f0d2d 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -311,6 +311,8 @@ export async function connect(viewItem: KdbNode | InsightsNode): Promise { refreshDataSourcesPanel(); ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); } export function activeConnection(viewItem: KdbNode | InsightsNode): void { @@ -318,6 +320,8 @@ export function activeConnection(viewItem: KdbNode | InsightsNode): void { connMngService.setActiveConnection(viewItem); refreshDataSourcesPanel(); ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); } export async function disconnect(connLabel: string): Promise { @@ -329,6 +333,8 @@ export async function disconnect(connLabel: string): Promise { queryConsole.dispose(); DataSourcesPanel.close(); ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); } } diff --git a/src/extension.ts b/src/extension.ts index a3838eb2..6b339ba5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -127,8 +127,14 @@ export async function activate(context: ExtensionContext) { ext.resultsViewProvider = new KdbResultsViewProvider( ext.context.extensionUri, ); - ext.scratchpadTreeProvider = new WorkspaceTreeProvider("**/*.kdb.{q,py}"); - ext.dataSourceTreeProvider = new WorkspaceTreeProvider("**/*.kdb.json"); + ext.scratchpadTreeProvider = new WorkspaceTreeProvider( + "**/*.kdb.{q,py}", + "scratchpad", + ); + ext.dataSourceTreeProvider = new WorkspaceTreeProvider( + "**/*.kdb.json", + "p-q-connection", + ); commands.executeCommand("setContext", "kdb.QHOME", env.QHOME); @@ -246,6 +252,8 @@ export async function activate(context: ExtensionContext) { ), commands.registerCommand("kdb.refreshServerObjects", () => { ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); ext.activeConnection?.update(); }), commands.registerCommand( @@ -389,7 +397,7 @@ export async function activate(context: ExtensionContext) { }, ), commands.registerCommand("kdb.refreshDataSourceExplorer", () => { - ext.dataSourceTreeProvider.refresh(); + ext.dataSourceTreeProvider.reload(); }), commands.registerCommand( "kdb.createScratchpad", @@ -401,7 +409,7 @@ export async function activate(context: ExtensionContext) { }, ), commands.registerCommand("kdb.refreshScratchpadExplorer", () => { - ext.scratchpadTreeProvider.refresh(); + ext.scratchpadTreeProvider.reload(); }), commands.registerCommand("kdb.pickConnection", async () => { const editor = ext.activeTextEditor; diff --git a/src/services/connectionManagerService.ts b/src/services/connectionManagerService.ts index 7c14cb09..7a7fbaa7 100644 --- a/src/services/connectionManagerService.ts +++ b/src/services/connectionManagerService.ts @@ -137,6 +137,8 @@ export class ConnectionManagementService { commands.executeCommand("setContext", "kdb.insightsConnected", false); } ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); } public disconnect(connLabel: string): void { @@ -220,6 +222,8 @@ export class ConnectionManagementService { this.setActiveConnection(connNode); ext.connectionNode = connNode; ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); } public isNotConnectedBehaviour(connLabel: string): void { @@ -254,6 +258,8 @@ export class ConnectionManagementService { `Connection stopped from ${connection.connLabel}`, ); ext.serverProvider.reload(); + ext.dataSourceProvider.refresh(); + ext.scratchpadTreeProvider.reload(); } public async executeQuery( diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 6a2dc45c..2a187786 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -21,14 +21,20 @@ import { Uri, workspace, } from "vscode"; +import Path from "path"; +import { getServerIconState } from "../utils/core"; +import { getConnectionForUri } from "../commands/scratchpadCommand"; export class WorkspaceTreeProvider implements TreeDataProvider { private _onDidChangeTreeData = new EventEmitter(); readonly onDidChangeTreeData = this._onDidChangeTreeData.event; - constructor(private readonly glob: string) {} + constructor( + private readonly glob: string, + private readonly baseIcon: string, + ) {} - refresh() { + reload() { this._onDidChangeTreeData.fire(); } @@ -39,7 +45,9 @@ export class WorkspaceTreeProvider implements TreeDataProvider { const folders = workspace.workspaceFolders; if (folders) { return Promise.resolve( - folders.map((folder) => new FileTreeItem(folder.uri, this.glob)), + folders.map( + (folder) => new FileTreeItem(folder.uri, this.baseIcon, this.glob), + ), ); } return Promise.resolve([]); @@ -53,7 +61,11 @@ export class WorkspaceTreeProvider implements TreeDataProvider { export class FileTreeItem extends TreeItem { private declare pattern?: RelativePattern; - constructor(resourceUri: Uri, glob?: string) { + constructor( + resourceUri: Uri, + private readonly baseIcon: string, + glob?: string, + ) { super(resourceUri); this.id = resourceUri.toString(); if (glob) { @@ -71,12 +83,32 @@ export class FileTreeItem extends TreeItem { } } + private updateIconPath() { + let state = ""; + if (this.resourceUri) { + const connection = getConnectionForUri(this.resourceUri); + if (connection) { + state = getServerIconState(connection.label); + } + } + this.iconPath = Path.join( + __filename, + "../".repeat(2), + "resources", + this.baseIcon + state + ".svg", + ); + } + async getChildren(): Promise { if (this.pattern) { const files = await workspace.findFiles(this.pattern); return files .sort((a, b) => a.path.localeCompare(b.path)) - .map((file) => new FileTreeItem(file)); + .map((file) => { + const item = new FileTreeItem(file, this.baseIcon); + item.updateIconPath(); + return item; + }); } return []; } From 3a38acb09dd80fa048d6fcc1f2c5933d8a76b635 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 1 May 2024 17:38:06 +0300 Subject: [PATCH 12/32] fixed tests --- src/classes/localConnection.ts | 2 - src/commands/scratchpadCommand.ts | 8 +-- src/commands/serverCommand.ts | 70 +++++++++++------------- src/extension.ts | 29 ++++------ src/services/connectionManagerService.ts | 6 -- 5 files changed, 47 insertions(+), 68 deletions(-) diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index 9fccb0d9..b4dabc16 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -76,8 +76,6 @@ export class LocalConnection { nodeq.connect(this.options, (err, conn) => { if (err || !conn) { ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); window.showErrorMessage( `Connection to server ${this.options.host}:${this.options.port} failed! Details: ${err?.message}`, diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index e887679f..980239b7 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -134,10 +134,10 @@ function setRealActiveTextEditor(editor?: TextEditor | undefined) { } export function activeEditorChanged(editor?: TextEditor | undefined) { - setRealActiveTextEditor(editor); + //setRealActiveTextEditor(editor); const item = ext.runScratchpadItem; - if (ext.activeTextEditor) { - const uri = ext.activeTextEditor.document.uri; + if (editor) { + const uri = editor.document.uri; const path = uri.path; if (path.endsWith(".kdb.q") || path.endsWith(".kdb.py")) { const server = getServerForUri(uri); @@ -194,7 +194,7 @@ export async function runScratchpad(uri: Uri) { } connectionService.setActiveConnection(connection); - await runQuery( + runQuery( uri.path.endsWith(".py") ? ExecutionTypes.PythonQueryFile : ExecutionTypes.QueryFile, diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index dd89185f..b534bb98 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -71,7 +71,7 @@ export async function addInsightsConnection(insightsData: InsightDetails) { let insights: Insights | undefined = getInsights(); if (insights != undefined && insights[getHash(insightsData.server!)]) { await window.showErrorMessage( - `Insights instance named ${insightsData.alias} already exists.` + `Insights instance named ${insightsData.alias} already exists.`, ); return; } else { @@ -99,7 +99,7 @@ export async function addInsightsConnection(insightsData: InsightDetails) { Telemetry.sendEvent("Connection.Created.Insights"); } window.showInformationMessage( - `Added Insights connection: ${insightsData.alias}` + `Added Insights connection: ${insightsData.alias}`, ); NewConnectionPannel.close(); } @@ -110,7 +110,7 @@ export async function addInsightsConnection(insightsData: InsightDetails) { export async function addAuthConnection( serverKey: string, username: string, - password: string + password: string, ): Promise { const validUsername = validateServerUsername(username); if (validUsername) { @@ -150,7 +150,7 @@ export async function enableTLS(serverKey: string): Promise { .showErrorMessage( "OpenSSL not found, please ensure this is installed", "More Info", - "Cancel" + "Cancel", ) .then(async (result) => { if (result === "More Info") { @@ -170,13 +170,13 @@ export async function enableTLS(serverKey: string): Promise { } window.showErrorMessage( "Server not found, please ensure this is a correct server", - "Cancel" + "Cancel", ); } export async function addKdbConnection( kdbData: ServerDetails, - isLocal?: boolean + isLocal?: boolean, ): Promise { const aliasValidation = validateServerAlias(kdbData.serverAlias, isLocal!); const hostnameValidation = validateServerName(kdbData.serverName); @@ -200,13 +200,13 @@ export async function addKdbConnection( servers[getHash(`${kdbData.serverName}:${kdbData.serverPort}`)] ) { await window.showErrorMessage( - `Server ${kdbData.serverName}:${kdbData.serverPort} already exists.` + `Server ${kdbData.serverName}:${kdbData.serverPort} already exists.`, ); } else { const key = kdbData.serverAlias != undefined ? getHash( - `${kdbData.serverName}${kdbData.serverPort}${kdbData.serverAlias}` + `${kdbData.serverName}${kdbData.serverPort}${kdbData.serverAlias}`, ) : getHash(`${kdbData.serverName}${kdbData.serverPort}`); if (servers === undefined) { @@ -247,7 +247,7 @@ export async function addKdbConnection( addAuthConnection(key, kdbData.username!, kdbData.password!); } window.showInformationMessage( - `Added kdb connection: ${kdbData.serverAlias}` + `Added kdb connection: ${kdbData.serverAlias}`, ); NewConnectionPannel.close(); } @@ -275,7 +275,7 @@ export async function connect(viewItem: KdbNode | InsightsNode): Promise { const result = await window.showInformationMessage( "TLS support requires OpenSSL to be installed.", "More Info", - "Cancel" + "Cancel", ); if (result === "More Info") { openUrl("https://code.kx.com/q/kb/ssl/"); @@ -292,8 +292,6 @@ export async function connect(viewItem: KdbNode | InsightsNode): Promise { refreshDataSourcesPanel(); ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); } export function activeConnection(viewItem: KdbNode | InsightsNode): void { @@ -301,8 +299,6 @@ export function activeConnection(viewItem: KdbNode | InsightsNode): void { connMngService.setActiveConnection(viewItem); refreshDataSourcesPanel(); ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); } export async function resetScratchPad(): Promise { @@ -319,22 +315,19 @@ export async function disconnect(connLabel: string): Promise { queryConsole.dispose(); DataSourcesPanel.close(); ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); } } -// TODO MS kdb.execute.entireFile -> kdb.execute.fileQuery export async function executeQuery( query: string, context?: string, - isPython?: boolean + isPython?: boolean, ): Promise { const connMngService = new ConnectionManagementService(); const queryConsole = ExecutionConsole.start(); if (ext.activeConnection === undefined && ext.connectionNode === undefined) { window.showInformationMessage( - "Please connect to a KDB instance or Insights Instance to execute a query" + "Please connect to a KDB instance or Insights Instance to execute a query", ); return undefined; } @@ -347,7 +340,7 @@ export async function executeQuery( query, "Query is empty", isConnected, - ext.connectionNode?.label ? ext.connectionNode.label : "" + ext.connectionNode?.label ? ext.connectionNode.label : "", ); return undefined; } @@ -384,8 +377,8 @@ export function getQueryContext(lineNum?: number): string { text = editor.document.getText( new Range( new Position(0, 0), - new Position(lineNum, line.range.end.character) - ) + new Position(lineNum, line.range.end.character), + ), ); } @@ -416,9 +409,8 @@ export function getConextForRerunQuery(query: string): string { return context; } -// TODO MS -export async function runQuery(type: ExecutionTypes, rerunQuery?: string) { - const editor = ext.activeTextEditor; +export function runQuery(type: ExecutionTypes, rerunQuery?: string) { + const editor = window.activeTextEditor; if (!editor) { return false; } @@ -428,7 +420,7 @@ export async function runQuery(type: ExecutionTypes, rerunQuery?: string) { const insightsNode = ext.kdbinsightsNodes.find((n) => ext.connectionNode instanceof InsightsNode ? n === ext.connectionNode?.details.alias + " (connected)" - : false + : false, ); switch (type) { case ExecutionTypes.QuerySelection: @@ -455,7 +447,7 @@ export async function runQuery(type: ExecutionTypes, rerunQuery?: string) { } break; } - await executeQuery(query, context, isPython); + executeQuery(query, context, isPython); } export function rerunQuery(rerunQueryElement: QueryHistory) { @@ -467,7 +459,7 @@ export function rerunQuery(rerunQueryElement: QueryHistory) { executeQuery( rerunQueryElement.query, context, - rerunQueryElement.language !== "q" + rerunQueryElement.language !== "q", ); } else { const dsFile = rerunQueryElement.query as DataSourceFiles; @@ -483,13 +475,13 @@ export async function loadServerObjects(): Promise { ext.activeConnection instanceof InsightsConnection ) { window.showInformationMessage( - "Please connect to a KDB instance to view the objects" + "Please connect to a KDB instance to view the objects", ); return new Array(); } const script = readFileSync( - ext.context.asAbsolutePath(join("resources", "list_mem.q")) + ext.context.asAbsolutePath(join("resources", "list_mem.q")), ).toString(); const cc = "\n" + script + "(::)"; const result = await ext.activeConnection?.executeQueryRaw(cc); @@ -509,7 +501,7 @@ export function writeQueryResultsToConsole( query: string, dataSourceType?: string, isPython?: boolean, - duration?: string + duration?: string, ): void { const queryConsole = ExecutionConsole.start(); const res = Array.isArray(result) @@ -525,7 +517,7 @@ export function writeQueryResultsToConsole( ext.connectionNode?.label ? ext.connectionNode.label : "", dataSourceType, isPython, - duration + duration, ); } else { if (!checkIfIsDatasource(dataSourceType)) { @@ -536,7 +528,7 @@ export function writeQueryResultsToConsole( ext.connectionNode?.label ? ext.connectionNode.label : "", isPython, undefined, - duration + duration, ); } } @@ -547,7 +539,7 @@ export function writeQueryResultsToView( query: string, dataSourceType?: string, isPython?: boolean, - duration?: string + duration?: string, ): void { commands.executeCommand("kdb.resultsPanel.update", result, dataSourceType); const connectionType: ServerType = @@ -563,7 +555,7 @@ export function writeQueryResultsToView( isPython, undefined, undefined, - duration + duration, ); } } @@ -572,7 +564,7 @@ export function writeScratchpadResult( result: ScratchpadResult, query: string, duration: string, - isPython?: boolean + isPython?: boolean, ): void { const queryConsole = ExecutionConsole.start(); @@ -581,7 +573,7 @@ export function writeScratchpadResult( query, result.errorMsg, true, - ext.connectionNode?.label ? ext.connectionNode.label : "" + ext.connectionNode?.label ? ext.connectionNode.label : "", ); } else { if (ext.resultsViewProvider.isVisible()) { @@ -590,7 +582,7 @@ export function writeScratchpadResult( query, "SCRATCHPAD", isPython, - duration + duration, ); } else { writeQueryResultsToConsole( @@ -598,7 +590,7 @@ export function writeScratchpadResult( query, undefined, isPython, - duration + duration, ); } } diff --git a/src/extension.ts b/src/extension.ts index 6895a620..956bbf28 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -255,8 +255,6 @@ export async function activate(context: ExtensionContext) { ), commands.registerCommand("kdb.refreshServerObjects", () => { ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); ext.activeConnection?.update(); }), commands.registerCommand( @@ -344,28 +342,25 @@ export async function activate(context: ExtensionContext) { checkLocalInstall(); } }), - commands.registerCommand("kdb.execute.selectedQuery", async () => { - await runQuery(ExecutionTypes.QuerySelection); + commands.registerCommand("kdb.execute.selectedQuery", () => { + runQuery(ExecutionTypes.QuerySelection); ext.activeConnection?.update(); }), - commands.registerCommand("kdb.execute.fileQuery", async () => { - await runQuery(ExecutionTypes.QueryFile); + commands.registerCommand("kdb.execute.fileQuery", () => { + runQuery(ExecutionTypes.QueryFile); ext.activeConnection?.update(); }), - commands.registerCommand("kdb.execute.pythonScratchpadQuery", async () => { - await runQuery(ExecutionTypes.PythonQuerySelection); + commands.registerCommand("kdb.execute.pythonScratchpadQuery", () => { + runQuery(ExecutionTypes.PythonQuerySelection); ext.activeConnection?.update(); }), commands.registerCommand("kdb.scratchpad.reset", async () => { await resetScratchPad(); }), - commands.registerCommand( - "kdb.execute.pythonFileScratchpadQuery", - async () => { - await runQuery(ExecutionTypes.PythonQueryFile); - ext.activeConnection?.update(); - }, - ), + commands.registerCommand("kdb.execute.pythonFileScratchpadQuery", () => { + runQuery(ExecutionTypes.PythonQueryFile); + ext.activeConnection?.update(); + }), // TODO MS REMOVE commands.registerCommand("kdb.execute.entireFile", async (uri: Uri) => { if (!uri) { @@ -418,13 +413,13 @@ export async function activate(context: ExtensionContext) { ext.scratchpadTreeProvider.reload(); }), commands.registerCommand("kdb.pickConnection", async () => { - const editor = ext.activeTextEditor; + const editor = window.activeTextEditor; if (editor) { await pickConnection(editor.document.uri); } }), commands.registerCommand("kdb.runScratchpad", async () => { - const editor = ext.activeTextEditor; + const editor = window.activeTextEditor; if (editor) { await runScratchpad(editor.document.uri); } diff --git a/src/services/connectionManagerService.ts b/src/services/connectionManagerService.ts index 3e6b106d..d764362e 100644 --- a/src/services/connectionManagerService.ts +++ b/src/services/connectionManagerService.ts @@ -137,8 +137,6 @@ export class ConnectionManagementService { commands.executeCommand("setContext", "kdb.insightsConnected", false); } ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); } public disconnect(connLabel: string): void { @@ -222,8 +220,6 @@ export class ConnectionManagementService { this.setActiveConnection(connNode); ext.connectionNode = connNode; ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); } public isNotConnectedBehaviour(connLabel: string): void { @@ -258,8 +254,6 @@ export class ConnectionManagementService { `Connection stopped from ${connection.connLabel}`, ); ext.serverProvider.reload(); - ext.dataSourceProvider.refresh(); - ext.scratchpadTreeProvider.reload(); } public async executeQuery( From 4faa7bb07da4d4cc080e602f6de555437cc60cbc Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 1 May 2024 17:46:40 +0300 Subject: [PATCH 13/32] refactor test to use ext.activeTextEditor --- package.json | 3 ++- src/commands/buildToolsCommand.ts | 4 ++-- src/commands/scratchpadCommand.ts | 6 +++--- src/commands/serverCommand.ts | 4 ++-- src/extension.ts | 8 ++++---- test/suite/commands.test.ts | 16 ++++++---------- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index dbfcba0a..204eac5f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "onView:kdb-query-history", "onView:kdb-datasources-explorer", "onView:kdb-results", - "onTerminalProfile:kdb.q-terminal" + "onTerminalProfile:kdb.q-terminal", + "onLanguage:python" ], "main": "./out/extension.js", "contributes": { diff --git a/src/commands/buildToolsCommand.ts b/src/commands/buildToolsCommand.ts index a64553c7..f09a1c03 100644 --- a/src/commands/buildToolsCommand.ts +++ b/src/commands/buildToolsCommand.ts @@ -259,8 +259,8 @@ export async function connectBuildTools() { } }); - if (window.activeTextEditor) { - const document = window.activeTextEditor.document; + if (ext.activeTextEditor) { + const document = ext.activeTextEditor.document; if (isAutoLintingSupported(document)) { await setDiagnostics(document); } diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index 980239b7..febf8e35 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -134,10 +134,10 @@ function setRealActiveTextEditor(editor?: TextEditor | undefined) { } export function activeEditorChanged(editor?: TextEditor | undefined) { - //setRealActiveTextEditor(editor); + setRealActiveTextEditor(editor); const item = ext.runScratchpadItem; - if (editor) { - const uri = editor.document.uri; + if (ext.activeTextEditor) { + const uri = ext.activeTextEditor.document.uri; const path = uri.path; if (path.endsWith(".kdb.q") || path.endsWith(".kdb.py")) { const server = getServerForUri(uri); diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index b534bb98..2a3e5b2c 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -363,7 +363,7 @@ export async function executeQuery( export function getQueryContext(lineNum?: number): string { let context = "."; - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; const fullText = typeof lineNum !== "number"; if (editor) { @@ -410,7 +410,7 @@ export function getConextForRerunQuery(query: string): string { } export function runQuery(type: ExecutionTypes, rerunQuery?: string) { - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; if (!editor) { return false; } diff --git a/src/extension.ts b/src/extension.ts index 956bbf28..4ca2c6ae 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -332,7 +332,7 @@ export async function activate(context: ExtensionContext) { }, ), commands.registerCommand("kdb.terminal.run", () => { - const filename = window.activeTextEditor?.document.fileName; + const filename = ext.activeTextEditor?.document.fileName; if (filename) runQFileTerminal(filename); }), commands.registerCommand("kdb.terminal.start", () => { @@ -413,13 +413,13 @@ export async function activate(context: ExtensionContext) { ext.scratchpadTreeProvider.reload(); }), commands.registerCommand("kdb.pickConnection", async () => { - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; if (editor) { await pickConnection(editor.document.uri); } }), commands.registerCommand("kdb.runScratchpad", async () => { - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; if (editor) { await runScratchpad(editor.document.uri); } @@ -432,7 +432,7 @@ export async function activate(context: ExtensionContext) { new ConnectionLensProvider(), ), commands.registerCommand("kdb.qlint", async () => { - const editor = window.activeTextEditor; + const editor = ext.activeTextEditor; if (editor) { await lintCommand(editor.document); } diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index f3ea89bd..3aa99c8b 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -1507,7 +1507,7 @@ describe("serverCommand", () => { }); describe("runQuery", () => { - const editor = { + const editor = ({ selection: { isEmpty: false, active: { line: 5 }, @@ -1517,16 +1517,12 @@ describe("serverCommand", () => { lineAt: sinon.stub().returns({ text: "SELECT * FROM table" }), getText: sinon.stub().returns("SELECT * FROM table"), }, - }; + }); - let getQueryContextStub, - activeTextEditorStub, - executeQueryStub: sinon.SinonStub; + let getQueryContextStub, executeQueryStub: sinon.SinonStub; beforeEach(() => { - activeTextEditorStub = sinon - .stub(vscode.window, "activeTextEditor") - .value(editor); + ext.activeTextEditor = editor; getQueryContextStub = sinon .stub(serverCommand, "getQueryContext") .returns("."); @@ -1537,14 +1533,14 @@ describe("serverCommand", () => { }); afterEach(() => { - activeTextEditorStub.restore(); + ext.activeTextEditor = undefined; getQueryContextStub.restore(); executeQueryStub.restore(); ext.kdbinsightsNodes.pop(); }); it("runQuery with undefined editor ", () => { - activeTextEditorStub.value(undefined); + ext.activeTextEditor = undefined; const result = serverCommand.runQuery(ExecutionTypes.PythonQueryFile); assert.equal(result, false); }); From 032bbb030b904ed0947de06aced3870c99a879e8 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 1 May 2024 19:24:40 +0300 Subject: [PATCH 14/32] support for all run commands added for ssratchpads --- package.json | 85 ++++++++++++++----------------- src/commands/scratchpadCommand.ts | 63 +++++++++++++---------- src/extension.ts | 53 +++++++++---------- 3 files changed, 98 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 204eac5f..24b237fc 100644 --- a/package.json +++ b/package.json @@ -229,11 +229,6 @@ "title": "Refresh scratchpads", "icon": "$(refresh)" }, - { - "category": "KX", - "command": "kdb.runScratchpad", - "title": "Run scratchpad" - }, { "category": "KX", "command": "kdb.pickConnection", @@ -395,8 +390,7 @@ "category": "KX", "command": "kdb.terminal.run", "title": "Run q file in a new q instance", - "icon": "$(run)", - "enablement": "(kdb.QHOME || config.kdb.qHomeDirectory)" + "icon": "$(run)" }, { "category": "KX", @@ -406,14 +400,14 @@ { "category": "KX", "command": "kdb.execute.selectedQuery", - "title": "Execute Current Selection" + "title": "Execute Current Selection", + "icon": "$(run)" }, { "category": "KX", "command": "kdb.execute.fileQuery", "title": "Execute Entire File", - "icon": "$(run)", - "enablement": "kdb.connected" + "icon": "$(run)" }, { "category": "KX", @@ -424,13 +418,7 @@ "category": "KX", "command": "kdb.execute.pythonFileScratchpadQuery", "title": "Execute Entire File in Insights Scratchpad", - "icon": "$(run)", - "enablement": "kdb.insightsConnected" - }, - { - "category": "KX", - "command": "kdb.execute.entireFile", - "title": "Execute Entire File" + "icon": "$(run)" }, { "command": "kdb.scratchpad.reset", @@ -448,31 +436,31 @@ "command": "kdb.execute.selectedQuery", "key": "ctrl+d", "mac": "cmd+d", - "when": "editorLangId == q && kdb.connected" + "when": "editorLangId == q" }, { "command": "kdb.execute.fileQuery", "key": "ctrl+shift+d", "mac": "cmd+shift+d", - "when": "editorLangId == q && kdb.connected" + "when": "editorLangId == q" }, { "command": "kdb.execute.pythonScratchpadQuery", "key": "ctrl+d", "mac": "cmd+d", - "when": "editorLangId == python && kdb.insightsConnected" + "when": "editorLangId == python" }, { "command": "kdb.execute.pythonFileScratchpadQuery", "key": "ctrl+shift+d", "mac": "cmd+shift+d", - "when": "editorLangId == python && kdb.insightsConnected" + "when": "editorLangId == python" }, { "command": "kdb.terminal.run", "key": "ctrl+shift+r", "mac": "cmd+shift+r", - "when": "editorLangId == q && (kdb.QHOME || config.kdb.qHomeDirectory)" + "when": "editorLangId == q" }, { "command": "kdb.scratchpad.reset", @@ -764,51 +752,54 @@ { "command": "kdb.execute.fileQuery", "group": "q@0", - "when": "resourceLangId == q" + "when": "editorLangId == q" }, { - "command": "kdb.terminal.run", + "command": "kdb.execute.selectedQuery", "group": "q@1", - "when": "resourceLangId == q" + "when": "editorLangId == q" + }, + { + "command": "kdb.terminal.run", + "group": "q@2", + "when": "editorLangId == q" }, { "command": "kdb.execute.pythonFileScratchpadQuery", "group": "q@0", - "when": "editorLangId == python && kdb.insightsConnected" + "when": "editorLangId == python" + }, + { + "command": "kdb.execute.pythonScratchpadQuery", + "group": "q@1", + "when": "editorLangId == python" } ], "editor/context": [ { - "command": "kdb.terminal.run", - "when": "resourceLangId == q && (kdb.QHOME || config.kdb.qHomeDirectory)", - "group": "q" + "command": "kdb.execute.fileQuery", + "group": "q@0", + "when": "editorLangId == q" }, { "command": "kdb.execute.selectedQuery", - "group": "q", - "when": "editorLangId == q && kdb.connected" - }, - { - "command": "kdb.execute.fileQuery", - "group": "q", - "when": "editorLangId == q && kdb.connected" + "group": "q@1", + "when": "editorLangId == q" }, { - "command": "kdb.execute.pythonScratchpadQuery", - "group": "q", - "when": "editorLangId == python && kdb.insightsConnected" + "command": "kdb.terminal.run", + "group": "q@2", + "when": "editorLangId == q" }, { "command": "kdb.execute.pythonFileScratchpadQuery", - "group": "q", - "when": "editorLangId == python && kdb.insightsConnected" - } - ], - "explorer/context": [ + "group": "q@0", + "when": "editorLangId == python" + }, { - "command": "kdb.execute.entireFile", - "group": "q", - "when": "(resourceExtname == .q && (kdb.connected || kdb.insightsConnected)) || (resourceExtname == .py && kdb.insightsConnected)" + "command": "kdb.execute.pythonScratchpadQuery", + "group": "q@1", + "when": "editorLangId == python" } ] }, diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index febf8e35..8a96d850 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -170,38 +170,45 @@ export async function pickConnection(uri: Uri) { return picked; } -export async function runScratchpad(uri: Uri) { - let server = getServerForUri(uri); +function isPython(uri: Uri) { + return uri.path.endsWith(".py"); +} - if (!server) { - server = await pickConnection(uri); - } +function isScratchpad(uri: Uri) { + return uri.path.endsWith(".kdb.q") || uri.path.endsWith(".kdb.py"); +} - if (server) { - const connection = await getConnectionForServer(server); - if (connection) { - if (!connectionService.isConnected(connection.label)) { - const action = await window.showWarningMessage( - `${connection.label} is not connected`, - "Connect", - ); - if (action === "Connect") { - await connectionService.connect(connection.label); - await waitForConnection(connection.label); +export async function runScratchpad(type: ExecutionTypes) { + if (ext.activeTextEditor) { + const uri = ext.activeTextEditor.document.uri; + if (isScratchpad(uri)) { + let server = getServerForUri(uri); + if (!server) { + server = await pickConnection(uri); + } + + if (server) { + const connection = await getConnectionForServer(server); + if (connection) { + if (!connectionService.isConnected(connection.label)) { + const action = await window.showWarningMessage( + `${connection.label} is not connected`, + "Connect", + ); + if (action === "Connect") { + await connectionService.connect(connection.label); + await waitForConnection(connection.label); + } else { + return; + } + } + connectionService.setActiveConnection(connection); } else { - return; + window.showErrorMessage(`${server} is not found`); } } - connectionService.setActiveConnection(connection); - - runQuery( - uri.path.endsWith(".py") - ? ExecutionTypes.PythonQueryFile - : ExecutionTypes.QueryFile, - ); - } else { - window.showErrorMessage(`${server} is not found`); } + runQuery(type); } } @@ -210,7 +217,9 @@ export class ConnectionLensProvider implements CodeLensProvider { const server = getServerForUri(document.uri); const top = new Range(0, 0, 0, 0); const runScratchpad = new CodeLens(top, { - command: "kdb.runScratchpad", + command: isPython(document.uri) + ? "kdb.execute.pythonFileScratchpadQuery" + : "kdb.execute.fileQuery", title: server ? `Run on ${server}` : "Run", }); const pickConnection = new CodeLens(top, { diff --git a/src/extension.ts b/src/extension.ts index 4ca2c6ae..6fac234a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -63,7 +63,6 @@ import { removeConnection, rerunQuery, resetScratchPad, - runQuery, } from "./commands/serverCommand"; import { showInstallationDetails } from "./commands/walkthroughCommand"; import { ext } from "./extensionVariables"; @@ -342,37 +341,39 @@ export async function activate(context: ExtensionContext) { checkLocalInstall(); } }), - commands.registerCommand("kdb.execute.selectedQuery", () => { - runQuery(ExecutionTypes.QuerySelection); + commands.registerCommand("kdb.runScratchpad", async () => { + if (ext.activeTextEditor) { + const path = ext.activeTextEditor.document.uri.path; + await runScratchpad( + path.endsWith(".kdb.py") + ? ExecutionTypes.PythonQueryFile + : ExecutionTypes.QuerySelection, + ); + ext.activeConnection?.update(); + } + }), + commands.registerCommand("kdb.execute.selectedQuery", async () => { + await runScratchpad(ExecutionTypes.QuerySelection); ext.activeConnection?.update(); }), - commands.registerCommand("kdb.execute.fileQuery", () => { - runQuery(ExecutionTypes.QueryFile); + commands.registerCommand("kdb.execute.fileQuery", async () => { + await runScratchpad(ExecutionTypes.QueryFile); ext.activeConnection?.update(); }), - commands.registerCommand("kdb.execute.pythonScratchpadQuery", () => { - runQuery(ExecutionTypes.PythonQuerySelection); + commands.registerCommand("kdb.execute.pythonScratchpadQuery", async () => { + await runScratchpad(ExecutionTypes.PythonQuerySelection); ext.activeConnection?.update(); }), commands.registerCommand("kdb.scratchpad.reset", async () => { await resetScratchPad(); }), - commands.registerCommand("kdb.execute.pythonFileScratchpadQuery", () => { - runQuery(ExecutionTypes.PythonQueryFile); - ext.activeConnection?.update(); - }), - // TODO MS REMOVE - commands.registerCommand("kdb.execute.entireFile", async (uri: Uri) => { - if (!uri) { - return; - } - const isPython = uri.fsPath.endsWith(".py"); - if (uri.fsPath.endsWith(".q") || isPython) { - const content = await workspace.fs.readFile(uri); - const query = content.toString(); - await executeQuery(query, undefined, isPython); - } - }), + commands.registerCommand( + "kdb.execute.pythonFileScratchpadQuery", + async () => { + await runScratchpad(ExecutionTypes.PythonQueryFile); + ext.activeConnection?.update(); + }, + ), commands.registerCommand( "kdb.createDataSource", async (item: FileTreeItem) => { @@ -418,12 +419,6 @@ export async function activate(context: ExtensionContext) { await pickConnection(editor.document.uri); } }), - commands.registerCommand("kdb.runScratchpad", async () => { - const editor = ext.activeTextEditor; - if (editor) { - await runScratchpad(editor.document.uri); - } - }), DataSourceEditorProvider.register(context), From 65b3d0c03712c65fa680bb5081e13e007fd0da6e Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 1 May 2024 23:55:26 +0300 Subject: [PATCH 15/32] checkpoint before ws datasource --- package.json | 11 ++-- src/commands/scratchpadCommand.ts | 92 ++++++++++++++------------- src/extension.ts | 26 ++------ src/services/workspaceTreeProvider.ts | 5 +- 4 files changed, 64 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 24b237fc..47c690d2 100644 --- a/package.json +++ b/package.json @@ -190,9 +190,9 @@ "description": "Enable linting for q files", "default": false }, - "kdb.scratchpads": { + "kdb.connectionMap": { "type": "object", - "description": "Scratchpads", + "description": "Connection map for workspace files", "default": {}, "scope": "resource" } @@ -390,7 +390,7 @@ "category": "KX", "command": "kdb.terminal.run", "title": "Run q file in a new q instance", - "icon": "$(run)" + "icon": "$(debug-alt)" }, { "category": "KX", @@ -401,7 +401,7 @@ "category": "KX", "command": "kdb.execute.selectedQuery", "title": "Execute Current Selection", - "icon": "$(run)" + "icon": "$(run-below)" }, { "category": "KX", @@ -412,7 +412,8 @@ { "category": "KX", "command": "kdb.execute.pythonScratchpadQuery", - "title": "Execute Current Selection in Insights Scratchpad" + "title": "Execute Current Selection in Insights Scratchpad", + "icon": "$(run-below)" }, { "category": "KX", diff --git a/src/commands/scratchpadCommand.ts b/src/commands/scratchpadCommand.ts index 8a96d850..38736175 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/scratchpadCommand.ts @@ -34,6 +34,11 @@ function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(run) ${text}`; } +export function workspaceFoldersChanged() { + ext.dataSourceTreeProvider.reload(); + ext.scratchpadTreeProvider.reload(); +} + function getServers() { const conf = workspace.getConfiguration("kdb"); const servers = conf.get<{ [key: string]: { serverAlias: string } }>( @@ -51,39 +56,29 @@ function getServers() { ]; } -async function setServerForScratchpad(uri: Uri, server: string | undefined) { +export function getServerForUri(uri: Uri) { const conf = workspace.getConfiguration("kdb", uri); const scratchpads = conf.get<{ [key: string]: string | undefined }>( - "scratchpads", + "connectionMap", {}, ); - scratchpads[uri.path] = server; - await conf.update("scratchpads", scratchpads); + return scratchpads[workspace.asRelativePath(uri)]; } -async function waitForConnection(name: string) { - return new Promise((resolve, reject) => { - let count = 0; - const retry = () => { - count++; - setTimeout(() => { - if (connectionService.isConnected(name)) { - resolve(); - } else if (count < 5) { - retry(); - } else { - reject(`Can not connect to ${name}`); - } - }, 50); - }; - retry(); - }); +async function setServerForUri(uri: Uri, server: string | undefined) { + const conf = workspace.getConfiguration("kdb", uri); + const map = conf.get<{ [key: string]: string | undefined }>( + "connectionMap", + {}, + ); + map[workspace.asRelativePath(uri)] = server; + await conf.update("connectionMap", map); } -async function getConnectionForServer(server: string) { +export function getConnectionForUri(uri: Uri) { + const server = getServerForUri(uri); if (server) { - const servers = await ext.serverProvider.getChildren(); - return servers.find((item) => { + return ext.connectionsList.find((item) => { if (item instanceof InsightsNode) { return item.details.alias === server; } else if (item instanceof KdbNode) { @@ -94,19 +89,10 @@ async function getConnectionForServer(server: string) { } } -export function getServerForUri(uri: Uri) { - const conf = workspace.getConfiguration("kdb", uri); - const scratchpads = conf.get<{ [key: string]: string | undefined }>( - "scratchpads", - {}, - ); - return scratchpads[uri.path]; -} - -export function getConnectionForUri(uri: Uri) { - const server = getServerForUri(uri); +async function getConnectionForServer(server: string) { if (server) { - return ext.connectionsList.find((item) => { + const servers = await ext.serverProvider.getChildren(); + return servers.find((item) => { if (item instanceof InsightsNode) { return item.details.alias === server; } else if (item instanceof KdbNode) { @@ -117,9 +103,23 @@ export function getConnectionForUri(uri: Uri) { } } -export function workspaceFoldersChanged() { - ext.dataSourceTreeProvider.reload(); - ext.scratchpadTreeProvider.reload(); +async function waitForConnection(name: string) { + return new Promise((resolve, reject) => { + let count = 0; + const retry = () => { + count++; + setTimeout(() => { + if (connectionService.isConnected(name)) { + resolve(); + } else if (count < 5) { + retry(); + } else { + reject(`Can not connect to ${name}`); + } + }, 50); + }; + retry(); + }); } function setRealActiveTextEditor(editor?: TextEditor | undefined) { @@ -138,8 +138,7 @@ export function activeEditorChanged(editor?: TextEditor | undefined) { const item = ext.runScratchpadItem; if (ext.activeTextEditor) { const uri = ext.activeTextEditor.document.uri; - const path = uri.path; - if (path.endsWith(".kdb.q") || path.endsWith(".kdb.py")) { + if (isScratchpad(uri)) { const server = getServerForUri(uri); setRunScratchpadItemText(server || "Run"); item.show(); @@ -163,7 +162,7 @@ export async function pickConnection(uri: Uri) { if (picked === "(none)") { picked = undefined; } - await setServerForScratchpad(uri, picked); + await setServerForUri(uri, picked); setRunScratchpadItemText(picked || "Run"); } @@ -178,7 +177,7 @@ function isScratchpad(uri: Uri) { return uri.path.endsWith(".kdb.q") || uri.path.endsWith(".kdb.py"); } -export async function runScratchpad(type: ExecutionTypes) { +export async function runActiveEditor(type?: ExecutionTypes) { if (ext.activeTextEditor) { const uri = ext.activeTextEditor.document.uri; if (isScratchpad(uri)) { @@ -208,7 +207,12 @@ export async function runScratchpad(type: ExecutionTypes) { } } } - runQuery(type); + + runQuery( + type || isPython(uri) + ? ExecutionTypes.PythonQueryFile + : ExecutionTypes.QueryFile, + ); } } diff --git a/src/extension.ts b/src/extension.ts index 6fac234a..0f0ed8ab 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -59,7 +59,6 @@ import { connect, disconnect, enableTLS, - executeQuery, removeConnection, rerunQuery, resetScratchPad, @@ -104,7 +103,7 @@ import { activeEditorChanged, ConnectionLensProvider, pickConnection, - runScratchpad, + runActiveEditor, workspaceFoldersChanged, } from "./commands/scratchpadCommand"; import { createDefaultDataSourceFile } from "./models/dataSource"; @@ -254,7 +253,6 @@ export async function activate(context: ExtensionContext) { ), commands.registerCommand("kdb.refreshServerObjects", () => { ext.serverProvider.reload(); - ext.activeConnection?.update(); }), commands.registerCommand( "kdb.queryHistory.rerun", @@ -342,27 +340,16 @@ export async function activate(context: ExtensionContext) { } }), commands.registerCommand("kdb.runScratchpad", async () => { - if (ext.activeTextEditor) { - const path = ext.activeTextEditor.document.uri.path; - await runScratchpad( - path.endsWith(".kdb.py") - ? ExecutionTypes.PythonQueryFile - : ExecutionTypes.QuerySelection, - ); - ext.activeConnection?.update(); - } + await runActiveEditor(); }), commands.registerCommand("kdb.execute.selectedQuery", async () => { - await runScratchpad(ExecutionTypes.QuerySelection); - ext.activeConnection?.update(); + await runActiveEditor(ExecutionTypes.QuerySelection); }), commands.registerCommand("kdb.execute.fileQuery", async () => { - await runScratchpad(ExecutionTypes.QueryFile); - ext.activeConnection?.update(); + await runActiveEditor(ExecutionTypes.QueryFile); }), commands.registerCommand("kdb.execute.pythonScratchpadQuery", async () => { - await runScratchpad(ExecutionTypes.PythonQuerySelection); - ext.activeConnection?.update(); + await runActiveEditor(ExecutionTypes.PythonQuerySelection); }), commands.registerCommand("kdb.scratchpad.reset", async () => { await resetScratchPad(); @@ -370,8 +357,7 @@ export async function activate(context: ExtensionContext) { commands.registerCommand( "kdb.execute.pythonFileScratchpadQuery", async () => { - await runScratchpad(ExecutionTypes.PythonQueryFile); - ext.activeConnection?.update(); + await runActiveEditor(ExecutionTypes.PythonQueryFile); }, ), commands.registerCommand( diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 2a187786..766c3c20 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -24,6 +24,7 @@ import { import Path from "path"; import { getServerIconState } from "../utils/core"; import { getConnectionForUri } from "../commands/scratchpadCommand"; +import { ext } from "../extensionVariables"; export class WorkspaceTreeProvider implements TreeDataProvider { private _onDidChangeTreeData = new EventEmitter(); @@ -32,7 +33,9 @@ export class WorkspaceTreeProvider implements TreeDataProvider { constructor( private readonly glob: string, private readonly baseIcon: string, - ) {} + ) { + ext.serverProvider.onDidChangeTreeData(() => this.reload()); + } reload() { this._onDidChangeTreeData.fire(); From 8f8b8abc1ba49d8a5621ea540b79beec30eddb77 Mon Sep 17 00:00:00 2001 From: ecmel Date: Thu, 2 May 2024 07:39:41 +0300 Subject: [PATCH 16/32] organized code --- ...ratchpadCommand.ts => workspaceCommand.ts} | 155 ++++++++++-------- src/extension.ts | 36 ++-- src/services/workspaceTreeProvider.ts | 2 +- 3 files changed, 102 insertions(+), 91 deletions(-) rename src/commands/{scratchpadCommand.ts => workspaceCommand.ts} (86%) diff --git a/src/commands/scratchpadCommand.ts b/src/commands/workspaceCommand.ts similarity index 86% rename from src/commands/scratchpadCommand.ts rename to src/commands/workspaceCommand.ts index 38736175..1cf4c503 100644 --- a/src/commands/scratchpadCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -14,10 +14,13 @@ import { CodeLens, CodeLensProvider, + Command, ProviderResult, Range, + StatusBarAlignment, TextDocument, TextEditor, + ThemeColor, Uri, window, workspace, @@ -30,15 +33,43 @@ import { ExecutionTypes } from "../models/execution"; const connectionService = new ConnectionManagementService(); -function setRunScratchpadItemText(text: string) { - ext.runScratchpadItem.text = `$(run) ${text}`; -} - -export function workspaceFoldersChanged() { +function workspaceFoldersChanged() { ext.dataSourceTreeProvider.reload(); ext.scratchpadTreeProvider.reload(); } +function setRealActiveTextEditor(editor?: TextEditor | undefined) { + if (editor) { + const scheme = editor.document.uri.scheme; + if (scheme === "file") { + ext.activeTextEditor = editor; + } + } else { + ext.activeTextEditor = undefined; + } +} + +function activeEditorChanged(editor?: TextEditor | undefined) { + setRealActiveTextEditor(editor); + const item = ext.runScratchpadItem; + if (ext.activeTextEditor) { + const uri = ext.activeTextEditor.document.uri; + if (isScratchpad(uri)) { + const server = getServerForUri(uri); + setRunScratchpadItemText(server || "Run"); + item.show(); + } else { + item.hide(); + } + } else { + item.hide(); + } +} + +function setRunScratchpadItemText(text: string) { + ext.runScratchpadItem.text = `$(run) ${text}`; +} + function getServers() { const conf = workspace.getConfiguration("kdb"); const servers = conf.get<{ [key: string]: { serverAlias: string } }>( @@ -56,39 +87,6 @@ function getServers() { ]; } -export function getServerForUri(uri: Uri) { - const conf = workspace.getConfiguration("kdb", uri); - const scratchpads = conf.get<{ [key: string]: string | undefined }>( - "connectionMap", - {}, - ); - return scratchpads[workspace.asRelativePath(uri)]; -} - -async function setServerForUri(uri: Uri, server: string | undefined) { - const conf = workspace.getConfiguration("kdb", uri); - const map = conf.get<{ [key: string]: string | undefined }>( - "connectionMap", - {}, - ); - map[workspace.asRelativePath(uri)] = server; - await conf.update("connectionMap", map); -} - -export function getConnectionForUri(uri: Uri) { - const server = getServerForUri(uri); - if (server) { - return ext.connectionsList.find((item) => { - if (item instanceof InsightsNode) { - return item.details.alias === server; - } else if (item instanceof KdbNode) { - return item.details.serverAlias === server; - } - return false; - }) as KdbNode | InsightsNode; - } -} - async function getConnectionForServer(server: string) { if (server) { const servers = await ext.serverProvider.getChildren(); @@ -122,31 +120,36 @@ async function waitForConnection(name: string) { }); } -function setRealActiveTextEditor(editor?: TextEditor | undefined) { - if (editor) { - const scheme = editor.document.uri.scheme; - if (scheme === "file") { - ext.activeTextEditor = editor; - } - } else { - ext.activeTextEditor = undefined; - } +async function setServerForUri(uri: Uri, server: string | undefined) { + const conf = workspace.getConfiguration("kdb", uri); + const map = conf.get<{ [key: string]: string | undefined }>( + "connectionMap", + {}, + ); + map[workspace.asRelativePath(uri)] = server; + await conf.update("connectionMap", map); } -export function activeEditorChanged(editor?: TextEditor | undefined) { - setRealActiveTextEditor(editor); - const item = ext.runScratchpadItem; - if (ext.activeTextEditor) { - const uri = ext.activeTextEditor.document.uri; - if (isScratchpad(uri)) { - const server = getServerForUri(uri); - setRunScratchpadItemText(server || "Run"); - item.show(); - } else { - item.hide(); - } - } else { - item.hide(); +export function getServerForUri(uri: Uri) { + const conf = workspace.getConfiguration("kdb", uri); + const scratchpads = conf.get<{ [key: string]: string | undefined }>( + "connectionMap", + {}, + ); + return scratchpads[workspace.asRelativePath(uri)]; +} + +export function getConnectionForUri(uri: Uri) { + const server = getServerForUri(uri); + if (server) { + return ext.connectionsList.find((item) => { + if (item instanceof InsightsNode) { + return item.details.alias === server; + } else if (item instanceof KdbNode) { + return item.details.serverAlias === server; + } + return false; + }) as KdbNode | InsightsNode; } } @@ -209,9 +212,11 @@ export async function runActiveEditor(type?: ExecutionTypes) { } runQuery( - type || isPython(uri) - ? ExecutionTypes.PythonQueryFile - : ExecutionTypes.QueryFile, + type === undefined + ? isPython(uri) + ? ExecutionTypes.PythonQueryFile + : ExecutionTypes.QueryFile + : type, ); } } @@ -233,3 +238,25 @@ export class ConnectionLensProvider implements CodeLensProvider { return [runScratchpad, pickConnection]; } } + +export function connectWorkspaceCommsnds() { + ext.runScratchpadItem = window.createStatusBarItem( + StatusBarAlignment.Right, + 10000, + ); + ext.runScratchpadItem.backgroundColor = new ThemeColor( + "statusBarItem.warningBackground", + ); + ext.runScratchpadItem.command = { + title: "", + command: "kdb.runScratchpad", + arguments: [], + }; + + const watcher = workspace.createFileSystemWatcher("**/*.kdb.{json,q,py}"); + watcher.onDidCreate(workspaceFoldersChanged); + watcher.onDidDelete(workspaceFoldersChanged); + workspace.onDidChangeWorkspaceFolders(workspaceFoldersChanged); + window.onDidChangeActiveTextEditor(activeEditorChanged); + activeEditorChanged(window.activeTextEditor); +} diff --git a/src/extension.ts b/src/extension.ts index 0f0ed8ab..dc1254f4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,7 +14,6 @@ import { env } from "node:process"; import path from "path"; import { - Command, commands, ConfigurationTarget, EventEmitter, @@ -22,9 +21,7 @@ import { extensions, languages, Range, - StatusBarAlignment, TextDocumentContentProvider, - ThemeColor, Uri, window, workspace, @@ -100,12 +97,11 @@ import { WorkspaceTreeProvider, } from "./services/workspaceTreeProvider"; import { - activeEditorChanged, ConnectionLensProvider, + connectWorkspaceCommsnds, pickConnection, runActiveEditor, - workspaceFoldersChanged, -} from "./commands/scratchpadCommand"; +} from "./commands/workspaceCommand"; import { createDefaultDataSourceFile } from "./models/dataSource"; import { connectBuildTools, lintCommand } from "./commands/buildToolsCommand"; import { CompletionProvider } from "./services/completionProvider"; @@ -423,6 +419,12 @@ export async function activate(context: ExtensionContext) { new QuickFixProvider(), ), ext.diagnosticCollection, + workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("kdb.connectionMap")) { + ext.dataSourceTreeProvider.reload(); + ext.scratchpadTreeProvider.reload(); + } + }), ); const lastResult: QueryResult | undefined = undefined; @@ -449,25 +451,8 @@ export async function activate(context: ExtensionContext) { ), ); - ext.runScratchpadItem = window.createStatusBarItem( - StatusBarAlignment.Right, - 10000, - ); - ext.runScratchpadItem.backgroundColor = new ThemeColor( - "statusBarItem.warningBackground", - ); - ext.runScratchpadItem.command = { - title: "", - command: "kdb.runScratchpad", - arguments: [], - }; - - const watcher = workspace.createFileSystemWatcher("**/*.kdb.{json,q,py}"); - watcher.onDidCreate(workspaceFoldersChanged); - watcher.onDidDelete(workspaceFoldersChanged); - workspace.onDidChangeWorkspaceFolders(workspaceFoldersChanged); - window.onDidChangeActiveTextEditor(activeEditorChanged); - activeEditorChanged(window.activeTextEditor); + connectWorkspaceCommsnds(); + await connectBuildTools(); //q language server const serverModule = path.join(context.extensionPath, "out", "server.js"); @@ -495,7 +480,6 @@ export async function activate(context: ExtensionContext) { ); await client.start(); - await connectBuildTools(); Telemetry.sendEvent("Extension.Activated"); const yamlExtension = extensions.getExtension("redhat.vscode-yaml"); diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 766c3c20..f6449df3 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -23,7 +23,7 @@ import { } from "vscode"; import Path from "path"; import { getServerIconState } from "../utils/core"; -import { getConnectionForUri } from "../commands/scratchpadCommand"; +import { getConnectionForUri } from "../commands/workspaceCommand"; import { ext } from "../extensionVariables"; export class WorkspaceTreeProvider implements TreeDataProvider { From d75bb7542766f1f1d1964a65303075a7c6cd23a5 Mon Sep 17 00:00:00 2001 From: Philip Carneiro Date: Thu, 2 May 2024 09:42:14 +0100 Subject: [PATCH 17/32] add ds icons --- resources/datasource-active.svg | 6 +++ resources/datasource-connected.svg | 6 +++ resources/datasource.svg | 6 +++ src/extensionVariables.ts | 3 ++ src/utils/core.ts | 10 ++++ test/suite/utils.test.ts | 86 ++++++++++++++++++++++++------ 6 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 resources/datasource-active.svg create mode 100644 resources/datasource-connected.svg create mode 100644 resources/datasource.svg diff --git a/resources/datasource-active.svg b/resources/datasource-active.svg new file mode 100644 index 00000000..8f0e696d --- /dev/null +++ b/resources/datasource-active.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/datasource-connected.svg b/resources/datasource-connected.svg new file mode 100644 index 00000000..8e535452 --- /dev/null +++ b/resources/datasource-connected.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/datasource.svg b/resources/datasource.svg new file mode 100644 index 00000000..e8b46bf6 --- /dev/null +++ b/resources/datasource.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 19238a2f..09fa6855 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -37,6 +37,7 @@ import { WorkspaceTreeProvider } from "./services/workspaceTreeProvider"; import { ScratchpadFile } from "./models/scratchpad"; import { LocalConnection } from "./classes/localConnection"; import { InsightsConnection } from "./classes/insightsConnection"; +import { DataSourceFiles } from "./models/dataSource"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { @@ -53,6 +54,8 @@ export namespace ext { export let runScratchpadItem: StatusBarItem; export const activeScratchPadList: Array = []; export const connectedScratchPadList: Array = []; + export const activeDatasourceList: Array = []; + export const connectedDatasourceList: Array = []; export let serverObjects: ServerObject; export let openSslVersion: string | null; export let resultPanelCSV: string; diff --git a/src/utils/core.ts b/src/utils/core.ts index 64d7423d..6a2f013b 100644 --- a/src/utils/core.ts +++ b/src/utils/core.ts @@ -295,6 +295,16 @@ export function getScratchpadStatusIcon(label: string) { } } +export function getDatasourceStatusIcon(label: string) { + if (ext.activeDatasourceList?.some((ds) => ds.name === label)) { + return "-active"; + } else if (ext.connectedDatasourceList?.some((ds) => ds.name === label)) { + return "-connected"; + } else { + return ""; + } +} + export async function checkLocalInstall(): Promise { const QHOME = workspace.getConfiguration().get("kdb.qHomeDirectory"); if (QHOME || env.QHOME) { diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index 0b5bd9f2..9e12059c 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -46,7 +46,7 @@ import { DDateTimeClass, DTimestampClass, } from "../../src/ipc/cClasses"; -import { DataSourceTypes } from "../../src/models/dataSource"; +import { DataSourceFiles, DataSourceTypes } from "../../src/models/dataSource"; import { InsightDetails } from "../../src/models/insights"; import { LocalConnection } from "../../src/classes/localConnection"; import { ScratchpadFile } from "../../src/models/scratchpad"; @@ -101,7 +101,7 @@ describe("Utils", () => { beforeEach(() => { getConfigurationStub = sinon.stub( vscode.workspace, - "getConfiguration" + "getConfiguration", ) as sinon.SinonStub; }); @@ -208,6 +208,60 @@ describe("Utils", () => { }); }); + describe("getDatasourceStatusIcon", () => { + const dsFileDummy: DataSourceFiles = { + name: "test", + dataSource: { + selectedType: DataSourceTypes.API, + api: { + selectedApi: "", + table: "", + startTS: "", + endTS: "", + fill: "zero", + temporality: "snapshot", + filter: [], + groupBy: [], + agg: [], + sortCols: [], + slice: [], + labels: [], + }, + qsql: { + query: "", + selectedTarget: "", + }, + sql: { + query: "", + }, + }, + }; + beforeEach(() => { + ext.activeDatasourceList.length = 0; + ext.connectedDatasourceList.length = 0; + }); + afterEach(() => { + ext.activeDatasourceList.length = 0; + ext.connectedDatasourceList.length = 0; + }); + it("should return active if scratchpad label is on the active list", () => { + ext.activeDatasourceList.push(dsFileDummy); + ext.connectedDatasourceList.push(dsFileDummy); + const result = coreUtils.getDatasourceStatusIcon("test"); + assert.strictEqual(result, "-active"); + }); + it("should return connected if scratchpad label is on the connected list", () => { + ext.connectedDatasourceList.push(dsFileDummy); + const result = coreUtils.getDatasourceStatusIcon("test"); + assert.strictEqual(result, "-connected"); + }); + + it("should return empty string if scratchpad label is not on the active or connected list", () => { + const result = coreUtils.getDatasourceStatusIcon("test"); + assert.strictEqual(result, ""); + }); + }); + describe("getServerIconState", () => { const localConn = new LocalConnection("127.0.0.1:5001", "testLabel"); afterEach(() => { @@ -293,12 +347,12 @@ describe("Utils", () => { it("checkIfTimeParamIsCorrect", () => { const result = dataSourceUtils.checkIfTimeParamIsCorrect( "2021-01-01", - "2021-01-02" + "2021-01-02", ); assert.strictEqual(result, true); const result2 = dataSourceUtils.checkIfTimeParamIsCorrect( "2021-01-02", - "2021-01-01" + "2021-01-01", ); assert.strictEqual(result2, false); }); @@ -537,7 +591,7 @@ describe("Utils", () => { auth: false, tls: false, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); const insightsNode = new InsightsNode( @@ -548,7 +602,7 @@ describe("Utils", () => { alias: "insightsserveralias", auth: true, }, - TreeItemCollapsibleState.None + TreeItemCollapsibleState.None, ); beforeEach(() => { @@ -596,7 +650,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, true); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.KDB + ServerType.KDB, ); getConfigurationStub.restore(); @@ -619,7 +673,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, true); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.KDB + ServerType.KDB, ); getConfigurationStub.restore(); }); @@ -636,7 +690,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, true); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.INSIGHTS + ServerType.INSIGHTS, ); }); @@ -652,7 +706,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, false); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.KDB + ServerType.KDB, ); }); @@ -668,7 +722,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, false); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.INSIGHTS + ServerType.INSIGHTS, ); }); @@ -684,7 +738,7 @@ describe("Utils", () => { assert.strictEqual(ext.kdbQueryHistoryList[0].success, false); assert.strictEqual( ext.kdbQueryHistoryList[0].connectionType, - ServerType.undefined + ServerType.undefined, ); }); }); @@ -712,7 +766,7 @@ describe("Utils", () => { connectionName, connectionType, true, - true + true, ); assert.strictEqual(ext.kdbQueryHistoryList.length, 1); }); @@ -749,7 +803,7 @@ describe("Utils", () => { "testPanel", "Test Panel", vscode.ViewColumn.One, - {} + {}, ); const webview = panel.webview; const extensionUri = vscode.Uri.parse("file:///path/to/extension"); @@ -763,7 +817,7 @@ describe("Utils", () => { "testPanel", "Test Panel", vscode.ViewColumn.One, - {} + {}, ); const webview = panel.webview; const extensionUri = vscode.Uri.parse("file:///path/to/extension"); @@ -1129,7 +1183,7 @@ describe("Utils", () => { getConfigurationStub = sinon.stub(vscode.workspace, "getConfiguration"); showInformationMessageStub = sinon.stub( vscode.window, - "showInformationMessage" + "showInformationMessage", ) as sinon.SinonStub< [ message: string, From 442121d6615db36e868e282f57a96622a16b7e55 Mon Sep 17 00:00:00 2001 From: ecmel Date: Thu, 2 May 2024 13:30:12 +0300 Subject: [PATCH 18/32] fixed multiple workspace issue --- resources/light/plus.svg | 3 --- src/commands/workspaceCommand.ts | 19 +++++++++++++++---- src/extension.ts | 2 +- src/services/workspaceTreeProvider.ts | 6 +++--- 4 files changed, 19 insertions(+), 11 deletions(-) delete mode 100644 resources/light/plus.svg diff --git a/resources/light/plus.svg b/resources/light/plus.svg deleted file mode 100644 index 14d0bdb4..00000000 --- a/resources/light/plus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index 1cf4c503..efa4e559 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -15,6 +15,7 @@ import { CodeLens, CodeLensProvider, Command, + ConfigurationTarget, ProviderResult, Range, StatusBarAlignment, @@ -41,7 +42,7 @@ function workspaceFoldersChanged() { function setRealActiveTextEditor(editor?: TextEditor | undefined) { if (editor) { const scheme = editor.document.uri.scheme; - if (scheme === "file") { + if (scheme !== "output") { ext.activeTextEditor = editor; } } else { @@ -120,23 +121,29 @@ async function waitForConnection(name: string) { }); } +function relativePath(uri: Uri) { + return workspace.asRelativePath(uri, false); +} + async function setServerForUri(uri: Uri, server: string | undefined) { + uri = Uri.file(uri.path); const conf = workspace.getConfiguration("kdb", uri); const map = conf.get<{ [key: string]: string | undefined }>( "connectionMap", {}, ); - map[workspace.asRelativePath(uri)] = server; + map[relativePath(uri)] = server; await conf.update("connectionMap", map); } export function getServerForUri(uri: Uri) { + uri = Uri.file(uri.path); const conf = workspace.getConfiguration("kdb", uri); - const scratchpads = conf.get<{ [key: string]: string | undefined }>( + const map = conf.get<{ [key: string]: string | undefined }>( "connectionMap", {}, ); - return scratchpads[workspace.asRelativePath(uri)]; + return map[relativePath(uri)]; } export function getConnectionForUri(uri: Uri) { @@ -172,6 +179,10 @@ export async function pickConnection(uri: Uri) { return picked; } +function isDataSource(uri: Uri) { + return uri.path.endsWith(".kdb.json"); +} + function isPython(uri: Uri) { return uri.path.endsWith(".py"); } diff --git a/src/extension.ts b/src/extension.ts index dc1254f4..1193614b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -130,7 +130,7 @@ export async function activate(context: ExtensionContext) { ); ext.dataSourceTreeProvider = new WorkspaceTreeProvider( "**/*.kdb.json", - "p-q-connection", + "datasource", ); commands.executeCommand("setContext", "kdb.QHOME", env.QHOME); diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index f6449df3..919e39fb 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -124,11 +124,11 @@ export async function addWorkspaceFile( ) { const folders = workspace.workspaceFolders; if (folders) { - const ws = + const folder = item && item.resourceUri ? workspace.getWorkspaceFolder(item.resourceUri) : folders[0]; - if (ws) { + if (folder) { let i = 1; while (true) { const files = await workspace.findFiles(`${name}-${i}${ext}`); @@ -137,7 +137,7 @@ export async function addWorkspaceFile( } i++; } - const uri = Uri.joinPath(ws.uri, `${name}-${i}${ext}`).with({ + const uri = Uri.joinPath(folder.uri, `${name}-${i}${ext}`).with({ scheme: "untitled", }); await workspace.openTextDocument(uri); From 140435f3bdbd99401e8b0f66ddca992f0af2aa1a Mon Sep 17 00:00:00 2001 From: ecmel Date: Fri, 3 May 2024 11:02:27 +0300 Subject: [PATCH 19/32] KXI-37408 --- package.json | 9 +- src/commands/workspaceCommand.ts | 23 +- src/extension.ts | 5 +- src/models/messages.ts | 20 ++ src/services/dataSourceEditorProvider.ts | 65 ++++- src/webview/components/kdbDataSourceView.ts | 298 ++++++++++---------- src/webview/components/styles.ts | 12 +- test/suite/webview.test.ts | 21 +- 8 files changed, 238 insertions(+), 215 deletions(-) diff --git a/package.json b/package.json index 47c690d2..49d34bc8 100644 --- a/package.json +++ b/package.json @@ -442,8 +442,7 @@ { "command": "kdb.execute.fileQuery", "key": "ctrl+shift+d", - "mac": "cmd+shift+d", - "when": "editorLangId == q" + "mac": "cmd+shift+d" }, { "command": "kdb.execute.pythonScratchpadQuery", @@ -540,12 +539,6 @@ "name": "Query History", "contextualTitle": "Query History", "icon": "resources/history.svg" - }, - { - "id": "kdb-datasources-explorer", - "name": "Data Sources", - "contextualTitle": "Data Sources", - "when": "kdb.insightsConnected" } ], "kdb-results": [ diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index efa4e559..4ec18f4c 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -15,7 +15,6 @@ import { CodeLens, CodeLensProvider, Command, - ConfigurationTarget, ProviderResult, Range, StatusBarAlignment, @@ -71,20 +70,26 @@ function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(run) ${text}`; } +export function getInsightsServers() { + const conf = workspace.getConfiguration("kdb"); + const servers = conf.get<{ [key: string]: { alias: string } }>( + "insightsEnterpriseConnections", + {}, + ); + + return Object.keys(servers).map((key) => servers[key].alias); +} + function getServers() { const conf = workspace.getConfiguration("kdb"); const servers = conf.get<{ [key: string]: { serverAlias: string } }>( "servers", {}, ); - const insights = conf.get<{ [key: string]: { alias: string } }>( - "insightsEnterpriseConnections", - {}, - ); return [ ...Object.keys(servers).map((key) => servers[key].serverAlias), - ...Object.keys(insights).map((key) => insights[key].alias), + ...getInsightsServers(), ]; } @@ -125,7 +130,7 @@ function relativePath(uri: Uri) { return workspace.asRelativePath(uri, false); } -async function setServerForUri(uri: Uri, server: string | undefined) { +export async function setServerForUri(uri: Uri, server: string | undefined) { uri = Uri.file(uri.path); const conf = workspace.getConfiguration("kdb", uri); const map = conf.get<{ [key: string]: string | undefined }>( @@ -179,10 +184,6 @@ export async function pickConnection(uri: Uri) { return picked; } -function isDataSource(uri: Uri) { - return uri.path.endsWith(".kdb.json"); -} - function isPython(uri: Uri) { return uri.path.endsWith(".py"); } diff --git a/src/extension.ts b/src/extension.ts index 1193614b..8c766f76 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -136,10 +136,7 @@ export async function activate(context: ExtensionContext) { commands.executeCommand("setContext", "kdb.QHOME", env.QHOME); window.registerTreeDataProvider("kdb-servers", ext.serverProvider); - window.registerTreeDataProvider( - "kdb-datasources-explorer", - ext.dataSourceProvider, - ); + window.registerTreeDataProvider( "kdb-query-history", ext.queryHistoryProvider, diff --git a/src/models/messages.ts b/src/models/messages.ts index 95c34afc..827bba38 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -20,4 +20,24 @@ export type DataSourceMessage = { dataSourceName: string; dataSourceFile: DataSourceFiles; running?: boolean; + servers?: string[]; + selectedServer?: string; }; + +export const enum DataSourceCommand { + Update, + Save, + Run, + Populate, + Refresh, + Server, +} + +export interface DataSourceMessage2 { + command: DataSourceCommand; + isInsights: boolean; + insightsMeta: MetaObjectPayload; + dataSourceFile: DataSourceFiles; + servers: string[]; + selectedServer: string; +} diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index ec3aaf10..9b0a3d18 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -20,11 +20,22 @@ import { Webview, WebviewPanel, WorkspaceEdit, + commands, window, workspace, } from "vscode"; import { getUri } from "../utils/getUri"; import { getNonce } from "../utils/getNonce"; +import { ConnectionManagementService } from "./connectionManagerService"; +import { ext } from "../extensionVariables"; +import { InsightsNode } from "./kdbTreeProvider"; +import { + getInsightsServers, + getServerForUri, + setServerForUri, +} from "../commands/workspaceCommand"; +import { InsightsConnection } from "../classes/insightsConnection"; +import { DataSourceCommand, DataSourceMessage2 } from "../models/messages"; export class DataSourceEditorProvider implements CustomTextEditorProvider { static readonly viewType = "kdb.dataSourceEditor"; @@ -37,23 +48,33 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { ); } + private connectionService = new ConnectionManagementService(); + constructor(private readonly context: ExtensionContext) {} - resolveCustomTextEditor( + async resolveCustomTextEditor( document: TextDocument, webviewPanel: WebviewPanel, - ): void | Thenable { + ): Promise { const webview = webviewPanel.webview; webview.options = { enableScripts: true }; webview.html = this.getWebviewContent(webview); - function updateWebview() { - webview.postMessage({ - type: "update", - text: document.getText(), - }); + if (ext.activeConnection instanceof InsightsConnection) { + Object.assign(ext.insightsMeta, await ext.activeConnection.getMeta()); } + const updateWebview = () => { + webview.postMessage({ + command: DataSourceCommand.Update, + isInsights: ext.connectionNode instanceof InsightsNode, + insightsMeta: ext.insightsMeta, + dataSourceFile: this.getDocumentAsJson(document), + servers: getInsightsServers(), + selectedServer: getServerForUri(document.uri) || "", + } as DataSourceMessage2); + }; + const changeDocumentSubscription = workspace.onDidChangeTextDocument( (event) => { if (event.document.uri.toString() === document.uri.toString()) { @@ -62,16 +83,36 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { }, ); + webviewPanel.onDidChangeViewState(() => { + if (webviewPanel.active) { + updateWebview(); + } + }); + webviewPanel.onDidDispose(() => { changeDocumentSubscription.dispose(); }); - webview.onDidReceiveMessage((event) => { - switch (event.type) { - case "add": + webview.onDidReceiveMessage((msg: DataSourceMessage2) => { + switch (msg.command) { + case DataSourceCommand.Save: + this.updateTextDocument(document, msg.dataSourceFile); + commands.executeCommand("workbench.action.files.save", document); break; - - case "delete": + case DataSourceCommand.Run: + commands.executeCommand( + "kdb.dataSource.runDataSource", + msg.dataSourceFile, + ); + break; + case DataSourceCommand.Populate: + commands.executeCommand( + "kdb.dataSource.populateScratchpad", + msg.dataSourceFile, + ); + break; + case DataSourceCommand.Server: + setServerForUri(document.uri, msg.selectedServer); break; } }); diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 3f5b1098..83b908ac 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -16,7 +16,6 @@ import { repeat } from "lit/directives/repeat.js"; import { customElement, state } from "lit/decorators.js"; import { Agg, - ColumnProvider, DataSourceFiles, DataSourceTypes, Filter, @@ -31,9 +30,9 @@ import { createSort, filterOperators, } from "../../models/dataSource"; -import { DataSourceMessage } from "../../models/messages"; import { MetaObjectPayload } from "../../models/meta"; import { kdbStyles, vscodeStyles } from "./styles"; +import { DataSourceCommand, DataSourceMessage2 } from "../../models/messages"; const MAX_RULES = 32; @@ -41,56 +40,29 @@ const MAX_RULES = 32; export class KdbDataSourceView extends LitElement { static styles = [vscodeStyles, kdbStyles]; - @state() declare isInsights: boolean; - @state() declare isMetaLoaded: boolean; - @state() declare insightsMeta: MetaObjectPayload; - @state() declare originalName: string; - @state() declare name: string; - @state() declare selectedType: DataSourceTypes; - @state() declare selectedApi: string; - @state() declare selectedTable: string; - @state() declare startTS: string; - @state() declare endTS: string; - @state() declare fill: string; - @state() declare filled: boolean; - @state() declare temporality: string; - @state() declare temporal: boolean; - @state() declare filters: Filter[]; - @state() declare labels: Label[]; - @state() declare sorts: Sort[]; - @state() declare aggs: Agg[]; - @state() declare groups: Group[]; - @state() declare qsqlTarget: string; - @state() declare qsql: string; - @state() declare sql: string; - @state() declare running: boolean; - - constructor() { - super(); - this.isInsights = false; - this.isMetaLoaded = false; - this.insightsMeta = {} as MetaObjectPayload; - this.originalName = ""; - this.name = ""; - this.selectedType = DataSourceTypes.API; - this.selectedApi = ""; - this.selectedTable = ""; - this.startTS = ""; - this.endTS = ""; - this.fill = ""; - this.filled = false; - this.temporality = ""; - this.temporal = false; - this.filters = [createFilter()]; - this.labels = [createLabel()]; - this.sorts = [createSort()]; - this.aggs = [createAgg()]; - this.groups = [createGroup()]; - this.qsqlTarget = ""; - this.qsql = ""; - this.sql = ""; - this.running = false; - } + @state() isInsights = false; + @state() isMetaLoaded = false; + @state() insightsMeta = {} as MetaObjectPayload; + @state() selectedType = DataSourceTypes.API; + @state() selectedApi = ""; + @state() selectedTable = ""; + @state() startTS = ""; + @state() endTS = ""; + @state() fill = ""; + @state() filled = false; + @state() temporality = ""; + @state() temporal = false; + @state() filters = [createFilter()]; + @state() labels = [createLabel()]; + @state() sorts = [createSort()]; + @state() aggs = [createAgg()]; + @state() groups = [createGroup()]; + @state() qsqlTarget = ""; + @state() qsql = ""; + @state() sql = ""; + @state() running = false; + @state() servers: string[] = []; + @state() selectedServer = ""; connectedCallback() { super.connectedCallback(); @@ -102,44 +74,44 @@ export class KdbDataSourceView extends LitElement { super.disconnectedCallback(); } - private message = (event: MessageEvent) => { - const params = event.data; - if (params.running !== undefined) { - this.running = params.running; - return; - } - const ds = params.dataSourceFile; - this.isInsights = params.isInsights; - this.isMetaLoaded = !!params.insightsMeta.dap; - this.insightsMeta = params.insightsMeta; - this.originalName = params.dataSourceName; - this.name = params.dataSourceName; - this.selectedType = ds.dataSource.selectedType; - this.selectedApi = ds.dataSource.api.selectedApi; - this.selectedTable = ds.dataSource.api.table; - this.startTS = ds.dataSource.api.startTS; - this.endTS = ds.dataSource.api.endTS; - this.fill = ds.dataSource.api.fill; - this.temporality = ds.dataSource.api.temporality; - this.qsqlTarget = ds.dataSource.qsql.selectedTarget; - this.qsql = ds.dataSource.qsql.query; - this.sql = ds.dataSource.sql.query; - const optional = ds.dataSource.api.optional; - if (optional) { - this.filled = optional.filled; - this.temporal = optional.temporal; - this.filters = optional.filters; - this.labels = optional.labels; - this.sorts = optional.sorts; - this.aggs = optional.aggs; - this.groups = optional.groups; + private message = (event: MessageEvent) => { + const msg = event.data; + switch (msg.command) { + case DataSourceCommand.Update: + this.servers = msg.servers; + this.selectedServer = msg.selectedServer; + const ds = msg.dataSourceFile; + this.isInsights = msg.isInsights; + this.isMetaLoaded = !!msg.insightsMeta.dap; + this.insightsMeta = msg.insightsMeta; + this.selectedType = ds.dataSource.selectedType; + this.selectedApi = ds.dataSource.api.selectedApi; + this.selectedTable = ds.dataSource.api.table; + this.startTS = ds.dataSource.api.startTS; + this.endTS = ds.dataSource.api.endTS; + this.fill = ds.dataSource.api.fill; + this.temporality = ds.dataSource.api.temporality; + this.qsqlTarget = ds.dataSource.qsql.selectedTarget; + this.qsql = ds.dataSource.qsql.query; + this.sql = ds.dataSource.sql.query; + const optional = ds.dataSource.api.optional; + if (optional) { + this.filled = optional.filled; + this.temporal = optional.temporal; + this.filters = optional.filters; + this.labels = optional.labels; + this.sorts = optional.sorts; + this.aggs = optional.aggs; + this.groups = optional.groups; + } + break; } }; private get data(): DataSourceFiles { return { - name: this.name, - originalName: this.originalName, + name: "", + originalName: "", dataSource: { selectedType: this.selectedType, api: { @@ -193,26 +165,26 @@ export class KdbDataSourceView extends LitElement { private save() { this.vscode.postMessage({ - command: "kdb.dataSource.saveDataSource", - data: this.data, - }); + command: DataSourceCommand.Save, + dataSourceFile: this.data, + } as DataSourceMessage2); } private run() { this.vscode.postMessage({ - command: "kdb.dataSource.runDataSource", - data: this.data, - }); + command: DataSourceCommand.Run, + dataSourceFile: this.data, + } as DataSourceMessage2); } private populateScratchpad() { this.vscode.postMessage({ - command: "kdb.dataSource.populateScratchpad", - data: this.data, - }); + command: DataSourceCommand.Populate, + dataSourceFile: this.data, + } as DataSourceMessage2); } - private renderApiOptions(selected: string) { + private renderApiOptions() { if (this.isInsights && this.isMetaLoaded) { return this.insightsMeta.api .filter( @@ -221,55 +193,38 @@ export class KdbDataSourceView extends LitElement { .map((api) => { const value = api.api === ".kxi.getData" ? api.api.replace(".kxi.", "") : api.api; - if (!this.selectedApi) { - this.selectedApi = value; - } return html` - ${value} + ${value} `; }); } return []; } - private renderTableOptions(selected: string) { + private renderTableOptions() { if (this.isInsights && this.isMetaLoaded) { - return this.insightsMeta.assembly.flatMap((assembly) => { - return assembly.tbls.map((value) => { - if (!this.selectedTable) { - this.selectedTable = value; - } - return html` - ${value} - `; - }); - }); + return this.insightsMeta.assembly.flatMap((assembly) => + assembly.tbls.map( + (value) => html` + ${value} + `, + ), + ); } return []; } - private renderColumnOptions(selected: ColumnProvider) { + private renderColumnOptions() { if (this.isInsights && this.isMetaLoaded) { const schema = this.insightsMeta.schema; if (schema) { const found = schema.find((item) => item.table === this.selectedTable); if (found) { - return found.columns.map(({ column }) => { - if (!selected.column) { - selected.column = column; - } - return html` - ${column} - `; - }); + return found.columns.map( + ({ column }) => html` + ${column} + `, + ); } } } @@ -311,8 +266,12 @@ export class KdbDataSourceView extends LitElement { ${this.renderColumnOptions(filter)} + (filter.column = (event.target as HTMLInputElement).value)}"> + ${filter.column} + + ${this.renderColumnOptions()}
@@ -526,7 +489,11 @@ export class KdbDataSourceView extends LitElement { class="dropdown" @change="${(event: Event) => (agg.column = (event.target as HTMLInputElement).value)}"> - ${this.renderColumnOptions(agg)} + ${agg.column} + + ${this.renderColumnOptions()}
@@ -581,7 +548,11 @@ export class KdbDataSourceView extends LitElement { class="dropdown" @change="${(event: Event) => (group.column = (event.target as HTMLInputElement).value)}"> - ${this.renderColumnOptions(group)} + ${group.column} + + ${this.renderColumnOptions()}
@@ -621,17 +592,6 @@ export class KdbDataSourceView extends LitElement { return html`
-
- Data Source Name -
-
SQL - +
-
-
@@ -692,9 +658,7 @@ export class KdbDataSourceView extends LitElement { ).value)}" >Start Time -
-
- +
-
+
Populate Scratchpad + Refresh + + ${this.selectedServer} + + ${this.servers.map( + (server) => html` + ${server} + `, + )} +
`; diff --git a/src/webview/components/styles.ts b/src/webview/components/styles.ts index 5066127f..8bbb1fcf 100644 --- a/src/webview/components/styles.ts +++ b/src/webview/components/styles.ts @@ -35,11 +35,15 @@ export const vscodeStyles = css` width: unset !important; min-width: 12em; } + + vscode-text-area::part(control) { + width: 48em !important; + } `; export const kdbStyles = css` - .w-full { - min-width: 50em; + .panel { + width: 50em; } .col { @@ -90,10 +94,6 @@ export const kdbStyles = css` margin-bottom: 1em; } - .mt-6 { - margin-top: 6em; - } - .dropdown, .text-field { width: 12em; diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index 2aa3c370..fcfcbdd3 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -79,8 +79,8 @@ describe("KdbDataSourceView", () => { const data = view["data"]; assert.deepEqual(data, { ...dataSourceFile, - name: "test", - originalName: "test", + name: "", + originalName: "", }); }); }); @@ -100,15 +100,6 @@ describe("KdbDataSourceView", () => { }); }); - describe("message", () => { - it("should update status", () => { - view["message"](>{ - data: { running: true }, - }); - assert.strictEqual(view.running, true); - }); - }); - describe("selectTab", () => { it("should return the selected tab", () => { sinon.stub(view, "selectedType").value("DEFAULT"); @@ -349,13 +340,7 @@ describe("KdbDataSourceView", () => { }); describe("render", () => { - it("should update state for name input", () => { - const result = view["render"](); - (result.values[1] as any)({ target: { value: "datatsource-test" } }); - assert.strictEqual(view.name, "datatsource-test"); - }); - - it("should update state for tab selection", () => { + it.skip("should update state for tab selection", () => { const result = view["render"](); (result.values[3] as any)(); assert.strictEqual(view.selectedType, "API"); From 87c489e8b5543655b3acb02dd36376aba15d413f Mon Sep 17 00:00:00 2001 From: ecmel Date: Sat, 4 May 2024 04:12:31 +0300 Subject: [PATCH 20/32] automatic updates for datasource --- src/commands/dataSourceCommand.ts | 11 + src/extension.ts | 4 + src/models/messages.ts | 7 +- src/services/dataSourceEditorProvider.ts | 32 +- src/webview/components/kdbDataSourceView.ts | 447 +++++++++++--------- src/webview/components/styles.ts | 12 +- 6 files changed, 297 insertions(+), 216 deletions(-) diff --git a/src/commands/dataSourceCommand.ts b/src/commands/dataSourceCommand.ts index 5d44e36e..000ae4f3 100644 --- a/src/commands/dataSourceCommand.ts +++ b/src/commands/dataSourceCommand.ts @@ -231,6 +231,17 @@ export async function populateScratchpad( }); } +export async function refreshDataSource(): Promise { + if ( + ext.activeConnection instanceof LocalConnection || + !ext.activeConnection + ) { + window.showErrorMessage("No Insights active connection found"); + return; + } + Object.assign(ext.insightsMeta, await ext.activeConnection.getMeta()); +} + export async function runDataSource( dataSourceForm: DataSourceFiles, ): Promise { diff --git a/src/extension.ts b/src/extension.ts index 8c766f76..b1e77e49 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -38,6 +38,7 @@ import { deleteDataSource, openDataSource, populateScratchpad, + refreshDataSource, renameDataSource, runDataSource, saveDataSource, @@ -279,6 +280,9 @@ export async function activate(context: ExtensionContext) { await runDataSource(dataSourceForm); }, ), + commands.registerCommand("kdb.dataSource.refreshDataSource", async () => { + await refreshDataSource(); + }), commands.registerCommand( "kdb.dataSource.renameDataSource", async (viewItem: KdbDataSourceTreeItem) => { diff --git a/src/models/messages.ts b/src/models/messages.ts index 827bba38..70190d1f 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -26,18 +26,19 @@ export type DataSourceMessage = { export const enum DataSourceCommand { Update, + Change, + Server, Save, Run, Populate, Refresh, - Server, } export interface DataSourceMessage2 { command: DataSourceCommand; + servers: string[]; + selectedServer: string; isInsights: boolean; insightsMeta: MetaObjectPayload; dataSourceFile: DataSourceFiles; - servers: string[]; - selectedServer: string; } diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 9b0a3d18..30319107 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -26,7 +26,6 @@ import { } from "vscode"; import { getUri } from "../utils/getUri"; import { getNonce } from "../utils/getNonce"; -import { ConnectionManagementService } from "./connectionManagerService"; import { ext } from "../extensionVariables"; import { InsightsNode } from "./kdbTreeProvider"; import { @@ -34,8 +33,8 @@ import { getServerForUri, setServerForUri, } from "../commands/workspaceCommand"; -import { InsightsConnection } from "../classes/insightsConnection"; import { DataSourceCommand, DataSourceMessage2 } from "../models/messages"; +import { isDeepStrictEqual } from "util"; export class DataSourceEditorProvider implements CustomTextEditorProvider { static readonly viewType = "kdb.dataSourceEditor"; @@ -48,8 +47,6 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { ); } - private connectionService = new ConnectionManagementService(); - constructor(private readonly context: ExtensionContext) {} async resolveCustomTextEditor( @@ -60,18 +57,14 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { webview.options = { enableScripts: true }; webview.html = this.getWebviewContent(webview); - if (ext.activeConnection instanceof InsightsConnection) { - Object.assign(ext.insightsMeta, await ext.activeConnection.getMeta()); - } - const updateWebview = () => { webview.postMessage({ command: DataSourceCommand.Update, + servers: getInsightsServers(), + selectedServer: getServerForUri(document.uri) || "", isInsights: ext.connectionNode instanceof InsightsNode, insightsMeta: ext.insightsMeta, dataSourceFile: this.getDocumentAsJson(document), - servers: getInsightsServers(), - selectedServer: getServerForUri(document.uri) || "", } as DataSourceMessage2); }; @@ -95,8 +88,18 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { webview.onDidReceiveMessage((msg: DataSourceMessage2) => { switch (msg.command) { + case DataSourceCommand.Server: + setServerForUri(document.uri, msg.selectedServer); + break; + case DataSourceCommand.Change: + const changed = msg.dataSourceFile; + const current = this.getDocumentAsJson(document); + if (isDeepStrictEqual(current, changed)) { + break; + } + this.updateTextDocument(document, changed); + break; case DataSourceCommand.Save: - this.updateTextDocument(document, msg.dataSourceFile); commands.executeCommand("workbench.action.files.save", document); break; case DataSourceCommand.Run: @@ -111,8 +114,11 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { msg.dataSourceFile, ); break; - case DataSourceCommand.Server: - setServerForUri(document.uri, msg.selectedServer); + case DataSourceCommand.Refresh: + commands.executeCommand( + "kdb.dataSource.refreshDataSource", + msg.selectedServer, + ); break; } }); diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 83b908ac..725f6e34 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -13,7 +13,8 @@ import { LitElement, html } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import { customElement, state } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; +import { css } from "lit"; import { Agg, DataSourceFiles, @@ -38,31 +39,44 @@ const MAX_RULES = 32; @customElement("kdb-data-source-view") export class KdbDataSourceView extends LitElement { - static styles = [vscodeStyles, kdbStyles]; - - @state() isInsights = false; - @state() isMetaLoaded = false; - @state() insightsMeta = {} as MetaObjectPayload; - @state() selectedType = DataSourceTypes.API; - @state() selectedApi = ""; - @state() selectedTable = ""; - @state() startTS = ""; - @state() endTS = ""; - @state() fill = ""; - @state() filled = false; - @state() temporality = ""; - @state() temporal = false; - @state() filters = [createFilter()]; - @state() labels = [createLabel()]; - @state() sorts = [createSort()]; - @state() aggs = [createAgg()]; - @state() groups = [createGroup()]; - @state() qsqlTarget = ""; - @state() qsql = ""; - @state() sql = ""; - @state() running = false; - @state() servers: string[] = []; - @state() selectedServer = ""; + static styles = [ + vscodeStyles, + kdbStyles, + css` + vscode-text-area::part(control) { + width: 48em !important; + } + + .panel { + width: 50em; + } + `, + ]; + + private isInsights = false; + private isMetaLoaded = false; + private insightsMeta = {} as MetaObjectPayload; + private selectedType = DataSourceTypes.API; + private selectedApi = ""; + private selectedTable = ""; + private startTS = ""; + private endTS = ""; + private fill = ""; + private filled = false; + private temporality = ""; + private temporal = false; + private filters = [createFilter()]; + private labels = [createLabel()]; + private sorts = [createSort()]; + private aggs = [createAgg()]; + private groups = [createGroup()]; + private qsqlTarget = ""; + private qsql = ""; + private sql = ""; + private running = false; + private servers: string[] = []; + private selectedServer = ""; + private updating = 0; connectedCallback() { super.connectedCallback(); @@ -80,10 +94,10 @@ export class KdbDataSourceView extends LitElement { case DataSourceCommand.Update: this.servers = msg.servers; this.selectedServer = msg.selectedServer; - const ds = msg.dataSourceFile; this.isInsights = msg.isInsights; this.isMetaLoaded = !!msg.insightsMeta.dap; this.insightsMeta = msg.insightsMeta; + const ds = msg.dataSourceFile; this.selectedType = ds.dataSource.selectedType; this.selectedApi = ds.dataSource.api.selectedApi; this.selectedTable = ds.dataSource.api.table; @@ -104,6 +118,7 @@ export class KdbDataSourceView extends LitElement { this.aggs = optional.aggs; this.groups = optional.groups; } + this.requestUpdate(); break; } }; @@ -150,19 +165,6 @@ export class KdbDataSourceView extends LitElement { private readonly vscode = acquireVsCodeApi(); - private get selectedTab() { - switch (this.selectedType) { - case DataSourceTypes.API: - return "tab-1"; - case DataSourceTypes.QSQL: - return "tab-2"; - case DataSourceTypes.SQL: - return "tab-3"; - default: - return "tab-1"; - } - } - private save() { this.vscode.postMessage({ command: DataSourceCommand.Save, @@ -184,6 +186,29 @@ export class KdbDataSourceView extends LitElement { } as DataSourceMessage2); } + private refresh() { + this.vscode.postMessage({ + command: DataSourceCommand.Refresh, + } as DataSourceMessage2); + } + + private requestChange() { + this.requestUpdate(); + this.vscode.postMessage({ + command: DataSourceCommand.Change, + dataSourceFile: this.data, + } as DataSourceMessage2); + } + + private requestServerChange(event: Event) { + this.selectedServer = (event.target as HTMLSelectElement).value; + this.requestUpdate(); + this.vscode.postMessage({ + command: DataSourceCommand.Server, + selectedServer: this.selectedServer, + } as DataSourceMessage2); + } + private renderApiOptions() { if (this.isInsights && this.isMetaLoaded) { return this.insightsMeta.api @@ -231,18 +256,14 @@ export class KdbDataSourceView extends LitElement { return []; } - private renderTargetOptions(selected: string) { + private renderTargetOptions() { if (this.isInsights && this.isMetaLoaded) { return this.insightsMeta.dap.map((dap) => { const value = `${dap.assembly}-qe ${dap.instance}`; if (!this.qsqlTarget) { this.qsqlTarget = value; } - return html` - ${value} - `; + return html` ${value} `; }); } return []; @@ -253,10 +274,10 @@ export class KdbDataSourceView extends LitElement {
+ @change="${(event: Event) => { + filter.active = (event.target as HTMLInputElement).checked; + this.requestChange(); + }}"> @@ -282,13 +305,17 @@ export class KdbDataSourceView extends LitElement { > + @change="${(event: Event) => { + filter.operator = (event.target as HTMLInputElement).value; + this.requestChange(); + }}"> + ${filter.operator} + Options ${filterOperators.map( (operator) => - html` ${operator}`, )} @@ -297,8 +324,10 @@ export class KdbDataSourceView extends LitElement { ${this.filters.indexOf(filter) === 0 ? "Set Parameter" : ""}
@@ -309,7 +338,7 @@ export class KdbDataSourceView extends LitElement { if (this.filters.length < MAX_RULES) { const index = this.filters.indexOf(filter); this.filters.splice(index + 1, 0, createFilter()); - this.requestUpdate(); + this.requestChange(); } }}" >+- + @change="${(event: Event) => { + label.active = (event.target as HTMLInputElement).checked; + this.requestChange(); + }}"> ${this.labels.indexOf(label) === 0 ? "Filter By Label" : ""}${this.labels.indexOf(label) === 0 ? "Value" : ""}
@@ -367,7 +400,7 @@ export class KdbDataSourceView extends LitElement { if (this.labels.length < MAX_RULES) { const index = this.labels.indexOf(label); this.labels.splice(index + 1, 0, createLabel()); - this.requestUpdate(); + this.requestChange(); } }}" >+- + @change="${(event: Event) => { + sort.active = (event.target as HTMLInputElement).checked; + this.requestChange(); + }}"> @@ -422,7 +457,7 @@ export class KdbDataSourceView extends LitElement { if (this.sorts.length < MAX_RULES) { const index = this.sorts.indexOf(sort); this.sorts.splice(index + 1, 0, createSort()); - this.requestUpdate(); + this.requestChange(); } }}" >+- + @change="${(event: Event) => { + agg.active = (event.target as HTMLInputElement).checked; + this.requestChange(); + }}"> ${this.aggs.indexOf(agg) === 0 ? "Define Output Aggregate" : ""} + @change="${(event: Event) => { + agg.operator = (event.target as HTMLInputElement).value; + this.requestChange(); + }}"> + ${agg.operator} + Options ${aggOperators.map( (operator) => - html`${operator}`, )} @@ -487,12 +528,14 @@ export class KdbDataSourceView extends LitElement { + @change="${(event: Event) => { + agg.column = (event.target as HTMLInputElement).value; + this.requestChange(); + }}"> ${agg.column} - + Options ${this.renderColumnOptions()}
@@ -504,7 +547,7 @@ export class KdbDataSourceView extends LitElement { if (this.aggs.length < MAX_RULES) { const index = this.aggs.indexOf(agg); this.aggs.splice(index + 1, 0, createAgg()); - this.requestUpdate(); + this.requestChange(); } }}" >+- + @change="${(event: Event) => { + group.active = (event.target as HTMLInputElement).checked; + this.requestChange(); + }}"> @@ -563,7 +608,7 @@ export class KdbDataSourceView extends LitElement { if (this.groups.length < MAX_RULES) { const index = this.groups.indexOf(group); this.groups.splice(index + 1, 0, createGroup()); - this.requestUpdate(); + this.requestChange(); } }}" >+-
- + API QSQL SQL @@ -617,14 +671,16 @@ export class KdbDataSourceView extends LitElement { { + this.selectedApi = ( event.target as HTMLInputElement - ).value)}"> + ).value; + this.requestChange(); + }}"> ${this.selectedApi} - + Options ${this.renderApiOptions()}
@@ -634,14 +690,16 @@ export class KdbDataSourceView extends LitElement { { + this.selectedTable = ( event.target as HTMLSelectElement - ).value)}"> + ).value; + this.requestChange(); + }}"> ${this.selectedTable} - + Options ${this.renderTableOptions()}
@@ -652,10 +710,12 @@ export class KdbDataSourceView extends LitElement { type="datetime-local" class="text-field larger" value="${this.startTS}" - @input="${(event: Event) => - (this.startTS = ( + @change="${(event: Event) => { + this.startTS = ( event.target as HTMLSelectElement - ).value)}" + ).value; + this.requestChange(); + }}" >Start Time @@ -663,10 +723,10 @@ export class KdbDataSourceView extends LitElement { type="datetime-local" class="text-field larger" value="${this.endTS}" - @input="${(event: Event) => - (this.endTS = ( - event.target as HTMLSelectElement - ).value)}" + @change="${(event: Event) => { + this.endTS = (event.target as HTMLSelectElement).value; + this.requestChange(); + }}" >End Time
@@ -674,29 +734,27 @@ export class KdbDataSourceView extends LitElement {
{ + this.filled = ( event.target as HTMLInputElement - ).checked)}"> + ).checked; + this.requestChange(); + }}">
@@ -704,29 +762,29 @@ export class KdbDataSourceView extends LitElement {
{ + this.temporal = ( event.target as HTMLInputElement - ).checked)}"> + ).checked; + this.requestChange(); + }}">
@@ -777,13 +835,17 @@ export class KdbDataSourceView extends LitElement { { + this.qsqlTarget = ( event.target as HTMLSelectElement - ).value)}"> - ${this.renderTargetOptions( - this.qsqlTarget, - )} + ${this.qsqlTarget} + Options + ${this.renderTargetOptions()}
@@ -791,10 +853,10 @@ export class KdbDataSourceView extends LitElement { Query
@@ -807,8 +869,10 @@ export class KdbDataSourceView extends LitElement { Query
@@ -818,7 +882,23 @@ export class KdbDataSourceView extends LitElement {
-
+ +
Populate Scratchpad - Refresh - - ${this.selectedServer} - - ${this.servers.map( - (server) => html` - ${server} - `, - )} -
`; diff --git a/src/webview/components/styles.ts b/src/webview/components/styles.ts index 8bbb1fcf..5066127f 100644 --- a/src/webview/components/styles.ts +++ b/src/webview/components/styles.ts @@ -35,15 +35,11 @@ export const vscodeStyles = css` width: unset !important; min-width: 12em; } - - vscode-text-area::part(control) { - width: 48em !important; - } `; export const kdbStyles = css` - .panel { - width: 50em; + .w-full { + min-width: 50em; } .col { @@ -94,6 +90,10 @@ export const kdbStyles = css` margin-bottom: 1em; } + .mt-6 { + margin-top: 6em; + } + .dropdown, .text-field { width: 12em; From a741b1a295f245daed3e4d25d1e27adcea5687bf Mon Sep 17 00:00:00 2001 From: ecmel Date: Sat, 4 May 2024 12:49:36 +0300 Subject: [PATCH 21/32] ability to add python sp --- package.json | 43 ++++++------------ src/commands/workspaceCommand.ts | 48 +++++++++++---------- src/extension.ts | 9 ++++ src/services/dataSourceEditorProvider.ts | 33 +++++++------- src/webview/components/kdbDataSourceView.ts | 33 +++++++------- 5 files changed, 83 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index 49d34bc8..92528bc8 100644 --- a/package.json +++ b/package.json @@ -223,6 +223,12 @@ "title": "Add a new scratchpad", "icon": "$(add)" }, + { + "category": "KX", + "command": "kdb.createPythonScratchpad", + "title": "Add a new Python scratchpad", + "icon": "$(file-add)" + }, { "category": "KX", "command": "kdb.refreshScratchpadExplorer", @@ -234,40 +240,11 @@ "command": "kdb.pickConnection", "title": "Pick connection" }, - { - "category": "KX", - "command": "kdb.createDataSource", - "title": "Add a new data source", - "icon": "$(add)" - }, - { - "category": "KX", - "command": "kdb.refreshDataSourceExplorer", - "title": "Refresh data sources", - "icon": "$(refresh)" - }, - { - "category": "KX", - "command": "kdb.createScratchpad", - "title": "Add a new scratchpad", - "icon": "$(add)" - }, - { - "category": "KX", - "command": "kdb.refreshScratchpadExplorer", - "title": "Refresh scratchpads", - "icon": "$(refresh)" - }, { "category": "KX", "command": "kdb.runScratchpad", "title": "Run scratchpad" }, - { - "category": "KX", - "command": "kdb.pickConnection", - "title": "Pick connection" - }, { "category": "KX", "command": "kdb.startLocalProcess", @@ -618,6 +595,7 @@ "group": "resultsPanel" } ], + "view/title/create": [], "view/title": [ { "command": "kdb.addConnection", @@ -645,10 +623,15 @@ "group": "navigation@1" }, { - "command": "kdb.refreshScratchpadExplorer", + "command": "kdb.createPythonScratchpad", "when": "view == kdb-scratchpad-explorer", "group": "navigation@2" }, + { + "command": "kdb.refreshScratchpadExplorer", + "when": "view == kdb-scratchpad-explorer", + "group": "navigation@3" + }, { "command": "kdb.queryHistory.clear", "when": "view == kdb-query-history", diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index 4ec18f4c..5f5b6826 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -107,18 +107,18 @@ async function getConnectionForServer(server: string) { } } -async function waitForConnection(name: string) { +async function waitForConnection(label: string) { return new Promise((resolve, reject) => { let count = 0; const retry = () => { count++; setTimeout(() => { - if (connectionService.isConnected(name)) { + if (connectionService.isConnected(label)) { resolve(); } else if (count < 5) { retry(); } else { - reject(`Can not connect to ${name}`); + reject(`Can not connect to ${label}`); } }, 50); }; @@ -192,6 +192,27 @@ function isScratchpad(uri: Uri) { return uri.path.endsWith(".kdb.q") || uri.path.endsWith(".kdb.py"); } +export async function activateConnectionForServer(server: string) { + const connection = await getConnectionForServer(server); + if (connection) { + if (!connectionService.isConnected(connection.label)) { + const action = await window.showWarningMessage( + `${connection.label} is not connected`, + "Connect", + ); + if (action === "Connect") { + await connectionService.connect(connection.label); + await waitForConnection(connection.label); + } else { + throw new Error(`${connection.label} is not connected`); + } + } + connectionService.setActiveConnection(connection); + } else { + throw new Error(`${server} is not found`); + } +} + export async function runActiveEditor(type?: ExecutionTypes) { if (ext.activeTextEditor) { const uri = ext.activeTextEditor.document.uri; @@ -200,29 +221,10 @@ export async function runActiveEditor(type?: ExecutionTypes) { if (!server) { server = await pickConnection(uri); } - if (server) { - const connection = await getConnectionForServer(server); - if (connection) { - if (!connectionService.isConnected(connection.label)) { - const action = await window.showWarningMessage( - `${connection.label} is not connected`, - "Connect", - ); - if (action === "Connect") { - await connectionService.connect(connection.label); - await waitForConnection(connection.label); - } else { - return; - } - } - connectionService.setActiveConnection(connection); - } else { - window.showErrorMessage(`${server} is not found`); - } + await activateConnectionForServer(server); } } - runQuery( type === undefined ? isPython(uri) diff --git a/src/extension.ts b/src/extension.ts index b1e77e49..48685e67 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -393,6 +393,15 @@ export async function activate(context: ExtensionContext) { } }, ), + commands.registerCommand( + "kdb.createPythonScratchpad", + async (item: FileTreeItem) => { + const uri = await addWorkspaceFile(item, "scratchpad", ".kdb.py"); + if (uri) { + await window.showTextDocument(uri); + } + }, + ), commands.registerCommand("kdb.refreshScratchpadExplorer", () => { ext.scratchpadTreeProvider.reload(); }), diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 30319107..262d0640 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -29,12 +29,18 @@ import { getNonce } from "../utils/getNonce"; import { ext } from "../extensionVariables"; import { InsightsNode } from "./kdbTreeProvider"; import { + activateConnectionForServer, getInsightsServers, getServerForUri, setServerForUri, } from "../commands/workspaceCommand"; import { DataSourceCommand, DataSourceMessage2 } from "../models/messages"; import { isDeepStrictEqual } from "util"; +import { + populateScratchpad, + refreshDataSource, + runDataSource, +} from "../commands/dataSourceCommand"; export class DataSourceEditorProvider implements CustomTextEditorProvider { static readonly viewType = "kdb.dataSourceEditor"; @@ -86,10 +92,10 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { changeDocumentSubscription.dispose(); }); - webview.onDidReceiveMessage((msg: DataSourceMessage2) => { + webview.onDidReceiveMessage(async (msg: DataSourceMessage2) => { switch (msg.command) { case DataSourceCommand.Server: - setServerForUri(document.uri, msg.selectedServer); + await setServerForUri(document.uri, msg.selectedServer); break; case DataSourceCommand.Change: const changed = msg.dataSourceFile; @@ -100,25 +106,22 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { this.updateTextDocument(document, changed); break; case DataSourceCommand.Save: - commands.executeCommand("workbench.action.files.save", document); + await commands.executeCommand( + "workbench.action.files.save", + document, + ); break; case DataSourceCommand.Run: - commands.executeCommand( - "kdb.dataSource.runDataSource", - msg.dataSourceFile, - ); + await activateConnectionForServer(msg.selectedServer); + await runDataSource(msg.dataSourceFile); break; case DataSourceCommand.Populate: - commands.executeCommand( - "kdb.dataSource.populateScratchpad", - msg.dataSourceFile, - ); + await activateConnectionForServer(msg.selectedServer); + await populateScratchpad(msg.dataSourceFile); break; case DataSourceCommand.Refresh: - commands.executeCommand( - "kdb.dataSource.refreshDataSource", - msg.selectedServer, - ); + await activateConnectionForServer(msg.selectedServer); + await refreshDataSource(); break; } }); diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 725f6e34..6648dcc0 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -49,6 +49,7 @@ export class KdbDataSourceView extends LitElement { .panel { width: 50em; + margin-top: 0.5em; } `, ]; @@ -172,9 +173,17 @@ export class KdbDataSourceView extends LitElement { } as DataSourceMessage2); } + private refresh() { + this.vscode.postMessage({ + command: DataSourceCommand.Refresh, + selectedServer: this.selectedServer, + } as DataSourceMessage2); + } + private run() { this.vscode.postMessage({ command: DataSourceCommand.Run, + selectedServer: this.selectedServer, dataSourceFile: this.data, } as DataSourceMessage2); } @@ -182,16 +191,11 @@ export class KdbDataSourceView extends LitElement { private populateScratchpad() { this.vscode.postMessage({ command: DataSourceCommand.Populate, + selectedServer: this.selectedServer, dataSourceFile: this.data, } as DataSourceMessage2); } - private refresh() { - this.vscode.postMessage({ - command: DataSourceCommand.Refresh, - } as DataSourceMessage2); - } - private requestChange() { this.requestUpdate(); this.vscode.postMessage({ @@ -905,22 +909,21 @@ export class KdbDataSourceView extends LitElement { @click="${this.save}" >Save - RunRefresh
+ Run Populate Scratchpad - Refresh
`; From 0c2c817d67ac915bd21fd67d8562d6346a117eb2 Mon Sep 17 00:00:00 2001 From: ecmel Date: Sat, 4 May 2024 14:45:24 +0300 Subject: [PATCH 22/32] call update after commands --- src/services/dataSourceEditorProvider.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 262d0640..77ea9d3d 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -114,14 +114,17 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { case DataSourceCommand.Run: await activateConnectionForServer(msg.selectedServer); await runDataSource(msg.dataSourceFile); + updateWebview(); break; case DataSourceCommand.Populate: await activateConnectionForServer(msg.selectedServer); await populateScratchpad(msg.dataSourceFile); + updateWebview(); break; case DataSourceCommand.Refresh: await activateConnectionForServer(msg.selectedServer); await refreshDataSource(); + updateWebview(); break; } }); From c58c471b42f3cc1678365accf64e546de9288639 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 6 May 2024 06:50:05 +0300 Subject: [PATCH 23/32] use value props in webview --- src/classes/localConnection.ts | 1 - src/services/dataSourceEditorProvider.ts | 11 +- src/webview/components/kdbDataSourceView.ts | 198 +++++++++++--------- test/suite/webview.test.ts | 22 +-- 4 files changed, 126 insertions(+), 106 deletions(-) diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index b4dabc16..4fb9e608 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -69,7 +69,6 @@ export class LocalConnection { return this.connection; } - // TODO MS This function is not async public async connect( callback: nodeq.AsyncValueCallback, ): Promise { diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 77ea9d3d..90d87815 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -111,6 +111,11 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { document, ); break; + case DataSourceCommand.Refresh: + await activateConnectionForServer(msg.selectedServer); + await refreshDataSource(); + updateWebview(); + break; case DataSourceCommand.Run: await activateConnectionForServer(msg.selectedServer); await runDataSource(msg.dataSourceFile); @@ -119,12 +124,6 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { case DataSourceCommand.Populate: await activateConnectionForServer(msg.selectedServer); await populateScratchpad(msg.dataSourceFile); - updateWebview(); - break; - case DataSourceCommand.Refresh: - await activateConnectionForServer(msg.selectedServer); - await refreshDataSource(); - updateWebview(); break; } }); diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 6648dcc0..78ec1d74 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -54,30 +54,32 @@ export class KdbDataSourceView extends LitElement { `, ]; - private isInsights = false; - private isMetaLoaded = false; - private insightsMeta = {} as MetaObjectPayload; - private selectedType = DataSourceTypes.API; - private selectedApi = ""; - private selectedTable = ""; - private startTS = ""; - private endTS = ""; - private fill = ""; - private filled = false; - private temporality = ""; - private temporal = false; - private filters = [createFilter()]; - private labels = [createLabel()]; - private sorts = [createSort()]; - private aggs = [createAgg()]; - private groups = [createGroup()]; - private qsqlTarget = ""; - private qsql = ""; - private sql = ""; - private running = false; - private servers: string[] = []; - private selectedServer = ""; - private updating = 0; + readonly vscode = acquireVsCodeApi(); + + isInsights = false; + isMetaLoaded = false; + insightsMeta = {} as MetaObjectPayload; + selectedType = DataSourceTypes.API; + selectedApi = ""; + selectedTable = ""; + startTS = ""; + endTS = ""; + fill = ""; + filled = false; + temporality = ""; + temporal = false; + filters = [createFilter()]; + labels = [createLabel()]; + sorts = [createSort()]; + aggs = [createAgg()]; + groups = [createGroup()]; + qsqlTarget = ""; + qsql = ""; + sql = ""; + running = false; + servers: string[] = []; + selectedServer = ""; + updating = 0; connectedCallback() { super.connectedCallback(); @@ -89,7 +91,7 @@ export class KdbDataSourceView extends LitElement { super.disconnectedCallback(); } - private message = (event: MessageEvent) => { + message = (event: MessageEvent) => { const msg = event.data; switch (msg.command) { case DataSourceCommand.Update: @@ -119,12 +121,12 @@ export class KdbDataSourceView extends LitElement { this.aggs = optional.aggs; this.groups = optional.groups; } - this.requestUpdate(); break; } + this.requestUpdate(); }; - private get data(): DataSourceFiles { + get data(): DataSourceFiles { return { name: "", originalName: "", @@ -164,73 +166,77 @@ export class KdbDataSourceView extends LitElement { }; } - private readonly vscode = acquireVsCodeApi(); + postMessage(msg: Partial) { + this.vscode.postMessage(msg); + } - private save() { - this.vscode.postMessage({ + save() { + this.postMessage({ command: DataSourceCommand.Save, dataSourceFile: this.data, - } as DataSourceMessage2); + }); } - private refresh() { - this.vscode.postMessage({ + refresh() { + this.postMessage({ command: DataSourceCommand.Refresh, selectedServer: this.selectedServer, - } as DataSourceMessage2); + }); } - private run() { + run() { this.vscode.postMessage({ command: DataSourceCommand.Run, selectedServer: this.selectedServer, dataSourceFile: this.data, - } as DataSourceMessage2); + }); } - private populateScratchpad() { + populateScratchpad() { this.vscode.postMessage({ command: DataSourceCommand.Populate, selectedServer: this.selectedServer, dataSourceFile: this.data, - } as DataSourceMessage2); + }); } - private requestChange() { + requestChange() { this.requestUpdate(); this.vscode.postMessage({ command: DataSourceCommand.Change, dataSourceFile: this.data, - } as DataSourceMessage2); + }); } - private requestServerChange(event: Event) { + requestServerChange(event: Event) { this.selectedServer = (event.target as HTMLSelectElement).value; this.requestUpdate(); this.vscode.postMessage({ command: DataSourceCommand.Server, selectedServer: this.selectedServer, - } as DataSourceMessage2); + }); } - private renderApiOptions() { + renderApiOptions() { if (this.isInsights && this.isMetaLoaded) { return this.insightsMeta.api .filter( - (api) => api.api === ".kxi.getData", //|| !api.api.startsWith(".kxi.") + (api) => api.api === ".kxi.getData" || !api.api.startsWith(".kxi."), ) .map((api) => { const value = api.api === ".kxi.getData" ? api.api.replace(".kxi.", "") : api.api; return html` - ${value} + ${value} `; }); } return []; } - private renderTableOptions() { + renderTableOptions() { if (this.isInsights && this.isMetaLoaded) { return this.insightsMeta.assembly.flatMap((assembly) => assembly.tbls.map( @@ -243,7 +249,7 @@ export class KdbDataSourceView extends LitElement { return []; } - private renderColumnOptions() { + renderColumnOptions() { if (this.isInsights && this.isMetaLoaded) { const schema = this.insightsMeta.schema; if (schema) { @@ -260,35 +266,36 @@ export class KdbDataSourceView extends LitElement { return []; } - private renderTargetOptions() { + renderTargetOptions() { if (this.isInsights && this.isMetaLoaded) { return this.insightsMeta.dap.map((dap) => { const value = `${dap.assembly}-qe ${dap.instance}`; if (!this.qsqlTarget) { this.qsqlTarget = value; } - return html` ${value} `; + return html`${value}`; }); } return []; } - private renderFilter(filter: Filter) { + renderFilter(filter: Filter) { return html`
@@ -856,7 +872,7 @@ export class KdbDataSourceView extends LitElement {
@@ -922,6 +943,7 @@ export class KdbDataSourceView extends LitElement { Populate Scratchpad
diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index fcfcbdd3..0abd605d 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -157,7 +157,7 @@ describe("KdbDataSourceView", () => { describe("renderApiOptions", () => { it("should render empty array", () => { - const result = view["renderApiOptions"](""); + const result = view["renderApiOptions"](); assert.deepEqual(result, []); }); @@ -167,22 +167,22 @@ describe("KdbDataSourceView", () => { sinon .stub(view, "insightsMeta") .value({ api: [{ api: ".kxi.getData" }] }); - const result = view["renderApiOptions"]("getData"); + const result = view["renderApiOptions"](); assert.deepEqual(result[0].values, ["getData", true, "getData"]); }); - it.skip("should render other api", () => { + it("should render other api", () => { sinon.stub(view, "isInsights").value(true); sinon.stub(view, "isMetaLoaded").value(true); sinon.stub(view, "insightsMeta").value({ api: [{ api: "other" }] }); - const result = view["renderApiOptions"]("other"); + const result = view["renderApiOptions"](); assert.deepEqual(result[0].values, ["other", true, "other"]); }); }); describe("renderTableOptions", () => { it("should render empty array", () => { - const result = view["renderTableOptions"](""); + const result = view["renderTableOptions"](); assert.deepEqual(result, []); }); @@ -192,14 +192,14 @@ describe("KdbDataSourceView", () => { sinon .stub(view, "insightsMeta") .value({ assembly: [{ tbls: ["table"] }] }); - const result = view["renderTableOptions"]("table"); + const result = view["renderTableOptions"](); assert.deepEqual(result[0].values, ["table", true, "table"]); }); }); describe("renderColumnOptions", () => { it("should render empty array", () => { - const result = view["renderColumnOptions"]({ column: "" }); + const result = view["renderColumnOptions"](); assert.deepEqual(result, []); }); @@ -211,7 +211,7 @@ describe("KdbDataSourceView", () => { sinon .stub(view, "insightsMeta") .value({ schema: [{ table: "table", columns: [{ column: "id" }] }] }); - const result = view["renderColumnOptions"](provider); + const result = view["renderColumnOptions"](); assert.strictEqual(provider.column, "id"); assert.deepEqual(result[0].values, ["id", true, "id"]); }); @@ -219,7 +219,7 @@ describe("KdbDataSourceView", () => { describe("renderTargetOptions", () => { it("should render empty array", () => { - const result = view["renderTargetOptions"](""); + const result = view["renderTargetOptions"](); assert.deepEqual(result, []); }); @@ -229,7 +229,7 @@ describe("KdbDataSourceView", () => { sinon .stub(view, "insightsMeta") .value({ dap: [{ assembly: "assembly", instance: "instance" }] }); - const result = view["renderTargetOptions"]("assembly-qe instance"); + const result = view["renderTargetOptions"](); assert.deepEqual(result[0].values, [ "assembly-qe instance", true, @@ -340,7 +340,7 @@ describe("KdbDataSourceView", () => { }); describe("render", () => { - it.skip("should update state for tab selection", () => { + it("should update state for tab selection", () => { const result = view["render"](); (result.values[3] as any)(); assert.strictEqual(view.selectedType, "API"); From 5e20fb10696a7a05e5ba7a4e259dbc0d73684165 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 6 May 2024 06:56:29 +0300 Subject: [PATCH 24/32] do not modify connect --- src/commands/serverCommand.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 2a3e5b2c..aac4df5c 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -260,7 +260,7 @@ export async function removeConnection(viewItem: KdbNode | InsightsNode) { export async function connect(viewItem: KdbNode | InsightsNode): Promise { const connMngService = new ConnectionManagementService(); - await commands.executeCommand("kdb-results.focus"); + commands.executeCommand("kdb-results.focus"); ExecutionConsole.start(); // handle cleaning up existing connection if (ext.activeConnection !== undefined) { @@ -272,14 +272,17 @@ export async function connect(viewItem: KdbNode | InsightsNode): Promise { // check for TLS support if (viewItem.details.tls) { if (!(await checkOpenSslInstalled())) { - const result = await window.showInformationMessage( - "TLS support requires OpenSSL to be installed.", - "More Info", - "Cancel", - ); - if (result === "More Info") { - openUrl("https://code.kx.com/q/kb/ssl/"); - } + window + .showInformationMessage( + "TLS support requires OpenSSL to be installed.", + "More Info", + "Cancel", + ) + .then(async (result) => { + if (result === "More Info") { + await openUrl("https://code.kx.com/q/kb/ssl/"); + } + }); } } } From c1646a4a3e0c5046d3a5dabd3e49e3fc6f6c3543 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 6 May 2024 18:12:26 +0300 Subject: [PATCH 25/32] increase coverage --- src/services/dataSourceEditorProvider.ts | 4 +- src/webview/components/kdbDataSourceView.ts | 9 +- test/suite/commands.test.ts | 26 ++ test/suite/services.test.ts | 53 ++- test/suite/webview.test.ts | 426 +++++++------------- 5 files changed, 231 insertions(+), 287 deletions(-) diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 90d87815..bc97a4c7 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -64,14 +64,14 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { webview.html = this.getWebviewContent(webview); const updateWebview = () => { - webview.postMessage({ + webview.postMessage({ command: DataSourceCommand.Update, servers: getInsightsServers(), selectedServer: getServerForUri(document.uri) || "", isInsights: ext.connectionNode instanceof InsightsNode, insightsMeta: ext.insightsMeta, dataSourceFile: this.getDocumentAsJson(document), - } as DataSourceMessage2); + }); }; const changeDocumentSubscription = workspace.onDidChangeTextDocument( diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 78ec1d74..4baddb20 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -166,6 +166,7 @@ export class KdbDataSourceView extends LitElement { }; } + /* istanbul ignore next */ postMessage(msg: Partial) { this.vscode.postMessage(msg); } @@ -185,7 +186,7 @@ export class KdbDataSourceView extends LitElement { } run() { - this.vscode.postMessage({ + this.postMessage({ command: DataSourceCommand.Run, selectedServer: this.selectedServer, dataSourceFile: this.data, @@ -193,7 +194,7 @@ export class KdbDataSourceView extends LitElement { } populateScratchpad() { - this.vscode.postMessage({ + this.postMessage({ command: DataSourceCommand.Populate, selectedServer: this.selectedServer, dataSourceFile: this.data, @@ -202,7 +203,7 @@ export class KdbDataSourceView extends LitElement { requestChange() { this.requestUpdate(); - this.vscode.postMessage({ + this.postMessage({ command: DataSourceCommand.Change, dataSourceFile: this.data, }); @@ -211,7 +212,7 @@ export class KdbDataSourceView extends LitElement { requestServerChange(event: Event) { this.selectedServer = (event.target as HTMLSelectElement).value; this.requestUpdate(); - this.vscode.postMessage({ + this.postMessage({ command: DataSourceCommand.Server, selectedServer: this.selectedServer, }); diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index 3aa99c8b..70a54373 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -50,6 +50,7 @@ import { MAX_STR_LEN } from "../../src/validators/kdbValidator"; import { LocalConnection } from "../../src/classes/localConnection"; import { ConnectionManagementService } from "../../src/services/connectionManagerService"; import { InsightsConnection } from "../../src/classes/insightsConnection"; +import * as workspaceCommand from "../../src/commands/workspaceCommand"; describe("dataSourceCommand", () => { afterEach(() => { @@ -1859,3 +1860,28 @@ describe("walkthroughCommand", () => { //function to be deleted after write the tests walkthroughCommand.showInstallationDetails(); }); + +describe("workspaceCommand", () => { + beforeEach(() => { + sinon.stub(vscode.workspace, "getConfiguration").value(() => { + return { + get(key: string) { + switch (key) { + case "insightsEnterpriseConnections": + return [{ alias: "connection1" }]; + } + return {}; + }, + }; + }); + }); + afterEach(() => { + sinon.restore(); + }); + describe("getInsightsServers", () => { + it("should return insights server aliases as array", () => { + const result = workspaceCommand.getInsightsServers(); + assert.strictEqual(result[0], "connection1"); + }); + }); +}); diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index 8d94740f..413aeaae 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -14,7 +14,13 @@ import axios from "axios"; import assert from "node:assert"; import sinon from "sinon"; -import { TreeItemCollapsibleState, commands, env, window } from "vscode"; +import { + ExtensionContext, + TreeItemCollapsibleState, + commands, + env, + window, +} from "vscode"; import { ext } from "../../src/extensionVariables"; import { Insights } from "../../src/models/insights"; import { QueryHistory } from "../../src/models/queryHistory"; @@ -42,6 +48,8 @@ import { ConnectionManagementService } from "../../src/services/connectionManage import { LocalConnection } from "../../src/classes/localConnection"; import { Telemetry } from "../../src/utils/telemetryClient"; import { InsightsConnection } from "../../src/classes/insightsConnection"; +import { DataSourceEditorProvider } from "../../src/services/dataSourceEditorProvider"; +import { WorkspaceTreeProvider } from "../../src/services/workspaceTreeProvider"; // eslint-disable-next-line @typescript-eslint/no-var-requires const codeFlow = require("../../src/services/kdbInsights/codeFlowLogin"); @@ -1011,3 +1019,46 @@ describe("connectionManagerService", () => { }); }); }); + +describe("dataSourceEditorProvider", () => { + let context: ExtensionContext; + + beforeEach(() => { + context = {}; + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("register", () => { + it("should register the provider", () => { + let result = undefined; + sinon + .stub(window, "registerCustomEditorProvider") + .value(() => (result = true)); + DataSourceEditorProvider.register(context); + assert.ok(result); + }); + }); +}); + +describe("workspaceTreeProvider", () => { + let provider: WorkspaceTreeProvider; + + beforeEach(() => { + sinon.stub(ext, "serverProvider").value({ onDidChangeTreeData() {} }); + provider = new WorkspaceTreeProvider("*.*", "icon"); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("getChildren", () => { + it("should return empty array", async () => { + const result = await provider.getChildren(); + assert.strictEqual(result.length, 0); + }); + }); +}); diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index 0abd605d..72bca698 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -16,25 +16,104 @@ import "../fixtures"; import * as assert from "assert"; import * as sinon from "sinon"; -import { DataSourceMessage } from "../../src/models/messages"; -import { MetaObjectPayload } from "../../src/models/meta"; +import { KdbDataSourceView } from "../../src/webview/components/kdbDataSourceView"; +import { KdbNewConnectionView } from "../../src/webview/components/kdbNewConnectionView"; +import { ServerType } from "../../src/models/server"; + +import { InsightDetails } from "../../src/models/insights"; import { + DataSourceCommand, + DataSourceMessage2, +} from "../../src/models/messages"; +import { + DataSourceTypes, createAgg, - createDefaultDataSourceFile, createFilter, createGroup, createLabel, createSort, } from "../../src/models/dataSource"; -import { KdbDataSourceView } from "../../src/webview/components/kdbDataSourceView"; -import { KdbNewConnectionView } from "../../src/webview/components/kdbNewConnectionView"; -import { ServerType } from "../../src/models/server"; - -import { InsightDetails } from "../../src/models/insights"; +import { MetaObjectPayload } from "../../src/models/meta"; +import { TemplateResult } from "lit"; describe("KdbDataSourceView", () => { let view: KdbDataSourceView; + function createValueEvent(value?: string): Event { + return ({ + target: { + value, + }, + }) as Event; + } + + function createMessageEvent(isInsights: boolean) { + return >{ + data: { + command: DataSourceCommand.Update, + servers: ["server"], + selectedServer: "server", + isInsights, + insightsMeta: { + dap: [{}], + api: [{ api: "getData" }], + assembly: [{ assembly: "assembly", tbls: ["table1"] }], + schema: [ + { + table: "table1", + columns: [{ column: "column1" }], + assembly: "assembly", + type: "type", + }, + ], + }, + dataSourceFile: { + dataSource: { + selectedType: DataSourceTypes.API, + api: { + selectedApi: "getData", + table: "table1", + startTS: "", + endTS: "", + fill: "zero", + temporality: "snapshot", + filter: [], + groupBy: [], + agg: [], + sortCols: [], + slice: [], + labels: [], + optional: { + filled: false, + temporal: false, + filters: [createFilter()], + labels: [createLabel()], + sorts: [createSort()], + aggs: [createAgg()], + groups: [createGroup()], + }, + }, + qsql: { + query: "", + selectedTarget: "", + }, + sql: { + query: "", + }, + }, + }, + }, + }; + } + + function testEventHandlers(result: TemplateResult) { + for (const value of result.values) { + if (value instanceof Function) { + value.bind(view)(createValueEvent("")); + } + } + } + beforeEach(async () => { view = new KdbDataSourceView(); }); @@ -44,310 +123,97 @@ describe("KdbDataSourceView", () => { }); describe("connectedCallback", () => { - it("should add message event listener", () => { - let cb: any; - sinon - .stub(global.window, "addEventListener") - .value((event: string, listener: any) => { - if (event === "message") { - cb = listener; - } - }); + it("should add an event listener", () => { + let result = undefined; + sinon.stub(window, "addEventListener").value(() => (result = true)); view.connectedCallback(); - const dataSourceFile = createDefaultDataSourceFile(); - dataSourceFile.dataSource.api.optional = { - filled: false, - temporal: false, - filters: [], - labels: [], - sorts: [], - aggs: [], - groups: [], - }; - const message: DataSourceMessage = { - isInsights: true, - insightsMeta: { - dap: {}, - }, - dataSourceName: "test", - dataSourceFile, - }; - const event = { - data: message, - }; - cb(event); - const data = view["data"]; - assert.deepEqual(data, { - ...dataSourceFile, - name: "", - originalName: "", - }); + assert.ok(result); }); }); describe("disconnectedCallback", () => { - it("should remove message event listener", () => { - let result = false; - sinon - .stub(global.window, "removeEventListener") - .value((event: string) => { - if (event === "message") { - result = true; - } - }); + it("should remove an event listener", () => { + let result = undefined; + sinon.stub(window, "removeEventListener").value(() => (result = true)); view.disconnectedCallback(); - assert.strictEqual(result, true); - }); - }); - - describe("selectTab", () => { - it("should return the selected tab", () => { - sinon.stub(view, "selectedType").value("DEFAULT"); - assert.strictEqual(view["selectedTab"], "tab-1"); - sinon.stub(view, "selectedType").value("API"); - assert.strictEqual(view["selectedTab"], "tab-1"); - sinon.stub(view, "selectedType").value("QSQL"); - assert.strictEqual(view["selectedTab"], "tab-2"); - sinon.stub(view, "selectedType").value("SQL"); - assert.strictEqual(view["selectedTab"], "tab-3"); - }); - }); - - describe("save", () => { - it("should post a message", () => { - let result: any; - const api = acquireVsCodeApi(); - sinon.stub(api, "postMessage").value(({ command, data }) => { - if (command === "kdb.dataSource.saveDataSource") { - result = data; - } - }); - view["save"](); assert.ok(result); }); }); - describe("run", () => { - it("should post a message", () => { - let result: any; - const api = acquireVsCodeApi(); - sinon.stub(api, "postMessage").value(({ command, data }) => { - if (command === "kdb.dataSource.runDataSource") { - result = data; - } - }); - view["run"](); + describe("render", () => { + it("should update from message", () => { + view.message(createMessageEvent(true)); + assert.ok(view.data); + const result = view.render(); assert.ok(result); }); - }); - - describe("populateScratchpad", () => { - it("should post a message", () => { - let result: any; - const api = acquireVsCodeApi(); - sinon.stub(api, "postMessage").value(({ command, data }) => { - if (command === "kdb.dataSource.populateScratchpad") { - result = data; - } - }); - view["populateScratchpad"](); + it("should update from offline message", () => { + view.message(createMessageEvent(false)); + assert.ok(view.data); + const result = view.render(); assert.ok(result); }); - }); - - describe("renderApiOptions", () => { - it("should render empty array", () => { - const result = view["renderApiOptions"](); - assert.deepEqual(result, []); - }); - - it("should render getData api", () => { - sinon.stub(view, "isInsights").value(true); - sinon.stub(view, "isMetaLoaded").value(true); - sinon - .stub(view, "insightsMeta") - .value({ api: [{ api: ".kxi.getData" }] }); - const result = view["renderApiOptions"](); - assert.deepEqual(result[0].values, ["getData", true, "getData"]); - }); - - it("should render other api", () => { - sinon.stub(view, "isInsights").value(true); - sinon.stub(view, "isMetaLoaded").value(true); - sinon.stub(view, "insightsMeta").value({ api: [{ api: "other" }] }); - const result = view["renderApiOptions"](); - assert.deepEqual(result[0].values, ["other", true, "other"]); - }); - }); - - describe("renderTableOptions", () => { - it("should render empty array", () => { - const result = view["renderTableOptions"](); - assert.deepEqual(result, []); - }); - - it("should render table", () => { - sinon.stub(view, "isInsights").value(true); - sinon.stub(view, "isMetaLoaded").value(true); - sinon - .stub(view, "insightsMeta") - .value({ assembly: [{ tbls: ["table"] }] }); - const result = view["renderTableOptions"](); - assert.deepEqual(result[0].values, ["table", true, "table"]); - }); - }); - - describe("renderColumnOptions", () => { - it("should render empty array", () => { - const result = view["renderColumnOptions"](); - assert.deepEqual(result, []); - }); - - it("should render columns for selected table", () => { - const provider = { column: "" }; - sinon.stub(view, "isInsights").value(true); - sinon.stub(view, "isMetaLoaded").value(true); - sinon.stub(view, "selectedTable").value("table"); - sinon - .stub(view, "insightsMeta") - .value({ schema: [{ table: "table", columns: [{ column: "id" }] }] }); - const result = view["renderColumnOptions"](); - assert.strictEqual(provider.column, "id"); - assert.deepEqual(result[0].values, ["id", true, "id"]); - }); - }); - - describe("renderTargetOptions", () => { - it("should render empty array", () => { - const result = view["renderTargetOptions"](); - assert.deepEqual(result, []); - }); - - it("should render targets", () => { - sinon.stub(view, "isInsights").value(true); - sinon.stub(view, "isMetaLoaded").value(true); - sinon - .stub(view, "insightsMeta") - .value({ dap: [{ assembly: "assembly", instance: "instance" }] }); - const result = view["renderTargetOptions"](); - assert.deepEqual(result[0].values, [ - "assembly-qe instance", - true, - "assembly-qe instance", - ]); + it("should call event handlers", () => { + testEventHandlers(view.render()); + testEventHandlers(view.renderFilter(createFilter())); + testEventHandlers(view.renderLabel(createLabel())); + testEventHandlers(view.renderSort(createSort())); + testEventHandlers(view.renderAgg(createAgg())); + testEventHandlers(view.renderGroup(createGroup())); }); }); - describe("renderFilter", () => { - it("should render filter", () => { - const filter = createFilter(); - const render = view["renderFilter"](filter); - (render.values[1] as any)({ target: { checked: true } }); - assert.strictEqual(filter.active, true); - (render.values[3] as any)({ target: { value: "test" } }); - assert.strictEqual(filter.column, "test"); - (render.values[6] as any)({ target: { value: "test" } }); - assert.strictEqual(filter.column, "test"); - (render.values[9] as any)({ target: { value: "test" } }); - assert.strictEqual(filter.column, "test"); - let result = false; - sinon.stub(view, "requestUpdate").value(() => (result = true)); - (render.values[11] as any)(); - assert.strictEqual(result, true); - result = false; - view.filters = [filter]; - (render.values[12] as any)(); - assert.strictEqual(result, true); + describe("save", () => { + it("should send a message", () => { + let result = undefined; + sinon.stub(view, "postMessage").value(() => (result = true)); + view.save(); + assert.ok(result); }); }); - describe("renderLabel", () => { - it("should render label", () => { - const label = createLabel(); - const render = view["renderLabel"](label); - (render.values[1] as any)({ target: { checked: true } }); - assert.strictEqual(label.active, true); - (render.values[3] as any)({ target: { value: "test" } }); - assert.strictEqual(label.key, "test"); - (render.values[6] as any)({ target: { value: "test" } }); - assert.strictEqual(label.key, "test"); - let result = false; - sinon.stub(view, "requestUpdate").value(() => (result = true)); - (render.values[8] as any)(); - assert.strictEqual(result, true); - view.labels = [label]; - (render.values[9] as any)(); - assert.strictEqual(result, true); + describe("refresh", () => { + it("should send a message", () => { + let result = undefined; + sinon.stub(view, "postMessage").value(() => (result = true)); + view.refresh(); + assert.ok(result); }); }); - describe("renderSort", () => { - it("should render sort", () => { - const sort = createSort(); - const render = view["renderSort"](sort); - (render.values[1] as any)({ target: { checked: true } }); - assert.strictEqual(sort.active, true); - (render.values[3] as any)({ target: { value: "test" } }); - assert.strictEqual(sort.column, "test"); - let result = false; - sinon.stub(view, "requestUpdate").value(() => (result = true)); - (render.values[5] as any)(); - assert.strictEqual(result, true); - view.sorts = [sort]; - (render.values[6] as any)(); - assert.strictEqual(result, true); + describe("run", () => { + it("should send a message", () => { + let result = undefined; + sinon.stub(view, "postMessage").value(() => (result = true)); + view.run(); + assert.ok(result); }); }); - describe("renderAgg", () => { - it("should render agg", () => { - const agg = createAgg(); - const render = view["renderAgg"](agg); - (render.values[1] as any)({ target: { checked: true } }); - assert.strictEqual(agg.active, true); - (render.values[3] as any)({ target: { value: "test" } }); - assert.strictEqual(agg.key, "test"); - (render.values[6] as any)({ target: { value: "test" } }); - assert.strictEqual(agg.operator, "test"); - (render.values[9] as any)({ target: { value: "test" } }); - assert.strictEqual(agg.column, "test"); - let result = false; - sinon.stub(view, "requestUpdate").value(() => (result = true)); - (render.values[11] as any)(); - assert.strictEqual(result, true); - view.aggs = [agg]; - (render.values[12] as any)(); - assert.strictEqual(result, true); + describe("populateScratchpad", () => { + it("should send a message", () => { + let result = undefined; + sinon.stub(view, "postMessage").value(() => (result = true)); + view.populateScratchpad(); + assert.ok(result); }); }); - describe("renderGroup", () => { - it("should render group", () => { - const group = createGroup(); - const render = view["renderGroup"](group); - (render.values[1] as any)({ target: { checked: true } }); - assert.strictEqual(group.active, true); - (render.values[3] as any)({ target: { value: "test" } }); - assert.strictEqual(group.column, "test"); - let result = false; - sinon.stub(view, "requestUpdate").value(() => (result = true)); - (render.values[5] as any)(); - assert.strictEqual(result, true); - view.groups = [group]; - (render.values[6] as any)(); - assert.strictEqual(result, true); + describe("requestChange", () => { + it("should send a message", () => { + let result = undefined; + sinon.stub(view, "postMessage").value(() => (result = true)); + view.requestChange(); + assert.ok(result); }); }); - describe("render", () => { - it("should update state for tab selection", () => { - const result = view["render"](); - (result.values[3] as any)(); - assert.strictEqual(view.selectedType, "API"); - (result.values[4] as any)(); - assert.strictEqual(view.selectedType, "QSQL"); - (result.values[5] as any)(); - assert.strictEqual(view.selectedType, "SQL"); + describe("requestServerChange", () => { + it("should send a message", () => { + let result = undefined; + sinon.stub(view, "postMessage").value(() => (result = true)); + view.requestServerChange(createValueEvent("server")); + assert.ok(result); }); }); }); From 853b6b509431ebd2b1c8e3a51f218a6b20403d21 Mon Sep 17 00:00:00 2001 From: ecmel Date: Mon, 6 May 2024 18:28:07 +0300 Subject: [PATCH 26/32] fixed the merge --- package.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/package.json b/package.json index cd099568..b25d6ac9 100644 --- a/package.json +++ b/package.json @@ -397,11 +397,6 @@ "title": "Execute Entire File in Insights Scratchpad", "icon": "$(run)" }, - { - "command": "kdb.scratchpad.reset", - "title": "Reset Scratchpad", - "when": "kdb.insightsConnected" - }, { "category": "KX", "command": "kdb.qlint", @@ -437,12 +432,6 @@ "key": "ctrl+shift+r", "mac": "cmd+shift+r", "when": "editorLangId == q" - }, - { - "command": "kdb.scratchpad.reset", - "key": "ctrl+shift+delete", - "mac": "cmd+shift+delete", - "when": "kdb.insightsConnected" } ], "snippets": [ From 0d253fad52daf849037f75a939cc792affa8d493 Mon Sep 17 00:00:00 2001 From: ecmel Date: Tue, 7 May 2024 12:25:13 +0300 Subject: [PATCH 27/32] more tests and remove old ds commands --- package.json | 65 ----------------- src/commands/dataSourceCommand.ts | 11 --- src/extension.ts | 49 ------------- src/services/dataSourceEditorProvider.ts | 12 +++- src/services/workspaceTreeProvider.ts | 3 + test/suite/services.test.ts | 88 ++++++++++++++++++++++-- 6 files changed, 97 insertions(+), 131 deletions(-) diff --git a/package.json b/package.json index b25d6ac9..33524d0d 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,6 @@ "onCommand:kdb.disconnect", "onCommand:kdb.addAuthentication", "onCommand:kdb.enableTLS", - "onCommand:kdb.dataSource.addDataSource", - "onCommand:kdb.dataSource.saveDataSource", - "onCommand:kdb.dataSource.runDataSource", - "onCommand:kdb.dataSource.renameDataSource", - "onCommand:kdb.dataSource.deleteDataSource", - "onCommand:kdb.dataSource.openDataSource", "onCommand:kdb.resultsPanel.update", "onCommand:kdb.resultsPanel.clear", "onCommand:kdb.resultsPanel.export.csv", @@ -312,41 +306,6 @@ "title": "Clear query history", "icon": "$(clear-all)" }, - { - "category": "KX", - "command": "kdb.dataSource.addDataSource", - "title": "Add new DataSource" - }, - { - "category": "KX", - "command": "kdb.dataSource.populateScratchpad", - "title": "Populate a new scratchpad" - }, - { - "category": "KX", - "command": "kdb.dataSource.saveDataSource", - "title": "Save changes in the DataSource" - }, - { - "category": "KX", - "command": "kdb.dataSource.runDataSource", - "title": "Execute DataSource at Inisghts Instance" - }, - { - "category": "KX", - "command": "kdb.dataSource.renameDataSource", - "title": "Rename DataSource" - }, - { - "category": "KX", - "command": "kdb.dataSource.deleteDataSource", - "title": "Delete DataSource" - }, - { - "category": "KX", - "command": "kdb.dataSource.openDataSource", - "title": "Open DataSource" - }, { "category": "KX", "command": "kdb.resultsPanel.update", @@ -518,10 +477,6 @@ { "view": "kdb-servers", "contents": "No connections registered.\n[Add Connection](command:kdb.addConnection)" - }, - { - "view": "kdb-datasources-explorer", - "contents": "No data sources registered.\n[Add Data Source](command:kdb.dataSource.addDataSource)" } ], "menus": { @@ -634,11 +589,6 @@ "command": "kdb.resultsPanel.export.csv", "when": "view == kdb-results", "group": "resultsPanel" - }, - { - "command": "kdb.dataSource.addDataSource", - "when": "view == kdb-datasources-explorer", - "group": "dataSourceFiles" } ], "view/item/context": [ @@ -696,21 +646,6 @@ "command": "kdb.queryHistory.rerun", "when": "view == kdb-query-history", "group": "queryHistory" - }, - { - "command": "kdb.dataSource.openDataSource", - "when": "view == kdb-datasources-explorer && viewItem in kdb.dataSourceTreeNodes", - "group": "dataSourceFiles" - }, - { - "command": "kdb.dataSource.renameDataSource", - "when": "view == kdb-datasources-explorer && viewItem in kdb.dataSourceTreeNodes", - "group": "dataSourceFiles" - }, - { - "command": "kdb.dataSource.deleteDataSource", - "when": "view == kdb-datasources-explorer && viewItem in kdb.dataSourceTreeNodes", - "group": "dataSourceFiles" } ], "editor/title/run": [ diff --git a/src/commands/dataSourceCommand.ts b/src/commands/dataSourceCommand.ts index 000ae4f3..5d44e36e 100644 --- a/src/commands/dataSourceCommand.ts +++ b/src/commands/dataSourceCommand.ts @@ -231,17 +231,6 @@ export async function populateScratchpad( }); } -export async function refreshDataSource(): Promise { - if ( - ext.activeConnection instanceof LocalConnection || - !ext.activeConnection - ) { - window.showErrorMessage("No Insights active connection found"); - return; - } - Object.assign(ext.insightsMeta, await ext.activeConnection.getMeta()); -} - export async function runDataSource( dataSourceForm: DataSourceFiles, ): Promise { diff --git a/src/extension.ts b/src/extension.ts index 133cb0d1..a66b42f7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -38,7 +38,6 @@ import { deleteDataSource, openDataSource, populateScratchpad, - refreshDataSource, renameDataSource, runDataSource, saveDataSource, @@ -259,54 +258,6 @@ export async function activate(context: ExtensionContext) { ext.kdbQueryHistoryNodes.length = 0; ext.queryHistoryProvider.refresh(); }), - commands.registerCommand("kdb.dataSource.addDataSource", async () => { - await addDataSource(); - }), - commands.registerCommand( - "kdb.dataSource.populateScratchpad", - async (dataSourceForm: any) => { - await populateScratchpad(dataSourceForm); - }, - ), - commands.registerCommand( - "kdb.dataSource.saveDataSource", - async (dataSourceForm: any) => { - await saveDataSource(dataSourceForm); - }, - ), - commands.registerCommand( - "kdb.dataSource.runDataSource", - async (dataSourceForm: any) => { - await runDataSource(dataSourceForm); - }, - ), - commands.registerCommand("kdb.dataSource.refreshDataSource", async () => { - await refreshDataSource(); - }), - commands.registerCommand( - "kdb.dataSource.renameDataSource", - async (viewItem: KdbDataSourceTreeItem) => { - window - .showInputBox({ prompt: "Enter new name for the DataSource" }) - .then(async (newName) => { - if (newName) { - await renameDataSource(viewItem.label, newName); - } - }); - }, - ), - commands.registerCommand( - "kdb.dataSource.deleteDataSource", - async (viewItem: KdbDataSourceTreeItem) => { - await deleteDataSource(viewItem); - }, - ), - commands.registerCommand( - "kdb.dataSource.openDataSource", - async (viewItem: KdbDataSourceTreeItem) => { - await openDataSource(viewItem, context.extensionUri); - }, - ), commands.registerCommand("kdb.showInstallationDetails", async () => { await showInstallationDetails(); }), diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index bc97a4c7..a89fc865 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -38,9 +38,10 @@ import { DataSourceCommand, DataSourceMessage2 } from "../models/messages"; import { isDeepStrictEqual } from "util"; import { populateScratchpad, - refreshDataSource, runDataSource, } from "../commands/dataSourceCommand"; +import { LocalConnection } from "../classes/localConnection"; +import { InsightsConnection } from "../classes/insightsConnection"; export class DataSourceEditorProvider implements CustomTextEditorProvider { static readonly viewType = "kdb.dataSourceEditor"; @@ -63,6 +64,15 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { webview.options = { enableScripts: true }; webview.html = this.getWebviewContent(webview); + const refreshDataSource = async () => { + // TODO ECMEL + if (ext.activeConnection instanceof InsightsConnection) { + Object.assign(ext.insightsMeta, await ext.activeConnection.getMeta()); + } + }; + + await refreshDataSource(); + const updateWebview = () => { webview.postMessage({ command: DataSourceCommand.Update, diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 919e39fb..8726b21a 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -136,6 +136,9 @@ export async function addWorkspaceFile( break; } i++; + if (i > 100) { + throw new Error("No available file name found"); + } } const uri = Uri.joinPath(folder.uri, `${name}-${i}${ext}`).with({ scheme: "untitled", diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index d16affa9..386f7bc0 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -17,9 +17,13 @@ import sinon from "sinon"; import { ExtensionContext, TreeItemCollapsibleState, + Uri, + WebviewPanel, + WebviewPanelOnDidChangeViewStateEvent, commands, env, window, + workspace, } from "vscode"; import { ext } from "../../src/extensionVariables"; import { Insights } from "../../src/models/insights"; @@ -49,7 +53,12 @@ import { LocalConnection } from "../../src/classes/localConnection"; import { Telemetry } from "../../src/utils/telemetryClient"; import { InsightsConnection } from "../../src/classes/insightsConnection"; import { DataSourceEditorProvider } from "../../src/services/dataSourceEditorProvider"; -import { WorkspaceTreeProvider } from "../../src/services/workspaceTreeProvider"; +import { + WorkspaceTreeProvider, + addWorkspaceFile, +} from "../../src/services/workspaceTreeProvider"; +import Path from "path"; +import * as utils from "../../src/utils/getUri"; // eslint-disable-next-line @typescript-eslint/no-var-requires const codeFlow = require("../../src/services/kdbInsights/codeFlowLogin"); @@ -1064,6 +1073,35 @@ describe("connectionManagerService", () => { describe("dataSourceEditorProvider", () => { let context: ExtensionContext; + function createPanel() { + const listeners = { + onDidReceiveMessage: undefined, + postMessage: undefined, + onDidChangeViewState: undefined, + onDidDispose: undefined, + }; + const panel = { + webview: { + onDidReceiveMessage(e) { + listeners.onDidReceiveMessage = e; + }, + postMessage(e) { + listeners.postMessage = e; + }, + }, + onDidChangeViewState(e) { + listeners.onDidChangeViewState = e; + }, + onDidDispose(e) { + listeners.onDidDispose = e; + }, + }; + return { + panel, + listeners, + }; + } + beforeEach(() => { context = {}; }); @@ -1082,14 +1120,44 @@ describe("dataSourceEditorProvider", () => { assert.ok(result); }); }); + + describe("resolveCustomTextEditor", () => { + it("should resolve ", async () => { + const provider = new DataSourceEditorProvider(context); + const document = await workspace.openTextDocument({ + language: "q", + content: "{}", + }); + sinon.stub(utils, "getUri").value(() => ""); + const panel = createPanel(); + await assert.doesNotReject(() => + provider.resolveCustomTextEditor(document, panel.panel), + ); + panel.listeners.onDidReceiveMessage({}); + panel.listeners.onDidChangeViewState(); + panel.listeners.onDidDispose(); + }); + }); }); describe("workspaceTreeProvider", () => { let provider: WorkspaceTreeProvider; + function stubWorkspaceFile(path: string) { + const parsed = Path.parse(path); + sinon + .stub(workspace, "workspaceFolders") + .value([{ uri: Uri.file(parsed.dir) }]); + sinon + .stub(workspace, "getWorkspaceFolder") + .value(() => Uri.file(parsed.dir)); + sinon.stub(workspace, "findFiles").value(async () => [Uri.file(path)]); + sinon.stub(workspace, "openTextDocument").value(async (uri: Uri) => uri); + } + beforeEach(() => { sinon.stub(ext, "serverProvider").value({ onDidChangeTreeData() {} }); - provider = new WorkspaceTreeProvider("*.*", "icon"); + provider = new WorkspaceTreeProvider("**/*.kdb.q", "scratchpad"); }); afterEach(() => { @@ -1097,9 +1165,19 @@ describe("workspaceTreeProvider", () => { }); describe("getChildren", () => { - it("should return empty array", async () => { - const result = await provider.getChildren(); - assert.strictEqual(result.length, 0); + it("should return workspace items", async () => { + stubWorkspaceFile("/workspace/test.kdb.q"); + let result = await provider.getChildren(); + assert.strictEqual(result.length, 1); + result = await provider.getChildren(provider.getTreeItem(result[0])); + assert.strictEqual(result.length, 1); + }); + }); + + describe("addWorkspaceFile", () => { + it("should break after try", async () => { + stubWorkspaceFile("/workspace/test.kdb.q"); + await assert.rejects(() => addWorkspaceFile(undefined, "test", ".kdb.q")); }); }); }); From cd3ef6516e9077c5f3ea88120ecba1fc7542b5dd Mon Sep 17 00:00:00 2001 From: ecmel Date: Tue, 7 May 2024 17:58:02 +0300 Subject: [PATCH 28/32] checkpoint before name change --- src/commands/workspaceCommand.ts | 2 +- src/extension.ts | 4 +-- src/services/dataSourceEditorProvider.ts | 40 ++++++++++++--------- src/webview/components/kdbDataSourceView.ts | 2 +- test/suite/commands.test.ts | 30 ++++++++++++++++ 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index 5f5b6826..ed12cf66 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -253,7 +253,7 @@ export class ConnectionLensProvider implements CodeLensProvider { } } -export function connectWorkspaceCommsnds() { +export function connectWorkspaceCommands() { ext.runScratchpadItem = window.createStatusBarItem( StatusBarAlignment.Right, 10000, diff --git a/src/extension.ts b/src/extension.ts index a66b42f7..014fd74a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -98,7 +98,7 @@ import { } from "./services/workspaceTreeProvider"; import { ConnectionLensProvider, - connectWorkspaceCommsnds, + connectWorkspaceCommands, pickConnection, runActiveEditor, } from "./commands/workspaceCommand"; @@ -413,7 +413,7 @@ export async function activate(context: ExtensionContext) { ), ); - connectWorkspaceCommsnds(); + connectWorkspaceCommands(); await connectBuildTools(); //q language server diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index a89fc865..822bca2c 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -27,7 +27,6 @@ import { import { getUri } from "../utils/getUri"; import { getNonce } from "../utils/getNonce"; import { ext } from "../extensionVariables"; -import { InsightsNode } from "./kdbTreeProvider"; import { activateConnectionForServer, getInsightsServers, @@ -40,8 +39,8 @@ import { populateScratchpad, runDataSource, } from "../commands/dataSourceCommand"; -import { LocalConnection } from "../classes/localConnection"; import { InsightsConnection } from "../classes/insightsConnection"; +import { MetaObjectPayload } from "../models/meta"; export class DataSourceEditorProvider implements CustomTextEditorProvider { static readonly viewType = "kdb.dataSourceEditor"; @@ -54,8 +53,23 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { ); } + private cache = new Map>(); + constructor(private readonly context: ExtensionContext) {} + getMeta(alias: string) { + let meta = this.cache.get(alias); + if (!meta) { + if (ext.activeConnection instanceof InsightsConnection) { + if (ext.activeConnection.node.details.alias === alias) { + meta = ext.activeConnection.getMeta(); + this.cache.set(alias, meta); + } + } + } + return meta || Promise.resolve({}); + } + async resolveCustomTextEditor( document: TextDocument, webviewPanel: WebviewPanel, @@ -64,23 +78,15 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { webview.options = { enableScripts: true }; webview.html = this.getWebviewContent(webview); - const refreshDataSource = async () => { - // TODO ECMEL - if (ext.activeConnection instanceof InsightsConnection) { - Object.assign(ext.insightsMeta, await ext.activeConnection.getMeta()); - } - }; - - await refreshDataSource(); - - const updateWebview = () => { + const updateWebview = async () => { + const selectedServer = getServerForUri(document.uri) || ""; webview.postMessage({ command: DataSourceCommand.Update, + selectedServer, servers: getInsightsServers(), - selectedServer: getServerForUri(document.uri) || "", - isInsights: ext.connectionNode instanceof InsightsNode, - insightsMeta: ext.insightsMeta, dataSourceFile: this.getDocumentAsJson(document), + insightsMeta: await this.getMeta(selectedServer), + isInsights: true, }); }; @@ -106,6 +112,7 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { switch (msg.command) { case DataSourceCommand.Server: await setServerForUri(document.uri, msg.selectedServer); + updateWebview(); break; case DataSourceCommand.Change: const changed = msg.dataSourceFile; @@ -123,7 +130,8 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { break; case DataSourceCommand.Refresh: await activateConnectionForServer(msg.selectedServer); - await refreshDataSource(); + const selectedServer = getServerForUri(document.uri) || ""; + this.cache.delete(selectedServer); updateWebview(); break; case DataSourceCommand.Run: diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 4baddb20..6fe4a59d 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -58,7 +58,7 @@ export class KdbDataSourceView extends LitElement { isInsights = false; isMetaLoaded = false; - insightsMeta = {} as MetaObjectPayload; + insightsMeta = {}; selectedType = DataSourceTypes.API; selectedApi = ""; selectedTable = ""; diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index 70a54373..dd002976 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -1878,10 +1878,40 @@ describe("workspaceCommand", () => { afterEach(() => { sinon.restore(); }); + describe("connectWorkspaceCommands", () => { + it("should connect listeners", () => { + workspaceCommand.connectWorkspaceCommands(); + }); + }); + describe("activateConnectionForServer", () => { + it("should not activate connection", async () => { + sinon.stub(ext, "serverProvider").value({ + getChildren() { + return []; + }, + }); + await assert.rejects(() => + workspaceCommand.activateConnectionForServer("test"), + ); + }); + }); describe("getInsightsServers", () => { it("should return insights server aliases as array", () => { const result = workspaceCommand.getInsightsServers(); assert.strictEqual(result[0], "connection1"); }); }); + describe("ConnectionLensProvider", () => { + describe("provideCodeLenses", () => { + it("should return lenses", async () => { + const document = await vscode.workspace.openTextDocument({ + language: "q", + content: "a:1", + }); + const provider = new workspaceCommand.ConnectionLensProvider(); + const result = await provider.provideCodeLenses(document); + assert.strictEqual(result.length, 2); + }); + }); + }); }); From 578020443864291cc48f6633c21637fcd0167f7e Mon Sep 17 00:00:00 2001 From: ecmel Date: Tue, 7 May 2024 18:29:57 +0300 Subject: [PATCH 29/32] cache meta per connection --- package.json | 18 +++++++++++------- src/extension.ts | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 33524d0d..48c14645 100644 --- a/package.json +++ b/package.json @@ -213,19 +213,18 @@ { "category": "KX", "command": "kdb.createScratchpad", - "title": "Add a new scratchpad", + "title": "Add a new q workbook", "icon": "$(add)" }, { "category": "KX", "command": "kdb.createPythonScratchpad", - "title": "Add a new Python scratchpad", - "icon": "$(file-add)" + "title": "Add a new Python workbook" }, { "category": "KX", "command": "kdb.refreshScratchpadExplorer", - "title": "Refresh scratchpads", + "title": "Refresh workbooks", "icon": "$(refresh)" }, { @@ -236,7 +235,7 @@ { "category": "KX", "command": "kdb.runScratchpad", - "title": "Run scratchpad" + "title": "Run workbook" }, { "category": "KX", @@ -455,7 +454,7 @@ }, { "id": "kdb-scratchpad-explorer", - "name": "Scratchpads", + "name": "Workbooks", "icon": "resources/server.svg" }, { @@ -565,10 +564,15 @@ "when": "view == kdb-scratchpad-explorer", "group": "navigation@1" }, + { + "command": "kdb.createScratchpad", + "when": "view == kdb-scratchpad-explorer", + "group": "q@1" + }, { "command": "kdb.createPythonScratchpad", "when": "view == kdb-scratchpad-explorer", - "group": "navigation@2" + "group": "q@2" }, { "command": "kdb.refreshScratchpadExplorer", diff --git a/src/extension.ts b/src/extension.ts index 014fd74a..c3f6a9ab 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -339,7 +339,7 @@ export async function activate(context: ExtensionContext) { commands.registerCommand( "kdb.createScratchpad", async (item: FileTreeItem) => { - const uri = await addWorkspaceFile(item, "scratchpad", ".kdb.q"); + const uri = await addWorkspaceFile(item, "workbook", ".kdb.q"); if (uri) { await window.showTextDocument(uri); } @@ -348,7 +348,7 @@ export async function activate(context: ExtensionContext) { commands.registerCommand( "kdb.createPythonScratchpad", async (item: FileTreeItem) => { - const uri = await addWorkspaceFile(item, "scratchpad", ".kdb.py"); + const uri = await addWorkspaceFile(item, "workbook", ".kdb.py"); if (uri) { await window.showTextDocument(uri); } From 5f7689d57926415d9cc69971e3c9409cc2ba00e6 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 8 May 2024 07:45:56 +0300 Subject: [PATCH 30/32] more coverage --- src/commands/workspaceCommand.ts | 10 +++++++++ src/services/dataSourceEditorProvider.ts | 1 - test/suite/commands.test.ts | 28 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index ed12cf66..43c437ac 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -33,11 +33,13 @@ import { ExecutionTypes } from "../models/execution"; const connectionService = new ConnectionManagementService(); +/* istanbul ignore next */ function workspaceFoldersChanged() { ext.dataSourceTreeProvider.reload(); ext.scratchpadTreeProvider.reload(); } +/* istanbul ignore next */ function setRealActiveTextEditor(editor?: TextEditor | undefined) { if (editor) { const scheme = editor.document.uri.scheme; @@ -49,6 +51,7 @@ function setRealActiveTextEditor(editor?: TextEditor | undefined) { } } +/* istanbul ignore next */ function activeEditorChanged(editor?: TextEditor | undefined) { setRealActiveTextEditor(editor); const item = ext.runScratchpadItem; @@ -66,6 +69,7 @@ function activeEditorChanged(editor?: TextEditor | undefined) { } } +/* istanbul ignore next */ function setRunScratchpadItemText(text: string) { ext.runScratchpadItem.text = `$(run) ${text}`; } @@ -93,6 +97,7 @@ function getServers() { ]; } +/* istanbul ignore next */ async function getConnectionForServer(server: string) { if (server) { const servers = await ext.serverProvider.getChildren(); @@ -107,6 +112,7 @@ async function getConnectionForServer(server: string) { } } +/* istanbul ignore next */ async function waitForConnection(label: string) { return new Promise((resolve, reject) => { let count = 0; @@ -126,6 +132,7 @@ async function waitForConnection(label: string) { }); } +/* istanbul ignore next */ function relativePath(uri: Uri) { return workspace.asRelativePath(uri, false); } @@ -151,6 +158,7 @@ export function getServerForUri(uri: Uri) { return map[relativePath(uri)]; } +/* istanbul ignore next */ export function getConnectionForUri(uri: Uri) { const server = getServerForUri(uri); if (server) { @@ -192,6 +200,7 @@ function isScratchpad(uri: Uri) { return uri.path.endsWith(".kdb.q") || uri.path.endsWith(".kdb.py"); } +/* istanbul ignore next */ export async function activateConnectionForServer(server: string) { const connection = await getConnectionForServer(server); if (connection) { @@ -213,6 +222,7 @@ export async function activateConnectionForServer(server: string) { } } +/* istanbul ignore next */ export async function runActiveEditor(type?: ExecutionTypes) { if (ext.activeTextEditor) { const uri = ext.activeTextEditor.document.uri; diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 822bca2c..b81c3839 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -137,7 +137,6 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { case DataSourceCommand.Run: await activateConnectionForServer(msg.selectedServer); await runDataSource(msg.dataSourceFile); - updateWebview(); break; case DataSourceCommand.Populate: await activateConnectionForServer(msg.selectedServer); diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index dd002976..7a7ef15b 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -1872,6 +1872,7 @@ describe("workspaceCommand", () => { } return {}; }, + update() {}, }; }); }); @@ -1901,6 +1902,33 @@ describe("workspaceCommand", () => { assert.strictEqual(result[0], "connection1"); }); }); + describe("setServerForUri", () => { + it("should associate a server with an uri", async () => { + await assert.doesNotReject(() => + workspaceCommand.setServerForUri( + vscode.Uri.file("test.kdb.q"), + "connection1", + ), + ); + }); + }); + describe("pickConnection", () => { + it("should pick from available servers", async () => { + sinon.stub(window, "showQuickPick").value(async () => "test"); + const result = await workspaceCommand.pickConnection( + vscode.Uri.file("test.kdb.q"), + ); + assert.strictEqual(result, "test"); + }); + it("should return undefined from (none)", async () => { + sinon.stub(window, "showQuickPick").value(async () => "(none)"); + const result = await workspaceCommand.pickConnection( + vscode.Uri.file("test.kdb.q"), + ); + assert.strictEqual(result, undefined); + }); + }); + describe("ConnectionLensProvider", () => { describe("provideCodeLenses", () => { it("should return lenses", async () => { From 181ff5a3ae8cffcdbfaa00094912cafa86298044 Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 8 May 2024 12:18:40 +0300 Subject: [PATCH 31/32] fixes KXI-44186 --- package.json | 20 ++++++++++++++++++++ src/extension.ts | 16 ++++++++++++++++ src/services/workspaceTreeProvider.ts | 1 + 3 files changed, 37 insertions(+) diff --git a/package.json b/package.json index 48c14645..475af9a4 100644 --- a/package.json +++ b/package.json @@ -359,6 +359,16 @@ "category": "KX", "command": "kdb.qlint", "title": "Lint q source file" + }, + { + "category": "KX", + "command": "kdb.renameFile", + "title": "Rename" + }, + { + "category": "KX", + "command": "kdb.deleteFile", + "title": "Delete" } ], "keybindings": [ @@ -650,6 +660,16 @@ "command": "kdb.queryHistory.rerun", "when": "view == kdb-query-history", "group": "queryHistory" + }, + { + "command": "kdb.renameFile", + "when": "(view == kdb-datasource-explorer || view == kdb-scratchpad-explorer) && viewItem == artifact", + "group": "kdbWorkspace@1" + }, + { + "command": "kdb.deleteFile", + "when": "(view == kdb-datasource-explorer || view == kdb-scratchpad-explorer) && viewItem == artifact", + "group": "kdbWorkspace@2" } ], "editor/title/run": [ diff --git a/src/extension.ts b/src/extension.ts index c3f6a9ab..4e4eb76a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -363,6 +363,22 @@ export async function activate(context: ExtensionContext) { await pickConnection(editor.document.uri); } }), + commands.registerCommand("kdb.renameFile", async (item: FileTreeItem) => { + if (item && item.resourceUri) { + const document = await workspace.openTextDocument(item.resourceUri); + await window.showTextDocument(document); + await commands.executeCommand("revealInExplorer"); + await commands.executeCommand("renameFile"); + } + }), + commands.registerCommand("kdb.deleteFile", async (item: FileTreeItem) => { + if (item && item.resourceUri) { + const document = await workspace.openTextDocument(item.resourceUri); + await window.showTextDocument(document); + await commands.executeCommand("revealInExplorer"); + await commands.executeCommand("deleteFile"); + } + }), DataSourceEditorProvider.register(context), diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 8726b21a..56f598c7 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -78,6 +78,7 @@ export class FileTreeItem extends TreeItem { this.collapsibleState = TreeItemCollapsibleState.Expanded; } } else { + this.contextValue = "artifact"; this.command = { title: "", command: "vscode.open", From 9a3e40c4ffad32b630960ed732d7f1646c71599c Mon Sep 17 00:00:00 2001 From: ecmel Date: Wed, 8 May 2024 12:43:01 +0300 Subject: [PATCH 32/32] small fixes --- src/commands/workspaceCommand.ts | 2 +- src/services/dataSourceEditorProvider.ts | 7 +-- src/webview/components/kdbDataSourceView.ts | 61 ++++++++++----------- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index 43c437ac..cba97155 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -124,7 +124,7 @@ async function waitForConnection(label: string) { } else if (count < 5) { retry(); } else { - reject(`Can not connect to ${label}`); + reject(new Error(`Can not connect to ${label}`)); } }, 50); }; diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index b81c3839..e7b23cb5 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -153,12 +153,7 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { if (text.trim().length === 0) { return {}; } - - try { - return JSON.parse(text); - } catch (error) { - throw error; - } + return JSON.parse(text); } private updateTextDocument(document: TextDocument, json: unknown) { diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 6fe4a59d..0af794e3 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -11,10 +11,9 @@ * specific language governing permissions and limitations under the License. */ -import { LitElement, html } from "lit"; +import { LitElement, html, css } from "lit"; import { repeat } from "lit/directives/repeat.js"; import { customElement } from "lit/decorators.js"; -import { css } from "lit"; import { Agg, DataSourceFiles, @@ -93,37 +92,35 @@ export class KdbDataSourceView extends LitElement { message = (event: MessageEvent) => { const msg = event.data; - switch (msg.command) { - case DataSourceCommand.Update: - this.servers = msg.servers; - this.selectedServer = msg.selectedServer; - this.isInsights = msg.isInsights; - this.isMetaLoaded = !!msg.insightsMeta.dap; - this.insightsMeta = msg.insightsMeta; - const ds = msg.dataSourceFile; - this.selectedType = ds.dataSource.selectedType; - this.selectedApi = ds.dataSource.api.selectedApi; - this.selectedTable = ds.dataSource.api.table; - this.startTS = ds.dataSource.api.startTS; - this.endTS = ds.dataSource.api.endTS; - this.fill = ds.dataSource.api.fill; - this.temporality = ds.dataSource.api.temporality; - this.qsqlTarget = ds.dataSource.qsql.selectedTarget; - this.qsql = ds.dataSource.qsql.query; - this.sql = ds.dataSource.sql.query; - const optional = ds.dataSource.api.optional; - if (optional) { - this.filled = optional.filled; - this.temporal = optional.temporal; - this.filters = optional.filters; - this.labels = optional.labels; - this.sorts = optional.sorts; - this.aggs = optional.aggs; - this.groups = optional.groups; - } - break; + if (msg.command === DataSourceCommand.Update) { + this.servers = msg.servers; + this.selectedServer = msg.selectedServer; + this.isInsights = msg.isInsights; + this.isMetaLoaded = !!msg.insightsMeta.dap; + this.insightsMeta = msg.insightsMeta; + const ds = msg.dataSourceFile; + this.selectedType = ds.dataSource.selectedType; + this.selectedApi = ds.dataSource.api.selectedApi; + this.selectedTable = ds.dataSource.api.table; + this.startTS = ds.dataSource.api.startTS; + this.endTS = ds.dataSource.api.endTS; + this.fill = ds.dataSource.api.fill; + this.temporality = ds.dataSource.api.temporality; + this.qsqlTarget = ds.dataSource.qsql.selectedTarget; + this.qsql = ds.dataSource.qsql.query; + this.sql = ds.dataSource.sql.query; + const optional = ds.dataSource.api.optional; + if (optional) { + this.filled = optional.filled; + this.temporal = optional.temporal; + this.filters = optional.filters; + this.labels = optional.labels; + this.sorts = optional.sorts; + this.aggs = optional.aggs; + this.groups = optional.groups; + } + this.requestUpdate(); } - this.requestUpdate(); }; get data(): DataSourceFiles {