diff --git a/.README/repl.png b/.README/repl.png index d9267cdd4..f9c95f839 100644 Binary files a/.README/repl.png and b/.README/repl.png differ diff --git a/.github/ISSUE_TEMPLATE/BUG.YML b/.github/ISSUE_TEMPLATE/BUG.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/BUG.YML rename to .github/ISSUE_TEMPLATE/BUG.yml diff --git a/.vscode/launch.json b/.vscode/launch.json index 3e1eac650..14ec81601 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,11 +29,12 @@ "type": "extensionHost", "request": "launch", "args": [ + "--profile=Extension Tests", "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/test/out/suite/index" + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" ], - "outFiles": ["${workspaceFolder}/test/out/**/*.js"], - "preLaunchTask": "${defaultBuildTask}" + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: pretest" }, { "name": "Extension UI Tests", diff --git a/CHANGELOG.md b/CHANGELOG.md index 254b453e4..ba4e1cec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ All notable changes to the **kdb VS Code extension** are documented in this file. +# v1.13.0 + +### Enhancements + +- Added a new setting to prevent focus change when executing a query +- Introduced SQL code block support in notebooks +- Enabled connection association and execution for plain SQL files +- Notebooks now support DAP targets and automatically populate scratchpad +- Starting from Insights Enterprise `version 1.14`, workbook enhancements enable you to target specific `tiers` and `DAP processes` within kdb Insights connections. +- Improved the q REPL command functionality +- Extended qsql API to allow targeting specific DAP processes, not just tiers +- Refactorings are connection aware + +### Fixes + +- Fixed an issue where opening "Edit Connection" on multiple connections grouped them under a single label +- Added confirmation prompt when removing a connection +- Resolved an issue where the selected tab changed on startup +- Fixed broken **Add Connection** action in the welcome view +- Addressed issue where the "Create KX Notebook" command failed if an unsaved notebook already existed +- Improved naming consistency for VSCode kdb+ connections +- Ensured connections are sorted alphabetically in VSCode +- Made notebook error messages on IE connections more descriptive, matching kdb+ standards +- Added separate connection checks for `getData` and scratchpad in VSCode +- Fixed issue where the "Enable TLS" checkbox state was not saved +- Resolved problems with previewing local kdb variables +- Corrected the displayed result count cap of KDB Results (now accurately reflects the 10,000 limit) +- Fixed keywords after underscore operator is not identified +- Fixed line comments syntax highlighting is wrong after system commands + +### Internal Improvements + +- Updated dependencies for better performance and security +- Added support for debugging unit tests +- Switched to the `c8` coverage tool for improved test coverage reporting +- Resolved Chevrotain (LS Server package) warnings related to test coverage +- Migrated from deprecated telemetry to the current standard +- Unified progress tracking, logging, telemetry, and notifications for consistency +- The extension now automatically detects the appropriate endpoint for `Query Environment-enabled` IE connections. + # v1.12.0 This release requires VS Code version 1.96.0 or higher. @@ -11,7 +51,7 @@ This release requires VS Code version 1.96.0 or higher. - Added **KX Notebooks**, which allows you to compose and execute Q, Python, and Markdown code blocks in a single notebook - Workspace enhancements now allow connection association for all **q** and **py** files and target selection for all **q** files. - Execute **q** code directly on kdb Insights Enterprise DAPs processes from the editor -- Added the **Help & Feedback** view to the activity bar, which provides quick links to documentation, feature suggestion, feedback, and bug reporting +- Added the **Help & Feedback** view to the activity bar, which provides quick links to documentation, feature suggestion, feedback, and bug reporting - Added **Feedback Survey**, inviting you to provide feedback - Added **Copy Query** for query history when query is executed @@ -293,7 +333,6 @@ This release requires VS Code version 1.86.0 or higher. ### Fixes - Ability to switch users connected to a kdb Insights Enterprise URL. The new flow to switch users when you are already logged in is as follows: - - Disconnect from the URL. - Log into the URL using browser and log out of environment. - On reconnecting you are asked to enter your login details and you can chose a different user. @@ -305,18 +344,15 @@ This release requires VS Code version 1.86.0 or higher. - Ensure the "Execute Entire File" button works even if the cursor is not in the code editor window. - Fixes for the Data Sources: - - Custom APIs are no longer listed, these will be added in a future release when the execution of a Custom API is supported. - The "Run" button will be greyed out while a Data Source is executing, to ensure there are no concurrent executions. - To see Data Source results within the "Output" tab, ensure that the output is from "q Console Output". - Fixes for the tree: - - The only variables being displayed were longs. - Connections were string queries were forbidden broke the tree - Fixes for KDB Results: - - incorrect display of empty tables - not displaying results for non-tables or non-atoms. diff --git a/README.md b/README.md index 3902488ae..4eacc17e0 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ The **kdb Visual Studio Code extension** provides developers with an extensive s This extension can be used with [kdb Insights Enterprise](https://code.kx.com/insights/enterprise/index.html) when using a shared kdb process. -> Please email vscode-questions@kx.com to raise any questions or provide feedback. - ## Contents This guide provides information on the following: @@ -23,7 +21,7 @@ This guide provides information on the following: - [Query History](#query-history) - [Viewing results](#view-results) - [AxLibraries](#axlibraries) -- [q REPL](#q-repl) +- [REPL](#repl) - [Settings](#settings) - [Help and feedback](#help-and-feedback) - [Shortcuts](#shortcuts) @@ -75,7 +73,6 @@ After you install **kdb VS Code extension**, if q is not already installed the e ![installnewinstance](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/installnewinstance.jpg) 1. A dropdown is displayed with the two options: - - **Select/Enter a license** - If you have already registered for any of the [versions of q available](#versions-available) choose this to enter the license details. - **Acquire license** - If you haven't yet registered for q, click this to open a dialog with a redirect link to register for [kdb Insights Personal Edition](https://kx.com/kdb-insights-personal-edition-license-download/). @@ -126,7 +123,6 @@ To add connections: ![connecttoakdbserver](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/connecttoakdbserver.png) This opens the **Add a new connection** screen which has three tabs; one for each of the three connection types. - - [Bundled q](#bundled-q): This is a managed q session, which uses the q installed as part of the **kdb VS Code extension** installation. It runs a child q process from within the extension and is fully managed by the extension. - [My q](#my-q): This is an unmanaged q session and is a connection to a remote q process. - [Insights](#insights-connection): This accesses **kdb Insights Enterprise** API endpoints and a user-specific scratchpad process within a **kdb Insights Enterprise** deployment. @@ -238,7 +234,7 @@ To edit an existing connection, right-click the connection you wish to edit and ![Edit connection option](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/select-edit-connection.png) -> NOTE: Editing an **active connection** may require you to **restart** the connection. If so, you will be prompted to reconnect after saving your changes. +> NOTE: Editing an **active connection** may require you to **restart** the connection. If so, you are prompted to reconnect after saving your changes. ![Edit connected connection dialog](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/edit-connected-connection-dialog.png) @@ -313,10 +309,14 @@ To export a connection: To verify the export is successful navigate to the saved location and open the configuration file to check its contents. +**Important to note!** When exporting a connection configuration, the **password and username are not included** in the export file. Upon importing the connection, you are prompted to enter the login details to re-establish the connection. This was introduced as a best practice as exporting credentials introduces significant security risks. + ## Connection Labels Connection Labels allow you to categorize and organize your connections by assigning them distinct names and colors, making it easier to manage and locate specific connections within the application. +Connections are organized in alphabetical order, with connections first sorted by type, then by label within each type, and finally, if there are multiple connections under a label, those are also listed alphabetically. + ![Connection Tree With Labels](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/conn-labels-tree.png) ### Create New Label @@ -451,6 +451,37 @@ When executing Python code against kdb+ connections, **note** the following: Similarly, you can execute arbitrary code against **kdb Insights Enterprise**. The code is executed on a user-specific scratchpad process within the **kdb Insights Enterprise deploy**. The scratchpad is instantiated upon the first request to execute code when connected to a **kdb Insights Enterprise** connection. It remains active until timed out or until you log out. +### Concurrent code execution and querying + +Within each tier, multiple processes are available to handle queries, ensuring that queries can be run simultaneously across these processes. For example, if the RDB tier has three processes (process 0, process 1, process 2), and the specific replica is not being targeted the queries are directed to whichever process is available, allowing multiple users to execute their queries in parallel. As soon as a process becomes available, it handles the next incoming query, ensuring efficient resource utilization and minimal delays. + +kdb VSCode allows users to execute code on a specific Data Access Process (DAP). That allows you to target specific replicas within the RDB, IDB, and HDB tiers when executing queries. To select a replica for query execution, simply choose the desired tier (RDB, IDB, HDB) and then select the specific replica from the list of available options, such as `demo-ui-fx rdb-0`, `demo-ui-fx idb-1`. Once selected, queries execute on that specific replica, ensuring better load distribution and minimizing execution time across the cluster. + +When connecting to a kdb Insights server version 1.14.2 or higher, you can see detailed information about the available replicas for each database (RDB, IDB, HDB). This allows you to choose a specific replica for your query. + +The list includes specific targeting of replicas and looks similar to the example below: + +``` +- scratchpad +- demo-ui-fx idb +- demo-ui-fx rdb +- demo-ui-fx hdb +- demo-ui-fx idb-0 +- demo-ui-fx idb-1 +- demo-ui-fx idb-2 +- … +- demo-ui-fx rdb-0 +- demo-ui-fx rdb-1 +- demo-ui-fx rdb-2 +- … +- demo-ui-fx hdb-0 +- demo-ui-fx hdb-1 +- demo-ui-fx hdb-2 +- … +``` + +If you are connecting to an older kdb Insights version (1.14.1 or lower), replica information is not available. However, you can still run queries on a general group of databases (RDB, IDB, HDB), but don’t have the option to target a specific replica. This ensures that the feature works even if you are using older versions of Insights. + ## Data sources KX data source files allow you to build queries within VS Code, associate them with a connection and run them against the [kdb Insights Enterprise API endpoints](https://code.kx.com/insights/api/index.html). These are workspace specific files that have the following features: @@ -485,7 +516,6 @@ To create a data source and run it against a specific connection: 1. The results are populated in the **KDB Results** window, if it is active. ![KDB Results](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/datasource-kdbresults.png) - - Otherwise the **Output** window is populated. ![Output](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/datasource-output.png) @@ -500,7 +530,7 @@ Refer to the [`getData` API](https://code.kx.com/insights/api/database/query/get ### QSQL queries -The `.com_kx_edi.qsql` API is a QSQL query builder that assembles QSQL queries based on a q expression. It is a developer tool that allows running freeform q code against a specific database tier. +The `.com_kx_edi.qsql` API is a QSQL query builder that assembles QSQL queries based on a q expression. It is a developer tool that allows running freeform q code against a specific database tier and if required against a specific data access process. This function runs an QSQL query. @@ -508,11 +538,11 @@ This function runs an QSQL query. .com_kx_edi.qsql[args] ``` -**Note**: Along with the query itself, you must also specify the target database and tier. +**Note**: Along with the query itself, you must also specify the target database and you can choose to specify the database tier and specific data access process if required. Refer to the [QSQL documentation](https://code.kx.com/insights/api/database/query/qsql.html) for more details. -**Warning!** Starting with kdb Insights Enterprise version 1.13, QSQL queries and populating QSQL only work if the Query Environment (QE) is enabled. Ensure you have enabled QEs to use QSQL; they are disabled by default in kdb VS Code. Refer to [Query Environments](https://code.kx.com/insights/enterprise/configuration/base.html#query-environments) for more details. +**Warning!** Starting with kdb Insights Enterprise version 1.13, QSQL queries and populating QSQL only work if the Query Environment (QE) is enabled. Ensure you have enabled QEs to use QSQL; they are disabled by default in kdb Insights Enterprise from version 1.13 onwards. Refer to [Query Environments](https://code.kx.com/insights/enterprise/configuration/base.html#query-environments) for more details. ### SQL queries @@ -524,6 +554,8 @@ This function runs an SQL query. .com_kx_edi.sql[query] ``` +You can run SQL files against any kdb Insights Enterprise connections and select to [populate the Scratchpad](#populate-scratchpad) if you wish. + Refer to the [SQL documentation](https://code.kx.com/insights/api/database/query/sql.html) for more details. ### UDA queries @@ -627,7 +659,7 @@ Key features of Workbooks: - Are listed in the **WORKBOOKS** view in the primary sidebar - Can be associated with a connection -- Support the **.kdb.q.**, **kdb.py** extensions +- Support the **.kdb.q.**, **kdb.py** extensions - Are stored in a **.kx** folder at the root of your open folder - You can have multiple Workbooks running against different connections at the same time @@ -643,9 +675,7 @@ Create a Workbook using the WORKBOOKS panel and run code against a specific conn 1. Write the code you wish to execute. 1. Run the code: - 1. To run all the code in the file you can use one of the following methods: - 1. Select **Run** from the upper right of the editor. Using the dropdown next to the button you can choose any of the [**KX:** menu items](#kdb-process-executing-q-and-python-code) to run some, or all of the code in the workbook. ![play dropdown](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/workbookplaydropdown.png) @@ -665,7 +695,7 @@ You can also change the connection associated with a workbook at any time by cli ### Source files -Regular `.q` and `.py` files now support enhanced functionality similar to [Workbooks](#workbooks), allowing you to write, test, and execute code directly against kdb Insights connections and endpoints. +Regular `.q` and `.py` files now support enhanced functionality similar to [Workbooks](#workbooks), allowing you to write, test, and execute code directly against kdb Insights connections and endpoints. You can run code on either the [scratchpad](#run-and-populate-scratchpad) or directly on DAP processes — such as RDB or HDB — without needing to copy/paste or switch between special file types. @@ -680,31 +710,28 @@ You can run code on either the [scratchpad](#run-and-populate-scratchpad) or dir - Run against the active connection if no specific association is made. - Can be explicitly associated with a connection using the **Choose Connection** code lens. - Once associated, allow execution against: - - Scratchpad (default for Insights) - - Any available DAP process (if the connection is an Insights type) + - Scratchpad (default for Insights) + - Any available DAP process (if the connection is an Insights type) This eliminates the need to copy code from files into the qSQL Data Source tab or Workbooks for testing against DAPs. -**Note!** DAP targeting is available only for `.q` files. `.py` files run exclusively on the scratchpad, even when associated with an Insights connection. - For selecting connections and endpoints for unassociated files, consider the following: - When a `.q` or `.py` file is not associated with a connection, it shows a **Choose Connection** code lens at the top and runs on the active connected connection. - Clicking **Choose Connection** allows you to associate the file with a connection. - Once associated, the file only executes on that connection. - ![choose connection](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/unassociated-file-workbook.png) + ![choose connection](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/unassociated-file-workbook.png) For associated files, take into account the following: - When a file is associated with a kdb Insights connection, a **scratchpad** code lens appears. - Clicking this allows you to choose the execution endpoint: + - Scratchpad (default) + - Any available DAP (for example, RDB, HDB) - - Scratchpad (default) - - Any available DAP (for example, RDB, HDB) - - ![choose connection](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/associated-file-workbook.png) + ![choose connection](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/associated-file-workbook.png) ## KX Notebooks in Visual Studio Code @@ -720,9 +747,11 @@ From this view, you can add either Markdown or Code blocks to the notebook by cl ![Add code blocks to notebook](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/add-notebook-code.png) -To change the language of the code block, click on the language labels and select language from the Command Palette. +In KX Notebooks, you can select a target and a variable name to populate the Scratchpad. When you select a connection, clicking on the Scratchpad tab displays a list where you can change between the Scratchpad and one of the database tiers (RDB, IDB, or HDB) or a specific database process on one of the tiers. -![Select notebook language](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/select-notebook-language.png) +Next to the Scratchpad tab, there is a language option. To change the language of the code block, click on the language labels and select language from the Command Palette. You can select between q, Python, Markdown, or MS SQL. + +When selecting a variable, the default option is **(none)**. You can click on **(none)** and enter a variable name, such as `mydata`. If you execute this, it populates the scratchpad with the variable. You can also choose a different tier to run the query and populate the scratchpad accordingly. If you don't enter any variable, only the results are displayed. ### Execute code blocks @@ -730,10 +759,14 @@ Code blocks are executed using the active KX connection, and the results are dis ![See notebook data](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/notebook-data.png) +You can run SQL queries on a tier and populate the Scratchpad with the SQL results. This functionality connects to the Q SQL endpoint and imports the data into the Scratchpad as a variable. + KX Notebooks detect [GGPlot2](#grammar-of-graphics) outputs. If the execution generates a plot, it is displayed inline for both q and PyKX. ![See notebook plot](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/notebook-plot.png) +Sample notebooks are available on [Developer Wiki](https://github.com/KxSystems/kx-vscode/wiki/Sample-KX-Notebooks). + ## Query History The **Query History** view in the primary sidebar captures each query execution and enables you to re-run any of the queries listed. Initially the query history view is empty but once you run a query it is captured and displayed in the window - with a separate row displayed for every execution. All information is stored in memory and not persisted upon application exit. @@ -810,19 +843,33 @@ You can make changes to the script before exporting the plot. Re-running the scr **Note**: When executing GG script commands, select the `KDB RESULTS` tab to display the plot. -## q REPL +## REPL + +REPL stands for **Read-Eval-Print Loop**, which is an interactive programming environment used in many languages. REPLs are particularly useful for interactive development, debugging, and testing because users can write and run code snippets in real-time, seeing immediate feedback. -q REPL can be started from the command prompt by searching **q REPL**. +REPL can be started from the command prompt by searching **>repl**. ![REPL](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/repl.png) +**Important!** Before running code in the REPL interactive terminal, ensure that your [Q Home Directory](#using-q-outside-of-vs-code) is correctly configured in VSCode. This setting is required to set up the q runtime environment for the interactive terminal. To configure the Q Home Directory, go to **VSCode Settings > Extension > kdb** and enter the path for the `q` runtime. + +To execute a q file in REPL: + +1. Click **Choose Connection** +1. Select **REPL** from the list +1. Execute your q file + +The results are shown in the terminal and you can continue to work either in your q file or directly in the terminal. + +Refer to the [REPL shortcuts table](https://github.com/KxSystems/kx-vscode/wiki/REPL) for information on the keyboard shortcuts you can use. + ## Logs -Any error or info will be posted at **OUTPUT** in **kdb** tab +Any error or info is posted at **OUTPUT** in **kdb** tab ![LOG](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/log-sample.png) -The format will be: +The format is: `[DATE TIME] [INFO or ERROR] Message` @@ -830,15 +877,23 @@ The format will be: To update kdb VS Code settings, search for **kdb** from _Preferences_ > _Settings_, or right-click the settings icon in kdb VS Code marketplace panel and choose **Extension Settings**. -| Setting | Action | -| -------------------------------------------------------------- | ------------------------------------------------------------------- | -| **Hide notification of installation path after first install** | yes/no; default no | -| **Hide subscription to newsletter after first install** | yes/no; default no | -| **Insights Enterprise Connections for Explorer** | [edit JSON settings](#insights-enterprise-connections-for-explorer) | -| **Linting** | Enable linting for q and quke files | -| **Refactoring** | Choose refactoring scope | -| **QHOME directory for q runtime** | Display location path of q installation | -| **Servers** | [edit JSON settings](#servers) | +| Setting | Action | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | +| **Hide notification for installation path, after first install** | yes/no; default no | +| **Hide subscribe for registration notification** | yes/no; default no | +| **Hide the extension survey dialog box** | yes/no; default no | +| **Hide detailed console query output** | yes/no; default yes | +| **kdb Insights Enterprise connections for explorer** | [edit JSON settings](#kdb-insights-enterprise-connections-for-explorer) | +| **Linting** | Enable linting for q and quke files | +| **Refactoring** | Choose refactoring scope | +| **QHOME directory for q runtime** | Display location path of q installation | +| **Never show q install walkthrough again** | yes/no; default no | +| **kdb servers for explorer** | [edit JSON settings](#servers) | +| **Automatically focus the output console when running a query without an active results tab or receive log entry** | yes/no; default yes | +| **Connection map for workspace files** | edit JSON settings | +| **Target map for workspace files** | edit JSON settings | +| **List of label names and colorset** | edit JSON settings | +| **Labels connection map** | edit JSON settings | ### Refactoring @@ -912,7 +967,7 @@ If you only need to apply the refactorings to the currently opened files, you ca ### Double Click Selection -The following setting will change double click behaviour to select the whole identifier including dots: +The following setting changes double click behavior to select the whole identifier including dots: ```JSON "[q]": { @@ -920,6 +975,12 @@ The following setting will change double click behaviour to select the whole ide } ``` +### Auto focus output on entry + +This setting automatically focuses the output console when running a query without an active results tab or receive log entry. This means that, when the setting is enabled, executing a query shows the q console in the output window even if the q console is not open in the output window. + +You can disable this option at any time in **Settings** if you do not want to auto-focus. + ## Help and feedback A **Help and Feedback** view is displayed in the primary sidebar of the kdb VS Code extension. This includes links to: @@ -954,7 +1015,7 @@ If you choose to opt out permanently but wish to revert this, open VS Code setti | Ctrl + Shift + R | Run q file in new q instance | | Ctrl + Shift + Y | Toggle parameter cache for lambda | | Ctrl + Shift + Delete | Reset scratchpad | -| Ctrl + Alt + T | Choose the execution target +| Ctrl + Alt + T | Choose the execution target | ### For MacOS @@ -969,4 +1030,8 @@ If you choose to opt out permanently but wish to revert this, open VS Code setti | ⌘ + Shift + R | Run q file in new q instance | | ⌘ + Shift + Y | Toggle parameter cache for lambda | | ⌘ + Shift + Delete | Reset scratchpad | -| ⌘ + Alt + T | Choose the execution target +| ⌘ + Alt + T | Choose the execution target | + +## Data and telemetry + +The KX kdb Extension for Visual Studio Code collects usage data and sends it to KX to help improve our products and services. Read our [privacy statement](https://kx.com/privacy-policy/) to learn more. This extension respects the telemetry.enableTelemetry setting which you can learn more about at https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting. diff --git a/eslint.config.mjs b/eslint.config.mjs index 4e9943b31..99f372928 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -50,7 +50,7 @@ export default [ "error", [ "/*", - ` * Copyright (c) 1998-${currentYear} Kx Systems Inc.`, + ` * Copyright (c) 1998-${currentYear} 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", diff --git a/package-lock.json b/package-lock.json index 25d1e7c98..15d11ef64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,20 @@ { "name": "kdb", - "version": "1.12.0", + "version": "1.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kdb", - "version": "1.12.0", + "version": "1.13.0", "license": "MIT", "dependencies": { "@ag-grid-community/core": "^32.3.5", - "@vscode/webview-ui-toolkit": "^1.4.0", + "@vscode/dts": "^0.4.1", + "@vscode/extension-telemetry": "^1.0.0", "@windozer/node-q": "^2.6.0", "ag-grid-community": "^33.3.2", - "axios": "^1.10.0", + "axios": "^1.11.0", "chevrotain": "^10.5.0", "extract-zip": "^2.0.1", "fs-extra": "^11.3.0", @@ -25,8 +26,7 @@ "node-q": "^2.7.0", "pick-port": "^2.0.1", "semver": "^7.7.2", - "vscode-extension-telemetry": "^0.4.5", - "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0" }, "devDependencies": { @@ -51,17 +51,18 @@ "@typescript-eslint/eslint-plugin": "^8.34.1", "@typescript-eslint/parser": "^8.34.1", "@vscode/test-electron": "^2.5.2", + "c8": "^10.1.3", "esbuild": "^0.25.5", "eslint": "^9.29.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.32.0", "eslint-plugin-license-header": "^0.8.0", "eslint-plugin-unused-imports": "^4.1.4", - "glob": "^8.1.0", + "glob": "^11.0.3", "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", + "istanbul-lib-instrument": "^6.0.1", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "jsdom": "^26.1.0", "lit": "^3.3.0", @@ -74,12 +75,10 @@ "sinon": "^21.0.0", "typescript": "^5.8.3", "typescript-eslint": "^8.34.1", - "vscode-dts": "^0.3.3", - "vscode-extension-tester": "^8.15.0", + "vscode-extension-tester": "^8.16.2", "vscode-languageclient": "^9.0.1", "vscode-languageserver": "^9.0.1", - "vscode-languageserver-textdocument": "^1.0.12", - "vscode-test": "^1.6.1" + "vscode-languageserver-textdocument": "^1.0.12" }, "engines": { "vscode": "^1.96.0" @@ -95,6 +94,20 @@ "tslib": "^2.3.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -109,6 +122,13 @@ "lru-cache": "^10.4.3" } }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@azu/format-text": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", @@ -310,6 +330,57 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.27.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", @@ -327,6 +398,65 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -347,14 +477,38 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -379,17 +533,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -408,9 +562,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "dev": true, "license": "MIT", "dependencies": { @@ -1051,9 +1205,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1090,9 +1244,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1171,9 +1325,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", - "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", + "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", "dev": true, "license": "MIT", "engines": { @@ -1194,13 +1348,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -1208,9 +1362,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1221,30 +1375,30 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", - "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", "dev": true, "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", - "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", "dev": true, "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.1", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "dev": true, "license": "MIT" }, @@ -1355,91 +1509,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1451,18 +1520,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1475,27 +1540,17 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", "dev": true, "license": "MIT", "dependencies": { @@ -1530,52 +1585,130 @@ "@lit-labs/ssr-dom-shim": "^1.2.0" } }, - "node_modules/@microsoft/fast-element": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.14.0.tgz", - "integrity": "sha512-zXvuSOzvsu8zDTy9eby8ix8VqLop2rwKRgp++ZN2kTCsoB3+QJVoaGD2T/Cyso2ViZQFXNpiNCVKfnmxBvmWkQ==", - "license": "MIT" + "node_modules/@microsoft/1ds-core-js": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.9.tgz", + "integrity": "sha512-T8s5qROH7caBNiFrUpN8vgC6wg7QysVPryZKprgl3kLQQPpoMFM6ffIYvUWD74KM9fWWLU7vzFFNBWDBsrTyWg==", + "license": "MIT", + "dependencies": { + "@microsoft/applicationinsights-core-js": "3.3.9", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.4 < 2.x", + "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + } }, - "node_modules/@microsoft/fast-foundation": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.50.0.tgz", - "integrity": "sha512-8mFYG88Xea1jZf2TI9Lm/jzZ6RWR8x29r24mGuLojNYqIR2Bl8+hnswoV6laApKdCbGMPKnsAL/O68Q0sRxeVg==", + "node_modules/@microsoft/1ds-post-js": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.9.tgz", + "integrity": "sha512-BvxI4CW8Ws+gfXKy+Y/9pmEXp88iU1GYVjkUfqXP7La59VHARTumlG5iIgMVvaifOrvSW7G6knvQM++0tEfMBQ==", "license": "MIT", "dependencies": { - "@microsoft/fast-element": "^1.14.0", - "@microsoft/fast-web-utilities": "^5.4.1", - "tabbable": "^5.2.0", - "tslib": "^1.13.0" + "@microsoft/1ds-core-js": "4.3.9", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.4 < 2.x", + "@nevware21/ts-utils": ">= 0.11.8 < 2.x" } }, - "node_modules/@microsoft/fast-foundation/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" + "node_modules/@microsoft/applicationinsights-channel-js": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.9.tgz", + "integrity": "sha512-/yEgSe6vT2ycQJkXu6VF04TB5XBurk46ECV7uo6KkNhWyDEctAk1VDWB7EqXYdwLhKMbNOYX1pvz7fj43fGNqg==", + "license": "MIT", + "dependencies": { + "@microsoft/applicationinsights-common": "3.3.9", + "@microsoft/applicationinsights-core-js": "3.3.9", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.4 < 2.x", + "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + }, + "peerDependencies": { + "tslib": ">= 1.0.0" + } }, - "node_modules/@microsoft/fast-react-wrapper": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.3.25.tgz", - "integrity": "sha512-jKzmk2xJV93RL/jEFXEZgBvXlKIY4N4kXy3qrjmBfFpqNi3VjY+oUTWyMnHRMC5EUhIFxD+Y1VD4u9uIPX3jQw==", + "node_modules/@microsoft/applicationinsights-common": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.9.tgz", + "integrity": "sha512-IgruOuDBxmBK9jYo7SqLJG7Z9OwmAmlvHET49srpN6pqQlEjRpjD1nfA3Ps4RSEbF89a/ad2phQaBp8jvm122g==", "license": "MIT", "dependencies": { - "@microsoft/fast-element": "^1.14.0", - "@microsoft/fast-foundation": "^2.50.0" + "@microsoft/applicationinsights-core-js": "3.3.9", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.11.8 < 2.x" }, "peerDependencies": { - "react": ">=16.9.0" + "tslib": ">= 1.0.0" } }, - "node_modules/@microsoft/fast-web-utilities": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", - "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", + "node_modules/@microsoft/applicationinsights-core-js": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.9.tgz", + "integrity": "sha512-xliiE9H09xCycndlua4QjajN8q5k/ET6VCv+e0Jjodxr9+cmoOP/6QY9dun9ptokuwR8TK0qOaIJ8z4fgslVSA==", + "license": "MIT", + "dependencies": { + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.4 < 2.x", + "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + }, + "peerDependencies": { + "tslib": ">= 1.0.0" + } + }, + "node_modules/@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", + "dependencies": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } + }, + "node_modules/@microsoft/applicationinsights-web-basic": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.9.tgz", + "integrity": "sha512-8tLaAgsCpWjoaxit546RqeuECnHQPBLnOZhzTYG76oPG1ku7dNXaRNieuZLbO+XmAtg/oxntKLAVoPND8NRgcA==", + "license": "MIT", + "dependencies": { + "@microsoft/applicationinsights-channel-js": "3.3.9", + "@microsoft/applicationinsights-common": "3.3.9", + "@microsoft/applicationinsights-core-js": "3.3.9", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.4 < 2.x", + "@nevware21/ts-utils": ">= 0.11.8 < 2.x" + }, + "peerDependencies": { + "tslib": ">= 1.0.0" + } + }, + "node_modules/@microsoft/dynamicproto-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", + "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", + "dependencies": { + "@nevware21/ts-utils": ">= 0.10.4 < 2.x" + } + }, + "node_modules/@nevware21/ts-async": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", + "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", "license": "MIT", "dependencies": { - "exenv-es6": "^1.1.1" + "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } }, + "node_modules/@nevware21/ts-utils": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.12.5.tgz", + "integrity": "sha512-JPQZWPKQJjj7kAftdEZL0XDFfbMgXCGiUAZe0d7EhLC3QlXTlZdSckGqqRIQ2QNl0VTEZyZUvRBw6Ednw089Fw==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1626,9 +1759,9 @@ } }, "node_modules/@redhat-developer/locators": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.13.0.tgz", - "integrity": "sha512-+v+FtxaPnCDguiwiU+UpWiNFaEyBXpRQMbf35SlJVAT5Yszb6qnDOFf9iTwtZNeeny4Cv+PVlYmoe/u9bRqByw==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.14.2.tgz", + "integrity": "sha512-q9wcsX+lw1eyki9cf2oPTF+l/LFTixjXbijmKEL+WAHy25YA/1pTqBXHJpn/0MufN3AWPhGihEZFYU4PVpGAFg==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -1637,9 +1770,9 @@ } }, "node_modules/@redhat-developer/page-objects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.13.0.tgz", - "integrity": "sha512-k53UVbw/5cD1dh1uagwGX48t7vtx9mqysaKEnx9hjuAaxHJ7aKF5KfF25mi31fvn6xvEdEvpBPrV2yoRgyD6Gg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.14.2.tgz", + "integrity": "sha512-na7stMt1IlnJRoidDxuY9FJXwfWrzZv8w9IDbR5t4t15vHXtJZqqmyBDIICP86qeNCpcoP9POuLKHibTgRgTOQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1669,34 +1802,34 @@ "license": "MIT" }, "node_modules/@secretlint/config-creator": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-9.3.4.tgz", - "integrity": "sha512-GRMYfHJ+rewwB26CC3USVObqSQ/mDLXzXcUMJw/wJisPr3HDZmdsYlcsNnaAcGN+EZmvqSDkgSibQm1hyZpzbg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.1.1.tgz", + "integrity": "sha512-TJ42CHZqqnEe9ORvIXVVMqdu3KAtyZRxLspjFexo6XgrwJ6CoFHQYzIihilqRjo2sJh9HMrpnYSj/5hopofGrA==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/types": "^9.3.4" + "@secretlint/types": "^10.1.1" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@secretlint/config-loader": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-9.3.4.tgz", - "integrity": "sha512-sy+yWDWh4cbAbpQYLiO39DjwNGEK1EUhTqNamLLBo163BdJP10FIWhqpe8mtGQBSBXRtxr8Hg/gc3Xe4meIoww==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.1.1.tgz", + "integrity": "sha512-jBClVFmS6Yu/zI5ejBCRF5a5ASYsE4gOjogjB+WsaHbQHtGvnyY7I26Qtdg4ihCc/VPKYQg0LdM75pLTXzwsjg==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/profiler": "^9.3.4", - "@secretlint/resolver": "^9.3.4", - "@secretlint/types": "^9.3.4", + "@secretlint/profiler": "^10.1.1", + "@secretlint/resolver": "^10.1.1", + "@secretlint/types": "^10.1.1", "ajv": "^8.17.1", "debug": "^4.4.1", "rc-config-loader": "^4.1.3" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@secretlint/config-loader/node_modules/ajv": { @@ -1724,33 +1857,33 @@ "license": "MIT" }, "node_modules/@secretlint/core": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-9.3.4.tgz", - "integrity": "sha512-ErIVHI6CJd191qdNKuMkH3bZQo9mWJsrSg++bQx64o0WFuG5nPvkYrDK0p/lebf+iQuOnzvl5HrZU6GU9a6o+Q==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.1.1.tgz", + "integrity": "sha512-COLCxSoH/iVQdLeaZPVtBj0UWKOagO09SqYkCQgfFfZ+soGxKVK405dL317r4PnH9Pm8/s8xQC6OSY5rWTRObQ==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/profiler": "^9.3.4", - "@secretlint/types": "^9.3.4", + "@secretlint/profiler": "^10.1.1", + "@secretlint/types": "^10.1.1", "debug": "^4.4.1", "structured-source": "^4.0.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@secretlint/formatter": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-9.3.4.tgz", - "integrity": "sha512-ARpoBOKz6WP3ocLITCFkR1/Lj636ugpBknylhlpc45r5aLdvmyvWAJqodlw5zmUCfgD6JXeAMf3Hi60aAiuqWQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.1.1.tgz", + "integrity": "sha512-Gpd8gTPN121SJ0h/9e6nWlZU7PitfhXUiEzW7Kyswg6kNGs+bSqmgTgWFtbo1VQ4ygJYiveWPNT05RCImBexJw==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/resolver": "^9.3.4", - "@secretlint/types": "^9.3.4", - "@textlint/linter-formatter": "^14.7.2", - "@textlint/module-interop": "^14.7.2", - "@textlint/types": "^14.7.2", + "@secretlint/resolver": "^10.1.1", + "@secretlint/types": "^10.1.1", + "@textlint/linter-formatter": "^14.8.4", + "@textlint/module-interop": "^14.8.4", + "@textlint/types": "^14.8.4", "chalk": "^4.1.2", "debug": "^4.4.1", "pluralize": "^8.0.0", @@ -1759,47 +1892,70 @@ "terminal-link": "^2.1.1" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@secretlint/node": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-9.3.4.tgz", - "integrity": "sha512-S0u8i+CnPmyAKtuccgot9L5cmw6DqJc0F+b3hhVIALd8kkeLt3RIXOOej15tU7N0V1ISph90Gz92V72ovsprgQ==", + "node_modules/@secretlint/formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@secretlint/formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/config-loader": "^9.3.4", - "@secretlint/core": "^9.3.4", - "@secretlint/formatter": "^9.3.4", - "@secretlint/profiler": "^9.3.4", - "@secretlint/source-creator": "^9.3.4", - "@secretlint/types": "^9.3.4", - "debug": "^4.4.1", - "p-map": "^4.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=8" } }, - "node_modules/@secretlint/profiler": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-9.3.4.tgz", - "integrity": "sha512-99WmaHd4dClNIm5BFsG++E6frNIZ3qVwg6s804Ql/M19pDmtZOoVCl4/UuzWpwNniBqLIgn9rHQZ/iGlIW3wyw==", + "node_modules/@secretlint/node": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.1.1.tgz", + "integrity": "sha512-AhN+IGqljVObm8a+B33b23FY79wihu5E61Nd3oYSoZV7SxUvMjpafqhLfpt4frNSY7Ghf/pirWu7JY7GMujFrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.1.1", + "@secretlint/core": "^10.1.1", + "@secretlint/formatter": "^10.1.1", + "@secretlint/profiler": "^10.1.1", + "@secretlint/source-creator": "^10.1.1", + "@secretlint/types": "^10.1.1", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.1.1.tgz", + "integrity": "sha512-kReI+Wr7IQz0LbVwYByzlnPbx4BEF2oEWJBc4Oa45g24alCjHu+jD9h9mzkTJqYUgMnVYD3o7HfzeqxFrV+9XA==", "dev": true, "license": "MIT" }, "node_modules/@secretlint/resolver": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-9.3.4.tgz", - "integrity": "sha512-L1lIrcjzqcspPzZttmOvMmOFDpJTYFyRBONg94TZBWrpv4x0w5G2SYR+K7EE1SbYQAiPxw1amoXT1YRP8cZF2A==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.1.1.tgz", + "integrity": "sha512-GdQzxnBtdBRjBULvZ8ERkaRqDp0njVwXrzBCav1pb0XshVk76C1cjeDqtTqM4RJ1Awo/g5U5MIWYztYv67v5Gg==", "dev": true, "license": "MIT" }, "node_modules/@secretlint/secretlint-formatter-sarif": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-9.3.4.tgz", - "integrity": "sha512-IpAl5gzKwpTRqoivKOTJB89l6b7uvBwjSNKzJb3oIGD9Jg3vXcQunSntvLv5XGynYtdi1NhANfEpbhavlmMSyA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.1.1.tgz", + "integrity": "sha512-Dyq8nzy6domjSlZKX1E5PEzuWxeTqjQJWrlXBmVmOjwLBLfRZDlm5Vq+AduBmEk03KEIKIZi4cZQwsniuRPO9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1807,50 +1963,50 @@ } }, "node_modules/@secretlint/secretlint-rule-no-dotenv": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-9.3.4.tgz", - "integrity": "sha512-lMSVwTrJiZ/zL9VIzpT7tMcb0ClI6u4cyJo2YKGSbuJErJG1zB4gQKtjIwCSt7px5JF6U+aFtpb9M8+s40WWCQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.1.1.tgz", + "integrity": "sha512-a3/sOUUtEHuw1HCadtxUjViNeomiiohfJj+rwtHxJkCq4pjITS3HSYhQBXnNvkctQNljKIzFm7JUA/4QJ6I4sQ==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/types": "^9.3.4" + "@secretlint/types": "^10.1.1" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@secretlint/secretlint-rule-preset-recommend": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-9.3.4.tgz", - "integrity": "sha512-RvzrLNN2A0B2bYQgRSRjh2dkdaIDuhXjj4SO5bElK1iBtJNiD6VBTxSSY1P3hXYaBeva7MEF+q1PZ3cCL8XYOA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.1.1.tgz", + "integrity": "sha512-+GeISCXVgpnoeRZE4ZPsuO97+fm6z8Ge23LNq6LvR9ZJAq018maXVftkJhHj4hnvYB5URUAEerBBkPGNk5/Ong==", "dev": true, "license": "MIT", "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@secretlint/source-creator": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-9.3.4.tgz", - "integrity": "sha512-I9ZA1gm9HJNaAhZiQdInY9VM04VTAGDV4bappVbEJzMUDnK/LTbYqfQ88RPqgCGCqa6ee8c0/j5Bn7ypweouIw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.1.1.tgz", + "integrity": "sha512-IWjvHcE0bhC/x88a9M9jbZlFRZGUEbBzujxrs2KzI5IQ2BXTBRBRhRSjE/BEpWqDHILB22c3mfam8X+UjukphA==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/types": "^9.3.4", + "@secretlint/types": "^10.1.1", "istextorbinary": "^9.5.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@secretlint/types": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-9.3.4.tgz", - "integrity": "sha512-z9rdKHNeL4xa48+367RQJVw1d7/Js9HIQ+gTs/angzteM9osfgs59ad3iwVRhCGYbeUoUUDe2yxJG2ylYLaH3Q==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.1.1.tgz", + "integrity": "sha512-/JGAvVkurVHkargk3AC7UxRy+Ymc+52AVBO/fZA5pShuLW2dX4O/rKc4n8cyhQiOb/3ym5ACSlLQuQ8apPfxrQ==", "dev": true, "license": "MIT", "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@shoelace-style/animations": { @@ -2006,6 +2162,16 @@ "text-table": "^0.2.0" } }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@textlint/linter-formatter/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2059,6 +2225,19 @@ "node": ">=8" } }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@textlint/module-interop": { "version": "14.8.4", "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-14.8.4.tgz", @@ -2083,16 +2262,6 @@ "@textlint/ast-node-types": "14.8.4" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/babel-types": { "version": "7.0.16", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.16.tgz", @@ -2247,9 +2416,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", - "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "version": "24.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", + "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2631,6 +2800,34 @@ "node": ">=18.0.0" } }, + "node_modules/@vscode/dts": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@vscode/dts/-/dts-0.4.1.tgz", + "integrity": "sha512-o8cI5Vqt6S6Y5mCI7yCkSQdiLQaLG5DMUpciJV3zReZwE+dA5KERxSVX8H3cPEhyKw21XwKGmIrg6YmN6M5uZA==", + "license": "MIT", + "dependencies": { + "https-proxy-agent": "^7.0.0", + "minimist": "^1.2.8", + "prompts": "^2.4.2" + }, + "bin": { + "dts": "index.js" + } + }, + "node_modules/@vscode/extension-telemetry": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.0.0.tgz", + "integrity": "sha512-vaTZE65zigWwSWYB6yaZUAyVC/Ux+6U82hnzy/ejuS/KpFifO+0oORNd5yAoPeIRnYjvllM6ES3YlX4K5tUuww==", + "license": "MIT", + "dependencies": { + "@microsoft/1ds-core-js": "^4.3.4", + "@microsoft/1ds-post-js": "^4.3.4", + "@microsoft/applicationinsights-web-basic": "^3.3.4" + }, + "engines": { + "vscode": "^1.75.0" + } + }, "node_modules/@vscode/test-electron": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", @@ -2649,17 +2846,17 @@ } }, "node_modules/@vscode/vsce": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.5.0.tgz", - "integrity": "sha512-2Eb6fBh8OzNhWqviCjeUPA1MW+d2GCb1QlVxrpOR8lrLHGk8x7HD4LbfELnZPyOz2X33Myz9FE9t4LwYbmeMRg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.0.tgz", + "integrity": "sha512-u2ZoMfymRNJb14aHNawnXJtXHLXDVKc1oKZaH4VELKT/9iWKRVgtQOdwxCgtwSxJoqYvuK4hGlBWQJ05wxADhg==", "dev": true, "license": "MIT", "dependencies": { "@azure/identity": "^4.1.0", - "@secretlint/node": "^9.3.4", - "@secretlint/secretlint-formatter-sarif": "^9.3.4", - "@secretlint/secretlint-rule-no-dotenv": "^9.3.4", - "@secretlint/secretlint-rule-preset-recommend": "^9.3.4", + "@secretlint/node": "^10.1.1", + "@secretlint/secretlint-formatter-sarif": "^10.1.1", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", @@ -2676,7 +2873,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "secretlint": "^9.3.4", + "secretlint": "^10.1.1", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", @@ -2861,46 +3058,6 @@ "node": ">=18" } }, - "node_modules/@vscode/vsce/node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/vsce/node_modules/glob/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vscode/vsce/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2914,22 +3071,6 @@ "node": "*" } }, - "node_modules/@vscode/webview-ui-toolkit": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz", - "integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==", - "deprecated": "This package has been deprecated, https://github.com/microsoft/vscode-webview-ui-toolkit/issues/561", - "license": "MIT", - "dependencies": { - "@microsoft/fast-element": "^1.12.0", - "@microsoft/fast-foundation": "^2.49.4", - "@microsoft/fast-react-wrapper": "^0.3.22", - "tslib": "^2.6.2" - }, - "peerDependencies": { - "react": ">=16.9.0" - } - }, "node_modules/@windozer/node-q": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@windozer/node-q/-/node-q-2.6.0.tgz", @@ -2992,26 +3133,11 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3059,13 +3185,16 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -3256,13 +3385,13 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3306,30 +3435,6 @@ "license": "MIT", "optional": true }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/binaryextensions": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", @@ -3426,6 +3531,39 @@ "dev": true, "license": "ISC" }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -3502,15 +3640,6 @@ "node": ">=0.10" } }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "dev": true, - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -3561,37 +3690,6 @@ } } }, - "node_modules/c8/node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/c8/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -3723,18 +3821,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", @@ -3844,16 +3950,6 @@ "dev": true, "license": "ISC" }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -3916,6 +4012,16 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3938,6 +4044,37 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -4069,9 +4206,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4086,9 +4223,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4533,14 +4670,21 @@ "url": "https://bevry.me/fund" } }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "node_modules/electron-to-chromium": { + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/encoding-sniffer": { + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", @@ -4796,19 +4940,19 @@ } }, "node_modules/eslint": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", - "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", + "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.1", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.30.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5241,12 +5385,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/exenv-es6": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", - "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", - "license": "MIT" - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -5493,9 +5631,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5539,103 +5677,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/fstream/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/fstream/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fstream/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/fstream/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/fstream/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5676,6 +5717,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5778,21 +5829,24 @@ "optional": true }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5812,16 +5866,19 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { @@ -6053,6 +6110,13 @@ "node": ">=10" } }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -6142,7 +6206,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -6241,28 +6304,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6879,284 +6920,126 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, "node_modules/istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/istanbul-lib-source-maps/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", "dev": true, - "license": "MIT", + "license": "Artistic-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" } }, - "node_modules/istanbul-lib-source-maps/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": "*" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/istanbul-lib-source-maps/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "argparse": "^2.0.1" }, - "engines": { - "node": "*" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/istanbul-lib-source-maps/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports/node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istextorbinary": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", - "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", - "dev": true, - "license": "Artistic-2.0", - "dependencies": { - "binaryextensions": "^6.11.0", - "editions": "^6.21.0", - "textextensions": "^6.11.0" - }, - "engines": { - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "dev": true, - "license": "MIT", + "license": "MIT", "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -7236,16 +7119,16 @@ "license": "MIT" }, "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, "bin": { "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/jsonc-parser": { @@ -7372,7 +7255,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7432,13 +7314,6 @@ "uc.micro": "^2.0.0" } }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", - "dev": true, - "license": "ISC" - }, "node_modules/lit": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", @@ -7606,34 +7481,29 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC" + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/markdown-it": { @@ -7819,7 +7689,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7912,6 +7781,29 @@ "mocha": ">=2.2.5" } }, + "node_modules/mocha-junit-reporter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha-junit-reporter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mocha-multi-reporters": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", @@ -7966,6 +7858,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/mocha/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/mocha/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -8149,6 +8048,13 @@ "node": ">=0.10" } }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-sarif-builder": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-2.0.3.tgz", @@ -8215,6 +8121,13 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/normalize-url": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz", @@ -8460,19 +8373,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ora/node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -8486,6 +8386,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, "node_modules/ora/node_modules/is-unicode-supported": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", @@ -8529,20 +8436,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/own-keys": { @@ -8606,16 +8515,13 @@ } }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8751,16 +8657,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8856,16 +8752,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -8925,9 +8811,9 @@ } }, "node_modules/prettier": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", - "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -8951,7 +8837,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -9094,19 +8979,6 @@ "require-from-string": "^2.0.2" } }, - "node_modules/rc-config-loader/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -9118,16 +8990,6 @@ "node": ">=0.10.0" } }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -9372,46 +9234,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -9570,16 +9392,16 @@ } }, "node_modules/secretlint": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-9.3.4.tgz", - "integrity": "sha512-iNOzgMX/+W1SQNW/TW6eikGChyaPiazr2AEXjzjpoB0R6QJEulvlwhn0KLT1/xjPfdYrk3yiXZM40csUqET8uQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.1.1.tgz", + "integrity": "sha512-q50i+I9w6HH8P6o34LVq6M3hm5GZn2Eq5lYGHkEByOAbVqBHn8gsMGgyxjP1xSrSv1QjDtjxs/zKPm6JtkNzGw==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/config-creator": "^9.3.4", - "@secretlint/formatter": "^9.3.4", - "@secretlint/node": "^9.3.4", - "@secretlint/profiler": "^9.3.4", + "@secretlint/config-creator": "^10.1.1", + "@secretlint/formatter": "^10.1.1", + "@secretlint/node": "^10.1.1", + "@secretlint/profiler": "^10.1.1", "debug": "^4.4.1", "globby": "^14.1.0", "read-pkg": "^8.1.0" @@ -9588,13 +9410,13 @@ "secretlint": "bin/secretlint.js" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.0.0" } }, "node_modules/selenium-webdriver": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.33.0.tgz", - "integrity": "sha512-5vRhk4iI0B9nYbEitfnCjPDXBfG6o9DNhj5DG2355eQo8idETknhj1tigqqlkHsGephSZwLZqEm/d+3e1stGUA==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.34.0.tgz", + "integrity": "sha512-zGfQFcsASAv3KrYzYh+iw4fFqB7iZAgHW7BU6rRz7isK1i1X4x3LvjmZad4bUUgHDwTnAhlqTzDh21byB+zHMg==", "dev": true, "funding": [ { @@ -9611,10 +9433,10 @@ "@bazel/runfiles": "^6.3.1", "jszip": "^3.10.1", "tmp": "^0.2.3", - "ws": "^8.18.0" + "ws": "^8.18.2" }, "engines": { - "node": ">= 18.20.5" + "node": ">= 20.0.0" } }, "node_modules/semver": { @@ -9891,7 +9713,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, "license": "MIT" }, "node_modules/slash": { @@ -10016,18 +9837,18 @@ } }, "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10049,6 +9870,16 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10056,33 +9887,17 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/string.prototype.trim": { @@ -10145,16 +9960,19 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi-cjs": { @@ -10171,6 +9989,16 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -10277,12 +10105,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tabbable": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "license": "MIT" - }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", @@ -10317,6 +10139,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/table/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10346,6 +10178,19 @@ "node": ">=8" } }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -10540,6 +10385,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/test-exclude/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -10692,16 +10544,6 @@ "node": ">=18" } }, - "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", - "dev": true, - "license": "MIT/X11", - "engines": { - "node": "*" - } - }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -10738,6 +10580,19 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10965,9 +10820,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", "dev": true, "license": "MIT", "engines": { @@ -11017,6 +10872,37 @@ "node-int64": "^0.4.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -11097,117 +10983,28 @@ "url": "https://bevry.me/fund" } }, - "node_modules/vscode-dts": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/vscode-dts/-/vscode-dts-0.3.3.tgz", - "integrity": "sha512-JfOsWL0NvfVw0UF9bcTjlv1Onz3Ted7cgpPWKWMnHGB+72t/tn8WFDeKLZO42l2k9KJq/NGS9rFC5gZbyI4FTg==", - "deprecated": "vscode-dts has been renamed to @vscode/dts. Install using @vscode/dts instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0", - "prompts": "^2.1.0", - "rimraf": "^3.0.0" - }, - "bin": { - "vscode-dts": "index.js" - } - }, - "node_modules/vscode-dts/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/vscode-dts/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vscode-dts/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/vscode-dts/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vscode-extension-telemetry": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.5.tgz", - "integrity": "sha512-YhPiPcelqM5xyYWmD46jIcsxLYWkPZhAxlBkzqmpa218fMtTT17ERdOZVCXcs1S5AjvDHlq43yCgi8TaVQjjEg==", - "deprecated": "This package has been renamed to @vscode/extension-telemetry, please update to the new name", - "license": "MIT", - "engines": { - "vscode": "^1.60.0" - } - }, "node_modules/vscode-extension-tester": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.15.0.tgz", - "integrity": "sha512-7eGada38DMsBWEzKicKy/3/sPGVZPQJM0RU4NW6KhTCHHhgMMTjANWZ4l1pT2oF3Pmets3efUI7RhNJTaVIeNA==", + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.16.2.tgz", + "integrity": "sha512-NiswerFGT32SlJ6vSe9i+iFZyO3mNC/azSVog8vT9gfreEqzFLiFlt212nounYGcWOEG6EPk6G0KJ8a3Ez85Rw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@redhat-developer/locators": "^1.13.0", - "@redhat-developer/page-objects": "^1.13.0", + "@redhat-developer/locators": "^1.14.2", + "@redhat-developer/page-objects": "^1.14.2", "@types/selenium-webdriver": "^4.1.28", - "@vscode/vsce": "^3.5.0", + "@vscode/vsce": "^3.6.0", "c8": "^10.1.3", "commander": "^14.0.0", "compare-versions": "^6.1.1", "find-up": "7.0.0", "fs-extra": "^11.3.0", - "glob": "^11.0.2", + "glob": "^11.0.3", "got": "^14.4.7", "hpagent": "^1.2.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", - "selenium-webdriver": "^4.33.0", + "selenium-webdriver": "^4.34.0", "targz": "^1.0.1", "unzipper": "^0.12.3" }, @@ -11237,30 +11034,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vscode-extension-tester/node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/vscode-extension-tester/node_modules/locate-path": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", @@ -11277,22 +11050,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vscode-extension-tester/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/vscode-extension-tester/node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -11437,154 +11194,6 @@ "dev": true, "license": "MIT" }, - "node_modules/vscode-test": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.6.1.tgz", - "integrity": "sha512-086q88T2ca1k95mUzffvbzb7esqQNvJgiwY4h29ukPhFo8u+vXOOmelUoU5EQUHs3Of8+JuQ3oGdbVCqaxuTXA==", - "deprecated": "This package has been renamed to @vscode/test-electron, please update to the new name", - "dev": true, - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "rimraf": "^3.0.2", - "unzipper": "^0.10.11" - }, - "engines": { - "node": ">=8.9.3" - } - }, - "node_modules/vscode-test/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/vscode-test/node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vscode-test/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/vscode-test/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vscode-test/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/vscode-test/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/vscode-test/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/vscode-test/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vscode-test/node_modules/unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -11774,25 +11383,25 @@ } }, "node_modules/workerpool": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", - "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz", + "integrity": "sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==", "dev": true, "license": "Apache-2.0" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -11817,6 +11426,16 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -11839,28 +11458,32 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11868,9 +11491,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { @@ -11958,9 +11581,9 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, @@ -12009,6 +11632,16 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -12031,6 +11664,19 @@ "node": ">=8" } }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index a96c08867..8f7fa291a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "kdb", "description": "IDE support for kdb product suite including the q programming language", "publisher": "KX", - "version": "1.12.0", + "version": "1.13.0", "engines": { "vscode": "^1.96.0" }, @@ -12,7 +12,7 @@ "type": "git", "url": "https://github.com/KxSystems/kx-vscode.git" }, - "aiKey": "93119af7-5d98-45d0-abf8-5074976a1472", + "aiConnString": "InstrumentationKey=93119af7-5d98-45d0-abf8-5074976a1472;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=a9021bff-f365-4b98-a81f-d393a5cb76de", "capabilities": { "untrustedWorkspaces": { "supported": true @@ -41,7 +41,8 @@ "onCommand:kdb.connections.labels.create", "onView:kdb-datasources-explorer", "onTerminalProfile:kdb.q-terminal", - "onLanguage:python" + "onLanguage:python", + "onLanguage:sql" ], "main": "./out/extension.js", "contributes": { @@ -79,12 +80,12 @@ { "id": "addConnection", "title": "Add a connection", - "description": "You can configure connections for multiple kdb servers \n[Add connection](command:kdb.addConnection)", + "description": "You can configure connections for multiple kdb servers \n[Add connection](command:kdb.connections.add)", "media": { "markdown": "resources/walkthrough/add_connection.md" }, "completionEvents": [ - "command:kdb.addConnection" + "command:kdb.connections.add" ], "when": "!kdb.showInstallWalkthrough" }, @@ -141,43 +142,58 @@ "properties": { "kdb.servers": { "type": "object", - "description": "kdb servers for explorer" + "description": "kdb servers for explorer", + "scope": "application" }, "kdb.insights": { "deprecationMessage": "This setting is deprecated, use kdb.insightsEnterpriseConnections instead", "type": "object", - "description": "kdb insights for explorer" + "description": "kdb insights for explorer", + "scope": "application" }, "kdb.insightsEnterpriseConnections": { "type": "object", - "description": "kdb insights enterprise connections for explorer" + "description": "kdb insights enterprise connections for explorer", + "scope": "application" }, "kdb.hideInstallationNotification": { "type": "boolean", - "description": "Hide notification for installation path, after first install" + "description": "Hide notification for installation path, after first install", + "scope": "application" }, "kdb.hideDetailedConsoleQueryOutput": { "type": "boolean", "description": "Hide detailed console query output", - "default": true + "default": true, + "scope": "application" + }, + "kdb.autoFocusOutputOnEntry": { + "type": "boolean", + "description": "Automatically focus the output console when running a query without an active results tab or receive log entry", + "default": true, + "scope": "application" }, "kdb.qHomeDirectory": { "type": "string", - "description": "QHOME directory for q runtime" + "description": "QHOME directory for q runtime", + "scope": "application" }, "kdb.neverShowQInstallAgain": { "type": "boolean", - "description": "Never show q install walkthrough again" + "description": "Never show q install walkthrough again", + "scope": "application" }, "kdb.hideSubscribeRegistrationNotification": { "type": "boolean", "description": "Hide subscribe for registration notification", - "default": false + "default": false, + "scope": "application" }, "kdb.linting": { "type": "boolean", "description": "Enable linting for q and quke files", - "default": false + "default": false, + "scope": "resource" }, "kdb.refactoring": { "type": "string", @@ -186,7 +202,8 @@ "Window" ], "description": "Enable refactoring across files", - "default": "Workspace" + "default": "Workspace", + "scope": "resource" }, "kdb.connectionMap": { "type": "object", @@ -196,7 +213,7 @@ }, "kdb.targetMap": { "type": "object", - "description": "Connection map for workspace files", + "description": "Target map for workspace files", "default": {}, "scope": "resource" }, @@ -204,18 +221,19 @@ "type": "array", "description": "List of label names and colorset", "default": [], - "scope": "resource" + "scope": "application" }, "kdb.labelsConnectionMap": { "type": "array", "description": "Labels connection map", "default": [], - "scope": "resource" + "scope": "application" }, "kdb.hideSurvey": { "type": "boolean", "default": false, - "description": "Hide the extension survey dialog box" + "description": "Hide the extension survey dialog box", + "scope": "application" } } }, @@ -291,12 +309,27 @@ { "category": "KX", "command": "kdb.file.pickConnection", - "title": "Pick connection" + "title": "Chooses Connection", + "shortTitle": "Connection", + "icon": "$(cloud)" }, { "category": "KX", "command": "kdb.file.pickTarget", - "title": "Pick target" + "title": "Choose Target", + "shortTitle": "Target", + "icon": "$(target)" + }, + { + "category": "KX", + "command": "kdb.file.inputVariable", + "title": "Input Variable Name" + }, + { + "category": "KX", + "command": "kdb.file.populateScratchpad", + "title": "KX: Populate Scratchpad", + "icon": "$(debug-rerun)" }, { "category": "KX", @@ -424,36 +457,36 @@ { "category": "KX", "command": "kdb.execute.terminal.run", - "title": "KX: Run q file in a new q instance", + "title": "KX: Run File in New q Instance", "icon": "$(debug-alt)" }, { "category": "KX", - "command": "kdb.execute.terminal.run.file", + "command": "kdb.start.repl", "title": "Start REPL" }, { "category": "KX", "command": "kdb.execute.selectedQuery", - "title": "KX: Execute Current q Selection", - "icon": "$(run-below)" + "title": "KX: Execute Current Selection", + "icon": "$(run-above)" }, { "category": "KX", "command": "kdb.execute.fileQuery", - "title": "KX: Execute Entire q File", + "title": "KX: Execute Entire File", "icon": "$(run)" }, { "category": "KX", "command": "kdb.scratchpad.python.run", - "title": "KX: Execute Current Python Selection", - "icon": "$(run-below)" + "title": "KX: Execute Current Selection", + "icon": "$(run-above)" }, { "category": "KX", "command": "kdb.scratchpad.python.run.file", - "title": "KX: Execute Entire Python File", + "title": "KX: Execute Entire File", "icon": "$(run)" }, { @@ -474,8 +507,8 @@ { "category": "KX", "command": "kdb.execute.block", - "title": "KX: Execute Current q Block", - "icon": "$(run-above)" + "title": "KX: Execute Current Block", + "icon": "$(run-below)" }, { "category": "KX", @@ -528,54 +561,61 @@ "command": "kdb.execute.selectedQuery", "key": "ctrl+d", "mac": "cmd+d", - "when": "editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { "command": "kdb.execute.fileQuery", "key": "ctrl+shift+d", - "mac": "cmd+shift+d" + "mac": "cmd+shift+d", + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { "command": "kdb.scratchpad.python.run", "key": "ctrl+d", "mac": "cmd+d", - "when": "editorLangId == python" + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { "command": "kdb.scratchpad.python.run.file", "key": "ctrl+shift+d", "mac": "cmd+shift+d", - "when": "editorLangId == python" + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { "command": "kdb.scratchpad.editor.reset", "key": "ctrl+shift+delete", "mac": "cmd+shift+delete", - "when": "editorLangId == python || editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" + }, + { + "command": "kdb.file.populateScratchpad", + "key": "alt+ctrl+shift+p", + "mac": "alt+cmd+shift+p", + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" }, { "command": "kdb.execute.terminal.run", "key": "ctrl+shift+r", "mac": "cmd+shift+r", - "when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.execute.block", "key": "ctrl+shift+e", "mac": "cmd+shift+e", - "when": "editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.toggleParameterCache", "key": "ctrl+shift+y", "mac": "cmd+shift+y", - "when": "editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q|quke)$/i" }, { "command": "kdb.file.pickTarget", "key": "ctrl+alt+t", "mac": "cmd+alt+t", - "when": "kdb.connected.active && (editorLangId == q || editorLangId == python)" + "when": "kdb.connected.active && resourceFilename =~ /\\.(?:q|py)$/i" } ], "snippets": [ @@ -704,7 +744,7 @@ "viewsWelcome": [ { "view": "kdb-servers", - "contents": "No connections registered.\n[Add Connection](command:kdb.addConnection)" + "contents": "No connections registered.\n[Add Connection](command:kdb.connections.add)" } ], "menus": { @@ -790,7 +830,6 @@ "group": "resultsPanel" } ], - "view/title/create": [], "view/title": [ { "command": "kdb.connections.add", @@ -962,66 +1001,73 @@ ], "editor/title/run": [ { - "command": "kdb.execute.fileQuery", - "group": "q@0", - "when": "editorLangId == q" + "command": "kdb.scratchpad.python.run.file", + "group": "p1@0", + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { - "command": "kdb.execute.selectedQuery", - "group": "q@1", - "when": "editorLangId == q" + "command": "kdb.scratchpad.python.run", + "group": "p1@1", + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { - "command": "kdb.execute.block", - "group": "q@2", - "when": "editorLangId == q" + "command": "kdb.execute.fileQuery", + "group": "q1@0", + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { - "command": "kdb.execute.terminal.run", - "group": "q@3", - "when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)" + "command": "kdb.execute.selectedQuery", + "group": "q1@1", + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { - "command": "kdb.scratchpad.python.run.file", - "group": "q@0", - "when": "editorLangId == python" + "command": "kdb.execute.block", + "group": "q1@2", + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { - "command": "kdb.scratchpad.python.run", - "group": "q@1", - "when": "editorLangId == python" + "command": "kdb.file.populateScratchpad", + "group": "q2@0", + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" } ], "editor/context": [ { "command": "kdb.execute.fileQuery", "group": "q@0", - "when": "editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { "command": "kdb.execute.selectedQuery", "group": "q@1", - "when": "editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { "command": "kdb.execute.block", "group": "q@2", - "when": "editorLangId == q" + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { - "command": "kdb.execute.terminal.run", - "group": "q@3", - "when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)" + "command": "kdb.file.populateScratchpad", + "group": "q2@0", + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" }, { "command": "kdb.scratchpad.python.run.file", - "group": "q@0", - "when": "editorLangId == python" + "group": "p@0", + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { "command": "kdb.scratchpad.python.run", - "group": "q@1", - "when": "editorLangId == python" + "group": "p@1", + "when": "resourceFilename =~ /\\.(?:py)$/i" + } + ], + "notebook/toolbar": [ + { + "command": "kdb.file.pickConnection", + "when": "resourceExtname == .kxnb", + "group": "navigation/execute@-1" } ], "explorer/context": [ @@ -1076,9 +1122,13 @@ "url": "https://github.com/KxSystems/kx-vscode/issues" }, "homepage": "https://kx.com", + "config": { + "ui_test_vscode_version": "1.102.0" + }, "scripts": { "build": "npm run -S esbuild-base -- --sourcemap", - "coverage": "tsc -p ./test && node ./out/test/runTest.js --coverage", + "coverage_old": "npm run test -- --coverage", + "coverage": "c8 -o coverage-reports -r lcov -r cobertura -r html npm run test", "esbuild-base": "rimraf out && node ./esbuild.js", "fmt": "prettier --write \"src/**/*.ts\"&& npm run test -- --fix", "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", @@ -1087,9 +1137,10 @@ "preui-test": "rimraf out-test .test-extensions", "preui-test-cmd": "tsc --outdir out-test -p ./test", "publish": "npx vsce publish", - "test": "tsc -p ./test && node ./out/test/runTest.js", + "pretest": "rimraf out-test && tsc --outdir out-test -p ./test", + "test": "node ./out-test/test/runTest.js", "ui-test": "npm run ui-test-cmd -- ./out-test/test/ui/**/*.test.js", - "ui-test-cmd": "extest setup-and-run --code_version 1.99.3 --code_settings ./test/ui/fixtures/settings.json --extensions_dir ./.test-extensions --storage ./.test-folder -m ./test/ui/fixtures/mocha.json", + "ui-test-cmd": "extest setup-and-run --code_version $npm_package_config_ui_test_vscode_version --code_settings ./test/ui/fixtures/settings.json --extensions_dir ./.test-extensions --storage ./.test-folder -m ./test/ui/fixtures/mocha.json", "vscode:prepublish": "npm run -S esbuild-base -- --minify --keep-names", "watch": "npm run -S esbuild-base -- --sourcemap --watch" }, @@ -1119,17 +1170,18 @@ "@typescript-eslint/eslint-plugin": "^8.34.1", "@typescript-eslint/parser": "^8.34.1", "@vscode/test-electron": "^2.5.2", + "c8": "^10.1.3", "esbuild": "^0.25.5", "eslint": "^9.29.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.32.0", "eslint-plugin-license-header": "^0.8.0", "eslint-plugin-unused-imports": "^4.1.4", - "glob": "^8.1.0", + "glob": "^11.0.3", "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", + "istanbul-lib-instrument": "^6.0.1", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "jsdom": "^26.1.0", "lit": "^3.3.0", @@ -1142,19 +1194,18 @@ "sinon": "^21.0.0", "typescript": "^5.8.3", "typescript-eslint": "^8.34.1", - "vscode-dts": "^0.3.3", - "vscode-extension-tester": "^8.15.0", + "vscode-extension-tester": "^8.16.2", "vscode-languageclient": "^9.0.1", "vscode-languageserver": "^9.0.1", - "vscode-languageserver-textdocument": "^1.0.12", - "vscode-test": "^1.6.1" + "vscode-languageserver-textdocument": "^1.0.12" }, "dependencies": { "@ag-grid-community/core": "^32.3.5", - "@vscode/webview-ui-toolkit": "^1.4.0", + "@vscode/dts": "^0.4.1", + "@vscode/extension-telemetry": "^1.0.0", "@windozer/node-q": "^2.6.0", "ag-grid-community": "^33.3.2", - "axios": "^1.10.0", + "axios": "^1.11.0", "chevrotain": "^10.5.0", "extract-zip": "^2.0.1", "fs-extra": "^11.3.0", @@ -1166,8 +1217,7 @@ "node-q": "^2.7.0", "pick-port": "^2.0.1", "semver": "^7.7.2", - "vscode-extension-telemetry": "^0.4.5", - "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0" } } diff --git a/server/src/linter/checks.ts b/server/src/linter/checks.ts index 43829d77f..8f1b88c62 100644 --- a/server/src/linter/checks.ts +++ b/server/src/linter/checks.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/server/src/linter/index.ts b/server/src/linter/index.ts index 3465e2688..d09f8c81e 100644 --- a/server/src/linter/index.ts +++ b/server/src/linter/index.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/server/src/linter/rules.ts b/server/src/linter/rules.ts index e8dde6b6c..992f12fb8 100644 --- a/server/src/linter/rules.ts +++ b/server/src/linter/rules.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/server/src/parser/checks.ts b/server/src/parser/checks.ts index 50cdb7a22..0478d0f66 100644 --- a/server/src/parser/checks.ts +++ b/server/src/parser/checks.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/server/src/parser/index.ts b/server/src/parser/index.ts index 669c426a5..d258ae2c4 100644 --- a/server/src/parser/index.ts +++ b/server/src/parser/index.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/server/src/parser/keywords.ts b/server/src/parser/keywords.ts index 4cc2dd3fe..e665e1003 100644 --- a/server/src/parser/keywords.ts +++ b/server/src/parser/keywords.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/server/src/parser/language.ts b/server/src/parser/language.ts index a415263c6..51277b283 100644 --- a/server/src/parser/language.ts +++ b/server/src/parser/language.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -50,6 +50,7 @@ import { TestBlock, TestLambdaBlock, Cond, + CutDrop, } from "./tokens"; const includes = [ @@ -184,19 +185,19 @@ const repository = { patterns: [ { name: "keyword.control.q", - match: `${_(Control)}\\b`, + match: `${__(Control)}`, }, { name: "keyword.other.reserved.q", - match: `${_(Reserved)}\\b`, + match: `${__(Reserved)}`, }, { name: "keyword.other.q", - match: `\\b${_(Keyword)}\\b`, + match: `${__(Keyword)}`, }, { name: "variable.other.q", - match: `${_(Identifier)}\\b`, + match: `${__(Identifier)}`, }, ], }, @@ -218,6 +219,10 @@ const repository = { name: "punctuation.assignment.q", match: _(DoubleColon), }, + { + name: "keyword.operator.arithmetic.q", + match: _(CutDrop), + }, { name: "keyword.operator.arithmetic.q", match: _(Operator), @@ -260,10 +265,14 @@ function _(token: TokenType | RegExp) { return options ? `(?${options})${result}` : result; } +function __(token: TokenType | RegExp) { + return `(?=|<>|[>=<~])/, }); +export const CutDrop = createToken({ + name: "CutDrop", + pattern: /(?; declare private cached: Map; declare public documents: TextDocuments; constructor(connection: Connection, params: InitializeParams) { this.connection = connection; this.params = params; - this.settings = defaultSettings; + this.opened = new Set(); this.cached = new Map(); this.documents = new TextDocuments(TextDocument); this.documents.listen(this.connection); + this.documents.onDidOpen(this.onDidOpen.bind(this)); this.documents.onDidClose(this.onDidClose.bind(this)); this.documents.onDidChangeContent(this.onDidChangeContent.bind(this)); this.connection.onDocumentSymbol(this.onDocumentSymbol.bind(this)); @@ -119,9 +111,6 @@ export default class QLangServer { this.connection.onDefinition(this.onDefinition.bind(this)); this.connection.onRenameRequest(this.onRenameRequest.bind(this)); this.connection.onCompletion(this.onCompletion.bind(this)); - this.connection.onDidChangeWatchedFiles( - this.onDidChangeWatchedFiles.bind(this), - ); this.connection.languages.callHierarchy.onPrepare( this.onPrepareCallHierarchy.bind(this), ); @@ -134,8 +123,8 @@ export default class QLangServer { this.connection.languages.semanticTokens.on( this.onSemanticTokens.bind(this), ); - this.connection.onDidChangeConfiguration( - this.onDidChangeConfiguration.bind(this), + this.connection.onDidChangeWatchedFiles( + this.onDidChangeWatchedFiles.bind(this), ); this.connection.onRequest( "kdb.qls.expressionRange", @@ -150,7 +139,7 @@ export default class QLangServer { public capabilities(): ServerCapabilities { return { - textDocumentSync: TextDocumentSyncKind.Full, + textDocumentSync: TextDocumentSyncKind.Incremental, documentSymbolProvider: true, referencesProvider: true, definitionProvider: true, @@ -168,45 +157,77 @@ export default class QLangServer { }; } - public setSettings(settings: LSPAny) { - this.settings = { - debug: settings.debug_parser || false, - linting: settings.linting || false, - refactoring: settings.refactoring || "Workspace", - }; + private async getDebug(uri: string): Promise { + const res = await this.connection.workspace.getConfiguration({ + scopeUri: uri, + section: "kdb.debug", + }); + return res ?? false; } - public onDidChangeConfiguration({ settings }: DidChangeConfigurationParams) { - if ("kdb" in settings) { - this.setSettings(settings.kdb); - } + private async getLinting(uri: string): Promise { + const res = await this.connection.workspace.getConfiguration({ + scopeUri: uri, + section: "kdb.linting", + }); + return res ?? false; + } + + private async getRefactoring(uri: string): Promise<"Workspace" | "Window"> { + const res = await this.connection.workspace.getConfiguration({ + scopeUri: uri, + section: "kdb.refactoring", + }); + return res ?? "Workspace"; + } + + private async getConnectionMap( + uri: string, + ): Promise<{ [key: string]: string }> { + const res = await this.connection.workspace.getConfiguration({ + scopeUri: uri, + section: "kdb.connectionMap", + }); + return res ?? {}; + } + + notify( + message: string, + kind: MessageKind, + options: { + logger?: string; + params?: any; + } = {}, + telemetry?: string | boolean, + ) { + this.connection.sendNotification("notify", { + message, + kind, + options, + telemetry, + }); } - /* istanbul ignore next */ public onDidChangeWatchedFiles({ changes }: DidChangeWatchedFilesParams) { - try { - this.parseFiles( - changes.reduce((matches, change) => { - if (change.type === FileChangeType.Deleted) { - this.cached.delete(change.uri); - } else { - matches.push(fileURLToPath(change.uri)); - } - return matches; - }, [] as string[]), - ); - } catch (error) { - this.connection.window.showErrorMessage(`${error}`); + for (const change of changes) { + this.cached.delete(change.uri); } } - public onDidChangeContent({ + public onDidOpen({ document }: TextDocumentChangeEvent) { + this.opened.add(document.uri); + } + + public async onDidChangeContent({ document, }: TextDocumentChangeEvent) { const uri = document.uri; - this.cached.delete(uri); - if (this.settings.linting) { - const diagnostics = lint(this.parse(document)).map((item) => + + if (this.opened.has(uri)) this.opened.delete(uri); + else this.cached.delete(uri); + + if (await this.getLinting(uri)) { + const diagnostics = lint(await this.parse(uri)).map((item) => Diagnostic.create( rangeFromToken(item.token), item.message, @@ -223,11 +244,11 @@ export default class QLangServer { this.connection.sendDiagnostics({ uri: document.uri, diagnostics: [] }); } - public onDocumentSymbol({ + public async onDocumentSymbol({ textDocument, - }: DocumentSymbolParams): DocumentSymbol[] { - const tokens = this.parse(textDocument); - if (this.settings.debug) { + }: DocumentSymbolParams): Promise { + const tokens = await this.parse(textDocument.uri); + if (await this.getDebug(textDocument.uri)) { return tokens.map((token) => createDebugSymbol(token)); } return tokens @@ -239,10 +260,13 @@ export default class QLangServer { .map((token) => createSymbol(token, tokens)); } - public onReferences({ textDocument, position }: ReferenceParams): Location[] { - const tokens = this.parse(textDocument); + public async onReferences({ + textDocument, + position, + }: ReferenceParams): Promise { + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); - return this.context({ uri: textDocument.uri, tokens }) + return (await this.context(textDocument.uri)) .map((document) => findIdentifiers(FindKind.Reference, document.tokens, source).map( (token) => Location.create(document.uri, rangeFromToken(token)), @@ -251,13 +275,13 @@ export default class QLangServer { .flat(); } - public onDefinition({ + public async onDefinition({ textDocument, position, - }: DefinitionParams): Location[] { - const tokens = this.parse(textDocument); + }: DefinitionParams): Promise { + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); - return this.context({ uri: textDocument.uri, tokens }) + return (await this.context(textDocument.uri)) .map((document) => findIdentifiers(FindKind.Definition, document.tokens, source).map( (token) => Location.create(document.uri, rangeFromToken(token)), @@ -266,15 +290,14 @@ export default class QLangServer { .flat(); } - public onRenameRequest({ + public async onRenameRequest({ textDocument, position, newName, - }: RenameParams): WorkspaceEdit { - const tokens = this.parse(textDocument); + }: RenameParams): Promise { + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); - const all = this.settings.refactoring === "Workspace"; - return this.context({ uri: textDocument.uri, tokens }, all).reduce( + return (await this.context(textDocument.uri)).reduce( (edit, document) => { const refs = findIdentifiers(FindKind.Rename, document.tokens, source); if (refs.length > 0) { @@ -292,13 +315,13 @@ export default class QLangServer { ); } - public onCompletion({ + public async onCompletion({ textDocument, position, - }: CompletionParams): CompletionItem[] { - const tokens = this.parse(textDocument); + }: CompletionParams): Promise { + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); - return this.context({ uri: textDocument.uri, tokens }) + return (await this.context(textDocument.uri)) .map((document) => findIdentifiers(FindKind.Completion, document.tokens, source).map( (token) => { @@ -316,11 +339,11 @@ export default class QLangServer { .flat(); } - public onExpressionRange({ + public async onExpressionRange({ textDocument, position, }: TextDocumentPositionParams) { - const tokens = this.parse(textDocument); + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); if (!source || !source.exprs) { return null; @@ -328,11 +351,11 @@ export default class QLangServer { return expressionToRange(tokens, source.exprs); } - public onParameterCache({ + public async onParameterCache({ textDocument, position, }: TextDocumentPositionParams) { - const tokens = this.parse(textDocument); + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); if (!source) { return null; @@ -370,11 +393,11 @@ export default class QLangServer { }; } - public onSelectionRanges({ + public async onSelectionRanges({ textDocument, positions, - }: SelectionRangeParams): SelectionRange[] { - const tokens = this.parse(textDocument); + }: SelectionRangeParams): Promise { + const tokens = await this.parse(textDocument.uri); const ranges: SelectionRange[] = []; for (const position of positions) { @@ -386,11 +409,11 @@ export default class QLangServer { return ranges; } - public onPrepareCallHierarchy({ + public async onPrepareCallHierarchy({ textDocument, position, - }: CallHierarchyPrepareParams): CallHierarchyItem[] { - const tokens = this.parse(textDocument); + }: CallHierarchyPrepareParams): Promise { + const tokens = await this.parse(textDocument.uri); const source = positionToToken(tokens, position); if (source && assignable(source)) { return [ @@ -407,13 +430,13 @@ export default class QLangServer { return []; } - public onIncomingCallsCallHierarchy({ + public async onIncomingCallsCallHierarchy({ item, - }: CallHierarchyIncomingCallsParams): CallHierarchyIncomingCall[] { - const tokens = this.parse({ uri: item.uri }); + }: CallHierarchyIncomingCallsParams): Promise { + const tokens = await this.parse(item.uri); const source = positionToToken(tokens, item.range.end); return item.data - ? this.context({ uri: item.uri, tokens }) + ? (await this.context(item.uri)) .map((document) => findIdentifiers(FindKind.Reference, document.tokens, source) .filter((token) => !assigned(token)) @@ -435,13 +458,13 @@ export default class QLangServer { : []; } - public onOutgoingCallsCallHierarchy({ + public async onOutgoingCallsCallHierarchy({ item, - }: CallHierarchyOutgoingCallsParams): CallHierarchyOutgoingCall[] { - const tokens = this.parse({ uri: item.uri }); + }: CallHierarchyOutgoingCallsParams): Promise { + const tokens = await this.parse(item.uri); const source = positionToToken(tokens, item.range.end); return item.data - ? this.context({ uri: item.uri, tokens }) + ? (await this.context(item.uri)) .map((document) => findIdentifiers(FindKind.Reference, document.tokens, source) .filter((token) => inLambda(token) && !assigned(token)) @@ -462,10 +485,10 @@ export default class QLangServer { : []; } - public onSemanticTokens({ + public async onSemanticTokens({ textDocument, - }: SemanticTokensParams): SemanticTokens { - const tokens = this.parse({ uri: textDocument.uri }); + }: SemanticTokensParams): Promise { + const tokens = await this.parse(textDocument.uri); const result = { data: [] } as SemanticTokens; let range: Range = Range.create(0, 0, 0, 0); let line = 0; @@ -492,71 +515,89 @@ export default class QLangServer { return result; } - /* istanbul ignore next */ - public scan() { - const folders = this.params.workspaceFolders; - if (folders) { - try { - for (const folder of folders) { - this.parseFiles( - glob("**/*.{q,quke}", { - dot: true, - absolute: true, - nodir: true, - follow: false, - ignore: ["**/node_modules/**/*", "**/build/**/*"], - cwd: fileURLToPath(folder.uri), - }), - ); + private async parse(uri: string): Promise { + let tokens = this.cached.get(uri); + if (!tokens) { + const document = this.documents.get(uri); + let text: string; + if (document) { + text = document.getText(); + } else { + const path = fileURLToPath(uri); + try { + text = await readFile(path, { encoding: "utf8" }); + } catch (error) { + this.notify(`Unable to read '${path}'.`, MessageKind.DEBUG, { + logger, + params: `${error}`, + }); + text = ""; } - } catch (error) { - this.connection.window.showErrorMessage(`${error}`); } + tokens = text ? parse(text) : []; + this.cached.set(uri, tokens); } + return tokens; } - /* istanbul ignore next */ - private parseFiles(matches: string[]) { - for (const match of matches) { - const file = readFileSync(match, "utf-8"); - this.cached.set(pathToFileURL(match).toString(), parse(file)); + private async related(uri: string): Promise { + let res = [uri]; + const folders = await this.connection.workspace.getWorkspaceFolders(); + + if (!folders) { + return res; } - } - private parse(textDocument: TextDocumentIdentifier): Token[] { - const uri = textDocument.uri; - let tokens = this.cached.get(uri); - if (!tokens) { - const document = this.documents.get(uri); - if (!document) { - return []; + let workspace: string | undefined; + + for (const folder of folders) { + if (uri.replace(folder.uri, "").startsWith("/")) { + workspace = folder.uri; + break; } - tokens = parse(document.getText()); - this.cached.set(uri, tokens); } - return tokens; + + if (!workspace) { + return res; + } + + const map = await this.getConnectionMap(uri); + const connections = new Map(); + let current: string | undefined; + + for (const key of Object.keys(map)) { + const target = map[key]; + let uris = connections.get(target); + + if (!uris) { + uris = []; + connections.set(target, uris); + } + uris.push(workspace + "/" + key); + + if (uri.endsWith(key)) current = target; + } + + if (current) { + res = connections.get(current) || res; + } + + return res; } - private context({ uri, tokens }: Tokenized, all = true): Tokenized[] { - if (all) { - this.documents.all().forEach((document) => { - const path = document.uri.startsWith("file://") - ? fileURLToPath(document.uri) - : ""; - this.cached.set( - path ? pathToFileURL(path).toString() : document.uri, - document.uri === uri ? tokens : parse(document.getText()), - ); - }); - return Array.from(this.cached.entries(), (entry) => ({ - uri: entry[0], - tokens: entry[1], - })); + private async context(uri: string): Promise { + const res: Tokenized[] = []; + const refactoring = await this.getRefactoring(uri); + if (refactoring === "Workspace") { + for (const item of await this.related(uri)) { + res.push({ uri: item, tokens: await this.parse(item) }); + } + } else if (refactoring === "Window") { + for (const item of this.documents.all()) { + res.push({ uri: item.uri, tokens: await this.parse(item.uri) }); + } } - return this.documents.all().map((document) => ({ - uri: document.uri, - tokens: document.uri === uri ? tokens : parse(document.getText()), - })); + return res; } } diff --git a/server/src/server.ts b/server/src/server.ts index 5d19d4442..b99db879c 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -35,13 +35,6 @@ connection.onInitialized(() => { connection.client.register(DidChangeConfigurationNotification.type, { section: "kdb", }); - - connection.workspace.getConfiguration("kdb").then((settings) => { - if (server) { - server.setSettings(settings); - server.scan(); - } - }); }); connection.listen(); diff --git a/sonar-project.properties b/sonar-project.properties index 595113af1..95eb6cd72 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,6 +3,10 @@ sonar.qualitygate.wait=true sonar.qualitygate.timeout=1000 sonar.sources=src,server sonar.tests=test +sonar.test.inclusions=test/**/*.ts +sonar.test.exclusions=**/*.js,**/node_modules/** sonar.javascript.lcov.reportPaths=lcov.info -sonar.coverage.exclusions=server/src/utils/parserUtils.ts,src/ipc/**,src/models/**,src/extension.ts,src/classes/**,src/commands/installTools.ts,src/utils/cpUtils.ts +sonar.typescript.lcov.reportPaths=lcov.info +sonar.coverage.exclusions=server/src/utils/parserUtils.ts,src/ipc/**,src/models/**,src/extension.ts,src/classes/**,src/commands/installTools.ts,src/utils/cpUtils.ts,test/**,**/*.test.ts,**/*.spec.ts sonar.cpd.exclusions=src/services/completionProvider.ts,src/extension.ts +sonar.sourceEncoding=UTF-8 \ No newline at end of file diff --git a/src/classes/insightsConnection.ts b/src/classes/insightsConnection.ts index c6b1dafb1..741203886 100644 --- a/src/classes/insightsConnection.ts +++ b/src/classes/insightsConnection.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -14,7 +14,6 @@ import axios, { AxiosRequestConfig } from "axios"; import { jwtDecode } from "jwt-decode"; import * as url from "url"; -import { ProgressLocation, window } from "vscode"; import { ext } from "../extensionVariables"; import { isCompressed, uncompress } from "../ipc/c"; @@ -39,18 +38,16 @@ import { InsightsNode } from "../services/kdbTreeProvider"; import { isBaseVersionGreaterOrEqual, invalidUsernameJWT, - kdbOutputLog, tokenUndefinedError, } from "../utils/core"; import { convertTimeToTimestamp } from "../utils/dataSource"; -import { - generateQSqlBody, - handleScratchpadTableRes, - handleWSResults, -} from "../utils/queryUtils"; -import { Telemetry } from "../utils/telemetryClient"; +import { MessageKind, notify } from "../utils/notifications"; +import { handleScratchpadTableRes, handleWSResults } from "../utils/queryUtils"; +import { normalizeAssemblyTarget } from "../utils/shared"; import { retrieveUDAtoCreateReqBody } from "../utils/uda"; +const logger = "insightsConnection"; + const customHeadersOctet = { Accept: "application/octet-stream", "Content-Type": "application/json", @@ -83,9 +80,8 @@ export class InsightsConnection { this.connected = token ? true : false; if (token) { await this.getConfig(); - await this.getMeta(); await this.getApiConfig(); - await this.getScratchpadQuery("", undefined, false, true); + await this.getMeta(); } }); return this.connected; @@ -153,9 +149,10 @@ export class InsightsConnection { public returnMetaObject(metaType: MetaInfoType): string { if (!this.meta) { - kdbOutputLog( + notify( `Meta data is undefined for connection ${this.connLabel}`, - "ERROR", + MessageKind.ERROR, + { logger }, ); return ""; } @@ -182,7 +179,9 @@ export class InsightsConnection { objectToReturn = this.meta.payload.rc; break; default: - kdbOutputLog(`Invalid meta type: ${metaType}`, "ERROR"); + notify(`Invalid meta type: ${metaType}`, MessageKind.ERROR, { + logger, + }); return ""; } @@ -190,18 +189,29 @@ export class InsightsConnection { } public async getMeta(): Promise { - if (this.connected) { + if (this.connected && this.connEndpoints) { const metaUrl = new url.URL( - ext.insightsServiceGatewayUrls.meta, + this.connEndpoints?.serviceGateway.meta, this.node.details.server, ); - const options = await this.getOptions(); + const options = await this.getOptions( + false, + undefined, + "POST", + metaUrl.toString(), + { advanced: true }, + ); if (!options) { return undefined; } - const metaResponse = await axios.post(metaUrl.toString(), {}, options); + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + + const metaResponse = await axios(options); const meta: MetaObject = metaResponse.data; this.meta = meta; return meta.payload; @@ -229,9 +239,16 @@ export class InsightsConnection { if (options === undefined) { return undefined; } + + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + const configResponse = await axios(options); this.apiConfig = configResponse.data; + this.defineEndpoints(); } } @@ -252,11 +269,15 @@ export class InsightsConnection { return undefined; } + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + const configResponse = await axios(options); this.config = configResponse.data; this.getInsightsVersion(); - this.defineEndpoints(); } } @@ -270,47 +291,76 @@ export class InsightsConnection { } public defineEndpoints() { - const baseEndpoints = { - scratchpad: { - scratchpad: "servicebroker/scratchpad/display", - import: "servicebroker/scratchpad/import/data", - importSql: "servicebroker/scratchpad/import/sql", - importQsql: "servicebroker/scratchpad/import/qsql", - importUDA: "servicebroker/scratchpad/import/uda", - reset: "scratchpadmanager/reset", - }, - serviceGateway: { - meta: "servicegateway/meta", - data: "servicegateway/data", - sql: "servicegateway/qe/sql", - qsql: "servicegateway/qe/qsql", - udaBase: "servicegateway/", - }, + const scratchpadEndpoints = { + scratchpad: "scratchpadmanager/scratchpad/display", + import: "scratchpadmanager/scratchpad/import/data", + importSql: "scratchpadmanager/scratchpad/import/sql", + importQsql: "scratchpadmanager/scratchpad/import/qsql", + importUDA: "scratchpadmanager/scratchpad/import/uda", + reset: "scratchpadmanager/reset", }; - const updatedEndpoints = { - scratchpad: { - scratchpad: "scratchpadmanager/scratchpad/display", - import: "scratchpadmanager/scratchpad/import/data", - importSql: "scratchpadmanager/scratchpad/import/sql", - importQsql: "scratchpadmanager/scratchpad/import/qsql", - importUDA: "scratchpadmanager/scratchpad/import/uda", - reset: "scratchpadmanager/reset", - }, - serviceGateway: { - meta: "servicegateway/meta", - data: "servicegateway/data", - sql: "servicegateway/qe/sql", - qsql: "servicegateway/qe/qsql", - udaBase: "servicegateway/", - }, + const getVersionGroup = (): string => { + if ( + !this.insightsVersion || + !isBaseVersionGreaterOrEqual(this.insightsVersion, 1.11) + ) { + return "pre-1.11"; + } + if (!isBaseVersionGreaterOrEqual(this.insightsVersion, 1.14)) { + return "v1.11-1.13"; + } + return "v1.14+"; }; - this.connEndpoints = - this.insightsVersion && - isBaseVersionGreaterOrEqual(this.insightsVersion, 1.11) - ? updatedEndpoints - : baseEndpoints; + const versionGroup = getVersionGroup(); + let serviceGatewayEndpoints; + const qePrefix = this.apiConfig?.queryEnvironmentsEnabled ? "qe/" : ""; + + switch (versionGroup) { + case "pre-1.11": + scratchpadEndpoints.scratchpad = "servicebroker/scratchpad/display"; + scratchpadEndpoints.import = "servicebroker/scratchpad/import/data"; + scratchpadEndpoints.importSql = "servicebroker/scratchpad/import/sql"; + scratchpadEndpoints.importQsql = "servicebroker/scratchpad/import/qsql"; + scratchpadEndpoints.importUDA = "servicebroker/scratchpad/import/uda"; + scratchpadEndpoints.reset = "scratchpadmanager/reset"; + + serviceGatewayEndpoints = { + meta: "servicegateway/meta", + data: "servicegateway/data", + sql: "servicegateway/qe/sql", + qsql: "servicegateway/qe/qsql", + udaBase: "servicegateway/", + }; + break; + + case "v1.11-1.13": + serviceGatewayEndpoints = { + meta: `servicegateway/${qePrefix}api/v3/meta`, + data: "servicegateway/data", + sql: `servicegateway/${qePrefix}sql`, + qsql: `servicegateway/${qePrefix}qsql`, + udaBase: "servicegateway/", + }; + break; + + case "v1.14+": + default: + serviceGatewayEndpoints = { + meta: `servicegateway/${qePrefix}api/v3/meta`, + data: "servicegateway/data", + sql: `servicegateway/${qePrefix}kxi/sql`, + qsql: `servicegateway/${qePrefix}kxi/qsql`, + udaBase: "servicegateway/", + }; + break; + } + + this.connEndpoints = { + scratchpad: scratchpadEndpoints, + serviceGateway: serviceGatewayEndpoints, + }; } public retrieveEndpoints( @@ -351,6 +401,61 @@ export class InsightsConnection { return new url.URL(endpoint, this.node.details.server).toString(); } + public generateQSqlBody( + query: string, + assemblyTarget: string, + version?: number, + ) { + const [plainAssembly, tier, plainDap] = + normalizeAssemblyTarget(assemblyTarget).split(/\s+/); + + const assembly = this.retrieveCorrectAssemblyName(plainAssembly); + const dap = this.retrieveCorrectDAPName(plainDap, tier); + + if (version && isBaseVersionGreaterOrEqual(version, 1.13)) { + return { + query, + scope: { + affinity: "soft", + assembly, + tier: dap ? undefined : tier, + dap: dap, + }, + }; + } + + return { query, assembly, tier, dap }; + } + + public retrieveCorrectAssemblyName( + plainAssembly: string, + ): string | undefined { + if (this.meta?.payload?.dap) { + const foundDap = this.meta.payload.dap.find((dap: any) => + dap.assembly?.startsWith(plainAssembly), + ); + + return foundDap ? foundDap.assembly : undefined; + } else { + return; + } + } + + public retrieveCorrectDAPName( + plainDAP: string | undefined, + tier: string | undefined, + ): string | undefined { + if (this.meta?.payload?.dap && plainDAP) { + const foundDap = this.meta.payload.dap.find( + (dap: any) => dap.dap?.startsWith(plainDAP) && dap.instance === tier, + ); + + return foundDap ? foundDap.dap : undefined; + } else { + return; + } + } + public async getDatasourceQuery( type: DataSourceTypes, body: any, @@ -380,54 +485,47 @@ export class InsightsConnection { return undefined; } options.responseType = "arraybuffer"; - const results = await window.withProgress( - { - location: ProgressLocation.Notification, - cancellable: true, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog(`User cancelled the Datasource Run.`, "WARNING"); - }); - - progress.report({ message: "Query executing..." }); - return await axios(options) - .then((response: any) => { - kdbOutputLog( - `[Datasource RUN] Status: ${response.status}.`, - "INFO", - ); - if (isCompressed(response.data)) { - response.data = uncompress(response.data); - } - return { - error: "", - arrayBuffer: response.data.buffer - ? response.data.buffer - : response.data, - }; - }) - .catch((error: any) => { - kdbOutputLog( - `[Datasource RUN] Status: ${error.response.status}.`, - "ERROR", - ); - return { - error: { buffer: error.response.data }, - arrayBuffer: undefined, - }; - }); - }, - ); - return results; + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + + return await axios(options) + .then((response: any) => { + notify( + `Datasource execution status: ${response.status}.`, + MessageKind.DEBUG, + { logger }, + ); + if (isCompressed(response.data)) { + response.data = uncompress(response.data); + } + return { + error: "", + arrayBuffer: response.data.buffer + ? response.data.buffer + : response.data, + }; + }) + .catch((error: any) => { + notify( + `Datasource execution status: ${error.response.status}.`, + MessageKind.DEBUG, + { logger, params: error }, + ); + return { + error: { buffer: error.response.data }, + arrayBuffer: undefined, + }; + }); } } public async importScratchpad( variableName: string, params: DataSourceFiles, - qeEnabled?: boolean, + silent?: boolean, ): Promise { let dsTypeString = ""; if (this.connected && this.connEndpoints) { @@ -454,11 +552,10 @@ export class InsightsConnection { break; } case DataSourceTypes.QSQL: { - body.params = generateQSqlBody( + body.params = this.generateQSqlBody( params.dataSource.qsql.query, params.dataSource.qsql.selectedTarget, this.insightsVersion, - qeEnabled, ); coreUrl = this.connEndpoints.scratchpad.importQsql; @@ -470,10 +567,10 @@ export class InsightsConnection { const udaReqBody = await retrieveUDAtoCreateReqBody(uda, this); if (udaReqBody.error) { - kdbOutputLog( - `[SCRATCHPAD] Error occurred while creating UDA request body: ${udaReqBody.error}`, - "ERROR", - ); + notify("Unable to create UDA request body.", MessageKind.ERROR, { + logger, + params: udaReqBody.error, + }); return; } body.params = udaReqBody.params; @@ -504,49 +601,35 @@ export class InsightsConnection { return; } - await window.withProgress( - { - location: ProgressLocation.Notification, - cancellable: false, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog(`User cancelled the scratchpad import.`, "WARNING"); - }); - - progress.report({ message: "Populating scratchpad..." }); - - return await axios(options).then((response: any) => { - if (response.data.error) { - kdbOutputLog( - `[SCRATCHPAD] Error occured while populating scratchpad: ${response.data.errorMsg}`, - "ERROR", - ); - Telemetry.sendEvent( + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + + return await axios(options).then((response: any) => { + if (response.data.error) { + notify( + "Error occured while populating scratchpad.", + silent ? MessageKind.DEBUG : MessageKind.ERROR, + { + logger, + params: { status: response.status }, + telemetry: "Datasource." + dsTypeString + ".Scratchpad.Populated.Errored", - ); - } else { - kdbOutputLog( - `Executed successfully, stored in ${variableName}.`, - "INFO", - ); - kdbOutputLog(`[SCRATCHPAD] Status: ${response.status}`, "INFO"); - kdbOutputLog( - `[SCRATCHPAD] Populated scratchpad with the following params: ${JSON.stringify( - body.params, - )}`, - "INFO", - ); - window.showInformationMessage( - `Executed successfully, stored in ${variableName}.`, - ); - Telemetry.sendEvent( - "Datasource." + dsTypeString + ".Scratchpad.Populated", - ); - } - }); - }, - ); + }, + ); + } else { + notify( + `Scratchpad variable (${variableName}) populated.`, + silent ? MessageKind.DEBUG : MessageKind.INFO, + { + logger, + params: { status: response.status }, + telemetry: "Datasource." + dsTypeString + ".Scratchpad.Populated", + }, + ); + } + }); } else { this.noConnectionOrEndpoints(); } @@ -576,8 +659,8 @@ export class InsightsConnection { if (this.meta.payload.api && Array.isArray(this.meta.payload.api)) { return this.meta.payload.api.some( - (apiItem: { api: string; custom: boolean }) => - apiItem.api === udaName && apiItem.custom === true, + (apiItem: { api: string; uda: boolean }) => + apiItem.api === udaName && apiItem.uda === true, ); } @@ -604,36 +687,29 @@ export class InsightsConnection { return; } - const udaResponse = await window.withProgress( - { - location: ProgressLocation.Notification, - cancellable: false, - }, - async (_progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog(`User cancelled the UDA execution.`, "WARNING"); - }); + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); - const udaRes = await axios(options).then((response: any) => { - if (response.data.error) { - return response.data; - } else { - kdbOutputLog(`[UDA] Status: ${response.status}`, "INFO"); - if (!response.data.error) { - if (isTableView) { - response.data = JSON.parse( - response.data.data, - ) as StructuredTextResults; - } - return response.data; - } - return response.data; - } + return await axios(options).then((response: any) => { + if (response.data.error) { + return response.data; + } else { + notify(`Status: ${response.status}`, MessageKind.DEBUG, { + logger, }); - return udaRes; - }, - ); - return udaResponse; + if (!response.data.error) { + if (isTableView) { + response.data = JSON.parse( + response.data.data, + ) as StructuredTextResults; + } + return response.data; + } + return response.data; + } + }); } else { this.noConnectionOrEndpoints(); } @@ -644,7 +720,6 @@ export class InsightsConnection { query: string, context?: string, isPython?: boolean, - isStarting?: boolean, isTableView?: boolean, ): Promise { if (this.connected && this.connEndpoints) { @@ -687,63 +762,51 @@ export class InsightsConnection { return; } - const spResponse = await window.withProgress( - { - location: ProgressLocation.Notification, - cancellable: false, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog(`User cancelled the scratchpad execution.`, "WARNING"); + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + + return await axios(options).then((response: any) => { + if (response.data.error) { + return response.data; + } else if (query === "") { + notify( + `Scratchpad created for connection: ${this.connLabel}.`, + MessageKind.DEBUG, + { logger }, + ); + } else { + notify(`Status: ${response.status}`, MessageKind.DEBUG, { + logger, }); - - if (isStarting) { - progress.report({ message: "Starting scratchpad..." }); - } else { - progress.report({ message: "Query is running..." }); - } - - const spRes = await axios(options).then((response: any) => { - if (response.data.error) { - return response.data; - } else if (query === "") { - kdbOutputLog( - `[SCRATCHPAD] scratchpad created for connection: ${this.connLabel}`, - "INFO", - ); - } else { - kdbOutputLog(`[SCRATCHPAD] Status: ${response.status}`, "INFO"); - if (!response.data.error) { - if (isTableView) { - if ( - /* TODO: Workaround for Python structuredText bug */ - !isPython && - this.insightsVersion && - isBaseVersionGreaterOrEqual(this.insightsVersion, 1.12) - ) { - response.data = JSON.parse( - response.data.data, - ) as StructuredTextResults; - } else { - const buffer = new Uint8Array( - response.data.data.map((x: string) => parseInt(x, 16)), - ).buffer; - - response.data.data = handleWSResults(buffer, isTableView); - response.data.data = handleScratchpadTableRes( - response.data.data, - ); - } - } - return response.data; + if (!response.data.error) { + if (isTableView) { + if ( + /* TODO: Workaround for Python structuredText bug */ + !isPython && + this.insightsVersion && + isBaseVersionGreaterOrEqual(this.insightsVersion, 1.12) + ) { + response.data = JSON.parse( + response.data.data, + ) as StructuredTextResults; + } else { + const buffer = new Uint8Array( + response.data.data.map((x: string) => parseInt(x, 16)), + ).buffer; + + response.data.data = handleWSResults(buffer, isTableView); + response.data.data = handleScratchpadTableRes( + response.data.data, + ); } - return response.data; } - }); - return spRes; - }, - ); - return spResponse; + return response.data; + } + return response.data; + } + }); } else { this.noConnectionOrEndpoints(); } @@ -768,43 +831,28 @@ export class InsightsConnection { return; } - return await window.withProgress( - { - location: ProgressLocation.Notification, - cancellable: false, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog(`User cancelled the scratchpad reset.`, "WARNING"); - return false; - }); - progress.report({ message: "Reseting scratchpad..." }); - const res = await axios(options) - .then((_response: any) => { - kdbOutputLog( - `[SCRATCHPAD] Executed successfully, scratchpad reset at ${this.connLabel} connection.`, - "INFO", - ); - window.showInformationMessage( - `Executed successfully, scratchpad reset at ${this.connLabel} connection.`, - ); - Telemetry.sendEvent("Scratchpad.Reseted"); - return true; - }) - .catch((_error: any) => { - kdbOutputLog( - `[SCRATCHPAD] Error occurred while resetting scratchpad in connection ${this.connLabel}, try again.`, - "ERROR", - ); - window.showErrorMessage( - "Error occurred while resetting scratchpad, try again.", - ); - return false; - }); - - return res; - }, - ); + notify("REST", MessageKind.DEBUG, { + logger, + params: { url: options.url }, + }); + + return await axios(options) + .then((_response: any) => { + notify( + `Scratchpad reset for ${this.connLabel} executed successfully.`, + MessageKind.INFO, + { logger, telemetry: "Scratchpad.Reseted" }, + ); + return true; + }) + .catch((_error: any) => { + notify( + `Error occurred while resetting scratchpad for ${this.connLabel}, try again.`, + MessageKind.ERROR, + { logger }, + ); + return false; + }); } else { this.noConnectionOrEndpoints(); return false; @@ -812,9 +860,10 @@ export class InsightsConnection { } public noConnectionOrEndpoints(): void { - kdbOutputLog( + notify( `No connection or endpoints defined for ${this.connLabel}`, - "ERROR", + MessageKind.ERROR, + { logger }, ); } } diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index f56c7c6d5..72b67312e 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,15 +11,21 @@ * specific language governing permissions and limitations under the License. */ +import { readFileSync } from "fs-extra"; import * as nodeq from "node-q"; -import { commands, window } from "vscode"; +import { join } from "path"; +import { commands } from "vscode"; import { ext } from "../extensionVariables"; import { QueryResult, QueryResultType } from "../models/queryResult"; -import { delay, kdbOutputLog } from "../utils/core"; +import { ServerObject } from "../models/serverObject"; +import { delay, getAutoFocusOutputOnEntrySetting } from "../utils/core"; import { convertStringToArray, handleQueryResults } from "../utils/execution"; +import { MessageKind, notify } from "../utils/notifications"; import { queryWrapper } from "../utils/queryUtils"; +const logger = "localConnection"; + export class LocalConnection { public connected: boolean; public connLabel: string; @@ -84,40 +90,41 @@ export class LocalConnection { ext.serverProvider.reload(); if (this.connLabel.endsWith("[local]")) { - window - .showErrorMessage( - `Connection to server ${this.options.host}:${this.options.port} failed.`, - "Start q process", - ) - .then((res) => { - if (res) { - commands.executeCommand( - "kdb.connections.localProcess.start", - ext.connectionsList.find( - (conn) => conn.label === this.connLabel, - ), - ); - } - }); + notify( + `Connection to server ${this.options.host}:${this.options.port} failed.`, + MessageKind.ERROR, + { logger, params: err }, + "Start q process", + ).then((res) => { + if (res) { + commands.executeCommand( + "kdb.connections.localProcess.start", + ext.connectionsList.find( + (conn) => conn.label === this.connLabel, + ), + ); + } + }); } else { - window.showErrorMessage( + notify( `Connection to server ${this.options.host}:${this.options.port} failed.`, + MessageKind.ERROR, + { logger, params: err }, ); } - kdbOutputLog( - `Connection to server ${this.options.host}:${this.options.port} failed! Details: ${err?.message}`, - "CONNECTION", - ); return; } conn.addListener("close", () => { commands.executeCommand("kdb.connections.disconnect", this.connLabel); - kdbOutputLog( + notify( `Connection closed: ${this.options.host}:${this.options.port}`, - "INFO", + MessageKind.DEBUG, + { logger }, ); - ext.outputChannel.show(); + if (getAutoFocusOutputOnEntrySetting()) { + ext.outputChannel.show(true); + } }); if (this.connection && this.connected) { @@ -127,6 +134,8 @@ export class LocalConnection { } else { this.onConnect(err, conn, callback); } + + this.connected = false; }); } @@ -238,7 +247,7 @@ export class LocalConnection { return result; } - public async executeQueryRaw(command: string): Promise { + public async executeQueryRaw(command: string): Promise { let result; let retryCount = 0; let error; @@ -249,7 +258,7 @@ export class LocalConnection { await delay(500); retryCount++; } - this.connection.k(command, (err: Error, res: string) => { + this.connection.k(command, (err: Error, res: any) => { if (err) { error = err; result = ""; @@ -269,6 +278,30 @@ export class LocalConnection { return result; } + public async loadServerObjects(): Promise { + if (!this.connection) { + notify( + "Please connect to a KDB instance to view the objects", + MessageKind.INFO, + ); + return new Array(); + } + const script = readFileSync( + ext.context.asAbsolutePath(join("resources", "list_mem.q")), + ).toString(); + const cc = "\n" + script + "(::)"; + const result = await this.executeQueryRaw(cc); + if (result !== undefined) { + const result2: ServerObject[] = (0, eval)(result); + const result3: ServerObject[] = result2.filter((item) => { + return ext.qNamespaceFilters.indexOf(item.name) === -1; + }); + return result3; + } else { + return new Array(); + } + } + private async waitForConnection(): Promise { let retryCount = 0; while (this.connection === undefined) { @@ -306,9 +339,10 @@ export class LocalConnection { '{[q] t:system"T";tm:@[{$[x>0;[system"T ",string x;1b];0b]};0;{0b}];r:$[tm;@[0;(q;::);{[tm; t; msgs] if[tm;system"T ",string t];\'msgs}[tm;t]];@[q;::;{\'x}]];if[tm;system"T ",string t];r}{do[1000;2+2];{@[{.z.ide.ns.r1:x;:.z.ide.ns.r1};x;{r:y;:r}[;x]]}({:x!{![sv[`;] each x cross `Tables`Functions`Variables; system each "afv" cross enlist[" "] cross enlist string x]} each x} [{raze x,.z.s\'[{x where{@[{1#get x};x;`]~1#.q}\'[x]}` sv\'x,\'key x]}`]),(enlist `.z)!flip (`.z.Tables`.z.Functions`.z.Variables)!(enlist 0#`;enlist `ac`bm`exit`pc`pd`pg`ph`pi`pm`po`pp`ps`pw`vs`ts`s`wc`wo`ws;enlist `a`b`e`f`h`i`k`K`l`o`q`u`w`W`x`X`n`N`p`P`z`Z`t`T`d`D`c`zd)}'; this.connection?.k(globalQuery, (err, result) => { if (err) { - window.showErrorMessage( - `Failed to retrieve kdb+ global variables: '${err.message}`, - ); + notify("Failed to retrieve kdb+ global variables.", MessageKind.ERROR, { + logger, + params: err, + }); return; } @@ -350,8 +384,10 @@ export class LocalConnection { const reservedQuery = ".Q.res"; this.connection?.k(reservedQuery, (err, result) => { if (err) { - window.showErrorMessage( - `Failed to retrieve kdb+ reserved keywords: '${err.message}`, + notify( + "Failed to retrieve kdb+ reserved keywords.", + MessageKind.ERROR, + { logger, params: err }, ); return; } diff --git a/src/classes/replConnection.ts b/src/classes/replConnection.ts new file mode 100644 index 000000000..39065089f --- /dev/null +++ b/src/classes/replConnection.ts @@ -0,0 +1,589 @@ +/* + * Copyright (c) 1998-2025 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 { ChildProcessWithoutNullStreams, spawn } from "node:child_process"; +import * as vscode from "vscode"; + +import { ext } from "../extensionVariables"; +import { + getAutoFocusOutputOnEntrySetting, + getQExecutablePath, +} from "../utils/core"; +import { normalizeQuery } from "../utils/queryUtils"; + +const ANSI = { + EMPTY: "", + SPACE: " ", + QUOTE: '"', + AT: "@", + CR: "\r", + CRLF: "\r\n", + DOWN: "\x1b[1B", + SAVE: "\x1b[s", + RESTORE: "\x1b[u", + ERASETOEND: "\x1b[0J", + LINESTART: "\x1b[0G", + FAINTON: "\x1b[2m", + FAINTOFF: "\x1b[22m", +}; + +const KEY = { + CR: "\r", + CTRLC: "\x03", + BS: "\b", + BSMAC: "\x7f", + DEL: "\x1b[3~", + UP: "\x1b[A", + DOWN: "\x1b[B", + LEFT: "\x1b[D", + RIGHT: "\x1b[C", + HOME: "\x1b[H", + HOMEMAC: "\x01", + END: "\x1b[F", + ENDMAC: "\x05", + ALTHOME: "\x1b[1;5A", + ALTEND: "\x1b[1;5B", + SHIFTUP: "\x1b[1;2A", + SHIFTDOWN: "\x1b[1;2B", + SHIFTLEFT: "\x1b[1;2D", + SHIFTRIGHT: "\x1b[1;2C", +}; + +const CTX = { + Q: "q", + K: "k", +}; + +const NS = { + Q: ',string system"d"', + K: ',$:."\\\\d"', +}; + +const CONF = { + TITLE: `KX ${ext.REPL}`, + PROMPT: ")", + MAX_INPUT: 80 * 40, +}; + +type Callable = () => void; + +export class ReplConnection { + private readonly history = new History(); + private readonly identity = crypto.randomUUID(); + private readonly token = new RegExp( + this.identity + + ANSI.AT + + "(\\d+)" + + ANSI.AT + + ".([0-9a-zA-Z_`]*)" + + `[${ANSI.CRLF}]*`, + "gs", + ); + + private readonly onDidWrite: vscode.EventEmitter; + private readonly decoder: TextDecoder; + private readonly terminal: vscode.Terminal; + private readonly process: ChildProcessWithoutNullStreams; + + private messages: string[] = []; + private buffer: string[] = []; + private input: string[] = []; + private executions?: Callable[] = []; + + private _context = CTX.Q; + private _namespace = ANSI.EMPTY; + private columns = 0; + private rows = 0; + private maxInputIndex = 0; + private inputIndex = 0; + + private serial = 0; + private executing = 0; + private exited = false; + + private constructor() { + this.onDidWrite = new vscode.EventEmitter(); + this.decoder = new TextDecoder("utf8"); + this.terminal = this.createTerminal(); + this.process = this.createProcess(); + this.connect(); + } + + private get context() { + return this._context; + } + + private set context(context: string) { + this._context = context; + this.updateMaxInputIndex(); + } + + private get namespace() { + return this._namespace; + } + + private set namespace(namespace: string) { + this._namespace = namespace; + this.updateMaxInputIndex(); + } + + private get visibleInputIndex() { + return this.input.length > this.maxInputIndex + ? this.maxInputIndex + : this.input.length; + } + + private get inputText() { + return this.input.join(ANSI.EMPTY); + } + + private createTerminal() { + return vscode.window.createTerminal({ + pty: { + open: this.open.bind(this), + close: this.close.bind(this), + setDimensions: this.setDimensions.bind(this), + handleInput: this.handleInput.bind(this), + onDidWrite: this.onDidWrite.event, + }, + name: CONF.TITLE, + }); + } + + private createProcess() { + return spawn(getQExecutablePath(), { + env: { ...process.env, QHOME: ext.REAL_QHOME }, + }); + } + + private connect() { + this.process.on("error", this.handleError.bind(this)); + this.process.on("close", this.handleClose.bind(this)); + this.process.stdout.on("data", this.handleOutput.bind(this)); + this.process.stdout.on("error", this.handleError.bind(this)); + this.process.stderr.on("data", this.handleErrorOutput.bind(this)); + this.process.stderr.on("error", this.handleError.bind(this)); + } + + private executeCommand(data: string) { + this.process.stdin.write(data + ANSI.CRLF); + } + + private sendDimensions() { + const LINES = process.env.LINES ?? this.rows.toString(); + let rows = parseInt(LINES.replace(/\D+/gs, "0") || "0"); + if (rows < 25) rows = 25; + if (rows > 500) rows = 500; + + const COLUMNS = process.env.COLUMNS ?? this.columns.toString(); + let columns = parseInt(COLUMNS.replace(/\D+/gs, "") || "0"); + if (columns < 50) columns = 50; + if (columns > 320) columns = 320; + + this.executeCommand(`\\c ${rows} ${columns}`); + } + + private stub(query: string) { + return query.replace( + // Stub read0 + /(? { + if (error) { + this.executing--; + } else { + this.process.stdin.write( + "2 {x}" + + ANSI.QUOTE + + this.identity + + ANSI.AT + + this.serial++ + + ANSI.AT + + ANSI.QUOTE + + (this.context === CTX.Q ? NS.Q : NS.K) + + ";" + + ANSI.CRLF, + ); + } + }); + this.executing++; + } + + private sendToTerminal(data: string) { + this.onDidWrite.fire(data); + } + + private promptProperties(context?: string, index?: number) { + const length = + 1 + + (context ?? this.context).length + + this.namespace.length + + CONF.PROMPT.length + + (index ?? this.visibleInputIndex); + + const lines = Math.ceil(length / this.columns); + const column = length % this.columns; + + return { length, lines, column }; + } + + private updateInputIndex(data?: string) { + this.inputIndex += data?.length ?? 0; + + if (this.inputIndex > this.maxInputIndex) { + this.inputIndex = this.maxInputIndex - 1; + } + } + + private updateMaxInputIndex() { + const { length } = this.promptProperties(this.context, 0); + const max = this.columns * this.rows; + this.maxInputIndex = (max > CONF.MAX_INPUT ? CONF.MAX_INPUT : max) - length; + this.updateInputIndex(); + } + + private moveCursorToColumn(column: number) { + return `\x1b[${column}G`; + } + + private moveCursorToContext(context?: string, length?: number) { + const { lines, column } = this.promptProperties(context, length); + + return ( + ANSI.RESTORE + + ANSI.DOWN.repeat(lines - (column === 0 ? 0 : 1)) + + this.moveCursorToColumn(column + 1) + ); + } + + private showPrompt(create?: boolean, context?: string) { + if (this.exited) { + return; + } + this.sendToTerminal( + (create ? ANSI.SAVE : ANSI.RESTORE) + + ANSI.FAINTON + + (context ?? this.context) + + this.namespace + + CONF.PROMPT + + ANSI.SPACE + + ANSI.FAINTOFF + + this.input.slice(0, this.visibleInputIndex).join(ANSI.EMPTY) + + ANSI.ERASETOEND + + this.moveCursorToContext(context, this.inputIndex), + ); + } + + private showExecutionPrompt() { + this.showPrompt( + false, + this.executing > 1 ? `execution-${this.executing}` : "execution", + ); + this.sendToTerminal(ANSI.CRLF); + } + + private showMessage(message: string) { + if (this.executions) { + this.messages.push(message); + } else { + this.sendToTerminal(message); + } + } + + private showOutput(decoded: string) { + if (this.exited) { + return; + } + const output = decoded.replace(/(?:\r\n|[\r\n])+/gs, ANSI.CRLF); + + if (this.executions) this.messages.push(output); + else this.buffer.push(output); + } + + private show() { + if (getAutoFocusOutputOnEntrySetting()) this.terminal.show(true); + } + + private recall(history?: HistoryItem) { + const input = history?.input ?? ANSI.EMPTY; + this.input = [...input]; + this.inputIndex = 0; + this.updateInputIndex(input); + this.showPrompt(); + } + + private handleError(error: Error) { + this.showMessage(error.message + ANSI.CRLF); + } + + private handleClose(code?: number) { + const message = `${CONF.TITLE} exited with code (${code ?? 0}).${ANSI.CRLF}`; + this.showMessage(message); + this.exited = true; + } + + private handleOutput(data: any) { + const decoded = this.decoder.decode(data); + this.showOutput(decoded); + } + + private handleErrorOutput(data: any) { + const decoded = this.decoder.decode(data); + this.token.lastIndex = 0; + const output = decoded.replace(this.token, ANSI.EMPTY); + this.showOutput(output); + + this.token.lastIndex = 0; + let match: RegExpMatchArray | null; + + while ((match = this.token.exec(decoded))) { + if (match[0]) { + this.namespace = match[2] ? `.${match[2]}` : ANSI.EMPTY; + + const serial = parseInt(match[1]); + if (serial + 1 === this.serial) { + const output = this.buffer.join(ANSI.EMPTY); + this.sendToTerminal(output); + this.buffer = []; + } + + this.executing--; + if (this.executing === 0) { + this.input = []; + this.inputIndex = 0; + this.updateInputIndex(); + this.showPrompt(true); + } else { + this.showExecutionPrompt(); + } + } + } + } + + private open(dimensions?: vscode.TerminalDimensions) { + if (dimensions) { + this.setDimensions(dimensions); + } + + this.sendToTerminal( + `${CONF.TITLE} Copyright (C) 1993-2025 KX Systems` + ANSI.CRLF.repeat(2), + ); + + this.messages.forEach((message) => this.sendToTerminal(message)); + this.messages = []; + + this.showPrompt(true); + + (this.executions || []).forEach((execution) => execution()); + this.executions = undefined; + } + + private setDimensions(dimensions: vscode.TerminalDimensions) { + this.rows = dimensions.rows; + this.columns = dimensions.columns; + this.updateMaxInputIndex(); + this.sendDimensions(); + if (!this.executions && !this.executing) this.showPrompt(); + } + + private close() { + if (ReplConnection.instance === this) { + ReplConnection.instance = undefined; + } + this.process.kill("SIGINT"); + this.process.kill("SIGTERM"); + this.onDidWrite.dispose(); + this.exited = true; + } + + private handleInput(data: string) { + if (this.exited) { + return; + } + + if (this.executing && data === KEY.CR) { + this.sendToTerminal(ANSI.CRLF); + return; + } + + switch (data) { + case KEY.CR: + if (this.input.length > 0) { + const input = this.inputText; + this.sendToProcess(input); + this.history.push(input); + this.inputIndex = this.visibleInputIndex; + this.showPrompt(); + if (/^(?:\\[\t ]|\\$)/m.test(input)) { + this.context = this.context === CTX.K ? CTX.Q : CTX.K; + } + } else { + this.sendToProcess(ANSI.EMPTY); + } + this.history.rewind(); + this.sendToTerminal(ANSI.CRLF); + break; + case KEY.CTRLC: + if (this.executing) { + this.process.kill("SIGINT"); + } + break; + case KEY.BS: + case KEY.BSMAC: + if (this.inputIndex > 0 && this.input.splice(this.inputIndex - 1, 1)) { + this.inputIndex--; + this.showPrompt(); + } + break; + case KEY.DEL: + if (this.input.splice(this.inputIndex, 1)) { + this.showPrompt(); + } + break; + case KEY.HOME: + case KEY.HOMEMAC: + this.inputIndex = 0; + this.showPrompt(); + break; + case KEY.END: + case KEY.ENDMAC: + if (this.visibleInputIndex > 0) { + this.inputIndex = this.visibleInputIndex - 1; + this.showPrompt(); + } + break; + case KEY.ALTHOME: + case KEY.SHIFTUP: + if (this.inputIndex >= this.columns) { + this.inputIndex -= this.columns; + this.showPrompt(); + } + break; + case KEY.ALTEND: + case KEY.SHIFTDOWN: + if (this.inputIndex <= this.visibleInputIndex - this.columns) { + this.inputIndex += this.columns; + this.showPrompt(); + } + break; + case KEY.LEFT: + case KEY.SHIFTLEFT: + if (this.inputIndex > 0) { + this.inputIndex--; + this.showPrompt(); + } + break; + case KEY.RIGHT: + case KEY.SHIFTRIGHT: + if (this.inputIndex < this.visibleInputIndex) { + this.inputIndex++; + this.showPrompt(); + } + break; + case KEY.DOWN: + this.recall(this.history.prev); + break; + case KEY.UP: + this.recall(this.history.next); + break; + default: + if (/(?:\r\n|[\r\n])/s.test(data)) { + this.executeQuery(data); + break; + } + if (data.length < CONF.MAX_INPUT) { + const target = data.replace(/[^\P{Cc}]/gsu, ANSI.EMPTY); + this.input.splice(this.inputIndex, 0, ...target); + this.updateInputIndex(target); + this.showPrompt(); + } + break; + } + } + + clearHistory() { + this.history.clear(); + } + + start() { + this.terminal.show(); + } + + executeQuery(text: string) { + const execution = () => { + this.sendToProcess(normalizeQuery(text)); + this.showExecutionPrompt(); + this.show(); + }; + if (this.executions) { + this.executions.push(execution); + } else { + execution(); + } + } + + private static instance?: ReplConnection; + + static getOrCreateInstance() { + if (!this.instance || this.instance.exited) { + this.instance = new ReplConnection(); + } + return this.instance; + } +} + +class HistoryItem { + prev?: HistoryItem; + next?: HistoryItem; + constructor(readonly input: string) {} +} + +class History { + private head?: HistoryItem; + private item?: HistoryItem; + + push(input: string) { + if (input === this.head?.input) { + return; + } + const item = new HistoryItem(input); + if (this.head) { + item.next = this.head; + this.head.prev = item; + } + this.head = item; + } + + get next() { + this.item = this.item === undefined ? this.head : this.item.next; + return this.item; + } + + get prev() { + this.item = this.item?.prev; + return this.item; + } + + rewind() { + this.item = undefined; + } + + clear() { + this.head = undefined; + this.rewind(); + } +} diff --git a/src/commands/buildToolsCommand.ts b/src/commands/buildToolsCommand.ts index c9a0e1b75..dbb2ab025 100644 --- a/src/commands/buildToolsCommand.ts +++ b/src/commands/buildToolsCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -18,15 +18,15 @@ import Path from "path"; import { Diagnostic, DiagnosticSeverity, - ProgressLocation, Range, TextDocument, Uri, - window, workspace, } from "vscode"; import { ext } from "../extensionVariables"; +import { getBasename } from "../utils/core"; +import { Runner } from "../utils/notifications"; const cache = new Map>(); @@ -182,39 +182,34 @@ const severity: { [key: string]: DiagnosticSeverity } = { }; function lint(document: TextDocument) { - return window.withProgress( - { - title: "Linting", - location: ProgressLocation.Window, - cancellable: true, - }, - async (_progress, token) => { - try { - const results = await getLinterResults(document.uri); - if (token.isCancellationRequested) { - cache.delete(document.uri.path); - return []; - } - return results.map((result) => { - const diagnostic = new Diagnostic( - new Range( - result.startLine - 1, - result.startCol - 1, - result.endLine - 1, - result.endCol, - ), - result.description, - severity[result.errorClass], - ); - diagnostic.source = "qlint"; - diagnostic.code = result.label; - return diagnostic; - }); - } catch (error) { - throw new Error(`Linting Failed ${error}`); + const runner = Runner.create(async (_, token) => { + try { + const results = await getLinterResults(document.uri); + if (token.isCancellationRequested) { + cache.delete(document.uri.path); + return []; } - }, - ); + return results.map((result) => { + const diagnostic = new Diagnostic( + new Range( + result.startLine - 1, + result.startCol - 1, + result.endLine - 1, + result.endCol, + ), + result.description, + severity[result.errorClass], + ); + diagnostic.source = "qlint"; + diagnostic.code = result.label; + return diagnostic; + }); + } catch (error) { + throw new Error(`Linting Failed ${error}`); + } + }); + runner.title = `Linting ${getBasename(document.uri)}.`; + return runner.execute(); } async function setDiagnostics(document: TextDocument) { diff --git a/src/commands/clientCommands.ts b/src/commands/clientCommands.ts index a5bf9c402..413715eb1 100644 --- a/src/commands/clientCommands.ts +++ b/src/commands/clientCommands.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/commands/dataSourceCommand.ts b/src/commands/dataSourceCommand.ts index 0724d491a..9da142552 100644 --- a/src/commands/dataSourceCommand.ts +++ b/src/commands/dataSourceCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -33,28 +33,26 @@ import { scratchpadVariableInput } from "../models/items/server"; import { UDARequestBody } from "../models/uda"; import { DataSourcesPanel } from "../panels/datasource"; import { ConnectionManagementService } from "../services/connectionManagerService"; -import { - kdbOutputLog, - noSelectedConnectionAction, - offerConnectAction, -} from "../utils/core"; +import { noSelectedConnectionAction } from "../utils/core"; import { checkIfTimeParamIsCorrect, convertTimeToTimestamp, createKdbDataSourcesFolder, getConnectedInsightsNode, } from "../utils/dataSource"; +import { MessageKind, notify } from "../utils/notifications"; import { addQueryHistory, - generateQSqlBody, + getQSQLWrapper, handleScratchpadTableRes, handleWSError, handleWSResults, } from "../utils/queryUtils"; -import { Telemetry } from "../utils/telemetryClient"; import { retrieveUDAtoCreateReqBody } from "../utils/uda"; import { validateScratchpadOutputVariableName } from "../validators/interfaceValidator"; +const logger = "dataSourceCommand"; + export async function addDataSource(): Promise { const kdbDataSourcesFolderPath = createKdbDataSourcesFolder(); @@ -74,60 +72,59 @@ export async function addDataSource(): Promise { defaultDataSourceContent.insightsNode = insightsNode; fs.writeFileSync(filePath, JSON.stringify(defaultDataSourceContent)); - window.showInformationMessage( + notify( `Created ${fileName} in ${kdbDataSourcesFolderPath}.`, + MessageKind.INFO, + { logger, telemetry: "Datasource.Created" }, ); - Telemetry.sendEvent("Datasource.Created"); } export async function populateScratchpad( dataSourceForm: DataSourceFiles, connLabel: string, + outputVariable?: string, + silent?: boolean, ): Promise { const connMngService = new ConnectionManagementService(); - const scratchpadVariable: InputBoxOptions = { - prompt: scratchpadVariableInput.prompt, - placeHolder: scratchpadVariableInput.placeholder, - validateInput: (value: string | undefined) => - validateScratchpadOutputVariableName(value), - }; - /* istanbul ignore next */ - window.showInputBox(scratchpadVariable).then(async (outputVariable) => { - if (outputVariable !== undefined && outputVariable !== "") { - const selectedConnection = - connMngService.retrieveConnectedConnection(connLabel); - - if ( - selectedConnection instanceof LocalConnection || - !selectedConnection - ) { - offerConnectAction(connLabel); - DataSourcesPanel.running = false; - return; - } - const qenvEnabled = - (await connMngService.retrieveInsightsConnQEEnabled(connLabel)) ?? ""; + if (!outputVariable) { + const scratchpadVariable: InputBoxOptions = { + prompt: scratchpadVariableInput.prompt, + placeHolder: scratchpadVariableInput.placeholder, + validateInput: (value: string | undefined) => + validateScratchpadOutputVariableName(value), + }; + outputVariable = await window.showInputBox(scratchpadVariable); + } - await selectedConnection.importScratchpad( - outputVariable!, - dataSourceForm!, - qenvEnabled === "Enabled", - ); - } else { - kdbOutputLog( - `[DATASOURCE] Invalid scratchpad output variable name: ${outputVariable}`, - "ERROR", - ); + if (outputVariable !== undefined && outputVariable !== "") { + const selectedConnection = + connMngService.retrieveConnectedConnection(connLabel); + + if (selectedConnection instanceof LocalConnection || !selectedConnection) { + DataSourcesPanel.running = false; + return; } - }); + + await selectedConnection.importScratchpad( + outputVariable, + dataSourceForm, + silent, + ); + } else { + notify( + `Invalid scratchpad output variable name: ${outputVariable}`, + MessageKind.ERROR, + { logger }, + ); + } } export async function runDataSource( dataSourceForm: DataSourceFiles, connLabel: string, executorName: string, -): Promise { +): Promise { if (DataSourcesPanel.running) { return; } @@ -144,7 +141,6 @@ export async function runDataSource( try { if (selectedConnection instanceof LocalConnection || !selectedConnection) { - offerConnectAction(connLabel); return; } selectedConnection.getMeta(); @@ -155,14 +151,17 @@ export async function runDataSource( dataSourceForm.insightsNode = getConnectedInsightsNode(); const fileContent = dataSourceForm; - kdbOutputLog( - `[DATASOURCE] Running ${fileContent.name} datasource...`, - "INFO", - ); let res: any; const selectedType = getSelectedType(fileContent); ext.isDatasourceExecution = true; - Telemetry.sendEvent("Datasource." + selectedType + ".Run"); + + notify(`Running ${fileContent.name} datasource...`, MessageKind.DEBUG, { + logger, + telemetry: "Datasource." + selectedType + ".Run", + }); + + const isNotebook = executorName.endsWith(".kxnb"); + switch (selectedType) { case "API": res = await runApiDataSource(fileContent, selectedConnection); @@ -171,7 +170,7 @@ export async function runDataSource( res = await runQsqlDataSource( fileContent, selectedConnection, - selectedConnection.apiConfig?.queryEnvironmentsEnabled, + isNotebook || undefined, ); break; case "UDA": @@ -179,7 +178,11 @@ export async function runDataSource( break; case "SQL": default: - res = await runSqlDataSource(fileContent, selectedConnection); + res = await runSqlDataSource( + fileContent, + selectedConnection, + isNotebook || undefined, + ); break; } @@ -189,16 +192,26 @@ export async function runDataSource( const query = getQuery(fileContent, selectedType); if (!success) { - Telemetry.sendEvent("Datasource." + selectedType + ".Run.Error"); - window.showErrorMessage(res.error); + notify("Query execution failed.", MessageKind.DEBUG, { + logger, + params: res.error, + telemetry: "Datasource." + selectedType + ".Run.Error", + }); } - if (ext.isResultsTabVisible) { + if (isNotebook || ext.isResultsTabVisible) { if (success) { const resultCount = typeof res === "string" ? "0" : res.rows.length; - kdbOutputLog(`[DATASOURCE] Results: ${resultCount} rows`, "INFO"); + notify(`Results: ${resultCount} rows`, MessageKind.DEBUG, { + logger, + }); } else if (!success) { res = res.errorMsg ? res.errorMsg : res.error; } + + if (isNotebook) { + return res; + } + await writeQueryResultsToView( res, query, @@ -209,9 +222,10 @@ export async function runDataSource( ); } else { if (success) { - kdbOutputLog( - `[DATASOURCE] Results is a string with length: ${res.length}`, - "INFO", + notify( + `Results is a string with length: ${res.length}`, + MessageKind.DEBUG, + { logger }, ); } else if (res.error) { res = res.errorMsg ? res.errorMsg : res.error; @@ -229,8 +243,10 @@ export async function runDataSource( addDStoQueryHistory(dataSourceForm, success, connLabel, executorName); } } catch (error) { - window.showErrorMessage((error as Error).message); - kdbOutputLog(`[DATASOURCE] ${(error as Error).message}`, "ERROR", true); + notify(`Datasource error: ${error}.`, MessageKind.DEBUG, { + logger, + params: error, + }); DataSourcesPanel.running = false; } finally { DataSourcesPanel.running = false; @@ -281,8 +297,10 @@ export async function runApiDataSource( fileContent.dataSource.api.endTS, ); if (!isTimeCorrect) { - window.showErrorMessage( - "The time parameters(startTS and endTS) are not correct, please check the format or if the startTS is before the endTS", + notify( + "The time parameters (startTS and endTS) are not correct, please check the format or if the startTS is before the endTS", + MessageKind.ERROR, + { logger }, ); return; } @@ -390,13 +408,12 @@ export function getApiBody( export async function runQsqlDataSource( fileContent: DataSourceFiles, selectedConn: InsightsConnection, - qeEnabled?: boolean, + isTableView?: boolean, ): Promise { - const qsqlBody = generateQSqlBody( + const qsqlBody = selectedConn.generateQSqlBody( fileContent.dataSource.qsql.query, fileContent.dataSource.qsql.selectedTarget, selectedConn.insightsVersion, - qeEnabled, ); const qsqlCall = await selectedConn.getDatasourceQuery( @@ -407,7 +424,7 @@ export async function runQsqlDataSource( if (qsqlCall?.error) { return parseError(qsqlCall.error); } else if (qsqlCall?.arrayBuffer) { - const results = handleWSResults(qsqlCall.arrayBuffer); + const results = handleWSResults(qsqlCall.arrayBuffer, isTableView); return handleScratchpadTableRes(results); } else { return { error: "Datasource QSQL call failed" }; @@ -417,6 +434,7 @@ export async function runQsqlDataSource( export async function runSqlDataSource( fileContent: DataSourceFiles, selectedConn: InsightsConnection, + isTableView?: boolean, ): Promise { const sqlBody = { query: fileContent.dataSource.sql.query, @@ -429,7 +447,7 @@ export async function runSqlDataSource( if (sqlCall?.error) { return parseError(sqlCall.error); } else if (sqlCall?.arrayBuffer) { - const results = handleWSResults(sqlCall.arrayBuffer); + const results = handleWSResults(sqlCall.arrayBuffer, isTableView); return handleScratchpadTableRes(results); } else { return { error: "Datasource SQL call failed" }; @@ -445,7 +463,10 @@ export async function runUDADataSource( const udaReqBody = await retrieveUDAtoCreateReqBody(uda, selectedConn); if (udaReqBody.error) { - kdbOutputLog(`[DATASOURCE] Error: ${udaReqBody.error}`, "ERROR", true); + notify(`Datasource error.`, MessageKind.DEBUG, { + logger, + params: udaReqBody.error, + }); return udaReqBody; } @@ -492,13 +513,33 @@ export function parseError(error: GetDataError) { if (error instanceof Object && error.buffer) { return handleWSError(error.buffer); } else { - kdbOutputLog( - `[DATASOURCE] Error: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, - "ERROR", - true, - ); + notify(`Datasource error.`, MessageKind.DEBUG, { + logger, + params: error, + }); return { error, }; } } + +export function getPartialDatasourceFile( + query: string, + selectedTarget?: string, + isSql?: boolean, + isPython?: boolean, +) { + return isSql + ? { + dataSource: { + selectedType: "SQL", + sql: { query }, + }, + } + : { + dataSource: { + selectedType: "QSQL", + qsql: { query: getQSQLWrapper(query, isPython), selectedTarget }, + }, + }; +} diff --git a/src/commands/installTools.ts b/src/commands/installTools.ts index e3881d9d3..34b517366 100644 --- a/src/commands/installTools.ts +++ b/src/commands/installTools.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -20,7 +20,6 @@ import { join } from "path"; import { ConfigurationTarget, InputBoxOptions, - ProgressLocation, QuickPickItem, Uri, commands, @@ -56,19 +55,19 @@ import { getServerName, getServers, getWorkspaceFolder, - kdbOutputLog, removeLocalConnectionStatus, saveLocalProcessObj, updateServers, } from "../utils/core"; import { executeCommand } from "../utils/cpUtils"; +import { MessageKind, Runner, notify } from "../utils/notifications"; import { openUrl } from "../utils/openUrl"; -import { Telemetry } from "../utils/telemetryClient"; import { validateServerPort } from "../validators/kdbValidator"; +const logger = "install"; + export async function installTools(): Promise { let file: Uri[] | undefined; - let runtimeUrl: string; await commands.executeCommand("notifications.clearAll"); await commands.executeCommand("welcome.goBack"); @@ -82,17 +81,17 @@ export async function installTools(): Promise { if (licenseTypeResult?.label === licenseAquire) { let licenseCancel; await openUrl(ext.kdbInstallUrl); - await window - .showInformationMessage( - licenseWorkflow.prompt, - licenseWorkflow.option1, - licenseWorkflow.option2, - ) - .then(async (res) => { - if (res === licenseWorkflow.option2) { - licenseCancel = true; - } - }); + await notify( + licenseWorkflow.prompt, + MessageKind.INFO, + {}, + licenseWorkflow.option1, + licenseWorkflow.option2, + ).then(async (res) => { + if (res === licenseWorkflow.option2) { + licenseCancel = true; + } + }); if (licenseCancel) return; } @@ -131,172 +130,154 @@ export async function installTools(): Promise { throw new Error(); } - window - .withProgress( - { - location: ProgressLocation.Notification, - title: "Installation of q", - cancellable: true, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog("[Install] User cancelled the installation.", "INFO"); - }); + const runner = Runner.create(async (progress, token) => { + token.onCancellationRequested(() => { + notify("User cancelled the installation.", MessageKind.DEBUG, { + logger, + }); + }); - progress.report({ increment: 0 }); + progress.report({ increment: 0 }); - // download the binaries - progress.report({ increment: 20, message: "Getting the binaries..." }); - const osFile = getOsFile(); - if (osFile === undefined) { - kdbOutputLog( - "[Install] Unsupported operating system, unable to download binaries for this.", - "ERROR", - ); - Telemetry.sendException( - new Error( - "Unsupported operating system, unable to download binaries", - ), - ); - } else { - const gpath = join(ext.context.globalStorageUri.fsPath, osFile); - if (!existsSync(gpath)) { - const response = await fetch( - `${ext.kdbDownloadPrefixUrl}${osFile}`, - ); - if (response.status > 200) { - Telemetry.sendException( - new Error("Invalid or unavailable download url."), - ); - kdbOutputLog( - `[Install] Invalid or unavailable download url: ${runtimeUrl}`, - "ERROR", - ); - window.showErrorMessage( - `Invalid or unavailable download url: ${runtimeUrl}`, - ); - exit(1); - } - await ensureDir(ext.context.globalStorageUri.fsPath); - await writeFile(gpath, Buffer.from(await response.arrayBuffer())); - } - await extract(gpath, { dir: ext.context.globalStorageUri.fsPath }); + // download the binaries + progress.report({ increment: 20, message: "Getting the binaries..." }); + const osFile = getOsFile(); + if (osFile === undefined) { + notify( + "Unsupported operating system, unable to download binaries.", + MessageKind.ERROR, + { logger, telemetry: true }, + ); + } else { + const gpath = join(ext.context.globalStorageUri.fsPath, osFile); + if (!existsSync(gpath)) { + const runtimeUrl = `${ext.kdbDownloadPrefixUrl}${osFile}`; + const response = await fetch(runtimeUrl); + if (response.status > 200) { + notify("Invalid or unavailable download url.", MessageKind.ERROR, { + logger, + params: runtimeUrl, + telemetry: true, + }); + exit(1); } - - // move the license file - progress.report({ increment: 30, message: "Moving license file..." }); - await delay(500); await ensureDir(ext.context.globalStorageUri.fsPath); - await copy( - file![0].fsPath, - join(ext.context.globalStorageUri.fsPath, ext.kdbLicName), - ); + await writeFile(gpath, Buffer.from(await response.arrayBuffer())); + } + await extract(gpath, { dir: ext.context.globalStorageUri.fsPath }); + } - // add the env var for the process - progress.report({ - increment: 60, - message: "Setting up environment...", - }); - await delay(500); - env.QHOME = ext.context.globalStorageUri.fsPath; + // move the license file + progress.report({ increment: 30, message: "Moving license file..." }); + await delay(500); + await ensureDir(ext.context.globalStorageUri.fsPath); + await copy( + file![0].fsPath, + join(ext.context.globalStorageUri.fsPath, ext.kdbLicName), + ); - // persist the QHOME to global settings - await workspace - .getConfiguration() - .update("kdb.qHomeDirectory", env.QHOME, ConfigurationTarget.Global); + // add the env var for the process + progress.report({ + increment: 60, + message: "Setting up environment...", + }); + await delay(500); + // TODO 1: This is wrong, env vars should be read only. + env.QHOME = ext.context.globalStorageUri.fsPath; - // update walkthrough - const QHOME = workspace - .getConfiguration() - .get("kdb.qHomeDirectory"); - if (QHOME) { - env.QHOME = QHOME; - if (!pathExists(env.QHOME)) { - kdbOutputLog("[Install] QHOME path stored is empty", "ERROR"); - } - await writeFile( - join(__dirname, "qinstall.md"), - `# q runtime installed location: \n### ${QHOME}`, - ); - kdbOutputLog( - `[Install] Installation of q found here: ${QHOME}`, - "INFO", - ); - } - }, - ) - .then(async () => { - window - .showInformationMessage( - onboardingWorkflow.prompt(ext.context.globalStorageUri.fsPath), - onboardingWorkflow.option1, - onboardingWorkflow.option2, - ) - .then(async (startResult) => { - if (startResult === onboardingWorkflow.option1) { - const portInput: InputBoxOptions = { - prompt: onboardingInput.prompt, - placeHolder: onboardingInput.placeholder, - ignoreFocusOut: true, - validateInput: (value: string | undefined) => - validateServerPort(value), - }; - window.showInputBox(portInput).then(async (port) => { - if (port) { - let servers: Server | undefined = getServers(); - if ( - servers != undefined && - servers[getKeyForServerName("local")] - ) { - Telemetry.sendEvent( - `Server localhost:${port} already exists in configuration store.`, - ); - await window.showErrorMessage( - `Server localhost:${port} already exists.`, - ); - } else { - const key = "local"; - if (servers === undefined) { - servers = { - key: { - auth: false, - serverName: "localhost", - serverPort: port, - serverAlias: "local", - managed: true, - tls: false, - }, - }; - await addLocalConnectionContexts(getServerName(servers[0])); - } else { - servers[key] = { - auth: false, - serverName: "localhost", - serverPort: port, - serverAlias: "local", - managed: true, - tls: false, - }; - await addLocalConnectionContexts( - getServerName(servers[key]), - ); - } - await updateServers(servers); - const newServers = getServers(); - if (newServers != undefined) { - ext.serverProvider.refresh(newServers); - } - } - } - await startLocalProcessByServerName( - `localhost:${port} [local]`, - getKeyForServerName("local"), - Number(port), + // persist the QHOME to global settings + await workspace + .getConfiguration() + .update("kdb.qHomeDirectory", env.QHOME, ConfigurationTarget.Global); + + // update walkthrough + const QHOME = workspace + .getConfiguration() + .get("kdb.qHomeDirectory"); + if (QHOME) { + // TODO 1: This is wrong, env vars should be read only. + env.QHOME = QHOME; + if (!pathExists(env.QHOME)) { + notify("QHOME path stored is empty", MessageKind.ERROR, { + logger, + }); + } + await writeFile( + join(__dirname, "qinstall.md"), + `# q runtime installed location: \n### ${QHOME}`, + ); + notify(`Installation of q found here: ${QHOME}`, MessageKind.DEBUG, { + logger, + }); + } + }); + runner.title = "Installing q."; + runner.execute().then(async () => { + notify( + onboardingWorkflow.prompt(ext.context.globalStorageUri.fsPath), + MessageKind.INFO, + {}, + onboardingWorkflow.option1, + onboardingWorkflow.option2, + ).then(async (startResult) => { + if (startResult === onboardingWorkflow.option1) { + const portInput: InputBoxOptions = { + prompt: onboardingInput.prompt, + placeHolder: onboardingInput.placeholder, + ignoreFocusOut: true, + validateInput: (value: string | undefined) => + validateServerPort(value), + }; + window.showInputBox(portInput).then(async (port) => { + if (port) { + let servers: Server | undefined = getServers(); + if (servers != undefined && servers[getKeyForServerName("local")]) { + notify( + `Server localhost:${port} already exists in configuration store`, + MessageKind.ERROR, + { logger, telemetry: true }, ); - }); + } else { + const key = "local"; + if (servers === undefined) { + servers = { + key: { + auth: false, + serverName: "localhost", + serverPort: port, + serverAlias: "local", + managed: true, + tls: false, + }, + }; + await addLocalConnectionContexts(getServerName(servers[0])); + } else { + servers[key] = { + auth: false, + serverName: "localhost", + serverPort: port, + serverAlias: "local", + managed: true, + tls: false, + }; + await addLocalConnectionContexts(getServerName(servers[key])); + } + await updateServers(servers); + const newServers = getServers(); + if (newServers != undefined) { + ext.serverProvider.refresh(newServers); + } + } } + await startLocalProcessByServerName( + `localhost:${port} [local]`, + getKeyForServerName("local"), + Number(port), + ); }); + } }); + }); } export async function startLocalProcessByServerName( @@ -320,7 +301,7 @@ export async function startLocalProcessByServerName( ); } catch { await removeLocalConnectionStatus(serverName); - window.showErrorMessage("Error starting q process."); + notify("Error starting q process.", MessageKind.ERROR, { logger }); } } @@ -334,10 +315,11 @@ export async function startLocalProcess(viewItem: KdbNode): Promise { export async function stopLocalProcess(viewItem: KdbNode): Promise { ext.localProcessObjects[viewItem.children[0]].kill(); - kdbOutputLog( + notify( `Child process id ${ext.localProcessObjects[viewItem.children[0]] .pid!} removed in cache.`, - "INFO", + MessageKind.DEBUG, + { logger }, ); await removeLocalConnectionStatus(`${getServerName(viewItem.details)}`); } @@ -346,8 +328,9 @@ export async function stopLocalProcessByServerName( serverName: string, ): Promise { ext.localProcessObjects[serverName].kill(); - kdbOutputLog( + notify( `Child process id ${ext.localProcessObjects[serverName].pid!} removed in cache.`, - "INFO", + MessageKind.DEBUG, + { logger }, ); } diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 5b5079436..9e4f1579f 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -12,13 +12,10 @@ */ import * as fs from "fs"; -import { readFileSync } from "fs-extra"; -import { join } from "path"; import * as url from "url"; import { CancellationToken, Position, - ProgressLocation, Range, Uri, ViewColumn, @@ -26,10 +23,15 @@ import { window, workspace, env, + ProgressLocation, } from "vscode"; import { ext } from "../extensionVariables"; -import { runDataSource } from "./dataSourceCommand"; +import { + getPartialDatasourceFile, + populateScratchpad, + runDataSource, +} from "./dataSourceCommand"; import { InsightsConnection } from "../classes/insightsConnection"; import { ExportedConnections, @@ -45,7 +47,6 @@ import { Plot } from "../models/plot"; import { QueryHistory } from "../models/queryHistory"; import { queryConstants } from "../models/queryResult"; import { ScratchpadResult } from "../models/scratchpadResult"; -import { ServerObject } from "../models/serverObject"; import { DataSourcesPanel } from "../panels/datasource"; import { NewConnectionPannel } from "../panels/newConnection"; import { ChartEditorProvider } from "../services/chartEditorProvider"; @@ -57,6 +58,7 @@ import { MetaObjectPayloadNode, } from "../services/kdbTreeProvider"; import { MetaContentProvider } from "../services/metaContentProvider"; +import { inputVariable } from "../services/notebookProviders"; import { handleLabelsConnMap, removeConnFromLabels } from "../utils/connLabel"; import { addLocalConnectionContexts, @@ -65,7 +67,6 @@ import { getKeyForServerName, getServerName, getServers, - kdbOutputLog, offerReconnectionAfterEdit, updateInsights, updateServers, @@ -73,14 +74,15 @@ import { import { refreshDataSourcesPanel } from "../utils/dataSource"; import { decodeQUTF } from "../utils/decode"; import { ExecutionConsole } from "../utils/executionConsole"; +import { MessageKind, Runner, notify } from "../utils/notifications"; import { openUrl } from "../utils/openUrl"; import { checkIfIsDatasource, addQueryHistory, formatScratchpadStacktrace, resultToBase64, + needsScratchpad, } from "../utils/queryUtils"; -import { Telemetry } from "../utils/telemetryClient"; import { addWorkspaceFile, openWith, @@ -94,6 +96,8 @@ import { validateServerUsername, } from "../validators/kdbValidator"; +const logger = "serverCommand"; + export async function addNewConnection(): Promise { NewConnectionPannel.close(); NewConnectionPannel.render(ext.context.extensionUri); @@ -115,7 +119,7 @@ export async function addInsightsConnection( ) { const aliasValidation = validateServerAlias(insightsData.alias, false); if (aliasValidation) { - window.showErrorMessage(aliasValidation); + notify(aliasValidation, MessageKind.ERROR, { logger }); return; } if (insightsData.alias === undefined || insightsData.alias === "") { @@ -128,8 +132,10 @@ export async function addInsightsConnection( insights != undefined && insights[getKeyForServerName(insightsData.alias)] ) { - await window.showErrorMessage( + notify( `Insights instance named ${insightsData.alias} already exists.`, + MessageKind.ERROR, + { logger }, ); return; } else { @@ -167,17 +173,23 @@ export async function addInsightsConnection( await handleLabelsConnMap(labels, insightsData.alias); } ext.serverProvider.refreshInsights(newInsights); - Telemetry.sendEvent("Connection.Created.Insights"); + notify("Created Insights connection.", MessageKind.DEBUG, { + logger, + telemetry: "Connection.Created.Insights", + }); } - window.showInformationMessage( + + notify( `Added Insights connection: ${insightsData.alias}`, + MessageKind.INFO, + { logger }, ); NewConnectionPannel.close(); } } -/* istanbul ignore next */ +/* c8 ignore next */ export async function editInsightsConnection( insightsData: InsightDetails, oldAlias: string, @@ -188,7 +200,7 @@ export async function editInsightsConnection( ? undefined : validateServerAlias(insightsData.alias, false); if (aliasValidation) { - window.showErrorMessage(aliasValidation); + notify(aliasValidation, MessageKind.ERROR, { logger }); return; } const isConnectedConn = isConnected(oldAlias); @@ -205,14 +217,18 @@ export async function editInsightsConnection( ? insights[getKeyForServerName(insightsData.alias)] : undefined; if (newAliasExists) { - await window.showErrorMessage( + notify( `Insights instance named ${insightsData.alias} already exists.`, + MessageKind.ERROR, + { logger }, ); return; } else { if (!oldInsights) { - await window.showErrorMessage( + notify( `Insights instance named ${oldAlias} does not exist.`, + MessageKind.ERROR, + { logger }, ); return; } else { @@ -258,13 +274,19 @@ export async function editInsightsConnection( removeConnFromLabels(insightsData.alias); } ext.serverProvider.refreshInsights(newInsights); - Telemetry.sendEvent("Connection.Edited.Insights"); + notify("Edited Insights connection.", MessageKind.DEBUG, { + logger, + telemetry: "Connection.Edited.Insights", + }); if (isConnectedConn) { offerReconnectionAfterEdit(insightsData.alias); } } - window.showInformationMessage( + + notify( `Edited Insights connection: ${insightsData.alias}`, + MessageKind.INFO, + { logger }, ); NewConnectionPannel.close(); @@ -274,7 +296,7 @@ export async function editInsightsConnection( } // Not possible to test secrets -/* istanbul ignore next */ +/* c8 ignore next */ export async function addAuthConnection( serverKey: string, username: string, @@ -282,7 +304,7 @@ export async function addAuthConnection( ): Promise { const validUsername = validateServerUsername(username); if (validUsername) { - window.showErrorMessage(validUsername); + notify(validUsername, MessageKind.ERROR, { logger }); return; } if (password?.trim()?.length) { @@ -305,7 +327,7 @@ export async function addAuthConnection( } // Not possible to test secrets -/* istanbul ignore next */ +/* c8 ignore next */ function removeAuthConnection(serverKey: string) { if ( Object.prototype.hasOwnProperty.call( @@ -324,17 +346,17 @@ export async function enableTLS(serverKey: string): Promise { // validate if TLS is possible if (ext.openSslVersion === null) { - window - .showErrorMessage( - "OpenSSL not found, please ensure this is installed", - "More Info", - "Cancel", - ) - .then(async (result) => { - if (result === "More Info") { - await openUrl("https://code.kx.com/q/kb/ssl/"); - } - }); + notify( + "OpenSSL not found, please ensure this is installed", + MessageKind.ERROR, + { logger }, + "More Info", + "Cancel", + ).then(async (result) => { + if (result === "More Info") { + await openUrl("https://code.kx.com/q/kb/ssl/"); + } + }); return; } if (servers && servers[serverKey]) { @@ -346,8 +368,10 @@ export async function enableTLS(serverKey: string): Promise { } return; } - window.showErrorMessage( + notify( "Server not found, please ensure this is a correct server", + MessageKind.ERROR, + { logger }, "Cancel", ); } @@ -361,15 +385,15 @@ export async function addKdbConnection( const hostnameValidation = validateServerName(kdbData.serverName); const portValidation = validateServerPort(kdbData.serverPort); if (aliasValidation) { - window.showErrorMessage(aliasValidation); + notify(aliasValidation, MessageKind.ERROR, { logger }); return; } if (hostnameValidation) { - window.showErrorMessage(hostnameValidation); + notify(hostnameValidation, MessageKind.ERROR, { logger }); return; } if (portValidation) { - window.showErrorMessage(portValidation); + notify(portValidation, MessageKind.ERROR, { logger }); return; } let servers: Server | undefined = getServers(); @@ -378,8 +402,10 @@ export async function addKdbConnection( servers != undefined && servers[getKeyForServerName(kdbData.serverAlias || "")] ) { - await window.showErrorMessage( + notify( `Server name ${kdbData.serverAlias} already exists.`, + MessageKind.ERROR, + { logger }, ); } else { const key = kdbData.serverAlias || ""; @@ -419,21 +445,25 @@ export async function addKdbConnection( ext.latestLblsChanged.push(...labels); await handleLabelsConnMap(labels, kdbData.serverAlias); } - Telemetry.sendEvent("Connection.Created.QProcess"); + notify("Created kdb connection.", MessageKind.DEBUG, { + logger, + telemetry: "Connection.Created.QProcess", + }); ext.serverProvider.refresh(newServers); } if (kdbData.auth) { addAuthConnection(key, kdbData.username!, kdbData.password!); } - window.showInformationMessage( - `Added kdb connection: ${kdbData.serverAlias}`, - ); + + notify(`Added kdb connection: ${kdbData.serverAlias}`, MessageKind.INFO, { + logger, + }); NewConnectionPannel.close(); } } -/* istanbul ignore next */ +/* c8 ignore next */ export async function editKdbConnection( kdbData: ServerDetails, oldAlias: string, @@ -448,15 +478,15 @@ export async function editKdbConnection( const hostnameValidation = validateServerName(kdbData.serverName); const portValidation = validateServerPort(kdbData.serverPort); if (aliasValidation) { - window.showErrorMessage(aliasValidation); + notify(aliasValidation, MessageKind.ERROR, { logger }); return; } if (hostnameValidation) { - window.showErrorMessage(hostnameValidation); + notify(hostnameValidation, MessageKind.ERROR, { logger }); return; } if (portValidation) { - window.showErrorMessage(portValidation); + notify(portValidation, MessageKind.ERROR, { logger }); return; } const isConnectedConn = isConnected(oldAlias); @@ -470,14 +500,18 @@ export async function editKdbConnection( ? servers[getKeyForServerName(kdbData.serverAlias)] : undefined; if (newAliasExists) { - await window.showErrorMessage( + notify( `KDB instance named ${kdbData.serverAlias} already exists.`, + MessageKind.ERROR, + { logger }, ); return; } else { if (!oldServer) { - await window.showErrorMessage( + notify( `KDB instance named ${oldAlias} does not exist.`, + MessageKind.ERROR, + { logger }, ); return; } else { @@ -527,14 +561,20 @@ export async function editKdbConnection( removeConnFromLabels(kdbData.serverAlias); } ext.serverProvider.refresh(newServers); - Telemetry.sendEvent("Connection.Edited.KDB"); + notify("Edited kdb connection.", MessageKind.DEBUG, { + logger, + telemetry: "Connection.Edited.KDB", + }); const connLabelToReconn = `${kdbData.serverName}:${kdbData.serverPort} [${kdbData.serverAlias}]`; if (isConnectedConn) { offerReconnectionAfterEdit(connLabelToReconn); } } - window.showInformationMessage( + + notify( `Edited KDB connection: ${kdbData.serverAlias}`, + MessageKind.INFO, + { logger }, ); if (oldKey !== newKey) { removeConnFromLabels(oldKey); @@ -558,7 +598,7 @@ export async function editKdbConnection( } // test fs readFileSync unit tests are flaky, no correct way to test them -/* istanbul ignore next */ +/* c8 ignore next */ export async function importConnections() { const options = { canSelectMany: false, @@ -571,7 +611,7 @@ export async function importConnections() { const fileUri = await window.showOpenDialog(options); if (!fileUri || fileUri.length === 0) { - kdbOutputLog("[IMPORT CONNECTION]No file selected", "ERROR"); + notify("No file selected.", MessageKind.ERROR, { logger }); return; } const filePath = fileUri[0].fsPath; @@ -581,24 +621,24 @@ export async function importConnections() { try { importedConnections = JSON.parse(fileContent); } catch { - kdbOutputLog("[IMPORT CONNECTION]Invalid JSON format", "ERROR"); + notify("Invalid JSON format.", MessageKind.ERROR, { logger }); return; } if (!isValidExportedConnections(importedConnections)) { - kdbOutputLog( - "[IMPORT CONNECTION]JSON does not match the required format", - "ERROR", - ); + notify("JSON does not match the required format.", MessageKind.ERROR, { + logger, + }); return; } if ( importedConnections.connections.KDB.length === 0 && importedConnections.connections.Insights.length === 0 ) { - kdbOutputLog( - "[IMPORT CONNECTION]There is no KDB or Insights connections to import in this JSON file", - "ERROR", + notify( + "There is no KDB or Insights connections to import in this JSON file.", + MessageKind.ERROR, + { logger }, ); return; } @@ -630,8 +670,10 @@ export async function addImportedConnections( let res: "Duplicate" | "Overwrite" | "Cancel" | undefined = "Duplicate"; if (hasDuplicates) { - res = await window.showInformationMessage( + res = await notify( "You are importing connections with the same name. Would you like to duplicate, overwrite or cancel the import?", + MessageKind.INFO, + {}, "Duplicate", "Overwrite", "Cancel", @@ -701,18 +743,29 @@ export async function addImportedConnections( ext.serverProvider.refresh(config); } - kdbOutputLog("[IMPORT CONNECTION]Connections imported successfully", "INFO"); - window.showInformationMessage("Connections imported successfully"); + notify("Connections imported successfully.", MessageKind.INFO, { + logger, + }); } export async function removeConnection(viewItem: KdbNode | InsightsNode) { - const connMngService = new ConnectionManagementService(); - removeConnFromLabels( - viewItem instanceof KdbNode - ? viewItem.details.serverAlias - : viewItem.details.alias, - ); - await connMngService.removeConnection(viewItem); + notify( + `You are going to remove ${viewItem.label}, would you like to proceed?`, + MessageKind.WARNING, + {}, + "Proceed", + "Cancel", + ).then(async (result) => { + if (result === "Proceed") { + const connMngService = new ConnectionManagementService(); + removeConnFromLabels( + viewItem instanceof KdbNode + ? viewItem.details.serverAlias + : viewItem.details.alias, + ); + await connMngService.removeConnection(viewItem); + } + }); } export async function connect(connLabel: string): Promise { @@ -720,7 +773,7 @@ export async function connect(connLabel: string): Promise { ExecutionConsole.start(); const viewItem = connMngService.retrieveConnection(connLabel); if (viewItem === undefined) { - window.showErrorMessage("Connection not found"); + notify("Connection not found.", MessageKind.ERROR, { logger }); return; } @@ -729,17 +782,17 @@ export async function connect(connLabel: string): 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/"); - } - }); + notify( + "TLS support requires OpenSSL to be installed.", + MessageKind.INFO, + { logger }, + "More Info", + "Cancel", + ).then(async (result) => { + if (result === "More Info") { + await openUrl("https://code.kx.com/q/kb/ssl/"); + } + }); } } } @@ -795,46 +848,12 @@ export async function executeQuery( isPython: boolean, isWorkbook: boolean, isFromConnTree?: boolean, -): Promise { - await window.withProgress( - { - cancellable: true, - location: ProgressLocation.Window, - title: `Executing query (${executorName})`, - }, - async (_progress, token) => { - await _executeQuery( - query, - connLabel, - executorName, - context, - isPython, - isWorkbook, - isFromConnTree, - token, - ); - }, - ); -} - -export async function _executeQuery( - query: string, - connLabel: string, - executorName: string, - context: string, - isPython: boolean, - isWorkbook: boolean, - isFromConnTree?: boolean, token?: CancellationToken, -): Promise { +): Promise { const connMngService = new ConnectionManagementService(); const queryConsole = ExecutionConsole.start(); if (connLabel === "") { if (ext.activeConnection === undefined) { - kdbOutputLog( - "No active connection found. Connect to one connection.", - "ERROR", - ); return undefined; } else { connLabel = ext.activeConnection.connLabel; @@ -842,8 +861,9 @@ export async function _executeQuery( } const isConnected = connMngService.isConnected(connLabel); if (!isConnected) { - window.showInformationMessage("The selected connection is not connected."); - kdbOutputLog("The selected connection is not connected.", "ERROR"); + notify(`Connection ${connLabel} is not connected.`, MessageKind.ERROR, { + logger, + }); return undefined; } @@ -852,11 +872,15 @@ export async function _executeQuery( const connVersion = isInsights ? (selectedConn.insightsVersion ?? 0) : 0; const telemetryLangType = isPython ? ".Python" : ".q"; const telemetryBaseMsg = isWorkbook ? "Workbook" : "Scratchpad"; - Telemetry.sendEvent(telemetryBaseMsg + ".Execute" + telemetryLangType); + notify("Query execution.", MessageKind.DEBUG, { + logger, + telemetry: telemetryBaseMsg + ".Execute" + telemetryLangType, + }); if (query.length === 0) { - Telemetry.sendEvent( - telemetryBaseMsg + ".Execute" + telemetryLangType + ".Error", - ); + notify("Empty query.", MessageKind.DEBUG, { + logger, + telemetry: telemetryBaseMsg + ".Execute" + telemetryLangType + ".Error", + }); queryConsole.appendQueryError( query, "Query is empty", @@ -872,7 +896,8 @@ export async function _executeQuery( ); return undefined; } - const isStringfy = !ext.isResultsTabVisible; + const isNotebook = executorName.endsWith(".kxnb"); + const isStringfy = isNotebook ? false : !ext.isResultsTabVisible; const startTime = Date.now(); const results = await connMngService.executeQuery( query, @@ -884,14 +909,14 @@ export async function _executeQuery( const endTime = Date.now(); const duration = (endTime - startTime).toString(); - /* istanbul ignore next */ + /* c8 ignore next */ if (token?.isCancellationRequested) { return undefined; } // set context for root nodes if (selectedConn instanceof InsightsConnection) { - await writeScratchpadResult( + const res = await writeScratchpadResult( results, query, connLabel, @@ -901,12 +926,20 @@ export async function _executeQuery( duration, connVersion, ); + if (isNotebook) { + return res; + } + } else if (isNotebook) { + return results; } else { - /* istanbul ignore next */ + /* c8 ignore next */ if (ext.isResultsTabVisible) { const data = resultToBase64(results); if (data) { - Telemetry.sendEvent("GGPLOT.Display" + (isPython ? ".Python" : ".q")); + notify("GG Plot displayed", MessageKind.DEBUG, { + logger, + telemetry: "GGPLOT.Display" + (isPython ? ".Python" : ".q"), + }); const active = ext.activeTextEditor; if (active) { const plot = { @@ -1005,21 +1038,26 @@ export function getConextForRerunQuery(query: string): string { return context; } -export function runQuery( +export async function runQuery( type: ExecutionTypes, connLabel: string, executorName: string, isWorkbook: boolean, rerunQuery?: string, target?: string, + isSql?: boolean, + isInsights?: boolean, ) { const editor = ext.activeTextEditor; if (!editor) { return false; } + let context; let query; let isPython = false; + let variable: string | undefined; + switch (type) { case ExecutionTypes.QuerySelection: case ExecutionTypes.PythonQuerySelection: { @@ -1047,23 +1085,47 @@ export function runQuery( break; } } - if (target) { - runDataSource( - { - dataSource: { - selectedType: "QSQL", - qsql: { - query, - selectedTarget: target, - }, - }, - }, - connLabel, - executorName, - ); - } else { - executeQuery(query, connLabel, executorName, context, isPython, isWorkbook); + + if (type === ExecutionTypes.PopulateScratchpad) { + if (executorName.endsWith(".py")) { + isPython = true; + } + variable = await inputVariable(); } + + const runner = Runner.create((_, token) => { + return target || isSql + ? variable + ? populateScratchpad( + getPartialDatasourceFile(query, target, isSql, isPython), + connLabel, + variable, + ) + : runDataSource( + getPartialDatasourceFile(query, target, isSql, isPython), + connLabel, + executorName, + ) + : executeQuery( + query, + connLabel, + executorName, + context, + isPython, + isWorkbook, + false, + token, + ); + }); + + if (isInsights) { + runner.location = ProgressLocation.Notification; + } + runner.title = `Executing ${executorName} on ${connLabel || "active connection"}.`; + + return (target || isSql) && !variable + ? runner.execute() + : needsScratchpad(connLabel, runner.execute()); } export function rerunQuery(rerunQueryElement: QueryHistory) { @@ -1097,36 +1159,7 @@ export function copyQuery(queryHistoryElement: QueryHistory) { typeof queryHistoryElement.query === "string" ) { env.clipboard.writeText(queryHistoryElement.query); - window.showInformationMessage("Query copied to clipboard."); - } -} - -export async function loadServerObjects(): Promise { - // check for valid connection - if ( - ext.activeConnection === undefined || - ext.activeConnection.connected === false || - ext.activeConnection instanceof InsightsConnection - ) { - window.showInformationMessage( - "Please connect to a KDB instance to view the objects", - ); - return new Array(); - } - - const script = readFileSync( - ext.context.asAbsolutePath(join("resources", "list_mem.q")), - ).toString(); - const cc = "\n" + script + "(::)"; - const result = await ext.activeConnection?.executeQueryRaw(cc); - if (result !== undefined) { - const result2: ServerObject[] = (0, eval)(result); // eval(result); - const result3: ServerObject[] = result2.filter((item) => { - return ext.qNamespaceFilters.indexOf(item.name) === -1; - }); - return result3; - } else { - return new Array(); + notify("Query copied to clipboard.", MessageKind.INFO, { logger }); } } @@ -1145,7 +1178,9 @@ export async function openMeta(node: MetaObjectPayloadNode | InsightsMetaNode) { viewColumn: ViewColumn.One, }); } else { - kdbOutputLog("[META] Meta content not found", "ERROR"); + notify("Meta content not found.", MessageKind.ERROR, { + logger, + }); } } @@ -1157,10 +1192,9 @@ export async function exportConnections(connLabel?: string) { }); if (!exportAuth) { - kdbOutputLog( - "[EXPORT CONNECTIONS] Export operation was cancelled by the user", - "INFO", - ); + notify("Export operation was cancelled by the user.", MessageKind.DEBUG, { + logger, + }); return; } @@ -1181,11 +1215,9 @@ export async function exportConnections(connLabel?: string) { if (uri) { fs.writeFile(uri.fsPath, formattedDoc, (err) => { if (err) { - kdbOutputLog( - `[EXPORT CONNECTIONS] Error saving file: ${err.message}`, - "ERROR", - ); - window.showErrorMessage(`Error saving file: ${err.message}`); + notify(`Error saving file: ${err.message}`, MessageKind.ERROR, { + logger, + }); } else { workspace.openTextDocument(uri).then((document) => { window.showTextDocument(document, { preview: false }); @@ -1193,16 +1225,14 @@ export async function exportConnections(connLabel?: string) { } }); } else { - kdbOutputLog( - "[EXPORT CONNECTIONS] Save operation was cancelled by the user", - "INFO", - ); + notify("Save operation was cancelled by the user.", MessageKind.DEBUG, { + logger, + }); } } else { - kdbOutputLog( - "[EXPORT CONNECTIONS] No connections found to be exported", - "ERROR", - ); + notify("No connections found to be exported.", MessageKind.ERROR, { + logger, + }); } } @@ -1279,9 +1309,11 @@ export async function writeQueryResultsToView( if (typeof result === "string") { const res = decodeQUTF(result); if (res.startsWith(queryConstants.error)) { - Telemetry.sendEvent( - telemetryBaseMsg + ".Execute" + telemetryLangType + ".Error", - ); + notify("Telemetry", MessageKind.DEBUG, { + logger, + telemetry: + telemetryBaseMsg + ".Execute" + telemetryLangType + ".Error", + }); isSuccess = false; } } @@ -1310,16 +1342,18 @@ export async function writeScratchpadResult( isWorkbook: boolean, duration: string, connVersion: number, -): Promise { +): Promise { const telemetryLangType = isPython ? ".Python" : ".q"; const telemetryBaseMsg = isWorkbook ? "Workbook" : "Scratchpad"; let errorMsg; if (result.error) { errorMsg = "Error: " + result.errorMsg; - Telemetry.sendEvent( - telemetryBaseMsg + ".Execute" + telemetryLangType + ".Error", - ); + + notify("Scratchpad query returned error", MessageKind.DEBUG, { + logger, + telemetry: telemetryBaseMsg + ".Execute" + telemetryLangType + ".Error", + }); if (result.stacktrace) { errorMsg = @@ -1331,6 +1365,10 @@ export async function writeScratchpadResult( } } + if (executorName.endsWith(".kxnb")) { + return errorMsg ?? result; + } + if (ext.isResultsTabVisible) { await writeQueryResultsToView( errorMsg ?? result, diff --git a/src/commands/walkthroughCommand.ts b/src/commands/walkthroughCommand.ts index 68a95a37b..1e04f8d23 100644 --- a/src/commands/walkthroughCommand.ts +++ b/src/commands/walkthroughCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,13 @@ * specific language governing permissions and limitations under the License. */ -import { window, workspace } from "vscode"; +import { workspace } from "vscode"; + +import { MessageKind, notify } from "../utils/notifications"; export async function showInstallationDetails(): Promise { const QHOME = await workspace .getConfiguration() .get("kdb.qHomeDirectory"); - window.showInformationMessage(`q runtime installed path: ${QHOME}`); + notify(`q runtime installed path: ${QHOME}`, MessageKind.INFO); } diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index 06481fa4d..4dc4a04e3 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -16,12 +16,13 @@ import { CodeLens, CodeLensProvider, Command, - ProgressLocation, + NotebookCell, + QuickPickItem, + QuickPickItemKind, Range, StatusBarAlignment, TextDocument, TextEditor, - ThemeColor, Uri, window, workspace, @@ -29,13 +30,25 @@ import { import { ext } from "../extensionVariables"; import { resetScratchpad, runQuery } from "./serverCommand"; +import { InsightsConnection } from "../classes/insightsConnection"; +import { LocalConnection } from "../classes/localConnection"; +import { ReplConnection } from "../classes/replConnection"; import { ExecutionTypes } from "../models/execution"; import { MetaDap } from "../models/meta"; import { ConnectionManagementService } from "../services/connectionManagerService"; import { InsightsNode, KdbNode, LabelNode } from "../services/kdbTreeProvider"; -import { kdbOutputLog, offerConnectAction } from "../utils/core"; +import { updateCellMetadata } from "../services/notebookProviders"; +import { getBasename, offerConnectAction } from "../utils/core"; import { importOldDsFiles, oldFilesExists } from "../utils/dataSource"; -import { normalizeAssemblyTarget } from "../utils/shared"; +import { MessageKind, notify, Runner } from "../utils/notifications"; +import { + cleanAssemblyName, + cleanDapName, + errorMessage, + normalizeAssemblyTarget, +} from "../utils/shared"; + +const logger = "workspaceCommand"; function setRealActiveTextEditor(editor?: TextEditor | undefined) { if (editor) { @@ -48,6 +61,30 @@ function setRealActiveTextEditor(editor?: TextEditor | undefined) { } } +/* c8 ignore next */ +function activeEditorChanged(editor?: TextEditor | undefined) { + setRealActiveTextEditor(editor); + const item = ext.runScratchpadItem; + if (ext.activeTextEditor) { + const uri = ext.activeTextEditor.document.uri; + const server = getServerForUri(uri); + if (server) { + setRunScratchpadItemText(uri, server); + item.show(); + } else { + item.hide(); + } + } else { + item.hide(); + } +} + +/* c8 ignore next */ +function setRunScratchpadItemText(uri: Uri, text: string) { + ext.runScratchpadItem.text = `$(cloud) ${text}`; + ext.runScratchpadItem.tooltip = `KX: Choose connection for '${getBasename(uri)}'`; +} + export function getInsightsServers() { const conf = workspace.getConfiguration("kdb"); const servers = conf.get<{ [key: string]: { alias: string } }>( @@ -116,8 +153,15 @@ export async function setServerForUri(uri: Uri, server: string | undefined) { "connectionMap", {}, ); - map[relativePath(uri)] = server; - await conf.update("connectionMap", map); + const relative = relativePath(uri); + if (relative.startsWith("/")) { + notify(`Document (${uri.path}) is not in workspace.`, MessageKind.ERROR, { + logger, + }); + } else { + map[relative] = server; + await conf.update("connectionMap", map); + } } export function getServerForUri(uri: Uri) { @@ -130,7 +174,9 @@ export function getServerForUri(uri: Uri) { const server = map[relativePath(uri)]; const servers = getServers(); - return server && servers.includes(server) ? server : undefined; + return server && (server === ext.REPL || servers.includes(server)) + ? server + : undefined; } export async function setTargetForUri(uri: Uri, target: string | undefined) { @@ -165,8 +211,14 @@ export async function pickConnection(uri: Uri) { const server = getServerForUri(uri); const servers = getServers(); - let picked = await window.showQuickPick(["(none)", ...servers], { - title: "Choose a connection", + const items = ["(none)"]; + if (isQ(uri)) { + items.push(ext.REPL); + } + items.push(...servers); + + let picked = await window.showQuickPick(items, { + title: `Choose Connection (${getBasename(uri)})`, placeHolder: server, }); @@ -175,66 +227,210 @@ export async function pickConnection(uri: Uri) { picked = undefined; await setTargetForUri(uri, undefined); } + if (picked) { + setRunScratchpadItemText(uri, picked); + ext.runScratchpadItem.show(); + } else { + ext.runScratchpadItem.hide(); + } await setServerForUri(uri, picked); } - return picked; } -export async function pickTarget(uri: Uri) { - const server = getServerForUri(uri); - if (!server) { - return; +export async function pickTarget(uri: Uri, cell?: NotebookCell) { + const conn = await findConnection(uri); + const isInsights = conn instanceof InsightsConnection; + + let daps: MetaDap[] = []; + + if (isInsights) { + const connMngService = new ConnectionManagementService(); + daps = JSON.parse( + connMngService.retrieveMetaContent(conn.connLabel, "DAP"), + ); } - const conn = await getConnectionForServer(server); - const isInsights = conn instanceof InsightsNode; - if (!isInsights) { - return; + const target = cell?.metadata.target || getTargetForUri(uri); + + if (target && !targetExists(target, daps) && !conn) { + daps.unshift(createMetaDapFromTarget(target)); } - let daps: MetaDap[] = []; + const tierItems = buildTierOptionsWithSeparators(daps); + const defaultOption = isInsights ? "scratchpad" : "default"; - if (!isPython(uri)) { - const connMngService = new ConnectionManagementService(); - const connected = connMngService.isConnected(conn.label); - if (connected) { - daps = JSON.parse(connMngService.retrieveMetaContent(conn.label, "DAP")); + const items: QuickPickItem[] = [{ label: defaultOption }]; + + if (tierItems.length > 0) { + items.push(...tierItems); + } + + const picked = await window.showQuickPick(items, { + title: `Choose Execution Target (${conn?.connLabel ?? "Not Connected"})`, + placeHolder: target || defaultOption, + }); + + let selectedValue = picked?.label; + + if (selectedValue) { + if (selectedValue === "scratchpad" || selectedValue === "default") { + selectedValue = undefined; + } + + if (cell) { + await updateCellMetadata(cell, { + target: selectedValue, + variable: selectedValue && cell.metadata.variable, + }); } else { - offerConnectAction(server); + await setTargetForUri(uri, selectedValue); } } - const target = getTargetForUri(uri); - if (target) { - const exists = daps.some( - (value) => `${value.assembly} ${value.instance}` === target, - ); - if (!exists) { - const [assembly, instance] = target.split(/\s+/); - daps.push({ assembly, instance } as MetaDap); + return selectedValue; +} + +function createTierKey(dap: MetaDap): string { + const cleanedAssembly = cleanAssemblyName(dap.assembly); + return `${cleanedAssembly} ${dap.instance}`; +} + +function targetExists(target: string, daps: MetaDap[]): boolean { + return daps.some((dap) => { + const tierKey = createTierKey(dap); + const processKey = createProcessKey(dap); + return tierKey === target || processKey === target; + }); +} + +function createMetaDapFromTarget(target: string): MetaDap { + const parts = target.split(/\s+/); + const [assembly, instance, ...dapParts] = parts; + + return dapParts.length > 0 + ? ({ assembly, instance, dap: dapParts.join(" ") } as MetaDap) + : ({ assembly, instance } as MetaDap); +} + +// TODO: Remove it if this don't going to be used from 1.14 +// Options separated by ties and DAP processes +// function buildTierOptionsWithSeparators(daps: MetaDap[]): QuickPickItem[] { +// const items: QuickPickItem[] = []; + +// const tierSet = new Set(); +// const processItems: QuickPickItem[] = []; + +// daps.forEach((dap) => { +// const cleanedAssembly = cleanAssemblyName(dap.assembly); +// const tierKey = `${cleanedAssembly} ${dap.instance}`; +// tierSet.add(tierKey); + +// if (dap.dap) { +// const cleanedDapName = cleanDapName(dap.dap); +// processItems.push({ label: `${tierKey} ${cleanedDapName}` }); +// } +// }); + +// if (tierSet.size > 0) { +// items.push({ +// kind: QuickPickItemKind.Separator, +// label: "Tiers", +// }); + +// const sortedTiers = Array.from(tierSet).sort((a, b) => a.localeCompare(b)); +// sortedTiers.forEach((tier) => { +// items.push({ label: tier }); +// }); +// } + +// if (processItems.length > 0) { +// items.push({ +// kind: QuickPickItemKind.Separator, +// label: "DAP Processes", +// }); + +// const sortedProcessItems = processItems +// .slice() +// .sort((a, b) => a.label.localeCompare(b.label)); +// sortedProcessItems.forEach((item) => { +// items.push(item); +// }); +// } + +// return items; +// } + +// Options separated by Assembly +function buildTierOptionsWithSeparators(daps: MetaDap[]): QuickPickItem[] { + const assemblyMap = new Map>(); + + daps.forEach((dap) => { + if (!assemblyMap.has(dap.assembly)) { + assemblyMap.set(dap.assembly, new Map()); } - } - let picked = await window.showQuickPick( - [ - "scratchpad", - ...daps.map((value) => `${value.assembly} ${value.instance}`), - ], - { - title: `Choose a target on ${server}`, - placeHolder: target, - }, - ); + const tierKey = `${cleanAssemblyName(dap.assembly)} ${dap.instance}`; + const tierMap = assemblyMap.get(dap.assembly)!; + const cleanedDap = { ...dap }; - if (picked) { - if (picked === "scratchpad") { - picked = undefined; + if (!tierMap.has(tierKey)) { + tierMap.set(tierKey, []); + } + if (cleanedDap.dap) { + cleanedDap.dap = cleanDapName(cleanedDap.dap); } - await setTargetForUri(uri, picked); - } - return picked; + tierMap.get(tierKey)!.push(cleanedDap); + }); + + const items: QuickPickItem[] = []; + const sortedAssemblies = Array.from(assemblyMap.keys()).sort((a, b) => + a.localeCompare(b), + ); + + sortedAssemblies.forEach((assembly) => { + items.push({ + kind: QuickPickItemKind.Separator, + label: `${assembly}`, + }); + + const tierMap = assemblyMap.get(assembly)!; + const sortedTierKeys = Array.from(tierMap.keys()).sort((a, b) => + a.localeCompare(b), + ); + + sortedTierKeys.forEach((tierKey) => { + const processes = tierMap.get(tierKey)!; + + items.push({ label: tierKey }); + + const sortedProcesses = processes + .filter((process) => process.dap) + .sort((a, b) => a.dap!.localeCompare(b.dap!)); + + sortedProcesses.forEach((process) => { + items.push({ label: `${tierKey} ${process.dap}` }); + }); + }); + }); + + return items; +} + +function createProcessKey(dap: MetaDap): string | null { + if (!dap.dap) return null; + + const cleanedDapName = cleanDapName(dap.dap); + return `${createTierKey(dap)} ${cleanedDapName}`; +} + +function isSql(uri: Uri | undefined) { + return uri && uri.path.endsWith(".sql"); +} + +function isQ(uri: Uri | undefined) { + return uri && uri.path.endsWith(".q"); } function isPython(uri: Uri | undefined) { @@ -249,50 +445,116 @@ function isDataSource(uri: Uri | undefined) { return uri && uri.path.endsWith(".kdb.json"); } -/* istanbul ignore next */ function isKxFolder(uri: Uri | undefined) { return uri && Path.basename(uri.path) === ".kx"; } +export async function startRepl() { + try { + ReplConnection.getOrCreateInstance().start(); + } catch (error) { + notify(errorMessage(error), MessageKind.ERROR, { + logger, + params: error, + }); + } +} + +export async function runOnRepl(editor: TextEditor, type?: ExecutionTypes) { + const basename = getBasename(editor.document.uri); + + let text: string; + + if (type === ExecutionTypes.QueryFile) { + text = editor.document.getText(); + } else if (type === ExecutionTypes.QuerySelection) { + const selection = editor.selection; + text = selection.isEmpty + ? editor.document.lineAt(selection.active.line).text + : editor.document.getText(selection); + } else { + notify( + `Executing ${basename} on ${ext.REPL} is not supported.`, + MessageKind.ERROR, + { logger }, + ); + return; + } + + try { + const runner = Runner.create(async () => { + ReplConnection.getOrCreateInstance().executeQuery(text); + }); + runner.title = `Executing ${basename} on ${ext.REPL}.`; + await runner.execute(); + } catch (error) { + notify(errorMessage(error), MessageKind.ERROR, { + logger, + params: error, + }); + } +} + export async function runActiveEditor(type?: ExecutionTypes) { if (ext.activeTextEditor) { - const connMngService = new ConnectionManagementService(); const uri = ext.activeTextEditor.document.uri; + if (getServerForUri(uri) === ext.REPL) { + runOnRepl(ext.activeTextEditor, type); + return; + } + const conn = await findConnection(uri); + if (!conn) { + return; + } - let isInsights = false; - let server = getServerForUri(uri) || ""; + const isInsights = conn instanceof InsightsConnection; + const executorName = getBasename(ext.activeTextEditor.document.uri); + const target = isInsights ? getTargetForUri(uri) : undefined; + const isSql = executorName.endsWith(".sql"); - if (server) { - const conn = await getConnectionForServer(server); - if (conn) { - isInsights = conn instanceof InsightsNode; - server = conn.label; - if (!connMngService.isConnected(server)) { - offerConnectAction(server); - return; - } - } else { - throw new Error("Connection for not found"); - } + if (isSql && !isInsights) { + notify( + `SQL execution is not supported on ${conn.connLabel}.`, + MessageKind.ERROR, + { logger }, + ); + return; } - const executorName = - ext.activeTextEditor.document.fileName.split("/").pop() || ""; - - const target = isInsights ? getTargetForUri(uri) : undefined; + if (type === ExecutionTypes.PopulateScratchpad && !isInsights) { + notify( + `Populating scratchpad is not supported on ${conn.connLabel}.`, + MessageKind.ERROR, + { logger }, + ); + return; + } - runQuery( - type === undefined - ? isPython(uri) - ? ExecutionTypes.PythonQueryFile - : ExecutionTypes.QueryFile - : type, - server, - executorName, - !isPython(uri), - undefined, - target, - ); + try { + await runQuery( + type === undefined + ? isPython(uri) + ? ExecutionTypes.PythonQueryFile + : ExecutionTypes.QueryFile + : type, + conn.connLabel, + executorName, + !isPython(uri), + undefined, + target, + isSql, + conn instanceof InsightsConnection, + ); + } catch (error) { + notify( + `Executing ${executorName} on ${conn.connLabel} failed.`, + MessageKind.ERROR, + { + logger, + params: error, + }, + ); + } } } @@ -335,7 +597,7 @@ export class ConnectionLensProvider implements CodeLensProvider { if (server) { const conn = await getConnectionForServer(server); - if (conn instanceof InsightsNode) { + if (!isSql(document.uri) && conn instanceof InsightsNode) { const pickTarget = new CodeLens(top, { command: "kdb.file.pickTarget", title: target || "scratchpad", @@ -353,20 +615,17 @@ export function connectWorkspaceCommands() { StatusBarAlignment.Right, 10000, ); - ext.runScratchpadItem.backgroundColor = new ThemeColor( - "statusBarItem.warningBackground", - ); ext.runScratchpadItem.command = { - title: "", - command: "kdb.scratchpad.run", + title: "Choose Connection", + command: "kdb.file.pickConnection", arguments: [], }; - const watcher = workspace.createFileSystemWatcher("**/*.{kdb.json,q,py}"); + const watcher = workspace.createFileSystemWatcher("**/*.{kdb.json,q,py,sql}"); watcher.onDidCreate(update); watcher.onDidDelete(update); - /* istanbul ignore next */ + /* c8 ignore next */ workspace.onDidDeleteFiles((event) => { for (const uri of event.files) { if (isKxFolder(uri)) { @@ -377,7 +636,7 @@ export function connectWorkspaceCommands() { } }); - /* istanbul ignore next */ + /* c8 ignore next */ workspace.onDidRenameFiles(async (event) => { for (const { oldUri, newUri } of event.files) { await setServerForUri(newUri, getServerForUri(oldUri)); @@ -387,13 +646,13 @@ export function connectWorkspaceCommands() { } }); - /* istanbul ignore next */ + /* c8 ignore next */ workspace.onDidChangeWorkspaceFolders(() => { ext.dataSourceTreeProvider.reload(); ext.scratchpadTreeProvider.reload(); }); - window.onDidChangeActiveTextEditor(setRealActiveTextEditor); - setRealActiveTextEditor(window.activeTextEditor); + window.onDidChangeActiveTextEditor(activeEditorChanged); + activeEditorChanged(window.activeTextEditor); } export function checkOldDatasourceFiles() { @@ -404,37 +663,54 @@ export async function importOldDSFiles() { if (ext.oldDSformatExists) { const folders = workspace.workspaceFolders; if (!folders) { - window.showErrorMessage( - "No workspace folder found. Please open a workspace folder.", - ); + notify("No workspace folder found.", MessageKind.ERROR, { logger }); return; } - return await window.withProgress( - { - location: ProgressLocation.Notification, - cancellable: false, - }, - async (progress, token) => { - token.onCancellationRequested(() => { - kdbOutputLog( - "[DATASOURCE] User cancelled the old DS files import.", - "INFO", - ); - return false; + const runner = Runner.create(async (_, token) => { + token.onCancellationRequested(() => { + notify("User cancelled the old DS files import.", MessageKind.DEBUG, { + logger, }); + return false; + }); - progress.report({ message: "Importing old DS files..." }); - await importOldDsFiles(); + await importOldDsFiles(); + }); + runner.title = "Importing old DS files."; + return await runner.execute(); + } else { + notify("No old Datasource files found on your VSCODE.", MessageKind.INFO, { + logger, + }); + } +} + +export async function findConnection(uri: Uri) { + const connMngService = new ConnectionManagementService(); + + let conn: InsightsConnection | LocalConnection | undefined; + let server = getServerForUri(uri) ?? ""; + + if (server) { + const node = await getConnectionForServer(server); + if (node) { + server = node.label; + conn = connMngService.retrieveConnectedConnection(server); + if (conn === undefined) { + offerConnectAction(server); return; - }, - ); + } + } else { + notify(`Connection ${server} not found.`, MessageKind.ERROR, { + logger, + }); + return; + } + } else if (ext.activeConnection) { + conn = ext.activeConnection; } else { - window.showInformationMessage( - "No old Datasource files found on your VSCODE.", - ); - kdbOutputLog( - "[DATASOURCE] No old Datasource files found on your VSCODE.", - "INFO", - ); + offerConnectAction(); + return; } + return conn; } diff --git a/src/extension.ts b/src/extension.ts index e446c81ba..e06991138 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -62,6 +62,7 @@ import { resetScratchpadFromEditor, runActiveEditor, setServerForUri, + startRepl, } from "./commands/workspaceCommand"; import { ext } from "./extensionVariables"; import { CommandRegistration } from "./models/commandRegistration"; @@ -86,6 +87,10 @@ import { MetaObjectPayloadNode, } from "./services/kdbTreeProvider"; import { KxNotebookController } from "./services/notebookController"; +import { + inputVariable, + KxNotebookTargetActionProvider, +} from "./services/notebookProviders"; import { KxNotebookSerializer } from "./services/notebookSerializer"; import { QueryHistoryProvider, @@ -113,23 +118,22 @@ import { getServers, hasWorkspaceOrShowOption, initializeLocalServers, - kdbOutputLog, } from "./utils/core"; import { runQFileTerminal } from "./utils/execution"; import { handleFeedbackSurvey } from "./utils/feedbackSurveyUtils"; import { getIconPath } from "./utils/iconsUtils"; +import { MessageKind, notify, Runner } from "./utils/notifications"; import AuthSettings from "./utils/secretStorage"; import { Telemetry } from "./utils/telemetryClient"; -import { - activateTextDocument, - addWorkspaceFile, - openWith, - setUriContent, -} from "./utils/workspace"; +import { addWorkspaceFile, openWith, setUriContent } from "./utils/workspace"; + +const logger = "extension"; let client: LanguageClient; export async function activate(context: vscode.ExtensionContext) { + // TODO 2: Workaround, remove when TODO 1 is complete + ext.REAL_QHOME = process.env.QHOME; ext.context = context; ext.outputChannel = vscode.window.createOutputChannel("kdb"); ext.openSslVersion = await checkOpenSslInstalled(); @@ -195,15 +199,11 @@ export async function activate(context: vscode.ExtensionContext) { AuthSettings.init(context); ext.secretSettings = AuthSettings.instance; - vscode.commands.executeCommand("kdb-results.focus"); - - kdbOutputLog("kdb extension is now active!", "INFO"); - try { // check for installed q runtime await checkLocalInstall(true); } catch (err) { - vscode.window.showErrorMessage(`${err}`); + notify(`${err}`, MessageKind.DEBUG, { logger }); } registerAllExtensionCommands(); @@ -219,7 +219,7 @@ export async function activate(context: vscode.ExtensionContext) { ChartEditorProvider.register(context), vscode.languages.registerCodeLensProvider( - { pattern: "**/*.{q,py}" }, + { pattern: "**/*.{q,py,sql}" }, new ConnectionLensProvider(), ), @@ -281,6 +281,13 @@ export async function activate(context: vscode.ExtensionContext) { new KxNotebookController(), ); + context.subscriptions.push( + vscode.notebooks.registerNotebookCellStatusBarItemProvider( + "kx-notebook", + new KxNotebookTargetActionProvider(), + ), + ); + connectWorkspaceCommands(); await connectBuildTools(); @@ -298,7 +305,11 @@ export async function activate(context: vscode.ExtensionContext) { const clientOptions: LanguageClientOptions = { documentSelector: [{ language: "q" }], synchronize: { - fileEvents: vscode.workspace.createFileSystemWatcher("**/*.{q,quke}"), + fileEvents: vscode.workspace.createFileSystemWatcher( + "**/*.{q,quke,k}", + true, + true, + ), }, }; @@ -309,11 +320,16 @@ export async function activate(context: vscode.ExtensionContext) { clientOptions, ); + context.subscriptions.push( + client.onNotification("notify", (params) => + notify(params.message, params.kind, params.options, params.telemetry), + ), + ); + await client.start(); connectClientCommands(context, client); - Telemetry.sendEvent("Extension.Activated"); const yamlExtension = vscode.extensions.getExtension("redhat.vscode-yaml"); if (yamlExtension) { const actualSchema = await vscode.workspace @@ -346,11 +362,19 @@ export async function activate(context: vscode.ExtensionContext) { if (authExtension) { const api = await authExtension.activate(); if ("auth" in api) { - Telemetry.sendEvent("CustomAuth.Extension.Actived"); + notify("Custom authentication activated.", MessageKind.DEBUG, { + logger, + telemetry: "CustomAuth.Extension.Actived", + }); ext.customAuth = api; } } handleFeedbackSurvey(); + + notify("kdb extension is now active.", MessageKind.DEBUG, { + logger, + telemetry: "Extension.Activated", + }); } function registerHelpCommands(): CommandRegistration[] { @@ -364,7 +388,10 @@ function registerHelpCommands(): CommandRegistration[] { "KX.kdb", ) .then(undefined, () => { - Telemetry.sendEvent("Help&Feedback.Open.ExtensionDocumentation"); + notify("Help&Feedback documentation selected.", MessageKind.DEBUG, { + logger, + telemetry: "Help&Feedback.Open.ExtensionDocumentation", + }); vscode.commands.executeCommand("extension.open", "KX.kdb"); }); }, @@ -372,21 +399,30 @@ function registerHelpCommands(): CommandRegistration[] { { command: "kdb.help.suggestFeature", callback: () => { - Telemetry.sendEvent("Help&Feedback.Open.SuggestFeature"); + notify("Help&Feedback suggest a feature selected.", MessageKind.DEBUG, { + logger, + telemetry: "Help&Feedback.Open.SuggestFeature", + }); vscode.env.openExternal(vscode.Uri.parse(ext.urlLinks.suggestFeature)); }, }, { command: "kdb.help.provideFeedback", callback: () => { - Telemetry.sendEvent("Help&Feedback.Open.Survey"); + notify("Help&Feedback survey selected.", MessageKind.DEBUG, { + logger, + telemetry: "Help&Feedback.Open.Survey", + }); vscode.env.openExternal(vscode.Uri.parse(ext.urlLinks.survey)); }, }, { command: "kdb.help.reportBug", callback: () => { - Telemetry.sendEvent("Help&Feedback.Open.ReportBug"); + notify("Help&Feedback report a bug selected.", MessageKind.DEBUG, { + logger, + telemetry: "Help&Feedback.Open.ReportBug", + }); vscode.env.openExternal(vscode.Uri.parse(ext.urlLinks.reportBug)); }, }, @@ -512,10 +548,7 @@ function registerScratchpadCommands(): CommandRegistration[] { }, { command: "kdb.scratchpad.python.run.file", - callback: async (item) => { - if (item instanceof vscode.Uri) { - await activateTextDocument(item); - } + callback: async () => { await runActiveEditor(ExecutionTypes.PythonQueryFile); }, }, @@ -644,30 +677,37 @@ function registerConnectionsCommands(): CommandRegistration[] { { command: "kdb.connections.export.all", callback: () => { - Telemetry.sendEvent("Connections.Export.All"); + notify("Export all conections.", MessageKind.DEBUG, { + logger, + telemetry: "Connections.Export.All", + }); exportConnections(); }, }, { command: "kdb.connections.export.single", callback: async (viewItem: KdbNode | InsightsNode) => { - Telemetry.sendEvent("Connections.Export.Single"); + notify("Export single conection.", MessageKind.DEBUG, { + logger, + telemetry: "Connections.Export.Single", + }); exportConnections(viewItem.label); }, }, { command: "kdb.connections.import", callback: async () => { - Telemetry.sendEvent("Connections.Import"); + notify("Import conections.", MessageKind.DEBUG, { + logger, + telemetry: "Connections.Import", + }); await importConnections(); }, }, { command: "kdb.connections.content.selectView", callback: async (viewItem) => { - const connLabel = viewItem.connLabel - ? viewItem.connLabel.split("[")[1].split("]")[0] - : undefined; + const connLabel = viewItem.connLabel; if (connLabel) { const executorName = viewItem.coreIcon.substring(2); executeQuery( @@ -680,7 +720,9 @@ function registerConnectionsCommands(): CommandRegistration[] { true, ); } else { - kdbOutputLog("Connection label not found", "ERROR"); + notify("Connection label not found", MessageKind.ERROR, { + logger, + }); } }, }, @@ -754,14 +796,22 @@ function registerConnectionsCommands(): CommandRegistration[] { { command: "kdb.connections.refresh.serverObjects", callback: async () => { - ext.serverProvider.reload(); - await refreshGetMeta(); + const runner = Runner.create(() => { + ext.serverProvider.reload(); + return refreshGetMeta(); + }); + runner.location = vscode.ProgressLocation.Notification; + runner.title = "Refreshing server objects for all connections."; + await runner.execute(); }, }, { command: "kdb.connections.refresh.meta", callback: async (viewItem: InsightsNode) => { - await refreshGetMeta(viewItem.label); + const runner = Runner.create(() => refreshGetMeta(viewItem.label)); + runner.location = vscode.ProgressLocation.Notification; + runner.title = `Refreshing meta data for ${viewItem.label || "all connections"}.`; + await runner.execute(); }, }, { @@ -841,10 +891,7 @@ function registerExecuteCommands(): CommandRegistration[] { }, { command: "kdb.execute.fileQuery", - callback: async (item) => { - if (item instanceof vscode.Uri) { - await activateTextDocument(item); - } + callback: async () => { await runActiveEditor(ExecutionTypes.QueryFile); }, }, @@ -858,6 +905,12 @@ function registerExecuteCommands(): CommandRegistration[] { } }, }, + { + command: "kdb.start.repl", + callback: () => { + startRepl(); + }, + }, { command: "kdb.execute.terminal.run", callback: async () => { @@ -874,7 +927,10 @@ function registerExecuteCommands(): CommandRegistration[] { ); runQFileTerminal(`"${uri.fsPath}"`); } catch (error) { - kdbOutputLog(`Unable to write temp file: ${error}`, "ERROR"); + notify(`Unable to write temp file.`, MessageKind.ERROR, { + logger, + params: error, + }); } } }, @@ -931,13 +987,25 @@ function registerFileCommands(): CommandRegistration[] { }, { command: "kdb.file.pickTarget", - callback: async () => { + callback: async (cell?: vscode.NotebookCell) => { const editor = ext.activeTextEditor; if (editor) { - await pickTarget(editor.document.uri); + await pickTarget(editor.document.uri, cell); } }, }, + { + command: "kdb.file.inputVariable", + callback: async (cell: vscode.NotebookCell) => { + await inputVariable(cell); + }, + }, + { + command: "kdb.file.populateScratchpad", + callback: async () => { + await runActiveEditor(ExecutionTypes.PopulateScratchpad); + }, + }, ]; return fileCommands; @@ -983,11 +1051,19 @@ function registerNotebookCommands(): CommandRegistration[] { { command: "kdb.createNotebook", callback: async () => { - if (hasWorkspaceOrShowOption("adding notebook")) { - const uri = await addWorkspaceFile(undefined, "notebook", ".kxnb"); - const notebook = await vscode.workspace.openNotebookDocument(uri); - await vscode.window.showNotebookDocument(notebook); - } + const notebook = await vscode.workspace.openNotebookDocument( + "kx-notebook", + { + cells: [ + { + kind: 2, + value: "", + languageId: "q", + }, + ], + }, + ); + await vscode.window.showNotebookDocument(notebook); }, }, ]; diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index e9e3e2130..90ba7f6e0 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -44,9 +44,11 @@ import AuthSettings from "./utils/secretStorage"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { + export let REAL_QHOME: string | undefined; export const EXTENSION_VERSION = extensions.getExtension("KX.kdb")?.packageJSON.version || "unknown"; export const isRCExtension = EXTENSION_VERSION.includes("rc"); + export const REPL = "REPL"; export let activeTextEditor: TextEditor | undefined; export let context: ExtensionContext; export let outputChannel: OutputChannel; @@ -76,7 +78,6 @@ export namespace ext { export const connectedContextStrings: Array = []; export const queryHistoryAvailableToCopy: Array = []; export const connectionsList: Array = []; - export let hideDetailedConsoleQueryOutput: boolean; export let networkChangesWatcher: boolean; export let insightsHydrate: boolean; export let connectionNode: KdbNode | InsightsNode | undefined; @@ -108,6 +109,7 @@ export namespace ext { export const jsonTypes = new Set([ 0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 77, 98, 99, ]); + export const scratchpadStarted = new Set(); export let secretSettings: AuthSettings; @@ -142,9 +144,7 @@ export namespace ext { const extensionId = "kx.kdb"; const packageJSON = extensions.getExtension(extensionId)!.packageJSON; - export const extensionName = packageJSON.name; - export const extensionVersion = packageJSON.version; - export const extensionKey = packageJSON.aiKey; + export const extAIConnString = packageJSON.aiConnString; export const localhost = "127.0.0.1"; export const networkProtocols = { diff --git a/src/ipc/F64.ts b/src/ipc/F64.ts index 0fcbdd6a4..b03a4e119 100644 --- a/src/ipc/F64.ts +++ b/src/ipc/F64.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/I32.ts b/src/ipc/I32.ts index 7d46d5949..7744b2b9c 100644 --- a/src/ipc/I32.ts +++ b/src/ipc/I32.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/I64.ts b/src/ipc/I64.ts index da882f453..7e075dfd7 100644 --- a/src/ipc/I64.ts +++ b/src/ipc/I64.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/Op.ts b/src/ipc/Op.ts index 2e7935bf0..5b01cf262 100644 --- a/src/ipc/Op.ts +++ b/src/ipc/Op.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QBoolean.ts b/src/ipc/QBoolean.ts index 546ac1fae..d079358d0 100644 --- a/src/ipc/QBoolean.ts +++ b/src/ipc/QBoolean.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,8 +11,8 @@ * specific language governing permissions and limitations under the License. */ -import U8 from "./U8"; import { TypeBase, TypeNum } from "./typeBase"; +import U8 from "./U8"; export default class QBoolean extends U8 { static readonly typeNum = 1; diff --git a/src/ipc/QByte.ts b/src/ipc/QByte.ts index d3493ba2c..92bb8fa51 100644 --- a/src/ipc/QByte.ts +++ b/src/ipc/QByte.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,8 +11,8 @@ * specific language governing permissions and limitations under the License. */ -import U8 from "./U8"; import { TypeBase, TypeNum } from "./typeBase"; +import U8 from "./U8"; export default class QByte extends U8 { static readonly typeNum = 4; diff --git a/src/ipc/QChar.ts b/src/ipc/QChar.ts index ebf7fca16..c4c3a70d5 100644 --- a/src/ipc/QChar.ts +++ b/src/ipc/QChar.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,9 +11,9 @@ * specific language governing permissions and limitations under the License. */ -import U8 from "./U8"; import { u16u8, u8u16 } from "./c"; import { TypeBase, TypeNum } from "./typeBase"; +import U8 from "./U8"; export default class QChar extends U8 { static readonly typeNum = 10; diff --git a/src/ipc/QDate.ts b/src/ipc/QDate.ts index ed87b4251..2e7056860 100644 --- a/src/ipc/QDate.ts +++ b/src/ipc/QDate.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -12,10 +12,11 @@ */ import moment from "moment"; -import I32 from "./I32"; -import QInt from "./QInt"; + import { DDateClass } from "./cClasses"; import Constants from "./constants"; +import I32 from "./I32"; +import QInt from "./QInt"; import { TypeBase, TypeNum } from "./typeBase"; export default class QDate extends I32 { diff --git a/src/ipc/QDateTime.ts b/src/ipc/QDateTime.ts index 32d1b9287..7d69645aa 100644 --- a/src/ipc/QDateTime.ts +++ b/src/ipc/QDateTime.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import F64 from "./F64"; -import QDouble from "./QDouble"; import { DDateTimeClass } from "./cClasses"; import Constants from "./constants"; +import F64 from "./F64"; import moment from "./moment.custom"; +import QDouble from "./QDouble"; import Tools from "./tools"; import { TypeBase, TypeNum } from "./typeBase"; diff --git a/src/ipc/QDict.ts b/src/ipc/QDict.ts index 0310eceb2..9ffa9a86c 100644 --- a/src/ipc/QDict.ts +++ b/src/ipc/QDict.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,9 +11,9 @@ * specific language governing permissions and limitations under the License. */ +import { DCDS } from "./c"; import QSymbol from "./QSymbol"; import QTable from "./QTable"; -import { DCDS } from "./c"; import Tools from "./tools"; import { TypeBase, TypeNum } from "./typeBase"; diff --git a/src/ipc/QDouble.ts b/src/ipc/QDouble.ts index 560ffc523..cc9d8acbe 100644 --- a/src/ipc/QDouble.ts +++ b/src/ipc/QDouble.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QFloat.ts b/src/ipc/QFloat.ts index 7e351a0f2..ae1fb3135 100644 --- a/src/ipc/QFloat.ts +++ b/src/ipc/QFloat.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QGuid.ts b/src/ipc/QGuid.ts index 68fa792c0..647cac7f8 100644 --- a/src/ipc/QGuid.ts +++ b/src/ipc/QGuid.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -44,10 +44,10 @@ export default class QGuid extends Vector { const d = i - start; s += d === 4 || d === 6 || d === 8 || d === 10 ? "-" : ""; - // eslint-disable-next-line no-bitwise + s += x[c >> 4]; - // eslint-disable-next-line no-bitwise + s += x[c & 15]; } if (s === UUID_NULL) { diff --git a/src/ipc/QInt.ts b/src/ipc/QInt.ts index a40e21897..1d9ef2455 100644 --- a/src/ipc/QInt.ts +++ b/src/ipc/QInt.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QLong.ts b/src/ipc/QLong.ts index 091cf08b1..f2cedc7fd 100644 --- a/src/ipc/QLong.ts +++ b/src/ipc/QLong.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QMinute.ts b/src/ipc/QMinute.ts index 7ad2d8a47..b30bf130c 100644 --- a/src/ipc/QMinute.ts +++ b/src/ipc/QMinute.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import I32 from "./I32"; -import QInt from "./QInt"; import { DMinuteClass } from "./cClasses"; import Constants from "./constants"; +import I32 from "./I32"; import moment from "./moment.custom"; +import QInt from "./QInt"; import { TypeBase, TypeNum } from "./typeBase"; export default class QMinute extends I32 { diff --git a/src/ipc/QMonth.ts b/src/ipc/QMonth.ts index 5ad746366..2112707da 100644 --- a/src/ipc/QMonth.ts +++ b/src/ipc/QMonth.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,10 +11,10 @@ * specific language governing permissions and limitations under the License. */ -import I32 from "./I32"; -import QInt from "./QInt"; import { DMonthClass } from "./cClasses"; +import I32 from "./I32"; import moment from "./moment.custom"; +import QInt from "./QInt"; import { TypeBase, TypeNum } from "./typeBase"; export default class QMonth extends I32 { diff --git a/src/ipc/QSecond.ts b/src/ipc/QSecond.ts index 2fbcb4010..f4d1b9490 100644 --- a/src/ipc/QSecond.ts +++ b/src/ipc/QSecond.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import I32 from "./I32"; -import QInt from "./QInt"; import { DSecondClass } from "./cClasses"; import Constants from "./constants"; +import I32 from "./I32"; import moment from "./moment.custom"; +import QInt from "./QInt"; import { TypeBase, TypeNum } from "./typeBase"; export default class QSecond extends I32 { diff --git a/src/ipc/QShort.ts b/src/ipc/QShort.ts index d1aef37d4..64634d9a9 100644 --- a/src/ipc/QShort.ts +++ b/src/ipc/QShort.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QString.ts b/src/ipc/QString.ts index 0ef5d6c1e..57ac7710c 100644 --- a/src/ipc/QString.ts +++ b/src/ipc/QString.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -14,7 +14,7 @@ import { QList } from "./parse.qlist"; import { TypeBase } from "./typeBase"; -/* eslint-disable no-bitwise */ + export default class QString { static typeNum = 10; diff --git a/src/ipc/QSymbol.ts b/src/ipc/QSymbol.ts index 1b96968bd..9f7dc8a2d 100644 --- a/src/ipc/QSymbol.ts +++ b/src/ipc/QSymbol.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/QTable.ts b/src/ipc/QTable.ts index 57abe513a..7e237ebcf 100644 --- a/src/ipc/QTable.ts +++ b/src/ipc/QTable.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,8 +11,8 @@ * specific language governing permissions and limitations under the License. */ -import QSymbol from "./QSymbol"; import { DCDS } from "./c"; +import QSymbol from "./QSymbol"; import { TypeBase } from "./typeBase"; import Vector from "./vector"; diff --git a/src/ipc/QTime.ts b/src/ipc/QTime.ts index 3702582a7..7647b70f0 100644 --- a/src/ipc/QTime.ts +++ b/src/ipc/QTime.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import I32 from "./I32"; -import QInt from "./QInt"; import { DTimeClass } from "./cClasses"; import Constants from "./constants"; +import I32 from "./I32"; import moment from "./moment.custom"; +import QInt from "./QInt"; import { TypeBase, TypeNum } from "./typeBase"; export default class QTime extends I32 { diff --git a/src/ipc/QTimespan.ts b/src/ipc/QTimespan.ts index 41235e5e2..b644d6c01 100644 --- a/src/ipc/QTimespan.ts +++ b/src/ipc/QTimespan.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import I64 from "./I64"; -import QLong from "./QLong"; import { DTimespanClass } from "./cClasses"; import Constants from "./constants"; +import I64 from "./I64"; import moment from "./moment.custom"; +import QLong from "./QLong"; import { TypeBase, TypeNum } from "./typeBase"; export default class QTimespan extends I64 { diff --git a/src/ipc/QTimestamp.ts b/src/ipc/QTimestamp.ts index 8f945bd0d..af936a3b5 100644 --- a/src/ipc/QTimestamp.ts +++ b/src/ipc/QTimestamp.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import I64 from "./I64"; -import QLong from "./QLong"; import { DTimestampClass } from "./cClasses"; import Constants from "./constants"; +import I64 from "./I64"; import moment from "./moment.custom"; +import QLong from "./QLong"; import Tools from "./tools"; import { TypeBase, TypeNum } from "./typeBase"; diff --git a/src/ipc/QUnary.ts b/src/ipc/QUnary.ts index 52b8f0add..8b2563fd3 100644 --- a/src/ipc/QUnary.ts +++ b/src/ipc/QUnary.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,8 +11,8 @@ * specific language governing permissions and limitations under the License. */ -import U8 from "./U8"; import { TypeNum } from "./typeBase"; +import U8 from "./U8"; export default class QUnary extends U8 { constructor(length: number, offset: number, dataView: DataView) { diff --git a/src/ipc/SparkMD5.ts b/src/ipc/SparkMD5.ts index 77850c6ff..a4b4ad9c9 100644 --- a/src/ipc/SparkMD5.ts +++ b/src/ipc/SparkMD5.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. */ -/* eslint-disable no-bitwise */ /* * Fastest md5 implementation around (JKM md5). * Credits: Joseph Myers diff --git a/src/ipc/U8.ts b/src/ipc/U8.ts index 0281c8686..7967c2b9f 100644 --- a/src/ipc/U8.ts +++ b/src/ipc/U8.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/c.ts b/src/ipc/c.ts index a3d9e5572..5d529536d 100644 --- a/src/ipc/c.ts +++ b/src/ipc/c.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/cClasses.ts b/src/ipc/cClasses.ts index bf2a63c57..ed2ef51a2 100644 --- a/src/ipc/cClasses.ts +++ b/src/ipc/cClasses.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/constants.ts b/src/ipc/constants.ts index 9d3221e97..5cc9e46b5 100644 --- a/src/ipc/constants.ts +++ b/src/ipc/constants.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/ipc/moment.custom.ts b/src/ipc/moment.custom.ts index 377ef85c8..e5f8a72b6 100644 --- a/src/ipc/moment.custom.ts +++ b/src/ipc/moment.custom.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -60,10 +60,10 @@ const padLeft = (input: string, length: number, padChar: string) => { moment.duration.fn.kdbType = moment.prototype.kdbType = function (type) { if (type !== undefined && type !== null) { - // eslint-disable-next-line no-underscore-dangle + this._kdbType = type; } else { - // eslint-disable-next-line no-underscore-dangle + return this._kdbType || null; } }; @@ -74,10 +74,10 @@ moment.duration.fn.nanosecond = moment.prototype.nanoseconds = function (value) { if (value !== undefined && value !== null) { - // eslint-disable-next-line no-underscore-dangle + this._n = value; } else { - // eslint-disable-next-line no-underscore-dangle + return this._n || 0; } }; @@ -249,7 +249,7 @@ moment.duration.fn.toISOStringNano = function () { moment.duration.fn.valueOfNano = moment.prototype.valueOfNano = function () { let value = this.valueOf() * 1000000; - // eslint-disable-next-line no-underscore-dangle + if (typeof this._n === "number") { value += this.nanoseconds(); } diff --git a/src/ipc/parse.qlist.ts b/src/ipc/parse.qlist.ts index 32a4aaf96..6680202a6 100644 --- a/src/ipc/parse.qlist.ts +++ b/src/ipc/parse.qlist.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,6 +11,7 @@ * specific language governing permissions and limitations under the License. */ +import * as c from "./c"; import QBoolean from "./QBoolean"; import QByte from "./QByte"; import QChar from "./QChar"; @@ -33,7 +34,6 @@ import QTimespan from "./QTimespan"; import QTimestamp from "./QTimestamp"; import QUnary from "./QUnary"; import SparkMD5 from "./SparkMD5"; -import * as c from "./c"; import Tools from "./tools"; import { TypeBase } from "./typeBase"; import Vector from "./vector"; @@ -364,7 +364,7 @@ export class QList extends TypeBase { return qa.offset + (c.getTypeSize(Math.abs(qa.qtype)) as number); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any + getValue(i: number): any { if (!this.dataView) { return this.values[(i + this.indexOffset) % this.length]; @@ -598,7 +598,7 @@ export class QList extends TypeBase { this.indexOffset = indexOffset; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any + toLegacy(i: number): any { const qCol = this.values[i]; if (!this.dataView) { diff --git a/src/ipc/tools.ts b/src/ipc/tools.ts index 6b4a736f6..7f67d839a 100644 --- a/src/ipc/tools.ts +++ b/src/ipc/tools.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -65,7 +65,7 @@ export default class Tools { let i; while (mid <= n) { - // eslint-disable-next-line no-bitwise + i = (n + mid) >> 1; cmp = compareFn(element, array[i]); if (cmp > 0) { diff --git a/src/ipc/typeBase.ts b/src/ipc/typeBase.ts index bdf12a5e3..1aab85b00 100644 --- a/src/ipc/typeBase.ts +++ b/src/ipc/typeBase.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -13,7 +13,7 @@ import Tools from "./tools"; -// eslint-disable-next-line no-shadow + export enum TypeNum { list = 0, bool = 1, @@ -82,9 +82,9 @@ export abstract class TypeBase { }, wi: (i: number) => { for (let j = 0; j < 4; j++) { - // eslint-disable-next-line no-bitwise + buffer.wb(i & 255); - // eslint-disable-next-line no-bitwise + i = i >> 8; } }, diff --git a/src/ipc/util.ts b/src/ipc/util.ts index 4546c08ea..6e0d195d0 100644 --- a/src/ipc/util.ts +++ b/src/ipc/util.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,6 +11,7 @@ * specific language governing permissions and limitations under the License. */ +import { QList } from "./parse.qlist"; import QBoolean from "./QBoolean"; import QByte from "./QByte"; import QChar from "./QChar"; @@ -29,7 +30,6 @@ import QSymbol from "./QSymbol"; import QTime from "./QTime"; import QTimespan from "./QTimespan"; import QTimestamp from "./QTimestamp"; -import { QList } from "./parse.qlist"; import { TypeBase } from "./typeBase"; export class Util { @@ -91,7 +91,7 @@ export class Util { static pack(bytes: Array) { const chars = []; for (let i = 0, n = bytes.length; i < n; ) { - // eslint-disable-next-line no-bitwise + chars.push(((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff)); } return String.fromCharCode.apply(null, chars); diff --git a/src/ipc/vector.ts b/src/ipc/vector.ts index 506d9b82a..a2744867b 100644 --- a/src/ipc/vector.ts +++ b/src/ipc/vector.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2023 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/cancellationEvent.ts b/src/models/cancellationEvent.ts index 2dadf688c..7ac91f945 100644 --- a/src/models/cancellationEvent.ts +++ b/src/models/cancellationEvent.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/commandRegistration.ts b/src/models/commandRegistration.ts index c98c8811e..054d31456 100644 --- a/src/models/commandRegistration.ts +++ b/src/models/commandRegistration.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/config.ts b/src/models/config.ts index a626e320d..958ae0a9b 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/connectionsModels.ts b/src/models/connectionsModels.ts index e37c276eb..09fec4a08 100644 --- a/src/models/connectionsModels.ts +++ b/src/models/connectionsModels.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/customAuth.ts b/src/models/customAuth.ts index 62d7a4650..38402a7fa 100644 --- a/src/models/customAuth.ts +++ b/src/models/customAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/data.ts b/src/models/data.ts index cfff65438..4f06b553c 100644 --- a/src/models/data.ts +++ b/src/models/data.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/dataSource.ts b/src/models/dataSource.ts index 6cacb62a9..b0c014d1c 100644 --- a/src/models/dataSource.ts +++ b/src/models/dataSource.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/execution.ts b/src/models/execution.ts index cac9da412..b5e4e8c21 100644 --- a/src/models/execution.ts +++ b/src/models/execution.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,10 +11,11 @@ * specific language governing permissions and limitations under the License. */ -export enum ExecutionTypes { +export const enum ExecutionTypes { QuerySelection, QueryFile, ReRunQuery, PythonQuerySelection, PythonQueryFile, + PopulateScratchpad, } diff --git a/src/models/items/license.ts b/src/models/items/license.ts index 28c81cf51..09a24d2a8 100644 --- a/src/models/items/license.ts +++ b/src/models/items/license.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/items/onboarding.ts b/src/models/items/onboarding.ts index a04073668..4c2b244b9 100644 --- a/src/models/items/onboarding.ts +++ b/src/models/items/onboarding.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/items/runtime.ts b/src/models/items/runtime.ts index d57e08cbc..ea19d69e1 100644 --- a/src/models/items/runtime.ts +++ b/src/models/items/runtime.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/items/server.ts b/src/models/items/server.ts index 5d6d23a14..ba5f73e50 100644 --- a/src/models/items/server.ts +++ b/src/models/items/server.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/jwt_user.ts b/src/models/jwt_user.ts index 7b660f395..4535e590a 100644 --- a/src/models/jwt_user.ts +++ b/src/models/jwt_user.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/labels.ts b/src/models/labels.ts index 261b34a88..97e93c19b 100644 --- a/src/models/labels.ts +++ b/src/models/labels.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/localProcess.ts b/src/models/localProcess.ts index 3bb9a6e26..40a0f62da 100644 --- a/src/models/localProcess.ts +++ b/src/models/localProcess.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/locationItem.ts b/src/models/locationItem.ts index ee8a97352..56b81960b 100644 --- a/src/models/locationItem.ts +++ b/src/models/locationItem.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/messages.ts b/src/models/messages.ts index 6e03887d7..d9faf3cce 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/meta.ts b/src/models/meta.ts index dee33af4b..8dfa90d7e 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -44,9 +44,23 @@ export type MetaApi = { api: string; kxname: string[]; aggFn: string; - custom: boolean; + custom?: boolean; + uda: boolean; full: boolean; - metadata?: MetaApiMetadata; // metadata pode ser undefined + metadata?: MetaApiMetadata; // can be undefined + params?: any[]; + description?: string; + return?: { + type: number[]; + description: string; + }; + aggReturn?: { + type: number; + description: string; + }; + misc?: { + [key: string]: any; + }; procs: any[]; }; @@ -98,11 +112,11 @@ export type MetaColumns = { }; export type MetaDap = { - dap: string; + dap?: string; assembly: string; startTS: string; endTS: string; - labels: string[]; + labels?: string[]; instance: string; }; diff --git a/src/models/metaResult.ts b/src/models/metaResult.ts index 1b3aaecb7..e6577f9a9 100644 --- a/src/models/metaResult.ts +++ b/src/models/metaResult.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/notebook.ts b/src/models/notebook.ts index 347f72986..057206662 100644 --- a/src/models/notebook.ts +++ b/src/models/notebook.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -22,6 +22,8 @@ export interface KxNotebookCell { value: string; languageId: string; outputs: KxNotebookOutput[]; + target?: string; + variable?: string; } export interface KxNotebookOutput { @@ -32,3 +34,10 @@ export interface KxNotebookOutputItem { data: string; mime: string; } + +export const enum CellKind { + MARKDOWN, + Q, + PYTHON, + SQL, +} diff --git a/src/models/plot.ts b/src/models/plot.ts index 7d2ffb75b..fe9edbfea 100644 --- a/src/models/plot.ts +++ b/src/models/plot.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/project.ts b/src/models/project.ts index e5a158038..d56ce946a 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/queryHistory.ts b/src/models/queryHistory.ts index 97106f437..7172553e8 100644 --- a/src/models/queryHistory.ts +++ b/src/models/queryHistory.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/queryResult.ts b/src/models/queryResult.ts index c97b0eb49..3717f68d0 100644 --- a/src/models/queryResult.ts +++ b/src/models/queryResult.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/scratchpad.ts b/src/models/scratchpad.ts index f1ea44d20..0ae31e6eb 100644 --- a/src/models/scratchpad.ts +++ b/src/models/scratchpad.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/scratchpadResult.ts b/src/models/scratchpadResult.ts index d105aacf6..bcb5adb20 100644 --- a/src/models/scratchpadResult.ts +++ b/src/models/scratchpadResult.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/serverObject.ts b/src/models/serverObject.ts index 7e04d11ac..c43cc09f5 100644 --- a/src/models/serverObject.ts +++ b/src/models/serverObject.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/models/uda.ts b/src/models/uda.ts index 6f3614fe3..3cabfb89d 100644 --- a/src/models/uda.ts +++ b/src/models/uda.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/panels/datasource.ts b/src/panels/datasource.ts index 9b4f3c08f..1b2dd57f8 100644 --- a/src/panels/datasource.ts +++ b/src/panels/datasource.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/panels/newConnection.ts b/src/panels/newConnection.ts index e73f46f3f..593cb2ed9 100644 --- a/src/panels/newConnection.ts +++ b/src/panels/newConnection.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -17,9 +17,15 @@ import { ext } from "../extensionVariables"; import { ConnectionType } from "../models/connectionsModels"; import { EditConnectionMessage } from "../models/messages"; import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; -import { retrieveConnLabelsNames } from "../utils/connLabel"; +import { + clearWorkspaceLabels, + retrieveConnLabelsNames, +} from "../utils/connLabel"; import { getNonce } from "../utils/getNonce"; import { getUri } from "../utils/getUri"; +import { MessageKind, notify } from "../utils/notifications"; + +const logger = "newConnection"; export class NewConnectionPannel { public static currentPanel: NewConnectionPannel | undefined; @@ -32,6 +38,7 @@ export class NewConnectionPannel { NewConnectionPannel.currentPanel._panel.dispose(); return; } + clearWorkspaceLabels(); const panel = vscode.window.createWebviewPanel( "kdbNewConnection", @@ -92,12 +99,14 @@ export class NewConnectionPannel { this._panel.webview, this._extensionUri, ); - /* istanbul ignore next */ + /* c8 ignore next */ this._panel.webview.onDidReceiveMessage((message) => { if (message.command === "kdb.connections.add.bundleq") { if (ext.isBundleQCreated) { - vscode.window.showErrorMessage( + notify( "Bundled Q is already created, please remove it first", + MessageKind.ERROR, + { logger }, ); } else { vscode.commands.executeCommand( diff --git a/src/services/chartEditorProvider.ts b/src/services/chartEditorProvider.ts index da5d52fb8..7011f3a1c 100644 --- a/src/services/chartEditorProvider.ts +++ b/src/services/chartEditorProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/completionProvider.ts b/src/services/completionProvider.ts index 2471329b4..8654f01fe 100644 --- a/src/services/completionProvider.ts +++ b/src/services/completionProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/connectionManagerService.ts b/src/services/connectionManagerService.ts index 0ecbd0f3f..00f08f6ca 100644 --- a/src/services/connectionManagerService.ts +++ b/src/services/connectionManagerService.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -import { window, commands } from "vscode"; +import { commands } from "vscode"; import { LocalConnection } from "../classes/localConnection"; import { ext } from "../extensionVariables"; @@ -31,14 +31,15 @@ import { getKeyForServerName, getServerName, getServers, - kdbOutputLog, removeLocalConnectionContext, updateInsights, updateServers, } from "../utils/core"; import { refreshDataSourcesPanel } from "../utils/dataSource"; -import { sanitizeQuery } from "../utils/queryUtils"; -import { Telemetry } from "../utils/telemetryClient"; +import { MessageKind, notify } from "../utils/notifications"; +import { resetScratchpadStarted, sanitizeQuery } from "../utils/queryUtils"; + +const logger = "connectionManagerService"; export class ConnectionManagementService { public retrieveConnection( @@ -124,7 +125,7 @@ export class ConnectionManagementService { } } - /* istanbul ignore next */ + /* c8 ignore next */ public async connect(connLabel: string): Promise { const connection = this.retrieveConnection(connLabel); if (!connection) { @@ -144,18 +145,19 @@ export class ConnectionManagementService { ); await localConnection.connect((err, conn) => { if (err) { - window.showErrorMessage(err.message); this.connectFailBehaviour(connLabel); return; } if (conn) { - kdbOutputLog( + notify( `Connection established successfully to: ${connLabel}`, - "CONNECTION", - ); - - Telemetry.sendEvent( - "Connection.Connected" + this.getTelemetryConnectionType(connLabel), + MessageKind.DEBUG, + { + logger, + telemetry: + "Connection.Connected" + + this.getTelemetryConnectionType(connLabel), + }, ); ext.connectedConnectionList.push(localConnection); @@ -171,16 +173,16 @@ export class ConnectionManagementService { ); await insightsConn.connect(); if (insightsConn.connected) { - Telemetry.sendEvent( - "Connection.Connected" + this.getTelemetryConnectionType(connLabel), - ); - kdbOutputLog( + notify( `Connection established successfully to: ${connLabel}`, - "CONNECTION", - ); - kdbOutputLog( - `${connLabel} connection insights version: ${insightsConn.insightsVersion}`, - "CONNECTION", + MessageKind.DEBUG, + { + logger, + params: { insightsVersion: insightsConn.insightsVersion }, + telemetry: + "Connection.Connected" + + this.getTelemetryConnectionType(connLabel), + }, ); ext.connectedConnectionList.push(insightsConn); this.connectSuccessBehaviour(connection); @@ -199,7 +201,10 @@ export class ConnectionManagementService { commands.executeCommand("setContext", "kdb.connected.active", [ `${node.label}`, ]); - Telemetry.sendEvent("Connection.Connected.Active"); + notify("Connection activated.", MessageKind.DEBUG, { + logger, + telemetry: "Connection.Connected.Active", + }); ext.activeConnection = connection; if (node instanceof InsightsNode) { @@ -226,7 +231,7 @@ export class ConnectionManagementService { if (!connection || !connectionNode) { return; } - /* istanbul ignore next */ + /* c8 ignore next */ connection.disconnect(); this.disconnectBehaviour(connection); } @@ -292,10 +297,11 @@ export class ConnectionManagementService { } public connectFailBehaviour(connLabel: string): void { - window.showErrorMessage(`Connection failed to: ${connLabel}`); - Telemetry.sendEvent( - "Connection.Failed" + this.getTelemetryConnectionType(connLabel), - ); + notify(`Connection failed to: ${connLabel}`, MessageKind.ERROR, { + logger, + telemetry: + "Connection.Failed" + this.getTelemetryConnectionType(connLabel), + }); } public disconnectBehaviour( @@ -321,11 +327,10 @@ export class ConnectionManagementService { commands.executeCommand("setContext", "kdb.connected.active", false); commands.executeCommand("setContext", "kdb.pythonEnabled", false); } - Telemetry.sendEvent("Connection.Disconnected." + connType); - kdbOutputLog( - `[CONNECTION] Connection closed: ${connection.connLabel}`, - "INFO", - ); + notify(`Connection closed: ${connection.connLabel}`, MessageKind.DEBUG, { + logger, + telemetry: "Connection.Disconnected." + connType, + }); ext.serverProvider.reload(); } @@ -361,7 +366,6 @@ export class ConnectionManagementService { command, context, isPython, - false, !stringify, ); } @@ -374,9 +378,10 @@ export class ConnectionManagementService { if (retrievedConn instanceof InsightsConnection) { conn = retrievedConn; } else { - kdbOutputLog( - "[RESET SCRATCHPAD] Please connect to an Insights connection to use this feature.", - "ERROR", + notify( + "Please connect to an Insights connection to use this feature.", + MessageKind.ERROR, + { logger }, ); return; } @@ -386,9 +391,10 @@ export class ConnectionManagementService { !ext.activeConnection || !(ext.activeConnection instanceof InsightsConnection) ) { - kdbOutputLog( - "[RESET SCRATCHPAD] Please activate an Insights connection to use this feature.", - "ERROR", + notify( + "Please activate an Insights connection to use this feature.", + MessageKind.ERROR, + { logger }, ); return; } @@ -400,25 +406,28 @@ export class ConnectionManagementService { isBaseVersionGreaterOrEqual(conn.insightsVersion, 1.13) ) { const confirmationPrompt = `Reset Scratchpad? All data in the ${conn.connLabel} Scratchpad will be lost, and variables will be reset.`; - const selection = await window.showInformationMessage( + const selection = await notify( confirmationPrompt, + MessageKind.INFO, + {}, "Yes", "No", ); if (selection === "Yes") { await conn.resetScratchpad(); + resetScratchpadStarted(conn.connLabel); } else { - kdbOutputLog( - "[RESET SCRATCHPAD] The user canceled the scratchpad reset.", - "INFO", - ); + notify("The user canceled the scratchpad reset.", MessageKind.DEBUG, { + logger, + }); return; } } else { - kdbOutputLog( - "[RESET SCRATCHPAD] Please connect to an Insights connection with version 1.13 or higher.", - "ERROR", + notify( + "Please connect to an Insights connection with version 1.13 or higher.", + MessageKind.ERROR, + { logger }, ); } } @@ -451,24 +460,27 @@ export class ConnectionManagementService { ): string { const metaType = this.getMetaInfoType(metaTypeString.toUpperCase()); if (!metaType) { - kdbOutputLog( - "[META] The meta info type that you try to open is not valid", - "ERROR", + notify( + "The meta info type that you try to open is not valid", + MessageKind.ERROR, + { logger }, ); return ""; } const connection = this.retrieveConnectedConnection(connLabel); if (!connection) { - kdbOutputLog( - "[META] The connection that you try to open meta info is not connected", - "ERROR", + notify( + "The connection that you try to open meta info is not connected", + MessageKind.ERROR, + { logger }, ); return ""; } if (connection instanceof LocalConnection) { - kdbOutputLog( - "[META] The connection that you try to open meta info is not an Insights connection", - "ERROR", + notify( + "The connection that you try to open meta info is not an Insights connection", + MessageKind.ERROR, + { logger }, ); return ""; } diff --git a/src/services/dataSourceEditorProvider.ts b/src/services/dataSourceEditorProvider.ts index 4510ca188..2722a1a18 100644 --- a/src/services/dataSourceEditorProvider.ts +++ b/src/services/dataSourceEditorProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -43,11 +43,14 @@ import { import { DataSourceCommand, DataSourceMessage2 } from "../models/messages"; import { MetaObjectPayload } from "../models/meta"; import { UDA } from "../models/uda"; -import { kdbOutputLog, offerConnectAction } from "../utils/core"; +import { getBasename, offerConnectAction } from "../utils/core"; import { getNonce } from "../utils/getNonce"; import { getUri } from "../utils/getUri"; +import { MessageKind, Runner, notify } from "../utils/notifications"; import { parseUDAList } from "../utils/uda"; +const logger = "dataSourceEditorProvider"; + export class DataSourceEditorProvider implements CustomTextEditorProvider { public filenname = ""; static readonly viewType = "kdb.dataSourceEditor"; @@ -92,16 +95,13 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { this.cache.set(connLabel, meta); } catch { - window.showErrorMessage( + notify( "No database running in this Insights connection.", + MessageKind.ERROR, + { logger }, ); meta = Promise.resolve({}); this.cache.set(connLabel, meta); - kdbOutputLog( - "No database running in this Insights connection.", - "ERROR", - true, - ); } return (await meta) || Promise.resolve({}); } @@ -138,7 +138,7 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { } }; - /* istanbul ignore next */ + /* c8 ignore next */ workspace.onDidChangeConfiguration((event) => { if ((event.affectsConfiguration("kdb.connectionMap"), document)) { updateWebview(); @@ -163,8 +163,11 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { changeDocumentSubscription.dispose(); }); - /* istanbul ignore next */ + /* c8 ignore next */ webview.onDidReceiveMessage(async (msg: DataSourceMessage2) => { + const selectedServer = getServerForUri(document.uri) || ""; + const connected = connMngService.isConnected(selectedServer); + switch (msg.command) { case DataSourceCommand.Server: { await setServerForUri(document.uri, msg.selectedServer); @@ -192,35 +195,47 @@ export class DataSourceEditorProvider implements CustomTextEditorProvider { break; } case DataSourceCommand.Refresh: { - const selectedServer = getServerForUri(document.uri) || ""; - if (!connMngService.isConnected(selectedServer)) { - offerConnectAction(selectedServer); - break; - } - await window.withProgress( - { - cancellable: false, - location: ProgressLocation.Notification, - title: "Refreshing meta data...", - }, - async () => { + if (connected) { + const runner = Runner.create(async () => { await connMngService.refreshGetMeta(selectedServer); this.cache.delete(selectedServer); updateWebview(); - }, - ); + }); + runner.location = ProgressLocation.Notification; + runner.title = `Refreshing meta data for ${selectedServer}.`; + await runner.execute(); + } else { + offerConnectAction(selectedServer); + } break; } case DataSourceCommand.Run: { - await runDataSource( - msg.dataSourceFile, - msg.selectedServer, - this.filenname, - ); + if (connected) { + const runner = Runner.create(() => + runDataSource( + msg.dataSourceFile, + msg.selectedServer, + this.filenname, + ), + ); + runner.location = ProgressLocation.Notification; + runner.title = `Running ${getBasename(document.uri)} on ${msg.selectedServer}.`; + await runner.execute(); + } else { + offerConnectAction(selectedServer); + } break; } case DataSourceCommand.Populate: { - await populateScratchpad(msg.dataSourceFile, msg.selectedServer); + if (connected) { + const runner = Runner.create(() => + populateScratchpad(msg.dataSourceFile, msg.selectedServer), + ); + runner.title = "Populating scratchpad."; + await runner.execute(); + } else { + offerConnectAction(selectedServer); + } break; } } diff --git a/src/services/dataSourceTreeProvider.ts b/src/services/dataSourceTreeProvider.ts index 8d56f81d2..03fdf4cf8 100644 --- a/src/services/dataSourceTreeProvider.ts +++ b/src/services/dataSourceTreeProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/helpFeedbackProvider.ts b/src/services/helpFeedbackProvider.ts index e1495b242..05741878d 100644 --- a/src/services/helpFeedbackProvider.ts +++ b/src/services/helpFeedbackProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/kdbInsights/codeFlowLogin.ts b/src/services/kdbInsights/codeFlowLogin.ts index e98c81ce7..be43a5a13 100644 --- a/src/services/kdbInsights/codeFlowLogin.ts +++ b/src/services/kdbInsights/codeFlowLogin.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -134,7 +134,7 @@ export async function refreshToken( }); } -/* istanbul ignore next */ +/* c8 ignore next */ export async function getCurrentToken( serverName: string, serverAlias: string, @@ -173,7 +173,7 @@ export async function getCurrentToken( return token; } -/* istanbul ignore next */ +/* c8 ignore next */ async function getToken( insightsUrl: string, realm: string, @@ -235,7 +235,7 @@ function queryString(options: any): string { .replace(/%2B/g, "+"); } -/* istanbul ignore next */ +/* c8 ignore next */ function createServer() { let deferredCode: IDeferred; const codePromise = new Promise( diff --git a/src/services/kdbTreeProvider.ts b/src/services/kdbTreeProvider.ts index ba95a15a0..8881243e6 100644 --- a/src/services/kdbTreeProvider.ts +++ b/src/services/kdbTreeProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -17,6 +17,7 @@ import { ext } from "../extensionVariables"; import { ConnectionManagementService } from "./connectionManagerService"; import { KdbTreeService } from "./kdbTreeService"; import { InsightsConnection } from "../classes/insightsConnection"; +import { LocalConnection } from "../classes/localConnection"; import { InsightDetails, Insights, @@ -25,8 +26,7 @@ import { } from "../models/connectionsModels"; import { Labels } from "../models/labels"; import { - getWorkspaceLabels, - getWorkspaceLabelsConnMap, + clearWorkspaceLabels, isLabelContentChanged, isLabelEmpty, retrieveConnLabelsNames, @@ -39,6 +39,7 @@ import { getStatus, } from "../utils/core"; import { getIconPath } from "../utils/iconsUtils"; +import { MessageKind, notify } from "../utils/notifications"; export class KdbTreeProvider implements vscode.TreeDataProvider @@ -100,14 +101,12 @@ export class KdbTreeProvider } async getChildren(element?: vscode.TreeItem): Promise { + clearWorkspaceLabels(); if (!this.serverList || !this.insightsList) { return []; } if (!element) { - getWorkspaceLabels(); - getWorkspaceLabelsConnMap(); - const orphans: vscode.TreeItem[] = []; const nodes = ext.connLabelList.map((label) => new LabelNode(label)); const items = this.getMergedElements(element); @@ -179,7 +178,7 @@ export class KdbTreeProvider return this.createInsightLeafItems(this.insightsList); } - /* istanbul ignore next */ + /* c8 ignore next */ private async getMetas(connLabel: string): Promise { const connMng = new ConnectionManagementService(); const conn = connMng.retrieveConnectedConnection(connLabel); @@ -198,9 +197,22 @@ export class KdbTreeProvider } } - /* istanbul ignore next */ - private async getNamespaces(connLabel?: string): Promise { - const ns = await KdbTreeService.loadNamespaces(); + /* c8 ignore next */ + private async getNamespaces(connLabel: string): Promise { + const connMng = new ConnectionManagementService(); + const conn = connMng.retrieveConnectedConnection(connLabel); + if (!conn) { + return new Array(); + } + if (conn instanceof InsightsConnection) { + // For Insights connections, namespaces are not applicable for now + notify( + "Please connect to a KDB instance to view the objects", + MessageKind.INFO, + ); + return new Array(); + } + const ns = await KdbTreeService.loadNamespaces(conn); const result = ns.map( (x) => new QNamespaceNode( @@ -219,7 +231,7 @@ export class KdbTreeProvider } } - /* istanbul ignore next */ + /* c8 ignore next */ private async getCategories( ns: string | undefined, objectCategories: string[], @@ -248,143 +260,183 @@ export class KdbTreeProvider return result; } - /* istanbul ignore next */ private async getServerObjects( serverType: QCategoryNode | vscode.TreeItem, ): Promise { if (serverType === undefined) return new Array(); + + const conn = this.validateAndGetConnection(serverType); + if (!conn) { + return new Array(); + } + const ns = serverType.contextValue ?? ""; const connLabel = serverType instanceof QCategoryNode ? serverType.connLabel : ""; - if (serverType.label === ext.qObjectCategories[0]) { - // dictionaries - const dicts = await KdbTreeService.loadDictionaries( - serverType.contextValue ?? "", - ); - const result = dicts.map( - (x) => - new QServerNode( - [], - `${ns === "." ? "" : ns + "."}${x.name}`, - "", - vscode.TreeItemCollapsibleState.None, - "dictionaries", - connLabel, - ), - ); - if (result !== undefined) { - return result; - } else { - return new Array(); - } - } else if (serverType.label === ext.qObjectCategories[1]) { - // functions - const funcs = await KdbTreeService.loadFunctions( - serverType.contextValue ?? "", - ); - const result = funcs.map( - (x) => - new QServerNode( - [], - `${ns === "." ? "" : ns + "."}${x.name}`, - "", - vscode.TreeItemCollapsibleState.None, - "functions", - connLabel, - ), - ); - if (result !== undefined) { - return result; - } else { - return new Array(); - } - } else if (serverType.label === ext.qObjectCategories[2]) { - // tables - const tables = await KdbTreeService.loadTables( - serverType.contextValue ?? "", - ); - const result = tables.map( - (x) => - new QServerNode( - [], - `${ns === "." ? "" : ns + "."}${x.name}`, - "", - vscode.TreeItemCollapsibleState.None, - "tables", - connLabel, - ), - ); - if (result !== undefined) { - return result; - } else { - return new Array(); - } - } else if (serverType.label === ext.qObjectCategories[3]) { - // variables - const vars = await KdbTreeService.loadVariables( - serverType.contextValue ?? "", - ); - const result = vars.map( - (x) => - new QServerNode( - [], - `${ns === "." ? "" : ns + "."}${x.name}`, - "", - vscode.TreeItemCollapsibleState.None, - "variables", - connLabel, - ), - ); - if (result !== undefined) { - return result; - } else { - return new Array(); - } - } else if (serverType.label === ext.qObjectCategories[4]) { - // views - const views = await KdbTreeService.loadViews(); - const result = views.map( - (x) => - new QServerNode( - [], - `${ns === "." ? "" : "."}${x}`, - "", - vscode.TreeItemCollapsibleState.None, - "views", - connLabel, - ), + + return this.loadObjectsByCategory(serverType, conn, ns, connLabel); + } + + private validateAndGetConnection( + serverType: QCategoryNode | vscode.TreeItem, + ): LocalConnection | null { + const connLabel = + serverType instanceof QCategoryNode ? serverType.connLabel : ""; + const connMng = new ConnectionManagementService(); + const conn = connMng.retrieveConnectedConnection(connLabel); + + if (!conn) { + return null; + } + + if (conn instanceof InsightsConnection) { + // For Insights connections server objects are not applicable now + notify( + "Please connect to a KDB instance to view the objects", + MessageKind.INFO, ); - if (result !== undefined) { - return result; - } else { + return null; + } + + return conn; + } + + private async loadObjectsByCategory( + serverType: QCategoryNode | vscode.TreeItem, + conn: LocalConnection, + ns: string, + connLabel: string, + ): Promise { + const categoryIndex = ext.qObjectCategories.indexOf( + serverType.label as string, + ); + + switch (categoryIndex) { + case 0: // dictionaries + return this.loadDictionaries( + conn, + serverType.contextValue ?? "", + ns, + connLabel, + ); + case 1: // functions + return this.loadFunctions( + conn, + serverType.contextValue ?? "", + ns, + connLabel, + ); + case 2: // tables + return this.loadTables( + conn, + serverType.contextValue ?? "", + ns, + connLabel, + ); + case 3: // variables + return this.loadVariables( + conn, + serverType.contextValue ?? "", + ns, + connLabel, + ); + case 4: // views + return this.loadViews(conn, ns, connLabel); + default: return new Array(); - } } - // Remove this for this moment, to investigate - // else if (serverType.label === ext.qObjectCategories[5]) { - // // nested namespaces - // const namespaces = await loadNamespaces(ns); - // const result = namespaces.map( - // (x) => - // new QNamespaceNode( - // [], - // x.fname, - // "", - // vscode.TreeItemCollapsibleState.Collapsed, - // x.fname, - // connLabel, - // ), - // ); - // if (result !== undefined) { - // return result; - // } else { - // return Array(); - // } - // } - return new Array(); - } - - /* istanbul ignore next */ + } + + private async loadDictionaries( + conn: LocalConnection, + namespace: string, + ns: string, + connLabel: string, + ): Promise { + const dicts = await KdbTreeService.loadDictionaries(conn, namespace); + return this.createQServerNodes(dicts, ns, connLabel, "dictionaries"); + } + + private async loadFunctions( + conn: LocalConnection, + namespace: string, + ns: string, + connLabel: string, + ): Promise { + const funcs = await KdbTreeService.loadFunctions(conn, namespace); + return this.createQServerNodes(funcs, ns, connLabel, "functions"); + } + + private async loadTables( + conn: LocalConnection, + namespace: string, + ns: string, + connLabel: string, + ): Promise { + const tables = await KdbTreeService.loadTables(conn, namespace); + return this.createQServerNodes(tables, ns, connLabel, "tables"); + } + + private async loadVariables( + conn: LocalConnection, + namespace: string, + ns: string, + connLabel: string, + ): Promise { + const vars = await KdbTreeService.loadVariables(conn, namespace); + return this.createQServerNodes(vars, ns, connLabel, "variables"); + } + + private async loadViews( + conn: LocalConnection, + ns: string, + connLabel: string, + ): Promise { + const views = await KdbTreeService.loadViews(conn); + return views.map( + (x) => + new QServerNode( + [], + `${ns === "." ? "" : "."}${x}`, + "", + vscode.TreeItemCollapsibleState.None, + "views", + connLabel, + ), + ); + } + + // Nested namespaces are not currently supported in the tree view + // private async loadNestedNamespaces( + // conn: LocalConnection, + // ns: string, + // connLabel: string, + // ): Promise { + // const nns = await KdbTreeService.loadNamespaces(conn); + // return this.createQServerNodes(nns, ns, connLabel, "namespaces"); + // } + + /* c8 ignore next */ + private createQServerNodes( + objects: any[], + ns: string, + connLabel: string, + iconType: string, + ): QServerNode[] { + return objects.map( + (x) => + new QServerNode( + [], + `${ns === "." ? "" : ns + "."}${x.name}`, + "", + vscode.TreeItemCollapsibleState.None, + iconType, + connLabel, + ), + ); + } + + /* c8 ignore next */ private async getMetaObjects( connLabel: string, ): Promise { @@ -470,7 +522,7 @@ export class KdbTreeProvider (x) => new KdbNode( x.split(":"), - `${servers[x].serverName}:${servers[x].serverPort}`, + x, servers[x], ext.connectionNode?.label === getServerName(servers[x]) ? vscode.TreeItemCollapsibleState.Collapsed @@ -506,8 +558,9 @@ export class KdbNode extends vscode.TreeItem { public readonly collapsibleState: vscode.TreeItemCollapsibleState, ) { if (details.serverAlias != "") { - label = label + ` [${details.serverAlias}]`; + label = label + ` `; } + label = label + `[${details.serverName}:${details.serverPort}]`; // set context for root nodes if (ext.kdbrootNodes.indexOf(label) === -1) { diff --git a/src/services/kdbTreeService.ts b/src/services/kdbTreeService.ts index 89d3fc63a..ac9ab3185 100644 --- a/src/services/kdbTreeService.ts +++ b/src/services/kdbTreeService.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -12,13 +12,14 @@ */ import { LocalConnection } from "../classes/localConnection"; -import { loadServerObjects } from "../commands/serverCommand"; -import { ext } from "../extensionVariables"; import { ServerObject } from "../models/serverObject"; export class KdbTreeService { - static async loadNamespaces(root?: string): Promise { - const serverObjects = await loadServerObjects(); + static async loadNamespaces( + conn: LocalConnection, + root?: string, + ): Promise { + const serverObjects = await conn.loadServerObjects(); if (serverObjects !== undefined) { const ns = serverObjects.filter((value) => { return value.isNs ? value : undefined; @@ -31,8 +32,11 @@ export class KdbTreeService { return new Array(); } - static async loadDictionaries(ns: string): Promise { - const serverObjects = await loadServerObjects(); + static async loadDictionaries( + conn: LocalConnection, + ns: string, + ): Promise { + const serverObjects = await conn.loadServerObjects(); if (serverObjects !== undefined) { const dicts = serverObjects.filter((value) => { return value.typeNum === 99 && !value.isNs && value.namespace === ns @@ -44,8 +48,11 @@ export class KdbTreeService { return new Array(); } - static async loadFunctions(ns: string): Promise { - const serverObjects = await loadServerObjects(); + static async loadFunctions( + conn: LocalConnection, + ns: string, + ): Promise { + const serverObjects = await conn.loadServerObjects(); if (serverObjects !== undefined) { const funcs = serverObjects.filter((value) => { return value.typeNum === 100 && !value.isNs && value.namespace === ns @@ -57,8 +64,11 @@ export class KdbTreeService { return new Array(); } - static async loadTables(ns: string): Promise { - const serverObjects = await loadServerObjects(); + static async loadTables( + conn: LocalConnection, + ns: string, + ): Promise { + const serverObjects = await conn.loadServerObjects(); if (!serverObjects) return []; const tables = serverObjects.filter( @@ -71,9 +81,12 @@ export class KdbTreeService { return KdbTreeService.sortObjects(tables); } - static async loadVariables(ns: string): Promise { - const serverObjects = await loadServerObjects(); - const views = await KdbTreeService.loadViews(); + static async loadVariables( + conn: LocalConnection, + ns: string, + ): Promise { + const serverObjects = await conn.loadServerObjects(); + const views = await KdbTreeService.loadViews(conn); if (serverObjects !== undefined) { const vars = serverObjects.filter((value) => { @@ -89,23 +102,20 @@ export class KdbTreeService { return new Array(); } - static async loadViews(): Promise { - if (ext.activeConnection instanceof LocalConnection) { - const rawViewArray = await ext.activeConnection?.executeQuery("views`"); - const views = rawViewArray?.filter((item: any) => { - return item !== "s#" && item !== "" && item !== ","; - }); - const sorted = views?.sort((object1: any, object2: any) => { - if (object1 < object2) { - return -1; - } else if (object1 > object2) { - return 1; - } - return 0; - }); - return sorted ?? new Array(); - } - return new Array(); + static async loadViews(conn: LocalConnection): Promise { + const rawViewArray = await conn.executeQueryRaw("views`"); + const views = rawViewArray?.filter((item: any) => { + return item !== "s#" && item !== "" && item !== ","; + }); + const sorted = views?.sort((object1: any, object2: any) => { + if (object1 < object2) { + return -1; + } else if (object1 > object2) { + return 1; + } + return 0; + }); + return sorted ?? new Array(); } private static getNamespaces( diff --git a/src/services/metaContentProvider.ts b/src/services/metaContentProvider.ts index d072348eb..61bdffb68 100644 --- a/src/services/metaContentProvider.ts +++ b/src/services/metaContentProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/notebookController.ts b/src/services/notebookController.ts index d2534c426..bbd67efbf 100644 --- a/src/services/notebookController.ts +++ b/src/services/notebookController.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -13,18 +13,29 @@ import * as vscode from "vscode"; +import { getCellKind } from "./notebookProviders"; import { InsightsConnection } from "../classes/insightsConnection"; -import { ext } from "../extensionVariables"; -import { ConnectionManagementService } from "../services/connectionManagerService"; -import { kdbOutputLog } from "../utils/core"; -import { resultToBase64 } from "../utils/queryUtils"; +import { LocalConnection } from "../classes/localConnection"; +import { + getPartialDatasourceFile, + populateScratchpad, + runDataSource, +} from "../commands/dataSourceCommand"; +import { executeQuery } from "../commands/serverCommand"; +import { findConnection } from "../commands/workspaceCommand"; +import { CellKind } from "../models/notebook"; +import { getBasename } from "../utils/core"; +import { MessageKind, notify } from "../utils/notifications"; +import { resultToBase64, needsScratchpad } from "../utils/queryUtils"; import { convertToGrid, formatResult } from "../utils/resultsRenderer"; +const logger = "notebookController"; + export class KxNotebookController { readonly controllerId = "kx-notebook-1"; readonly notebookType = "kx-notebook"; readonly label = "KX Notebook"; - readonly supportedLanguages = ["q", "python"]; + readonly supportedLanguages = ["q", "python", "sql"]; protected readonly controller: vscode.NotebookController; protected order = 0; @@ -44,55 +55,178 @@ export class KxNotebookController { this.controller.dispose(); } - createConnectionManager() { - return new ConnectionManagementService(); - } - async execute( cells: vscode.NotebookCell[], - _notebook: vscode.NotebookDocument, - _controller: vscode.NotebookController, + notebook: vscode.NotebookDocument, + controller: vscode.NotebookController, ): Promise { - const conn = ext.activeConnection; - if (conn === undefined) { - vscode.window.showErrorMessage( - "You aren't connected to any connection. Once connected please try again.", - ); + const conn = await findConnection(notebook.uri); + if (!conn) { return; } - const manager = this.createConnectionManager(); - const isInsights = conn instanceof InsightsConnection; - const connVersion = isInsights ? (conn.insightsVersion ?? 0) : 0; + const { isInsights, connVersion } = this.getInsightProps(conn); for (const cell of cells) { - const isPython = cell.document.languageId === "python"; - const execution = this.controller.createNotebookCellExecution(cell); + const execution = controller.createNotebookCellExecution(cell); + execution.executionOrder = ++this.order; execution.start(Date.now()); + let success = false; + let cancellationDisposable: vscode.Disposable | undefined; + try { - const results = await manager.executeQuery( - cell.document.getText(), - conn.connLabel, - ".", - false, - isPython, + const kind = getCellKind(cell); + + const { target, variable } = this.getCellMetadata( + cell, + kind, + isInsights, + conn, ); - const rendered = render(results, isPython, isInsights, connVersion); + const executor = this.getQueryExecutor( + conn, + execution, + cell, + kind, + target, + variable, + ); - execution.replaceOutput([ - new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text(rendered.text, rendered.mime), - ]), + let results = await Promise.race([ + (target || kind === CellKind.SQL) && !variable + ? executor + : needsScratchpad(conn.connLabel, executor), + new Promise((_, reject) => { + const updateCancelled = () => { + if (execution.token.isCancellationRequested) { + reject(new vscode.CancellationError()); + } + }; + updateCancelled(); + cancellationDisposable = + execution.token.onCancellationRequested(updateCancelled); + }), ]); + + if (variable) { + results = `Scratchpad variable (${variable}) populated.`; + } + + const rendered = + target || kind === CellKind.SQL + ? render(results, kind === CellKind.PYTHON, isInsights) + : render( + results, + kind === CellKind.PYTHON, + isInsights, + connVersion, + ); + + this.replaceOutput(execution, rendered); + success = true; } catch (error) { - kdbOutputLog(`${error}`, "ERROR"); + notify(`Execution on ${conn.connLabel} stopped.`, MessageKind.DEBUG, { + logger, + params: error, + }); + this.replaceOutput(execution, { + text: `

Execution stopped.

${error instanceof Error ? error.message : error}

`, + mime: "text/html", + }); + break; } finally { - execution.end(true, Date.now()); + cancellationDisposable?.dispose(); + execution.end(success, Date.now()); } } } + + getInsightProps(conn: LocalConnection | InsightsConnection) { + let isInsights = false; + let connVersion = 0; + + if (conn instanceof InsightsConnection) { + isInsights = true; + connVersion = conn.insightsVersion ?? 0; + } + + return { isInsights, connVersion }; + } + + getCellMetadata( + cell: vscode.NotebookCell, + kind: CellKind, + isInsights: boolean, + conn: InsightsConnection | LocalConnection, + ): { target?: string; variable?: string } { + const target = cell.metadata?.target; + const variable = cell.metadata?.variable; + + if (!isInsights) { + if (kind === CellKind.SQL) { + throw new Error(`SQL is not supported on ${conn.connLabel}`); + } + if (target) { + throw new Error( + `Setting execution target (${target}) is not supported on ${conn.connLabel}.`, + ); + } + if (variable) { + throw new Error( + `Setting output variable ${variable} is not supported on ${conn.connLabel}.`, + ); + } + } + + return { target, variable }; + } + + getQueryExecutor( + conn: LocalConnection | InsightsConnection, + execution: vscode.NotebookCellExecution, + cell: vscode.NotebookCell, + kind: CellKind, + target?: string, + variable?: string, + ): Promise { + const executorName = getBasename(cell.notebook.uri); + + if (target || kind === CellKind.SQL) { + const params = getPartialDatasourceFile( + cell.document.getText(), + target, + kind === CellKind.SQL, + kind === CellKind.PYTHON, + ); + return variable + ? populateScratchpad(params, conn.connLabel, variable, true) + : runDataSource(params, conn.connLabel, executorName); + } else { + return executeQuery( + cell.document.getText(), + conn.connLabel, + executorName, + ".", + kind === CellKind.PYTHON, + false, + false, + execution.token, + ); + } + } + + replaceOutput( + execution: vscode.NotebookCellExecution, + rendered: Rendered, + ): void { + execution.replaceOutput([ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text(rendered.text, rendered.mime), + ]), + ]); + } } interface Rendered { @@ -104,7 +238,7 @@ function render( results: any, isPython: boolean, isInsights: boolean, - connVersion: number, + connVersion?: number, ): Rendered { let text = "No results."; let mime = "text/plain"; diff --git a/src/services/notebookProviders.ts b/src/services/notebookProviders.ts new file mode 100644 index 000000000..d1de49cc4 --- /dev/null +++ b/src/services/notebookProviders.ts @@ -0,0 +1,147 @@ +/* + * Copyright (c) 1998-2025 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 * as vscode from "vscode"; + +import { InsightsNode } from "./kdbTreeProvider"; +import { + getConnectionForServer, + getServerForUri, +} from "../commands/workspaceCommand"; +import { CellKind } from "../models/notebook"; + +export class KxNotebookTargetActionProvider + implements vscode.NotebookCellStatusBarItemProvider +{ + private readonly _onDidChangeCellStatusBarItems = + new vscode.EventEmitter(); + readonly onDidChangeCellStatusBarItems = + this._onDidChangeCellStatusBarItems.event; + + constructor() { + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("kdb.connectionMap")) { + this._onDidChangeCellStatusBarItems.fire(); + } + }); + } + + async provideCellStatusBarItems( + cell: vscode.NotebookCell, + _token: vscode.CancellationToken, + ) { + const server = getServerForUri(cell.notebook.uri); + const conn = server ? await getConnectionForServer(server) : undefined; + const isInsights = conn instanceof InsightsNode; + + const actions: vscode.NotebookCellStatusBarItem[] = []; + const kind = getCellKind(cell); + const target = cell.metadata?.target; + + if (kind === CellKind.Q || kind === CellKind.PYTHON) { + const targetItem = new vscode.NotebookCellStatusBarItem( + target || (isInsights ? "scratchpad" : "default"), + vscode.NotebookCellStatusBarAlignment.Right, + ); + + targetItem.command = { + title: "Choose Target", + command: "kdb.file.pickTarget", + arguments: [cell], + }; + + targetItem.tooltip = "Execution Target"; + + actions.push(targetItem); + } + + if (target || kind === CellKind.SQL) { + const variableNameItem = new vscode.NotebookCellStatusBarItem( + `(${cell.metadata?.variable || "none"})`, + vscode.NotebookCellStatusBarAlignment.Right, + ); + + variableNameItem.tooltip = "Output Variable Name"; + + variableNameItem.command = { + title: "Input Variable Name", + command: "kdb.file.inputVariable", + arguments: [cell], + }; + + actions.push(variableNameItem); + } + + return actions; + } +} + +export async function inputVariable(cell?: vscode.NotebookCell) { + const variable = await vscode.window.showInputBox({ + title: "Enter Output Variable Name", + value: cell?.metadata?.variable, + validateInput, + }); + if (variable !== undefined) { + if (cell) { + await updateCellMetadata(cell, { + target: cell.metadata?.target, + variable: variable, + }); + } + return variable; + } +} + +export function validateInput(value?: string) { + if (value === undefined) { + return undefined; + } + if (value.length > 32) { + return "Variable name should be less than or equal to 32 characters."; + } + if (/^[_0-9]/s.test(value)) { + return "Variable name can't start with a number or underscore."; + } + if (/[^a-zA-Z_0-9.]/s.test(value)) { + return "Variable name contains invalid characters."; + } + return undefined; +} + +export async function updateCellMetadata( + cell: vscode.NotebookCell, + metadata: { target?: string; variable?: string }, +) { + const edit = new vscode.WorkspaceEdit(); + edit.set(cell.notebook.uri, [ + vscode.NotebookEdit.updateCellMetadata(cell.index, { + target: metadata.target || undefined, + variable: metadata.variable || undefined, + }), + ]); + await vscode.workspace.applyEdit(edit); +} + +export function getCellKind(cell: vscode.NotebookCell) { + switch (cell.document.languageId) { + case "q": + return CellKind.Q; + case "python": + return CellKind.PYTHON; + case "sql": + return CellKind.SQL; + default: + return CellKind.MARKDOWN; + } +} diff --git a/src/services/notebookSerializer.ts b/src/services/notebookSerializer.ts index 7626721eb..2c29b12ea 100644 --- a/src/services/notebookSerializer.ts +++ b/src/services/notebookSerializer.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -34,6 +34,7 @@ export class KxNotebookSerializer implements vscode.NotebookSerializer { cell.value, cell.languageId, ); + target.metadata = { target: cell.target, variable: cell.variable }; target.outputs = cell.outputs.map((output) => { return new vscode.NotebookCellOutput( output.items.map((item) => { @@ -61,6 +62,8 @@ export class KxNotebookSerializer implements vscode.NotebookSerializer { kind: cell.kind, value: cell.value, languageId: cell.languageId, + target: cell.metadata?.target || undefined, + variable: cell.metadata?.variable || undefined, outputs: (cell.outputs || []).map((output) => { return { items: output.items.map((item) => { diff --git a/src/services/queryHistoryProvider.ts b/src/services/queryHistoryProvider.ts index 3d3d2aa4a..f4caeb75e 100644 --- a/src/services/queryHistoryProvider.ts +++ b/src/services/queryHistoryProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/quickFixProvider.ts b/src/services/quickFixProvider.ts index 30cc06ea2..aeaac6d38 100644 --- a/src/services/quickFixProvider.ts +++ b/src/services/quickFixProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/services/resultsPanelProvider.ts b/src/services/resultsPanelProvider.ts index 5746983f0..7ca086dec 100644 --- a/src/services/resultsPanelProvider.ts +++ b/src/services/resultsPanelProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -22,19 +22,21 @@ import { } from "vscode"; import { ext } from "../extensionVariables"; -import { kdbOutputLog } from "../utils/core"; import * as utils from "../utils/execution"; import { getNonce } from "../utils/getNonce"; import { getUri } from "../utils/getUri"; +import { MessageKind, notify } from "../utils/notifications"; import { convertToGrid, formatResult } from "../utils/resultsRenderer"; +const logger = "resultsPanelProvider"; + export class KdbResultsViewProvider implements WebviewViewProvider { public static readonly viewType = "kdb-results"; public isInsights = false; public isPython = false; public _colorTheme: any; private _view?: WebviewView; - private savedParamStates: any; + private savedParamStates: any = {}; private _results: string | string[] = ""; constructor(private readonly _extensionUri: Uri) { @@ -48,10 +50,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider { this.savedParamStates.isPython, ); }); - ext.isResultsTabVisible = true; } - /* istanbul ignore next */ + /* c8 ignore next */ public resolveWebviewView(webviewView: WebviewView) { this._view = webviewView; @@ -61,6 +62,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider { }; webviewView.webview.html = this._getWebviewContent(); + + ext.isResultsTabVisible = this._view?.visible || false; + this.updateWebView(""); webviewView.webview.onDidReceiveMessage((data) => { @@ -101,12 +105,14 @@ export class KdbResultsViewProvider implements WebviewViewProvider { exportToCsv() { if (ext.resultPanelCSV === "") { - window.showErrorMessage("No results to export"); + notify("No results to export", MessageKind.ERROR, { logger }); return; } const workspaceFolders = workspace.workspaceFolders; if (!workspaceFolders) { - window.showErrorMessage("Open a folder to export results"); + notify("Open a folder to export results", MessageKind.ERROR, { + logger, + }); return; } const workspaceUri = workspaceFolders[0].uri; @@ -137,7 +143,9 @@ export class KdbResultsViewProvider implements WebviewViewProvider { let gridOptions = undefined; if (!this._view) { - kdbOutputLog("[Results Tab] No view to update", "ERROR"); + notify("No view to update", MessageKind.ERROR, { + logger, + }); return; } diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 5f7d3f132..ca54edf1a 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/connLabel.ts b/src/utils/connLabel.ts index 76f02face..f320a05fb 100644 --- a/src/utils/connLabel.ts +++ b/src/utils/connLabel.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -14,44 +14,110 @@ import { workspace } from "vscode"; import { ext } from "../extensionVariables"; -import { kdbOutputLog } from "./core"; -import { Telemetry } from "./telemetryClient"; +import { MessageKind, notify } from "./notifications"; import { ConnectionLabel, Labels } from "../models/labels"; import { NewConnectionPannel } from "../panels/newConnection"; import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; +const logger = "connLabel"; + export function getWorkspaceLabels() { const existingConnLbls = workspace .getConfiguration() .get("kdb.connectionLabels"); ext.connLabelList.length = 0; if (existingConnLbls && existingConnLbls.length > 0) { - existingConnLbls.forEach((label: Labels) => { + const sortedLabels = existingConnLbls.sort((a, b) => + a.name.localeCompare(b.name), + ); + sortedLabels.forEach((label: Labels) => { ext.connLabelList.push(label); }); } } +export function clearWorkspaceLabels() { + getWorkspaceLabels(); + getWorkspaceLabelsConnMap(); + + if (ext.connLabelList.length === 0) { + notify( + "Cleaning connection mappings for nonexistent labels.", + MessageKind.DEBUG, + { + logger, + telemetry: "Label.Cleanup.NoLabels", + }, + ); + workspace.getConfiguration().update("kdb.labelsConnectionMap", [], true); + return; + } + + const validLabelNames = new Set(ext.connLabelList.map((label) => label.name)); + const initialLength = ext.labelConnMapList.length; + + for (let i = ext.labelConnMapList.length - 1; i >= 0; i--) { + if (!validLabelNames.has(ext.labelConnMapList[i].labelName)) { + ext.labelConnMapList.splice(i, 1); + } + } + + const removedCount = initialLength - ext.labelConnMapList.length; + if (removedCount > 0) { + workspace + .getConfiguration() + .update("kdb.labelsConnectionMap", ext.labelConnMapList, true); + + notify( + `Removed ${removedCount} orphaned label connection mapping${removedCount > 1 ? "s" : ""}.`, + MessageKind.DEBUG, + { + logger, + telemetry: "Label.Cleanup.OrphanedMappings", + measurements: { removedMappings: removedCount }, + }, + ); + } +} + export function createNewLabel(name: string, colorName: string) { getWorkspaceLabels(); const color = ext.labelColors.find( (color) => color.name.toLowerCase() === colorName.toLowerCase(), ); if (name === "") { - kdbOutputLog("Label name can't be empty", "ERROR"); + notify("Label name can't be empty.", MessageKind.ERROR, { logger }); + return; + } + if (checkIfLabelExists(name)) { + notify("Label with this name already exists.", MessageKind.ERROR, { + logger, + telemetry: "Label.Create.Exists", + }); + return; } if (color && name !== "") { const newLbl: Labels = { name: name, color: color, }; + ext.connLabelList.push(newLbl); + ext.connLabelList.sort((a, b) => a.name.localeCompare(b.name)); + workspace .getConfiguration() .update("kdb.connectionLabels", ext.connLabelList, true); - Telemetry.sendEvent("Label.Create", {}, getLabelStatistics()); + + notify("Connection label created.", MessageKind.DEBUG, { + logger, + telemetry: "Label.Create", + measurements: getLabelStatistics(), + }); } else { - kdbOutputLog("No Color selected for the label", "ERROR"); + notify("No Color selected for the label.", MessageKind.ERROR, { + logger, + }); } } @@ -62,7 +128,14 @@ export function getWorkspaceLabelsConnMap() { ext.labelConnMapList.length = 0; if (existingLabelConnMaps && existingLabelConnMaps.length > 0) { existingLabelConnMaps.forEach((labelConnMap: ConnectionLabel) => { - ext.labelConnMapList.push(labelConnMap); + const sortedLabelConnMap: ConnectionLabel = { + labelName: labelConnMap.labelName, + connections: labelConnMap.connections.sort((a, b) => + a.localeCompare(b), + ), + }; + + ext.labelConnMapList.push(sortedLabelConnMap); }); } } @@ -92,11 +165,11 @@ export function addConnToLabel(labelName: string, connName: string) { connections: [connName], }); } - Telemetry.sendEvent( - "Label.Assign.Connection", - {}, - getConnectionLabelStatistics(connName), - ); + notify("Connection assigned to label.", MessageKind.DEBUG, { + logger, + telemetry: "Label.Assign.Connection", + measurements: getConnectionLabelStatistics(connName), + }); } } @@ -111,11 +184,12 @@ export function removeConnFromLabels(connName: string) { workspace .getConfiguration() .update("kdb.labelsConnectionMap", ext.labelConnMapList, true); - Telemetry.sendEvent( - "Label.Remove.Connection", - {}, - getConnectionLabelStatistics(connName), - ); + + notify("Connection removed from label.", MessageKind.DEBUG, { + logger, + telemetry: "Label.Remove.Connection", + measurements: getConnectionLabelStatistics(connName), + }); } export async function handleLabelsConnMap(labels: string[], connName: string) { @@ -143,7 +217,17 @@ export function retrieveConnLabelsNames( } export function renameLabel(name: string, newName: string) { + if (name === newName || newName === "") { + return; + } getWorkspaceLabels(); + if (checkIfLabelExists(newName)) { + notify("Label with this name already exists.", MessageKind.ERROR, { + logger, + telemetry: "Label.Rename.Exists", + }); + return; + } const found = ext.connLabelList.find((item) => item.name === name); if (found) { found.name = newName; @@ -188,7 +272,12 @@ export function deleteLabel(name: string) { workspace .getConfiguration() .update("kdb.connectionLabels", ext.connLabelList, true); - Telemetry.sendEvent("Label.Delete", {}, getLabelStatistics()); + + notify("Connection label deleted.", MessageKind.DEBUG, { + logger, + telemetry: "Label.Delete", + measurements: getLabelStatistics(), + }); NewConnectionPannel.refreshLabels(); } @@ -251,3 +340,7 @@ export function getConnectionLabelStatistics( return statistics; } + +export function checkIfLabelExists(name: string): boolean { + return ext.connLabelList.some((label) => label.name === name); +} diff --git a/src/utils/core.ts b/src/utils/core.ts index ceb75f1c0..d01ccf2f7 100644 --- a/src/utils/core.ts +++ b/src/utils/core.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -15,17 +15,20 @@ import { ChildProcess } from "child_process"; import { createHash } from "crypto"; import { writeFile } from "fs/promises"; import { pathExists } from "fs-extra"; +import path from "node:path"; import { env } from "node:process"; import { tmpdir } from "os"; import { join } from "path"; import * as semver from "semver"; -import { commands, ConfigurationTarget, Uri, window, workspace } from "vscode"; +import { commands, ConfigurationTarget, Uri, workspace } from "vscode"; import { installTools } from "../commands/installTools"; import { ext } from "../extensionVariables"; import { tryExecuteCommand } from "./cpUtils"; +import { MessageKind, notify } from "./notifications"; import { showRegistrationNotification } from "./registration"; -import { Telemetry } from "./telemetryClient"; +import { errorMessage } from "./shared"; +import { stat, which } from "./shell"; import { InsightDetails, Insights, @@ -34,8 +37,12 @@ import { } from "../models/connectionsModels"; import { QueryResult } from "../models/queryResult"; +const logger = "core"; + export function log(childProcess: ChildProcess): void { - kdbOutputLog(`Process ${childProcess.pid} started`, "INFO"); + notify(`Process ${childProcess.pid} started`, MessageKind.DEBUG, { + logger, + }); } export async function checkOpenSslInstalled(): Promise { @@ -50,9 +57,10 @@ export async function checkOpenSslInstalled(): Promise { const matcher = /(\d+.\d+.\d+)/; const installedVersion = result.cmdOutput.match(matcher); - kdbOutputLog( + notify( `Detected version ${installedVersion} of OpenSSL installed.`, - "INFO", + MessageKind.DEBUG, + { logger }, ); return semver.clean(installedVersion ? installedVersion[1] : ""); @@ -60,7 +68,10 @@ export async function checkOpenSslInstalled(): Promise { } catch (err) { // Disabled the error, as it is not critical // kdbOutputLog(`Error in checking OpenSSL version: ${err}`, "ERROR"); - Telemetry.sendException(err as Error); + notify("OpenSSL not found.", MessageKind.DEBUG, { + logger, + telemetry: err as Error, + }); } return null; } @@ -151,7 +162,11 @@ export function saveLocalProcessObj( childProcess: ChildProcess, args: string[], ): void { - kdbOutputLog(`Child process id ${childProcess.pid} saved in cache.`, "INFO"); + notify( + `Child process id ${childProcess.pid} saved in cache.`, + MessageKind.DEBUG, + { logger }, + ); ext.localProcessObjects[args[2]] = childProcess; } @@ -167,17 +182,55 @@ export function getOsFile(): string | undefined { } } -export function getPlatformFolder(platform: string): string | undefined { +export function getPlatformFolder( + platform: string, + arch?: string, +): string | undefined { if (platform === "win32") { return "w64"; } else if (platform === "darwin") { return "m64"; } else if (platform === "linux") { - return "l64"; + return arch === "arm64" ? "l64arm" : "l64"; } return undefined; } +export function getQExecutablePath() { + const folder = getPlatformFolder(process.platform, process.arch); + + if (!folder) { + throw new Error( + `Unsupported platform (${process.platform}) or architecture (${process.arch}).`, + ); + } + + if (ext.REAL_QHOME) { + const q = path.join(ext.REAL_QHOME, "bin", "q"); + return stat(q) ? q : path.join(ext.REAL_QHOME, folder, "q"); + } else { + try { + for (const target of which("q")) { + if (target.endsWith(path.join("bin", "q"))) return target; + } + } catch (error) { + notify(errorMessage(error), MessageKind.DEBUG, { logger }); + } + } + + const qHomeDirectory = workspace + .getConfiguration("kdb") + .get("qHomeDirectory", ""); + + if (qHomeDirectory) { + return path.join(qHomeDirectory, folder, "q"); + } + + throw new Error( + `Neither QHOME environment variable nor qHomeDirectory is set.`, + ); +} + export async function getWorkspaceFolder( corePath: string, ): Promise { @@ -192,11 +245,19 @@ export async function getWorkspaceFolder( } export function getServers(): Server | undefined { - return workspace.getConfiguration().get("kdb.servers"); + const servers = workspace.getConfiguration().get("kdb.servers"); + + return servers + ? Object.fromEntries( + Object.entries(servers).sort(([, a], [, b]) => + a.serverAlias.localeCompare(b.serverAlias), + ), + ) + : servers; } // TODO: Remove this on 1.9.0 release -/* istanbul ignore next */ +/* c8 ignore next */ export function fixUnnamedAlias(): void { const servers = getServers(); const insights = getInsights(); @@ -235,22 +296,16 @@ export function fixUnnamedAlias(): void { } } -export function getHideDetailedConsoleQueryOutput(): void { - const setting = workspace - .getConfiguration() - .get("kdb.hideDetailedConsoleQueryOutput"); - if (setting === undefined) { - workspace - .getConfiguration() - .update( - "kdb.hideDetailedConsoleQueryOutput", - true, - ConfigurationTarget.Global, - ); - ext.hideDetailedConsoleQueryOutput = true; - } else { - ext.hideDetailedConsoleQueryOutput = setting; - } +export function getAutoFocusOutputOnEntrySetting(): boolean { + return workspace + .getConfiguration("kdb") + .get("autoFocusOutputOnEntry", true); +} + +export function getHideDetailedConsoleQueryOutputSetting(): boolean { + return workspace + .getConfiguration("kdb") + .get("hideDetailedConsoleQueryOutput", true); } export function setOutputWordWrapper(): void { @@ -279,9 +334,22 @@ export function getInsights(): Insights | undefined { "kdb.insightsEnterpriseConnections", ); - return insights && Object.keys(insights).length > 0 - ? insights - : configuration.get("kdb.insights"); + const insightsList: Insights | undefined = + insights && Object.keys(insights).length > 0 + ? insights + : configuration.get("kdb.insights"); + + if (!insightsList || Object.keys(insightsList).length === 0) { + return undefined; + } + + return insightsList + ? Object.fromEntries( + Object.entries(insightsList).sort(([, a], [, b]) => + a.alias.localeCompare(b.alias), + ), + ) + : insightsList; } export async function updateServers(servers: Server): Promise { @@ -302,8 +370,8 @@ export async function updateInsights(insights: Insights): Promise { export function getServerName(server: ServerDetails): string { return server.serverAlias != "" - ? `${server.serverName}:${server.serverPort} [${server.serverAlias}]` - : `${server.serverName}:${server.serverPort}`; + ? `${server.serverAlias} [${server.serverName}:${server.serverPort}]` + : `[${server.serverName}:${server.serverPort}]`; } export function getServerAlias(serverList: ServerDetails[]): void { @@ -314,44 +382,32 @@ export function getServerAlias(serverList: ServerDetails[]): void { }); } -export function kdbOutputLog( - message: string, - type: string, - supressDialog?: boolean, -): void { - const dateNow = new Date().toLocaleDateString(); - const timeNow = new Date().toLocaleTimeString(); - ext.outputChannel.appendLine(`[${dateNow} ${timeNow}] [${type}] ${message}`); - if (type === "ERROR" && !supressDialog) { - window.showErrorMessage( - `Error occured, check kdb output channel for details.`, - ); - } -} - export function tokenUndefinedError(connLabel: string): void { - kdbOutputLog( + notify( `Error retrieving access token for Insights connection named: ${connLabel}`, - "ERROR", + MessageKind.ERROR, + { logger }, ); } export function invalidUsernameJWT(connLabel: string): void { - kdbOutputLog( + notify( `JWT did not contain a valid preferred username for Insights connection: ${connLabel}`, - "ERROR", + MessageKind.ERROR, + { logger }, ); } -/* istanbul ignore next */ -export function offerConnectAction(connLabel: string): void { - window - .showInformationMessage( +/* c8 ignore next */ +export function offerConnectAction(connLabel?: string): void { + if (connLabel) { + notify( `You aren't connected to ${connLabel}, would you like to connect? Once connected please try again.`, + MessageKind.WARNING, + {}, "Connect", "Cancel", - ) - .then(async (result) => { + ).then(async (result) => { if (result === "Connect") { await commands.executeCommand( "kdb.connections.connect.via.dialog", @@ -359,30 +415,38 @@ export function offerConnectAction(connLabel: string): void { ); } }); + } else { + notify( + "You aren't connected to any connection. Once connected please try again.", + MessageKind.WARNING, + { logger }, + ); + } } export function noSelectedConnectionAction(): void { - window.showInformationMessage( + notify( `You didn't selected any existing connection to execute this action, please select a connection and try again.`, + MessageKind.INFO, ); } -/* istanbul ignore next */ +/* c8 ignore next */ export function offerReconnectionAfterEdit(connLabel: string): void { - window - .showInformationMessage( - `You are no longer connected to ${connLabel}, would you like to connect?`, - "Connect", - "Cancel", - ) - .then(async (result) => { - if (result === "Connect") { - await commands.executeCommand( - "kdb.connections.connect.via.dialog", - connLabel, - ); - } - }); + notify( + `You are no longer connected to ${connLabel}, would you like to connect?`, + MessageKind.INFO, + {}, + "Connect", + "Cancel", + ).then(async (result) => { + if (result === "Connect") { + await commands.executeCommand( + "kdb.connections.connect.via.dialog", + connLabel, + ); + } + }); } export function getInsightsAlias(insightsList: InsightDetails[]): void { @@ -439,9 +503,10 @@ export async function checkLocalInstall( } } if (QHOME || env.QHOME) { + // TODO 1: This is wrong, env vars should be read only. env.QHOME = QHOME || env.QHOME; if (!pathExists(env.QHOME!)) { - kdbOutputLog("QHOME path stored is empty", "ERROR"); + notify("QHOME path stored is empty.", MessageKind.ERROR, { logger }); } await writeFile( join(__dirname, "qinstall.md"), @@ -453,7 +518,9 @@ export async function checkLocalInstall( .getConfiguration() .update("kdb.qHomeDirectory", env.QHOME, ConfigurationTarget.Global); - kdbOutputLog(`Installation of q found here: ${env.QHOME}`, "INFO"); + notify(`Installation of q found here: ${env.QHOME}`, MessageKind.DEBUG, { + logger, + }); showRegistrationNotification(); @@ -461,9 +528,9 @@ export async function checkLocalInstall( .getConfiguration() .get("kdb.hideInstallationNotification"); if (!hideNotification) { - window.showInformationMessage( - `Installation of q found here: ${env.QHOME}`, - ); + notify(`Installation of q found here: ${env.QHOME}`, MessageKind.INFO, { + logger, + }); } // persist the notification seen option @@ -481,28 +548,24 @@ export async function checkLocalInstall( // set custom context that QHOME is not setup to control walkthrough visibility commands.executeCommand("setContext", "kdb.showInstallWalkthrough", true); - window - .showInformationMessage( - "Local q installation not found!", - "Install new instance", - "No", - "Never show again", - ) - .then(async (installResult) => { - if (installResult === "Install new instance") { - await installTools(); - } else if (installResult === "Never show again") { - await workspace - .getConfiguration() - .update( - "kdb.neverShowQInstallAgain", - true, - ConfigurationTarget.Global, - ); - } else { - showRegistrationNotification(); - } - }); + notify( + "Local q installation not found!", + MessageKind.INFO, + { logger }, + "Install new instance", + "No", + "Never show again", + ).then(async (installResult) => { + if (installResult === "Install new instance") { + await installTools(); + } else if (installResult === "Never show again") { + await workspace + .getConfiguration() + .update("kdb.neverShowQInstallAgain", true, ConfigurationTarget.Global); + } else { + showRegistrationNotification(); + } + }); } export async function convertBase64License( @@ -644,16 +707,16 @@ export function hasWorkspaceOrShowOption(action: string) { if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { return true; } - window - .showWarningMessage( - `No workspace folder is open. Please open a folder to enable ${action}.`, - "Open", - ) - .then((res) => { - if (res === "Open") { - commands.executeCommand("workbench.action.files.openFolder"); - } - }); + notify( + `No workspace folder is open. Please open a folder to enable ${action}.`, + MessageKind.WARNING, + {}, + "Open", + ).then((res) => { + if (res === "Open") { + commands.executeCommand("workbench.action.files.openFolder"); + } + }); return false; } @@ -703,3 +766,7 @@ export function isBaseVersionGreaterOrEqual( ): boolean { return semver.gte(`${baseVersion}.0`, `${targetVersion}.0`); } + +export function getBasename(uri: Uri): string { + return path.basename(uri.path); +} diff --git a/src/utils/cpUtils.ts b/src/utils/cpUtils.ts index 117e9fe70..23f7af884 100644 --- a/src/utils/cpUtils.ts +++ b/src/utils/cpUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -16,7 +16,10 @@ import * as os from "os"; import { join } from "path"; import { ext } from "../extensionVariables"; -import { kdbOutputLog } from "./core"; +import { getAutoFocusOutputOnEntrySetting } from "./core"; +import { MessageKind, notify } from "./notifications"; + +const logger = "cpUtils"; export async function executeCommand( workingDirectory: string | undefined, @@ -30,15 +33,18 @@ export async function executeCommand( spawnCallback, ...args, ); - ext.outputChannel.show(); + if (getAutoFocusOutputOnEntrySetting()) { + ext.outputChannel.show(true); + } if (result.code !== 0) { throw new Error( `Failed to run ${command} command. Check output window for more details.`, ); } else { - kdbOutputLog( + notify( `Finished running command: ${command} ${result.formattedArgs}`, - "INFO", + MessageKind.DEBUG, + { logger }, ); } return result.cmdOutput; @@ -89,17 +95,17 @@ export async function tryExecuteCommand( data = data.toString(); cmdOutput = cmdOutput.concat(data); cmdOutputIncludingStderr = cmdOutputIncludingStderr.concat(data); - kdbOutputLog(data, "INFO"); + notify(data, MessageKind.DEBUG, { logger }); }); childProc.stderr?.on("data", (data: string | Buffer) => { data = data.toString(); cmdOutputIncludingStderr = cmdOutputIncludingStderr.concat(data); - kdbOutputLog(data, "INFO"); + notify(data, MessageKind.DEBUG, { logger }); }); childProc.on("error", (error) => { - kdbOutputLog(error.message, "ERROR"); + notify(error.message, MessageKind.ERROR, { logger }); reject(error); }); diff --git a/src/utils/dataSource.ts b/src/utils/dataSource.ts index 7e4e5f293..1c1728f93 100644 --- a/src/utils/dataSource.ts +++ b/src/utils/dataSource.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -13,29 +13,32 @@ import * as fs from "fs"; import path from "path"; -import { workspace, window, Uri } from "vscode"; +import { workspace, Uri } from "vscode"; import { InsightsConnection } from "../classes/insightsConnection"; import { ext } from "../extensionVariables"; -import { kdbOutputLog } from "./core"; -import { Telemetry } from "./telemetryClient"; +import { MessageKind, notify } from "./notifications"; import { DataSourceFiles } from "../models/dataSource"; import { DataSourcesPanel } from "../panels/datasource"; +const logger = "dataSource"; + export function createKdbDataSourcesFolder(): string { const rootPath = ext.context.globalStorageUri.fsPath; const kdbDataSourcesFolderPath = path.join(rootPath, ext.kdbDataSourceFolder); if (!fs.existsSync(rootPath)) { - kdbOutputLog( - `[DATSOURCE] Directory created to the extension folder: ${rootPath}`, - "INFO", + notify( + `Directory created to the extension folder: ${rootPath}`, + MessageKind.DEBUG, + { logger }, ); fs.mkdirSync(rootPath); } if (!fs.existsSync(kdbDataSourcesFolderPath)) { - kdbOutputLog( - `[DATSOURCE] Directory created to the extension folder: ${kdbDataSourcesFolderPath}`, - "INFO", + notify( + `Directory created to the extension folder: ${kdbDataSourcesFolderPath}`, + MessageKind.DEBUG, + { logger }, ); fs.mkdirSync(kdbDataSourcesFolderPath); } @@ -51,13 +54,10 @@ export function convertTimeToTimestamp(time: string): string { const timePart = parts[1].replace("Z", "0").padEnd(9, "0"); return `${datePart}.${timePart}`; } catch (error) { - kdbOutputLog( - `The string param is in an incorrect format. Param: ${time} Error: ${error}`, - "ERROR", - ); - console.error( - `The string param is in an incorrect format. Param: ${time} Error: ${error}`, - ); + notify("The string param is in an incorrect format.", MessageKind.ERROR, { + logger, + params: { time, error }, + }); return ""; } } @@ -116,7 +116,7 @@ export function oldFilesExists(): boolean { return files.length > 0; } -/* istanbul ignore next */ +/* c8 ignore next */ export async function importOldDsFiles(): Promise { const kdbDataSourcesFolderPath = createKdbDataSourcesFolder(); const files = fs.readdirSync(kdbDataSourcesFolderPath); @@ -135,7 +135,7 @@ export async function importOldDsFiles(): Promise { ext.oldDSformatExists = false; } -/* istanbul ignore next */ +/* c8 ignore next */ export async function addDSToLocalFolder(ds: DataSourceFiles): Promise { const folders = workspace.workspaceFolders; if (folders) { @@ -151,7 +151,9 @@ export async function addDSToLocalFolder(ds: DataSourceFiles): Promise { filePath = path.join(importToUri.fsPath, fileName); } fs.writeFileSync(filePath, JSON.stringify(ds)); - window.showInformationMessage(`Datasource created.`); - Telemetry.sendEvent("Datasource.Created"); + notify(`Datasource created.`, MessageKind.INFO, { + logger, + telemetry: "Datasource.Created", + }); } } diff --git a/src/utils/decode.ts b/src/utils/decode.ts index 68afbdaec..1c5dccdd0 100644 --- a/src/utils/decode.ts +++ b/src/utils/decode.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/execution.ts b/src/utils/execution.ts index 205f2e973..2b9b9cb7e 100644 --- a/src/utils/execution.ts +++ b/src/utils/execution.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -16,9 +16,12 @@ import path from "path"; import { Uri, window, workspace } from "vscode"; import { ext } from "../extensionVariables"; -import { kdbOutputLog } from "./core"; +import { getAutoFocusOutputOnEntrySetting } from "./core"; +import { MessageKind, notify } from "./notifications"; import { QueryResultType } from "../models/queryResult"; +const logger = "execution"; + interface tblHeader { label: string; count: number; @@ -37,7 +40,9 @@ export function runQFileTerminal(filename?: string): void { }); const terminal = window.createTerminal(terminalName); if (env.QHOME) { - terminal.show(); + if (getAutoFocusOutputOnEntrySetting()) { + terminal.show(true); + } terminal.sendText(command); } } @@ -130,12 +135,15 @@ export async function exportToCsv(workspaceUri: Uri): Promise { try { await workspace.fs.writeFile(filePath, Buffer.from(ext.resultPanelCSV)); - kdbOutputLog("file located at: " + filePath.fsPath, "INFO"); + notify("file located at: " + filePath.fsPath, MessageKind.DEBUG, { + logger, + }); window.showTextDocument(filePath, { preview: false }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - kdbOutputLog(`Error writing file: ${errorMessage}`, "ERROR"); - window.showErrorMessage(`Failed to write file: ${errorMessage}`); + notify(`Failed to write file: ${errorMessage}`, MessageKind.ERROR, { + logger, + }); } } diff --git a/src/utils/executionConsole.ts b/src/utils/executionConsole.ts index f821cf7ad..0a9c677a2 100644 --- a/src/utils/executionConsole.ts +++ b/src/utils/executionConsole.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -13,11 +13,12 @@ import { OutputChannel, commands, window } from "vscode"; -import { ext } from "../extensionVariables"; import { - getHideDetailedConsoleQueryOutput, setOutputWordWrapper, + getAutoFocusOutputOnEntrySetting, + getHideDetailedConsoleQueryOutputSetting, } from "./core"; +import { MessageKind, notify } from "./notifications"; import { addQueryHistory, checkIfIsDatasource, @@ -25,6 +26,8 @@ import { } from "./queryUtils"; import { ServerType } from "../models/connectionsModels"; +const logger = "executionConsole"; + export class ExecutionConsole { public static current: ExecutionConsole | undefined; private _console: OutputChannel; @@ -86,11 +89,12 @@ export class ExecutionConsole { duration?: string, isFromConnTree?: boolean, ): void { - getHideDetailedConsoleQueryOutput(); - const hideDetails = ext.hideDetailedConsoleQueryOutput; + const hideDetails = getHideDetailedConsoleQueryOutputSetting(); output = this.checkOutput(output, query); let dataSourceRes: string[] = []; - this._console.show(true); + if (getAutoFocusOutputOnEntrySetting()) { + this._console.show(true); + } if (Array.isArray(output)) { dataSourceRes = convertRowsToConsole(output); @@ -147,9 +151,10 @@ export class ExecutionConsole { duration?: string, isFromConnTree?: boolean, ): void { - getHideDetailedConsoleQueryOutput(); - const hideDetails = ext.hideDetailedConsoleQueryOutput; - this._console.show(true); + const hideDetails = getHideDetailedConsoleQueryOutputSetting(); + if (getAutoFocusOutputOnEntrySetting()) { + this._console.show(true); + } //TODO: this._console.clear(); Add an option in the future to clear or not the console const date = new Date(); if (!hideDetails) { @@ -180,7 +185,9 @@ export class ExecutionConsole { ); } } else { - window.showErrorMessage(`Please connect to a KDB or Insights server`); + notify(`Please connect to a KDB or Insights server`, MessageKind.ERROR, { + logger, + }); this._console.appendLine(`Please connect to a KDB or Insights server`); commands.executeCommand("kdb.connections.disconnect"); addQueryHistory( diff --git a/src/utils/feedbackSurveyUtils.ts b/src/utils/feedbackSurveyUtils.ts index 01c0f405b..1cbe86a94 100644 --- a/src/utils/feedbackSurveyUtils.ts +++ b/src/utils/feedbackSurveyUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -14,7 +14,9 @@ import * as vscode from "vscode"; import { ext } from "../extensionVariables"; -import { Telemetry } from "./telemetryClient"; +import { MessageKind, notify } from "./notifications"; + +const logger = "feedbackSurveyUtils"; export async function feedbackSurveyDialog( sawSurveyAlready: boolean, @@ -48,22 +50,28 @@ export async function feedbackSurveyDialog( async function showSurveyDialog() { const SURVEY_URL = ext.urlLinks.survey; - const result = await vscode.window.showInformationMessage( + const result = await notify( "Got 2 Minutes? Help us make the KX extension even better for your workflows.", + MessageKind.INFO, + {}, "Take Survey", "Don't show me this message next time", ); if (result === "Take Survey") { vscode.env.openExternal(vscode.Uri.parse(SURVEY_URL)); } else if (result === "Don't show me this message next time") { - Telemetry.sendEvent("Help&Feedback.Hide.Survey"); + notify("Take survey message silenced.", MessageKind.DEBUG, { + logger, + telemetry: "Help&Feedback.Hide.Survey", + }); + await vscode.workspace .getConfiguration("kdb") .update("hideSurvey", true, vscode.ConfigurationTarget.Global); } } -/* istanbul ignore next */ +/* c8 ignore next */ export async function handleFeedbackSurvey() { const context = ext.context; diff --git a/src/utils/getNonce.ts b/src/utils/getNonce.ts index 259774674..c2f8e5d18 100644 --- a/src/utils/getNonce.ts +++ b/src/utils/getNonce.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/getUri.ts b/src/utils/getUri.ts index 2121f76f4..06dc74e71 100644 --- a/src/utils/getUri.ts +++ b/src/utils/getUri.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/iconsUtils.ts b/src/utils/iconsUtils.ts index f737a5276..a7525b6f5 100644 --- a/src/utils/iconsUtils.ts +++ b/src/utils/iconsUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/loggers.ts b/src/utils/loggers.ts new file mode 100644 index 000000000..154b56427 --- /dev/null +++ b/src/utils/loggers.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 1998-2025 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 * as vscode from "vscode"; + +import { ext } from "../extensionVariables"; + +export function kdbOutputLog( + message: string, + type: string, + supressDialog?: boolean, +): void { + const dateNow = new Date().toLocaleDateString(); + const timeNow = new Date().toLocaleTimeString(); + ext.outputChannel.appendLine(`[${dateNow} ${timeNow}] [${type}] ${message}`); + if (type === "ERROR" && !supressDialog) { + vscode.window.showErrorMessage( + `Error occured, check kdb output channel for details.`, + ); + } +} diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts new file mode 100644 index 000000000..93c71ce25 --- /dev/null +++ b/src/utils/notifications.ts @@ -0,0 +1,187 @@ +/* + * Copyright (c) 1998-2025 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 * as vscode from "vscode"; + +import { ext } from "../extensionVariables"; +import { kdbOutputLog } from "./loggers"; +import { stripUnprintableChars } from "./shared"; +import { Telemetry } from "./telemetryClient"; + +const logger = "notifications"; + +export const enum Cancellable { + NONE = 0, + EXECUTOR = 1, + RUNNER = 2, +} + +export type Executor = ( + progress: vscode.Progress<{ + message?: string; + increment?: number; + }>, + token: vscode.CancellationToken, +) => Promise; + +export class Runner { + public title = ""; + public location = vscode.ProgressLocation.Window; + public cancellable = Cancellable.RUNNER; + protected _cancelled = false; + private constructor(protected readonly executor: Executor) {} + + public get cancelled(): boolean { + return this._cancelled; + } + + public execute(): Promise { + return new Promise((resolve, reject) => { + vscode.window + .withProgress( + { + title: this.title, + location: this.location, + cancellable: this.cancellable !== Cancellable.NONE, + }, + (progress, token) => { + return this.cancellable === Cancellable.RUNNER + ? Promise.race([ + this.executor(progress, token), + new Promise((_, reject) => { + const updateCancelled = () => { + this._cancelled = token.isCancellationRequested; + if (this._cancelled) { + notify(`${this.title} cancelled.`, MessageKind.DEBUG, { + logger, + }); + reject(new vscode.CancellationError()); + } + }; + token.onCancellationRequested(updateCancelled); + updateCancelled(); + }), + ]) + : this.executor(progress, token); + }, + ) + .then( + (result: T) => { + resolve(result); + }, + (error) => { + notify( + `Runner (${this.title || "untitled"}) failed.`, + MessageKind.DEBUG, + { + logger, + params: error, + }, + ); + reject(new Error(`${error}`)); + }, + ); + }); + } + + public static create(executor: Executor): Runner { + return new Runner(executor); + } +} + +export function sleep(ms: number): Promise { + return new Promise((resolve, _) => setTimeout(() => resolve(), ms)); +} + +export const enum MessageKind { + DEBUG = "DEBUG", + INFO = "INFO", + WARNING = "WARNING", + ERROR = "ERROR", +} + +export function notify( + message: string, + kind: MessageKind, + options: { + logger?: string; + params?: any; + telemetry?: string | boolean | Error; + properties?: { [key: string]: string }; + measurements?: { [key: string]: number }; + } = {}, + ...items: T[] +): Thenable { + message = stripUnprintableChars(message); + + if (options.logger) { + const params = getParams(options.params); + const log = `[${options.logger}] ${message} ${params}`.trim(); + kdbOutputLog(log, kind, true); + } + + if (options.telemetry) { + if (typeof options.telemetry === "boolean") { + Telemetry.sendError(new Error(message)); + } else if (options.telemetry instanceof Error) { + Telemetry.sendError(options.telemetry); + } else { + Telemetry.sendEvent( + options.telemetry, + options.properties, + options.measurements, + ); + } + } + + let action: "Details" | "Dismiss" | undefined; + + if (items.length === 0 && kind !== MessageKind.DEBUG) { + action = options.params ? "Details" : "Dismiss"; + items.push(action); + } + + const notification = + kind === MessageKind.ERROR + ? vscode.window.showErrorMessage(message, ...items) + : kind === MessageKind.WARNING + ? vscode.window.showWarningMessage(message, ...items) + : kind === MessageKind.INFO + ? vscode.window.showInformationMessage(message, ...items) + : Promise.resolve(undefined); + + if (action === "Details") { + notification.then((res) => { + if (res === "Details") { + ext.outputChannel.show(true); + } + }); + } + + return notification; +} + +function getParams(params?: any) { + if (params) { + try { + if (params instanceof Error) { + return JSON.stringify({ name: params.name, message: params.message }); + } + return JSON.stringify(params); + } catch (error) { + return `Parsing log params failed: ${error}`; + } + } else { + return ""; + } +} diff --git a/src/utils/openUrl.ts b/src/utils/openUrl.ts index 53b603230..be785a553 100644 --- a/src/utils/openUrl.ts +++ b/src/utils/openUrl.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/output.ts b/src/utils/output.ts index b9e5d4cd1..aa3f481e8 100644 --- a/src/utils/output.ts +++ b/src/utils/output.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -13,6 +13,8 @@ import { OutputChannel, window } from "vscode"; +import { getAutoFocusOutputOnEntrySetting } from "./core"; + export class Output { public static output(label: string, message: string): void { this._outputChannel.append(this.formatMessage(label, message)); @@ -23,7 +25,9 @@ export class Output { } public static show(): void { - this._outputChannel.show(); + if (getAutoFocusOutputOnEntrySetting()) { + this._outputChannel.show(true); + } } public static hide(): void { @@ -37,7 +41,6 @@ export class Output { public static _outputChannel: OutputChannel = window.createOutputChannel("kdb-telemetry"); - private static formatMessage(label = "", message = ""): string { return `${label ? `[${label}] ` : ""}${message}`; } diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index 12ea038b6..fd7d838f0 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -15,8 +15,7 @@ import { readFileSync } from "fs"; import { join } from "path"; import { ext } from "../extensionVariables"; -import { isBaseVersionGreaterOrEqual, kdbOutputLog } from "./core"; -import { normalizeAssemblyTarget } from "./shared"; +import { MessageKind, notify, Runner } from "./notifications"; import { DCDS, deserialize, isCompressed, uncompress } from "../ipc/c"; import { DDateClass, DDateTimeClass, DTimestampClass } from "../ipc/cClasses"; import { Parse } from "../ipc/parse.qlist"; @@ -26,6 +25,10 @@ import { DataSourceFiles, DataSourceTypes } from "../models/dataSource"; import { QueryHistory } from "../models/queryHistory"; import { ScratchpadStacktrace } from "../models/scratchpadResult"; +const logger = "queryUtils"; + +const QUERY_LIMIT = 250_000; + export function sanitizeQuery(query: string): string { if (query[0] === "`") { query = query + " "; @@ -96,7 +99,7 @@ export function handleWSError(ab: ArrayBuffer): any { } } - kdbOutputLog(`Error : ${errorString}`, "ERROR", true); + notify(`Error : ${errorString}`, MessageKind.DEBUG, { logger }); return { error: errorString }; } @@ -208,52 +211,76 @@ export function getValueFromArray(results: DCDS): any { return results; } -export function sanitizeQsqlQuery(query: string): string { +function queryLimitCheck(query: string): string { + if (query.length > QUERY_LIMIT) { + throw new Error(`Query length limit (${QUERY_LIMIT}) reached.`); + } + return query; +} + +export function normalizeQuery(query: string): string { return ( - query - .trim() + queryLimitCheck(query) // Remove block comments - .replace(/^\/[^]*?^\\/gm, "") + .replace(/^\/[\t ]*$[^]*?^\\[\t ]*$/gm, "") + // Remove terminate comments + .replace(/^\\[\t ]*(?:\r\n|[\r\n])[^]*/gm, "") // Remove single line comments - .replace(/^\/.*\r?\n/gm, "") + .replace(/^\/.+/gm, "") // Remove line comments .replace( /(?:("([^"\\]*(?:\\.[^"\\]*)*)")|([ \t]+\/.*))/gm, (matched, isString) => (isString ? matched : ""), ) - // Replace end of statements - .replace(/(? + matched.replace(/(?:\r\n|[\r\n])/gs, "\\n"), + ) + // Remove none end of statement new lines + .replace(/(?:\r\n|[\r\n])+(?=[\t ])/gs, "") + // Normalize new lines + .replace(/(?:\r\n|[\r\n])+/gs, "\r\n") ); } -export function generateQSqlBody( - query: string, - assemblyTarget: string, - version?: number, - qeEnabled?: boolean, -) { - query = sanitizeQsqlQuery(query); - - const [plainAssembly, target] = - normalizeAssemblyTarget(assemblyTarget).split(/\s+/); +export function normalizeQSQLQuery(query: string): string { + return ( + normalizeQuery(query) + // Replace system commands + .replace(/^\\([a-zA-Z_1-2\\]+)[\t ]*(.*)/gm, (matched, command, args) => + matched === "\\\\" + ? 'system"\\\\"' + : `system"${command} ${args.trim()}"`, + ) + // Replace end of statements + .replace(/\r\n/gs, ";") + // Remove start of file + .replace(/^[;\s]+/gs, "") + // Remove end of file + .replace(/[;\s]+$/gs, "") + ); +} - let assembly = plainAssembly; - if (qeEnabled) { - assembly += "-qe"; - } +export function normalizePyQuery(query: string): string { + return ( + queryLimitCheck(query) + // Replace double quotes + .replace(/"/gs, '\\"') + ); +} - if (version && isBaseVersionGreaterOrEqual(version, 1.13)) { - return { - query, - scope: { - affinity: "soft", - assembly, - tier: target, - }, +export function getQSQLWrapper(query: string, isPython?: boolean): string { + if (isPython) { + const wrapper = normalizeQSQLQuery(queryWrapper(true)); + const args = { + returnFormat: <"serialized" | "text" | "structuredText">"serialized", + code: normalizePyQuery(query), + sample_fn: "first", + sample_size: 10000, }; + return `${wrapper}["${args.returnFormat}";"${args.code}";"${args.sample_fn}";${args.sample_size}]\`result`; } - - return { query, assembly, target }; + return normalizeQSQLQuery(query); } export function generateQTypes(meta: { [key: string]: number }): any { @@ -468,3 +495,18 @@ export function resultToBase64(result: any): string | undefined { } return undefined; } + +export function needsScratchpad(connLabel: string, target: Promise) { + if (!ext.scratchpadStarted.has(connLabel)) { + const runner = Runner.create(() => + target.then(() => ext.scratchpadStarted.add(connLabel)), + ); + runner.title = `Starting scratchpad on ${connLabel}.`; + runner.execute(); + } + return target; +} + +export function resetScratchpadStarted(connLabel: string) { + ext.scratchpadStarted.delete(connLabel); +} diff --git a/src/utils/registration.ts b/src/utils/registration.ts index f50d532ae..ff4343051 100644 --- a/src/utils/registration.ts +++ b/src/utils/registration.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,9 +11,10 @@ * specific language governing permissions and limitations under the License. */ -import { ConfigurationTarget, window, workspace } from "vscode"; +import { ConfigurationTarget, workspace } from "vscode"; import { ext } from "../extensionVariables"; +import { MessageKind, notify } from "./notifications"; import { openUrl } from "./openUrl"; export function showRegistrationNotification(): void { @@ -21,13 +22,17 @@ export function showRegistrationNotification(): void { .getConfiguration() .get("kdb.hideSubscribeRegistrationNotification"); if (setting !== undefined && setting === false) { - window - .showInformationMessage("Subscribe to updates", "Opt-In", "Ignore") - .then((result) => { - if (result === "Opt-In") { - openUrl(ext.kdbNewsletterUrl); - } - }); + notify( + "Subscribe to updates", + MessageKind.INFO, + {}, + "Opt-In", + "Ignore", + ).then((result) => { + if (result === "Opt-In") { + openUrl(ext.kdbNewsletterUrl); + } + }); } // hide notification for future extension use @@ -36,6 +41,6 @@ export function showRegistrationNotification(): void { .update( "kdb.hideSubscribeRegistrationNotification", true, - ConfigurationTarget.Global + ConfigurationTarget.Global, ); } diff --git a/src/utils/resultsRenderer.ts b/src/utils/resultsRenderer.ts index aaa9674a9..b6e66b3e2 100644 --- a/src/utils/resultsRenderer.ts +++ b/src/utils/resultsRenderer.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/secretStorage.ts b/src/utils/secretStorage.ts index d4c9da0a6..a5a5618d1 100644 --- a/src/utils/secretStorage.ts +++ b/src/utils/secretStorage.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/shared.ts b/src/utils/shared.ts index 1ab11b5f3..814de8516 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,8 +11,25 @@ * specific language governing permissions and limitations under the License. */ -export function normalizeAssemblyTarget(assemblyTarget: string) { - const [dirtyAssembly, target] = assemblyTarget.split(/\s+/); - const assembly = dirtyAssembly.replace(/-qe$/gm, ""); - return `${assembly} ${target}`; +export function normalizeAssemblyTarget(assemblyTarget: string): string { + return assemblyTarget?.trim().replace(/\s+/g, " ") || ""; +} + +export function stripUnprintableChars(text: string): string { + return text + .replace(/\p{Cc}/gu, "") + .replace(/\p{Co}/gu, "") + .replace(/\p{Cn}/gu, ""); +} + +export function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : `${error}`; +} + +export function cleanDapName(dapName: string): string { + return dapName.replace(/:\d+$/, ""); +} + +export function cleanAssemblyName(assemblyName: string): string { + return assemblyName.replace(/-qe$/, ""); } diff --git a/src/utils/shell.ts b/src/utils/shell.ts index da7295bc0..e97517575 100644 --- a/src/utils/shell.ts +++ b/src/utils/shell.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,15 +11,20 @@ * specific language governing permissions and limitations under the License. */ -import { ChildProcess } from "node:child_process"; +import { ChildProcess, execFileSync } from "node:child_process"; +import { existsSync } from "node:fs"; -import { kdbOutputLog } from "./core"; import { ICommandResult, tryExecuteCommand } from "./cpUtils"; +import { MessageKind, notify } from "./notifications"; + +const logger = "shell"; const isWin = process.platform === "win32"; export function log(childProcess: ChildProcess): void { - kdbOutputLog(`Process ${childProcess.pid} killed`, "INFO"); + notify(`Process ${childProcess.pid} killed`, MessageKind.DEBUG, { + logger, + }); } export async function killPid(pid = NaN): Promise { @@ -33,10 +38,24 @@ export async function killPid(pid = NaN): Promise { } else if (process.platform === "darwin") { result = await tryExecuteCommand("/bin", killPidCommand(pid), log); } - kdbOutputLog(`Destroying q process result: ${result}`, "INFO"); + notify(`Destroying q process result: ${result}`, MessageKind.DEBUG, { + logger, + }); } function killPidCommand(pid: number): string { return `kill ${pid}`; // return process.platform === 'win32' ? `taskkill /PID ${pid} /T /F` : `kill -9 ${pid}`; } + +/* c8 ignore next */ +export function which(cmd: string): string[] { + // This works on WSL, MacOS, Linux + const res = execFileSync("/usr/bin/which", ["-a", cmd]); + return new TextDecoder().decode(res).split(/(?:\r\n|[\r\n])/gs); +} + +/* c8 ignore next */ +export function stat(path: string): boolean { + return existsSync(path); +} diff --git a/src/utils/telemetryClient.ts b/src/utils/telemetryClient.ts index e584f6992..23e1e3b84 100644 --- a/src/utils/telemetryClient.ts +++ b/src/utils/telemetryClient.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,10 +11,10 @@ * specific language governing permissions and limitations under the License. */ +import { TelemetryReporter } from "@vscode/extension-telemetry"; import * as crypto from "crypto"; import * as os from "os"; import { OutputChannel, window, workspace } from "vscode"; -import TelemetryReporter from "vscode-extension-telemetry"; import { ext } from "../extensionVariables"; @@ -36,11 +36,7 @@ class ExtensionTelemetry { this.output = window.createOutputChannel("telemetry-client-test"); } else { try { - this.reporter = new TelemetryReporter( - ext.extensionName, - ext.extensionVersion, - ext.extensionKey, - ); + this.reporter = new TelemetryReporter(ext.extAIConnString); this.defaultProperties["common.vscodemachineid"] = generateMachineId(); this.defaultProperties["common.vscodesessionid"] = @@ -68,22 +64,26 @@ class ExtensionTelemetry { } } - public sendException( - exception: Error, + public sendError( + error: Error, properties?: { [key: string]: string }, measurements?: { [key: string]: number }, ): void { - const props = Object.assign({}, this.defaultProperties, properties); - const error = new Error(exception.message); - error.stack = ""; + const props = { + ...this.defaultProperties, + ...properties, + message: error.message, + name: error.name, + stack: error.stack ?? "", + }; if (this.reporter) { - this.reporter.sendTelemetryException(error, props, measurements); + this.reporter.sendTelemetryErrorEvent(error.name, props, measurements); } if (this.output) { this.output.appendLine( - `telemetry/${error}${JSON.stringify({ props, measurements })}`, + `telemetry/exception ${JSON.stringify({ props, measurements })}`, ); } } diff --git a/src/utils/uda.ts b/src/utils/uda.ts index f5c12436b..7ce4bdbf6 100644 --- a/src/utils/uda.ts +++ b/src/utils/uda.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -178,22 +178,18 @@ export function fixTimeAtUDARequestBody( } export function getIncompatibleError( - metadata: any, parsedParams: any, ): InvalidParamFieldErrors | undefined { - if (!metadata) { - return InvalidParamFieldErrors.NoMetadata; - } if (parsedParams === ParamFieldType.Invalid) { return InvalidParamFieldErrors.BadField; } return undefined; } -export function createUDAReturn(metadata: any): UDAReturn { +export function createUDAReturn(uda: any): UDAReturn { return { - type: convertTypesToString(metadata?.return.type || []), - description: metadata?.return.description || "", + type: convertTypesToString(uda?.return.type || []), + description: uda?.return.description || "", }; } @@ -204,9 +200,9 @@ export function createUDAObject( ): UDA { return { name: uda.api, - description: uda.metadata?.description || "", + description: uda?.description || "", params: Array.isArray(parsedParams) ? parsedParams : [], - return: createUDAReturn(uda.metadata), + return: createUDAReturn(uda), incompatibleError, }; } @@ -214,14 +210,11 @@ export function createUDAObject( export function parseUDAList(getMeta: MetaObjectPayload): UDA[] { const UDAs: UDA[] = []; if (getMeta.api !== undefined) { - const getMetaUDAs = getMeta.api.filter((api) => api.custom === true); + const getMetaUDAs = getMeta.api.filter((api) => api.uda === true); if (getMetaUDAs.length !== 0) { for (const uda of getMetaUDAs) { - const parsedParams = parseUDAParams(uda.metadata?.params); - const incompatibleError = getIncompatibleError( - uda.metadata, - parsedParams, - ); + const parsedParams = parseUDAParams(uda.params); + const incompatibleError = getIncompatibleError(parsedParams); UDAs.push(createUDAObject(uda, parsedParams, incompatibleError)); } } diff --git a/src/utils/userInteraction.ts b/src/utils/userInteraction.ts index 5320d1a03..591be73c4 100644 --- a/src/utils/userInteraction.ts +++ b/src/utils/userInteraction.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/validateUtils.ts b/src/utils/validateUtils.ts index 22781adb9..ea6a667e4 100644 --- a/src/utils/validateUtils.ts +++ b/src/utils/validateUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/utils/workspace.ts b/src/utils/workspace.ts index a7bc2dfa8..b5aa1590e 100644 --- a/src/utils/workspace.ts +++ b/src/utils/workspace.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -22,7 +22,9 @@ import { WorkspaceEdit, } from "vscode"; -import { Telemetry } from "./telemetryClient"; +import { MessageKind, notify } from "./notifications"; + +const logger = "workspace"; export function getWorkspaceRoot( ignoreException: boolean = false, @@ -93,7 +95,11 @@ export async function addWorkspaceFile( const telemetryStats = await getWorkbookStatistics(ext, directory); const isPython = ext === ".kdb.py" ? ".Python" : ".q"; - Telemetry.sendEvent("Workbook.Create" + isPython, {}, telemetryStats); + notify("Workbook created.", MessageKind.DEBUG, { + logger, + telemetry: "Workbook.Create" + isPython, + measurements: telemetryStats, + }); return uri; } diff --git a/src/validators/interfaceValidator.ts b/src/validators/interfaceValidator.ts index 38af7c4dc..02c0db1b6 100644 --- a/src/validators/interfaceValidator.ts +++ b/src/validators/interfaceValidator.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/kdbValidator.ts b/src/validators/kdbValidator.ts index 37a812162..8ce3d8fb5 100644 --- a/src/validators/kdbValidator.ts +++ b/src/validators/kdbValidator.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -45,6 +45,10 @@ export function validateServerAlias( if (!isLocal && input.toLowerCase() === "local") { return "The server name “local” is reserved for connections to the Bundled q process"; } + + if (input.toUpperCase() === ext.REPL) { + return `The server name '${input}' is reserved for connections to the REPL`; + } } return undefined; } diff --git a/src/validators/rule.ts b/src/validators/rule.ts index fab64ec17..ed8a286a7 100644 --- a/src/validators/rule.ts +++ b/src/validators/rule.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validationFunctions/hasLowerCase.ts b/src/validators/validationFunctions/hasLowerCase.ts index 9b1bd93bf..aab52144c 100644 --- a/src/validators/validationFunctions/hasLowerCase.ts +++ b/src/validators/validationFunctions/hasLowerCase.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validationFunctions/hasNoForbiddenChar.ts b/src/validators/validationFunctions/hasNoForbiddenChar.ts index fcd7b960e..6c5dfd5fe 100644 --- a/src/validators/validationFunctions/hasNoForbiddenChar.ts +++ b/src/validators/validationFunctions/hasNoForbiddenChar.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validationFunctions/hasSpecialChar.ts b/src/validators/validationFunctions/hasSpecialChar.ts index 08081b8a5..02d19946b 100644 --- a/src/validators/validationFunctions/hasSpecialChar.ts +++ b/src/validators/validationFunctions/hasSpecialChar.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validationFunctions/hasUpperCase.ts b/src/validators/validationFunctions/hasUpperCase.ts index b32427740..a273b9a17 100644 --- a/src/validators/validationFunctions/hasUpperCase.ts +++ b/src/validators/validationFunctions/hasUpperCase.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validationFunctions/isNotEmpty.ts b/src/validators/validationFunctions/isNotEmpty.ts index 1bdda82e2..6fd5b5fe9 100644 --- a/src/validators/validationFunctions/isNotEmpty.ts +++ b/src/validators/validationFunctions/isNotEmpty.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validationFunctions/lengthRange.ts b/src/validators/validationFunctions/lengthRange.ts index e440971ad..87f85bc95 100644 --- a/src/validators/validationFunctions/lengthRange.ts +++ b/src/validators/validationFunctions/lengthRange.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/validators/validator.ts b/src/validators/validator.ts index 014eefa94..622484089 100644 --- a/src/validators/validator.ts +++ b/src/validators/validator.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/webview/components/custom-fields/date-time-nano-picker.ts b/src/webview/components/custom-fields/date-time-nano-picker.ts index 4db533fd4..11c899644 100644 --- a/src/webview/components/custom-fields/date-time-nano-picker.ts +++ b/src/webview/components/custom-fields/date-time-nano-picker.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -40,7 +40,7 @@ export class DateTimeNanoPicker extends LitElement { }; } - /* istanbul ignore next */ + /* c8 ignore next */ connectedCallback() { super.connectedCallback(); if (this.value) { @@ -80,7 +80,7 @@ export class DateTimeNanoPicker extends LitElement { ); } - /* istanbul ignore next */ + /* c8 ignore next */ render() { return html`
diff --git a/src/webview/components/kdbChartView.ts b/src/webview/components/kdbChartView.ts index 973b12875..62d137d8e 100644 --- a/src/webview/components/kdbChartView.ts +++ b/src/webview/components/kdbChartView.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/webview/components/kdbDataSourceView.ts b/src/webview/components/kdbDataSourceView.ts index 2305b42e5..57e4cbd7d 100644 --- a/src/webview/components/kdbDataSourceView.ts +++ b/src/webview/components/kdbDataSourceView.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -37,7 +37,11 @@ import { DataSourceCommand, DataSourceMessage2 } from "../../models/messages"; import { MetaObjectPayload } from "../../models/meta"; import { ParamFieldType, UDA, UDAParam } from "../../models/uda"; import "./custom-fields/date-time-nano-picker"; -import { normalizeAssemblyTarget } from "../../utils/shared"; +import { + cleanAssemblyName, + cleanDapName, + normalizeAssemblyTarget, +} from "../../utils/shared"; const MAX_RULES = 32; const UDA_DISTINGUISED_PARAMS: UDAParam[] = [ @@ -154,6 +158,31 @@ export class KdbDataSourceView extends LitElement { servers: string[] = []; selectedServer = ""; updating = 0; + view: { + dap: ( + | { + assembly: string; + instance: string; + dap: string; + startTS: string; + endTS: string; + } + | { + assembly: string; + instance: string; + startTS: string; + endTS: string; + } + )[]; + api: any[]; + assembly: any[]; + schema: any[]; + } = { + dap: [], + api: [], + assembly: [], + schema: [], + }; connectedCallback() { super.connectedCallback(); @@ -306,7 +335,7 @@ export class KdbDataSourceView extends LitElement { `; } - /* istanbul ignore next */ + /* c8 ignore next */ postMessage(msg: Partial) { this.vscode.postMessage(msg); } @@ -414,7 +443,7 @@ export class KdbDataSourceView extends LitElement { .filter( (api) => (api.api === ".kxi.getData" || !api.api.startsWith(".kxi.")) && - api.custom === false, + api.uda === false, ) .map((api) => { const value = @@ -457,17 +486,124 @@ export class KdbDataSourceView extends LitElement { return []; } + // TODO: Remove in 1.14 if decided to not use it + // Options separated by tiers and DAP processes + // renderTargetOptions() { + // if ( + // this.isInsights && + // this.isMetaLoaded && + // this.insightsMeta.dap.length > 0 + // ) { + // const tierMap = new Map(); + + // this.insightsMeta.dap.forEach((dap) => { + // const tierKey = `${cleanAssemblyName(dap.assembly)} ${dap.instance}`; + // if (!tierMap.has(tierKey)) { + // tierMap.set(tierKey, []); + // } + // tierMap.get(tierKey)!.push(dap); + // }); + + // const tierOptions: any[] = []; + // const dapOptions: any[] = []; + + // tierMap.forEach((processes, tierKey) => { + // tierOptions.push( + // html`${tierKey}`, + // ); + // processes.forEach((process) => { + // if (process.dap) { + // const processValue = `${tierKey} ${cleanDapName(process.dap)}`; + // dapOptions.push( + // html`${processValue}`, + // ); + // } + // }); + // }); + + // if (!this.qsqlTarget && tierOptions.length > 0) { + // this.qsqlTarget = decodeURIComponent(tierOptions[0]["values"][1] || ""); + // } + + // const resOptions = [html`Tiers`, ...tierOptions]; + + // if (dapOptions.length > 0) { + // resOptions.push(html`DAP Process`, ...dapOptions); + // } + + // return resOptions; + // } + // return []; + // } + + // Options separated by assembly renderTargetOptions() { - if (this.isInsights && this.isMetaLoaded) { - return this.insightsMeta.dap.map((dap) => { - const value = `${dap.assembly} ${dap.instance}`; - if (!this.qsqlTarget) { - this.qsqlTarget = value; + if ( + this.isInsights && + this.isMetaLoaded && + this.insightsMeta.dap.length > 0 + ) { + const assemblyMap = new Map< + string, + Map + >(); + + this.insightsMeta.dap.forEach((dap) => { + const assemblyName = cleanAssemblyName(dap.assembly); + const tierKey = `${assemblyName} ${dap.instance}`; + + if (!assemblyMap.has(assemblyName)) { + assemblyMap.set(assemblyName, new Map()); } - return html`${value}`; + + const tierMap = assemblyMap.get(assemblyName)!; + if (!tierMap.has(tierKey)) { + tierMap.set(tierKey, []); + } + tierMap.get(tierKey)!.push(dap); + }); + + const resOptions: any[] = []; + let firstTierOption: any = null; + + assemblyMap.forEach((tierMap, assemblyName) => { + resOptions.push(html`${assemblyName}`); + + tierMap.forEach((processes, tierKey) => { + const tierOption = html`${tierKey}`; + resOptions.push(tierOption); + + if (!firstTierOption) { + firstTierOption = tierOption; + } + + processes.forEach((process) => { + if (process.dap) { + const processValue = `${tierKey} ${cleanDapName(process.dap)}`; + resOptions.push( + html`${processValue}`, + ); + } + }); + }); }); + + if (!this.qsqlTarget && firstTierOption) { + this.qsqlTarget = decodeURIComponent( + firstTierOption["values"][1] || "", + ); + } + + return resOptions; } return []; } @@ -965,6 +1101,7 @@ export class KdbDataSourceView extends LitElement { ${this.qsqlTarget || "(none)"} - ${this.isMetaLoaded ? "Meta Targets" : "Meta Not Loaded"} + ${this.isMetaLoaded ? "" : html`Meta Not Loaded`} ${this.renderTargetOptions()} ${type === "datetime-local" || type === "date" ? html` - - +
+ + +
+ ${this.renderDeleteUDAParamButton(param)} +
+
` : html` `}
-
- ${this.renderDeleteUDAParamButton(param)} -
+ ${type !== "datetime-local" + ? html` +
+ ${this.renderDeleteUDAParamButton(param)} +
+ ` + : html``} `; } diff --git a/src/webview/components/kdbNewConnectionView.ts b/src/webview/components/kdbNewConnectionView.ts index e1c96ffe9..dc2c6681b 100644 --- a/src/webview/components/kdbNewConnectionView.ts +++ b/src/webview/components/kdbNewConnectionView.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -181,7 +181,7 @@ export class KdbNewConnectionView extends LitElement { >`; } - /* istanbul ignore next */ + /* c8 ignore next */ renderServerNameField(serverType: ServerType, isBundleQ?: boolean) { return isBundleQ ? html` @@ -339,16 +339,16 @@ export class KdbNewConnectionView extends LitElement { renderInsecureSSL() { return html`
- Accept insecure SSL certifcates + }}"> + Accept insecure SSL certifcates +
`; } @@ -449,9 +449,9 @@ export class KdbNewConnectionView extends LitElement { class="text-field larger" value="${live(this.newLblName)}" @sl-input="${(event: Event) => { - /* istanbul ignore next */ + /* c8 ignore next */ this.newLblName = (event.target as HTMLInputElement).value; - /* istanbul ignore next */ + /* c8 ignore next */ this.requestUpdate(); }}" id="label-name"> @@ -464,9 +464,9 @@ export class KdbNewConnectionView extends LitElement { id="label-color" value="${live(this.newLblColorName)}" @sl-change="${(event: Event) => { - /* istanbul ignore next */ + /* c8 ignore next */ this.newLblColorName = (event.target as HTMLInputElement).value; - /* istanbul ignore next */ + /* c8 ignore next */ this.requestUpdate(); }}" class="dropdown" @@ -548,7 +548,7 @@ export class KdbNewConnectionView extends LitElement { `; } - /* istanbul ignore next */ + /* c8 ignore next */ renderNewMyQConnectionForm() { return html`
@@ -564,27 +564,25 @@ export class KdbNewConnectionView extends LitElement {
- Username + event.target as HTMLInputElement + ).value)}">
- Password + event.target as HTMLInputElement + ).value)}">
Add required authentication to get access to the server connection @@ -596,11 +594,11 @@ export class KdbNewConnectionView extends LitElement {
Optional: Enable TLS Encryption
- Enable TLS Encryption on the kdb connection + + Enable TLS Encryption on the kdb connection +
@@ -679,7 +677,7 @@ export class KdbNewConnectionView extends LitElement { this.selectedTab === ConnectionType.BundledQ, )}" @click="${() => { - /* istanbul ignore next */ + /* c8 ignore next */ this.selectedTab = ConnectionType.BundledQ; this.serverType = ServerType.KDB; this.isBundledQ = true; @@ -691,7 +689,7 @@ export class KdbNewConnectionView extends LitElement { panel="${ConnectionType.Kdb}" ?active="${live(this.selectedTab === ConnectionType.Kdb)}" @click="${() => { - /* istanbul ignore next */ + /* c8 ignore next */ this.isBundledQ = false; this.serverType = ServerType.KDB; this.selectedTab = ConnectionType.Kdb; @@ -705,7 +703,7 @@ export class KdbNewConnectionView extends LitElement { this.selectedTab === ConnectionType.Insights, )}" @click="${() => { - /* istanbul ignore next */ + /* c8 ignore next */ this.isBundledQ = false; this.serverType = ServerType.INSIGHTS; this.selectedTab = ConnectionType.Insights; @@ -878,11 +876,11 @@ export class KdbNewConnectionView extends LitElement {
Optional: Edit Auth options
- Edit existing auth on the kdb connection + + Edit existing auth on the kdb connection +
@@ -891,31 +889,25 @@ export class KdbNewConnectionView extends LitElement {
- Username + event.target as HTMLInputElement + ).value)}">
- Password + event.target as HTMLInputElement + ).value)}">
Add required authentication to get access to the server @@ -929,11 +921,11 @@ export class KdbNewConnectionView extends LitElement {
Optional: Enable TLS Encryption
- Enable TLS Encryption on the kdb connection + + Enable TLS Encryption on the kdb connection +
diff --git a/src/webview/components/styles.ts b/src/webview/components/styles.ts index 3b875034b..bbe96bfc7 100644 --- a/src/webview/components/styles.ts +++ b/src/webview/components/styles.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/src/webview/main.ts b/src/webview/main.ts index 2a0cfd3e9..b75418343 100644 --- a/src/webview/main.ts +++ b/src/webview/main.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -32,12 +32,6 @@ import "@shoelace-style/shoelace/dist/components/menu/menu.js"; import "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js"; import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; -import { - allComponents, - provideVSCodeDesignSystem, -} from "@vscode/webview-ui-toolkit"; import "./components/kdbDataSourceView"; import "./components/kdbNewConnectionView"; import "./components/kdbChartView"; - -provideVSCodeDesignSystem().register(allComponents); diff --git a/syntaxes/q.tmLanguage.json b/syntaxes/q.tmLanguage.json index 4757e3205..34493120f 100644 --- a/syntaxes/q.tmLanguage.json +++ b/syntaxes/q.tmLanguage.json @@ -156,19 +156,19 @@ "patterns": [ { "name": "keyword.control.q", - "match": "(?:while|if|do)\\b" + "match": "(? tree.visit(report, context)); + + reports.forEach((report) => { + try { + report.execute(context); + console.log( + `✅ Generated ${report.constructor.name} report successfully`, + ); + } catch (err) { + console.error( + `❌ Failed to generate ${report.constructor.name} report:`, + err, + ); + } + }); + + const summary = coverageMap.getCoverageSummary(); + console.log("\n=== COVERAGE SUMMARY ==="); + console.log( + `Lines: ${summary.lines.pct}% (${summary.lines.covered}/${summary.lines.total})`, + ); + console.log( + `Functions: ${summary.functions.pct}% (${summary.functions.covered}/${summary.functions.total})`, + ); +} + +export function fixLcovPaths(): void { + const lcovPath = path.join(REPO_ROOT, "coverage-reports", "lcov.info"); + + if (!fs.existsSync(lcovPath)) { + console.warn("❌ lcov.info file not found"); + return; + } + + let content = fs.readFileSync(lcovPath, "utf8"); + + // console.log("\n=== FIXING LCOV PATHS ==="); + + // const originalSfLines = content.match(/^SF:.*$/gm); + // if (originalSfLines) { + // console.log("Original SF lines (first 5):"); + // originalSfLines.slice(0, 5).forEach((line) => console.log(` ${line}`)); + // } + + content = content.replace(/\\/g, "/"); + + const repoRootEscaped = REPO_ROOT.replace(/[/\\]/g, "[/\\\\]"); + content = content.replace( + new RegExp(`^SF:.*${repoRootEscaped}[/\\\\](.*)$`, "gm"), + "SF:$1", + ); + + content = content.replace(/^SF:.*\/out\/(.*?)\.js$/gm, "SF:$1.ts"); + + content = content.replace(/^SF:(src|server)\/(.*?)\.js$/gm, "SF:$1/$2.ts"); + + content = content.replace(/^SF:(.*?)\.js$/gm, "SF:$1.ts"); + + content = content.replace(/^SF:(src)\/\1\//gm, "SF:$1/"); + content = content.replace(/^SF:(server)\/\1\//gm, "SF:$1/"); + + fs.writeFileSync(lcovPath, content); + + // const fixedSfLines = content.match(/^SF:.*$/gm); + // if (fixedSfLines) { + // console.log("Fixed SF lines (first 5):"); + // fixedSfLines.slice(0, 5).forEach((line) => console.log(` ${line}`)); + // } + + console.log("✅ Fixed lcov.info paths"); +} + +export function debugLcov(): void { + const lcovPath = path.join(REPO_ROOT, "coverage-reports", "lcov.info"); + + if (!fs.existsSync(lcovPath)) { + console.warn("❌ lcov.info file not found"); + return; + } + + const content = fs.readFileSync(lcovPath, "utf8"); + + console.log("\n=== LCOV DEBUG ==="); + + const sfLines = content.match(/^SF:.*$/gm); + if (sfLines) { + console.log(`Found ${sfLines.length} source files:`); + sfLines.forEach((line) => console.log(` ${line}`)); + } else { + console.log("❌ No SF (Source File) lines found in lcov.info"); + } +} + +export function generateCoverageReport(): void { + const coverageReportsDir = path.join(REPO_ROOT, "coverage-reports"); + + const global = new Function("return this")(); + if (!global.__coverage__ || Object.keys(global.__coverage__).length === 0) { + console.warn("❌ No coverage data available to generate report"); + console.warn("This might be because:"); + console.warn("1. Tests are not running with instrumented code"); + console.warn("2. No tests are actually executing the source code"); + console.warn("3. The coverage variable is not being set correctly"); + return; + } + + if (!fs.existsSync(coverageReportsDir)) { + console.log(`Creating coverage reports directory: ${coverageReportsDir}`); + fs.mkdirSync(coverageReportsDir, { recursive: true }); + } + + createReport(); + + const lcovPath = path.join(coverageReportsDir, "lcov.info"); + if (!fs.existsSync(lcovPath)) { + console.error(`❌ lcov.info was not created at ${lcovPath}`); + return; + } + + console.log(`✅ lcov.info created at ${lcovPath}`); + const stats = fs.statSync(lcovPath); + console.log(`File size: ${stats.size} bytes`); + + fixLcovPaths(); + + if (fs.existsSync(lcovPath)) { + const finalStats = fs.statSync(lcovPath); + console.log(`✅ Final lcov.info size: ${finalStats.size} bytes`); + } else { + console.error(`❌ lcov.info missing after processing!`); + } + + console.log("✅ Coverage report generation completed!"); } function copyFile(inputPath: string, outputPath: string): void { diff --git a/test/fixtures/index.ts b/test/fixtures/index.ts index d6364c2ba..a5e437140 100644 --- a/test/fixtures/index.ts +++ b/test/fixtures/index.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/fixtures/webview.ts b/test/fixtures/webview.ts index c2d7c8b51..59ae8baf0 100644 --- a/test/fixtures/webview.ts +++ b/test/fixtures/webview.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/runTest.ts b/test/runTest.ts index a9725e4cf..8b7418654 100644 --- a/test/runTest.ts +++ b/test/runTest.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -21,18 +21,26 @@ async function main() { const extensionDevelopmentPath = path.join(__dirname, "../../"); let extensionTestsPath = path.join(__dirname, "./suite/index"); - if (process.argv.indexOf("--coverage") >= 0) { - // generate instrumented files at out-cov - instrument(); - // load the instrumented files + const hasCoverageFlag = process.argv.indexOf("--coverage") >= 0; + + if (hasCoverageFlag) { + try { + instrument(); + console.log("✅ Code instrumentation completed"); + } catch (error) { + console.error("❌ Code instrumentation failed:", error); + throw error; + } + extensionTestsPath = path.join( __dirname, - "../../out-cov/test/suite/index" + "../../out-cov/test/suite/index", ); - // signal that the coverage data should be gathered process.env["GENERATE_COVERAGE"] = "1"; + } else { + //console.log("⚠️ Coverage mode disabled, running normal tests"); } await runTests({ diff --git a/test/suite/buildTools.test.ts b/test/suite/buildTools.test.ts index 6247c4fa8..6fe23a42c 100644 --- a/test/suite/buildTools.test.ts +++ b/test/suite/buildTools.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/suite/classes.test.ts b/test/suite/classes.test.ts index 2a7cb793f..4ad04388c 100644 --- a/test/suite/classes.test.ts +++ b/test/suite/classes.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index ab1a41106..28f0cff44 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -19,6 +19,7 @@ import { LanguageClient } from "vscode-languageclient/node"; import { InsightsConnection } from "../../src/classes/insightsConnection"; import { LocalConnection } from "../../src/classes/localConnection"; +import { ReplConnection } from "../../src/classes/replConnection"; import * as clientCommand from "../../src/commands/clientCommands"; import * as dataSourceCommand from "../../src/commands/dataSourceCommand"; import * as installTools from "../../src/commands/installTools"; @@ -57,8 +58,10 @@ import * as coreUtils from "../../src/utils/core"; import * as dsUtils from "../../src/utils/dataSource"; import * as dataSourceUtils from "../../src/utils/dataSource"; import { ExecutionConsole } from "../../src/utils/executionConsole"; +import * as loggers from "../../src/utils/loggers"; +import * as notifications from "../../src/utils/notifications"; import * as queryUtils from "../../src/utils/queryUtils"; -import { MAX_STR_LEN } from "../../src/validators/kdbValidator"; +import * as kdbValidators from "../../src/validators/kdbValidator"; describe("dataSourceCommand", () => { afterEach(() => { @@ -1158,7 +1161,7 @@ describe("dataSourceCommand2", () => { .withArgs("Please connect to an Insights server"); }); - it("should show error msg if qe is off", async () => { + it.skip("should show error msg if qe is off", async () => { qeStatusStub.returns("disabled"); await dataSourceCommand.populateScratchpad( dummyFileContent, @@ -1175,7 +1178,7 @@ describe("dataSourceCommand2", () => { let kdbOutputLogStub: sinon.SinonStub; beforeEach(() => { - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); }); afterEach(() => { sinon.restore(); @@ -1274,6 +1277,7 @@ describe("serverCommand", () => { let insightsData: InsightDetails; let updateInsightsStub, getInsightsStub: sinon.SinonStub; let windowMock: sinon.SinonMock; + beforeEach(() => { insightsData = { server: "https://insightsservername.com/", @@ -1283,11 +1287,14 @@ describe("serverCommand", () => { windowMock = sinon.mock(vscode.window); updateInsightsStub = sinon.stub(coreUtils, "updateInsights"); getInsightsStub = sinon.stub(coreUtils, "getInsights"); + ext.serverProvider = new KdbTreeProvider(servers, insights); }); + afterEach(() => { sinon.restore(); windowMock.restore(); }); + it("should add new Insights connection", async () => { getInsightsStub.returns({}); await serverCommand.addInsightsConnection(insightsData, ["lblTest"]); @@ -1297,6 +1304,7 @@ describe("serverCommand", () => { .once() .withArgs("Insights connection added successfully"); }); + it("should show error message if Insights connection already exists", async () => { getInsightsStub.returns(insights); await serverCommand.addInsightsConnection(insightsData); @@ -1305,6 +1313,7 @@ describe("serverCommand", () => { .once() .withArgs("Insights connection already exists"); }); + it("should show error message if Insights connection is invalid", async () => { insightsData.server = "invalid"; await serverCommand.addInsightsConnection(insightsData); @@ -1318,7 +1327,11 @@ describe("serverCommand", () => { describe("addKdbConnection", () => { let kdbData: ServerDetails; let windowMock: sinon.SinonMock; - let updateServersStub, getServersStub: sinon.SinonStub; + let updateServersStub, + getServersStub, + validationServerAliasStub, + validationHostnameStub, + validationPortStub: sinon.SinonStub; beforeEach(() => { kdbData = { serverName: "testServer", @@ -1331,6 +1344,12 @@ describe("serverCommand", () => { windowMock = sinon.mock(vscode.window); updateServersStub = sinon.stub(coreUtils, "updateServers"); getServersStub = sinon.stub(coreUtils, "getServers"); + validationServerAliasStub = sinon.stub( + kdbValidators, + "validateServerAlias", + ); + validationHostnameStub = sinon.stub(kdbValidators, "validateServerName"); + validationPortStub = sinon.stub(kdbValidators, "validateServerPort"); }); afterEach(() => { @@ -1340,6 +1359,9 @@ describe("serverCommand", () => { it("should add new Kdb connection", async () => { getServersStub.returns({}); + validationServerAliasStub.returns(false); + validationHostnameStub.returns(false); + validationPortStub.returns(false); await serverCommand.addKdbConnection(kdbData, false, ["lblTest"]); sinon.assert.calledOnce(updateServersStub); windowMock @@ -1347,6 +1369,7 @@ describe("serverCommand", () => { .once() .withArgs("Kdb connection added successfully"); }); + it("should show error message if Kdb connection already exists", async () => { getServersStub.returns(servers); await serverCommand.addKdbConnection(kdbData); @@ -1355,6 +1378,7 @@ describe("serverCommand", () => { .once() .withArgs("Kdb connection already exists"); }); + it("should show error message if Kdb connection is invalid", async () => { kdbData.serverPort = "invalid"; await serverCommand.addKdbConnection(kdbData); @@ -1363,6 +1387,7 @@ describe("serverCommand", () => { .once() .withArgs("Invalid Kdb connection"); }); + it("should show error message if connection where alias is not provided", async () => { kdbData.serverAlias = ""; await serverCommand.addKdbConnection(kdbData); @@ -1371,7 +1396,11 @@ describe("serverCommand", () => { .once() .withArgs("Server Name is required"); }); + it("should give error if alias is local and isLocal is false", async () => { + validationServerAliasStub.returns( + "The server name “local” is reserved for connections to the Bundled q process", + ); kdbData.serverAlias = "local"; kdbData.managed = true; await serverCommand.addKdbConnection(kdbData); @@ -1387,7 +1416,7 @@ describe("serverCommand", () => { kdbData.username = "username"; getServersStub.returns({}); await serverCommand.addKdbConnection(kdbData); - sinon.assert.calledOnce(updateServersStub); + sinon.assert.called(updateServersStub); windowMock .expects("showInformationMessage") .once() @@ -1404,7 +1433,7 @@ describe("serverCommand", () => { }); it("should return error when the servername with an invalid length", async () => { - kdbData.serverName = "a".repeat(MAX_STR_LEN + 1); + kdbData.serverName = "a".repeat(kdbValidators.MAX_STR_LEN + 1); await serverCommand.addKdbConnection(kdbData); windowMock .expects("showErrorMessage") @@ -1420,7 +1449,7 @@ describe("serverCommand", () => { beforeEach(() => { showOpenDialogStub = sinon.stub(vscode.window, "showOpenDialog"); - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); _addImportedConnectionsStub = sinon.stub( serverCommand, "addImportedConnections", @@ -1439,8 +1468,9 @@ describe("serverCommand", () => { assert( kdbOutputLogStub.calledWith( - "[IMPORT CONNECTION]No file selected", + "[serverCommand] No file selected.", "ERROR", + true, ), ); }); @@ -1503,7 +1533,7 @@ describe("serverCommand", () => { "addInsightsConnection", ); addKdbConnectionStub = sinon.stub(serverCommand, "addKdbConnection"); - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); _getInsightsStub = sinon .stub(coreUtils, "getInsights") .returns(undefined); @@ -1563,13 +1593,13 @@ describe("serverCommand", () => { assert( kdbOutputLogStub.calledWith( - "[IMPORT CONNECTION]Connections imported successfully", + "[serverCommand] Connections imported successfully.", "INFO", ), ); assert( showInformationMessageStub.calledWith( - "Connections imported successfully", + "Connections imported successfully.", ), ); }); @@ -1902,9 +1932,9 @@ describe("serverCommand", () => { ext.kdbinsightsNodes.pop(); }); - it("runQuery with undefined editor ", () => { + it("runQuery with undefined editor ", async () => { ext.activeTextEditor = undefined; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.PythonQueryFile, "", "", @@ -1913,9 +1943,9 @@ describe("serverCommand", () => { assert.equal(result, false); }); - it("runQuery with QuerySelection", () => { + it("runQuery with QuerySelection", async () => { ext.connectionNode = undefined; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.QuerySelection, "", "", @@ -1924,9 +1954,9 @@ describe("serverCommand", () => { assert.equal(result, undefined); }); - it("runQuery with PythonQueryFile not connected to inisghts node", () => { + it("runQuery with PythonQueryFile not connected to inisghts node", async () => { ext.connectionNode = undefined; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.PythonQuerySelection, "", "", @@ -1935,9 +1965,9 @@ describe("serverCommand", () => { assert.equal(result, undefined); }); - it("runQuery with PythonQueryFile connected to inisghts node", () => { + it("runQuery with PythonQueryFile connected to inisghts node", async () => { ext.connectionNode = insightsNode; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.PythonQuerySelection, "", "", @@ -1946,9 +1976,9 @@ describe("serverCommand", () => { assert.equal(result, undefined); }); - it("runQuery with QueryFile", () => { + it("runQuery with QueryFile", async () => { ext.connectionNode = undefined; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.QueryFile, "", "", @@ -1957,9 +1987,9 @@ describe("serverCommand", () => { assert.equal(result, undefined); }); - it("runQuery with ReRunQuery", () => { + it("runQuery with ReRunQuery", async () => { ext.connectionNode = undefined; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.ReRunQuery, "", "", @@ -1969,9 +1999,9 @@ describe("serverCommand", () => { assert.equal(result, undefined); }); - it("runQuery with PythonQueryFile", () => { + it("runQuery with PythonQueryFile", async () => { ext.connectionNode = undefined; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.PythonQueryFile, "", "", @@ -1980,9 +2010,9 @@ describe("serverCommand", () => { assert.equal(result, undefined); }); - it("runQuery with PythonQueryFile", () => { + it("runQuery with PythonQueryFile", async () => { ext.connectionNode = insightsNode; - const result = serverCommand.runQuery( + const result = await serverCommand.runQuery( ExecutionTypes.PythonQueryFile, "", "", @@ -2271,7 +2301,8 @@ describe("serverCommand", () => { getInsightsStub, removeLocalConnectionContextStub, updateServersStub, - refreshStub: sinon.SinonStub; + refreshStub, + notifyStub: sinon.SinonStub; beforeEach(() => { indexOfStub = sinon.stub(ext.connectedContextStrings, "indexOf"); @@ -2286,6 +2317,8 @@ describe("serverCommand", () => { ); updateServersStub = sinon.stub(coreUtils, "updateServers"); refreshStub = sinon.stub(ext.serverProvider, "refresh"); + + notifyStub = sinon.stub(notifications, "notify"); }); afterEach(() => { @@ -2295,13 +2328,17 @@ describe("serverCommand", () => { ext.connectedContextStrings.length = 0; }); - it("should remove connection and refresh server provider", async () => { + it("should remove connection and refresh server provider when user clicks Proceed", async () => { + notifyStub.resolves("Proceed"); + indexOfStub.returns(1); getServersStub.returns({ testKey: {} }); getKeyStub.returns("testKey"); await serverCommand.removeConnection(kdbNode); + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.ok( removeLocalConnectionContextStub.calledWith( coreUtils.getServerName(kdbNode.details), @@ -2311,24 +2348,65 @@ describe("serverCommand", () => { assert.ok(refreshStub.calledOnce); }); - it("should remove connection, but disconnect it before", async () => { + it("should remove connection, but disconnect it before when user clicks Proceed", async () => { + notifyStub.resolves("Proceed"); + ext.connectedContextStrings.push(kdbNode.label); indexOfStub.returns(1); getServersStub.returns({ testKey: {} }); getKeyStub.returns("testKey"); await serverCommand.removeConnection(kdbNode); + + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.ok(updateServersStub.calledOnce); }); - it("should remove connection Insights, but disconnect it before", async () => { + + it("should remove connection Insights, but disconnect it before when user clicks Proceed", async () => { + notifyStub.resolves("Proceed"); + ext.connectedContextStrings.push(insightsNode.label); indexOfStub.returns(1); getInsightsStub.returns({ testKey: {} }); getHashStub.returns("testKey"); await serverCommand.removeConnection(insightsNode); + + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.ok(updateServersStub.notCalled); }).timeout(5000); + + it("should not remove connection when user clicks Cancel", async () => { + notifyStub.resolves("Cancel"); + + getServersStub.returns({ testKey: {} }); + getKeyStub.returns("testKey"); + + await serverCommand.removeConnection(kdbNode); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + assert.ok(removeLocalConnectionContextStub.notCalled); + assert.ok(updateServersStub.notCalled); + assert.ok(refreshStub.notCalled); + }); + + it("should not remove connection when user dismisses dialog", async () => { + notifyStub.resolves(undefined); + + getServersStub.returns({ testKey: {} }); + getKeyStub.returns("testKey"); + + await serverCommand.removeConnection(kdbNode); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + assert.ok(removeLocalConnectionContextStub.notCalled); + assert.ok(updateServersStub.notCalled); + assert.ok(refreshStub.notCalled); + }); }); describe("connect", () => { @@ -2443,7 +2521,7 @@ describe("serverCommand", () => { beforeEach(() => { sandbox = sinon.createSandbox(); - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); }); afterEach(() => { @@ -2465,7 +2543,7 @@ describe("serverCommand", () => { sinon.assert.calledOnce(kdbOutputLogStub); sinon.assert.calledWith( kdbOutputLogStub, - "[EXPORT CONNECTIONS] No connections found to be exported", + "[serverCommand] No connections found to be exported.", "ERROR", ); @@ -2489,8 +2567,8 @@ describe("serverCommand", () => { sinon.assert.calledOnce(kdbOutputLogStub); sinon.assert.calledWith( kdbOutputLogStub, - "[EXPORT CONNECTIONS] Save operation was cancelled by the user", - "INFO", + "[serverCommand] Save operation was cancelled by the user.", + "DEBUG", ); exportConnectionStub.restore(); @@ -2520,10 +2598,11 @@ describe("serverCommand", () => { success: true, }; - await serverCommand.copyQuery(queryHistory); + serverCommand.copyQuery(queryHistory); sinon.assert.calledOnceWithExactly( showInfoStub, "Query copied to clipboard.", + "Dismiss", ); }); @@ -2755,7 +2834,6 @@ describe("workspaceCommand", () => { }); describe("checkOldDatasourceFiles", () => { let oldFilesExistsStub: sinon.SinonStub; - beforeEach(() => { oldFilesExistsStub = sinon.stub(dataSourceUtils, "oldFilesExists"); }); @@ -2788,7 +2866,7 @@ describe("workspaceCommand", () => { task({}, token); }); - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); }); afterEach(() => { sinon.restore(); @@ -2803,7 +2881,7 @@ describe("workspaceCommand", () => { await workspaceCommand.importOldDSFiles(); sinon.assert.calledOnce(windowErrorStub); }); - it("should show not show error or info message if workspace do exist", async () => { + it.skip("should show not show error or info message if workspace do exist", async () => { ext.oldDSformatExists = true; workspaceFolderStub.get(() => [ { @@ -2817,7 +2895,7 @@ describe("workspaceCommand", () => { sinon.assert.notCalled(windowShowInfo); }); - it("should log cancellation if user cancels the request", async () => { + it.skip("should log cancellation if user cancels the request", async () => { ext.oldDSformatExists = true; workspaceFolderStub.get(() => [ { @@ -2834,10 +2912,61 @@ describe("workspaceCommand", () => { sinon.assert.calledOnce(kdbOutputLogStub); sinon.assert.calledWith( kdbOutputLogStub, - "[DATASOURCE] User cancelled the old DS files import.", - "INFO", + "[workspaceCommand] User cancelled the old DS files import.", + "DEBUG", ); }); + describe("runOnRepl", () => { + const editor = { + document: { + uri: kdbUri, + lineAt() { + return "a:1;a"; + }, + getText() { + return "a:1;a"; + }, + }, + selection: { active: { line: 0 } }, + }; + let notifyStub: sinon.SinonStub; + let executeStub: sinon.SinonStub; + beforeEach(() => { + notifyStub = sinon.stub(notifications, "notify"); + executeStub = sinon.stub(notifications.Runner.prototype, "execute"); + }); + it("should execute q file", async () => { + await workspaceCommand.runOnRepl(editor, ExecutionTypes.QueryFile); + sinon.assert.calledOnce(executeStub); + }); + it("should execute q selection", async () => { + await workspaceCommand.runOnRepl(editor, ExecutionTypes.QuerySelection); + sinon.assert.calledOnce(executeStub); + }); + it("should notify for other execution types", async () => { + await workspaceCommand.runOnRepl( + editor, + ExecutionTypes.PopulateScratchpad, + ); + sinon.assert.calledOnce(notifyStub); + }); + it("should notify execution error", async () => { + executeStub.rejects(new Error("Test")); + await workspaceCommand.runOnRepl(editor, ExecutionTypes.QueryFile); + sinon.assert.calledOnce(notifyStub); + }); + describe("startRepl", () => { + const conn = { start() {} }; + beforeEach(() => { + sinon.stub(ReplConnection, "getOrCreateInstance").returns(conn); + }); + it("should notify error", async () => { + sinon.stub(conn, "start").throws(new Error("Test")); + await workspaceCommand.startRepl(); + sinon.assert.calledOnce(notifyStub); + }); + }); + }); }); describe("resetScratchpadFromEditor", () => { @@ -2963,4 +3092,19 @@ describe("clientCommands", () => { assert.strictEqual(edit.size, 1); }); }); + + describe("getPartialDatasourceFile", () => { + it("should return qsql datatsource", () => { + const res = dataSourceCommand.getPartialDatasourceFile("query"); + assert.strictEqual(res.dataSource.selectedType, "QSQL"); + }); + it("should return sql datatsource", () => { + const res = dataSourceCommand.getPartialDatasourceFile( + "query", + "dap", + true, + ); + assert.strictEqual(res.dataSource.selectedType, "SQL"); + }); + }); }); diff --git a/test/suite/customFields.test.ts b/test/suite/customFields.test.ts index 010c26298..947a22590 100644 --- a/test/suite/customFields.test.ts +++ b/test/suite/customFields.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/suite/index.ts b/test/suite/index.ts index 3d70142a3..2ef5fe7da 100644 --- a/test/suite/index.ts +++ b/test/suite/index.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,17 +11,20 @@ * specific language governing permissions and limitations under the License. */ -import glob from "glob"; +import { glob } from "glob"; import Mocha from "mocha"; import * as path from "path"; -import { createReport } from "../coverage"; +import { generateCoverageReport } from "../coverage"; + +export async function run(): Promise { + const headless = !!process.env.CI; -export function run(): Promise { const options: Mocha.MochaOptions = { ui: "bdd", color: true, reporter: "mocha-multi-reporters", + timeout: headless ? 2_000 : 600_000, reporterOptions: { reporterEnabled: "spec, mocha-junit-reporter", mochaJunitReporterReporterOptions: { @@ -33,30 +36,36 @@ export function run(): Promise { const mocha = new Mocha(options); const testsRoot = path.join(__dirname, ".."); - return new Promise((c, e) => { - glob("**/suite/**.test.js", { cwd: testsRoot }, (err, files) => { - if (err) { - return e(err); - } + try { + const files = await glob("**/suite/**.test.js", { cwd: testsRoot }); + + files.forEach((f) => mocha.addFile(path.join(testsRoot, f))); - files.forEach((f) => mocha.addFile(path.join(testsRoot, f))); - - try { - mocha.run((failures) => { - if (failures > 0) { - e(new Error(`${failures} tests failed.`)); - } else { - c(); - } - }); - } catch (err) { - console.error(err); - e(err); + return new Promise((resolve, reject) => { + mocha.run((failures) => { + if (failures > 0) { + reject(new Error(`${failures} tests failed.`)); + } else { + resolve(); + } + }); + }).then(() => { + if (process.env["GENERATE_COVERAGE"]) { + try { + generateCoverageReport(); + console.log("✅ Coverage generation completed successfully"); + } catch (error) { + console.error("❌ Coverage generation failed:", error); + throw error; + } + } else { + // console.log( + // "❌ GENERATE_COVERAGE not set, skipping coverage generation", + // ); } }); - }).then(() => { - if (process.env["GENERATE_COVERAGE"]) { - createReport(); - } - }); + } catch (err) { + console.error(err); + throw err; + } } diff --git a/test/suite/notebooks.test.ts b/test/suite/notebooks.test.ts index 7c077e9f1..84a2b3b09 100644 --- a/test/suite/notebooks.test.ts +++ b/test/suite/notebooks.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -15,160 +15,358 @@ import * as assert from "assert"; import * as sinon from "sinon"; import * as vscode from "vscode"; +import { InsightsConnection } from "../../src/classes/insightsConnection"; +import { LocalConnection } from "../../src/classes/localConnection"; +import * as serverCommand from "../../src/commands/serverCommand"; +import * as workspaceCommand from "../../src/commands/workspaceCommand"; import { ext } from "../../src/extensionVariables"; import { ConnectionManagementService } from "../../src/services/connectionManagerService"; +import { InsightsNode, KdbNode } from "../../src/services/kdbTreeProvider"; import * as controlller from "../../src/services/notebookController"; +import * as providers from "../../src/services/notebookProviders"; import * as serializer from "../../src/services/notebookSerializer"; +import * as notifications from "../../src/utils/notifications"; describe("Notebooks", () => { - afterEach(() => { - sinon.restore(); - }); + const result = { + table: { + count: 2, + columns: [ + { + name: "x", + type: "longs", + values: ["0", "1"], + order: [0, 1], + }, + { + name: "y", + type: "longs", + values: ["0", "1"], + order: [0, 1], + }, + ], + }, - describe("Controller", () => { - const cells: vscode.NotebookCell[] = [ - { - document: { - getText() { - return "a:1;a"; - }, + png: { + count: 66, + columns: [ + { + name: "values", + type: "bytes", + values: [ + "0x89", + "0x50", + "0x4e", + "0x47", + "0x0d", + "0x0a", + "0x1a", + "0x0a", + ..."0x00,".repeat(58).split(","), + ], }, - executionSummary: {}, - index: 1, - kind: vscode.NotebookCellKind.Code, - metadata: {}, - notebook: {}, - outputs: [], - }, - ]; + ], + }, - const executor = { - executionOrder: 1, - start() {}, - end() {}, - replaceOutput() {}, - }; + text: "text result", + }; - let instance: controlller.KxNotebookController; + function createNotebook() { + return { uri: vscode.Uri.file("test.kxnb") }; + } - function createResult(result: any) { - sinon.stub(ext, "activeConnection").value({ connLabel: "test" }); - sinon.stub(instance, "createConnectionManager").returns(< - ConnectionManagementService - >({ - executeQuery() { - return result; + function createCell(languageId?: string, metadata = {}) { + return { + document: { + languageId, + getText() { + return "expressions"; }, - })); - } + }, + notebook: { uri: vscode.Uri.file("test.kxnb") }, + metadata, + }; + } + + afterEach(() => { + sinon.restore(); + }); + + describe("Controller", () => { + let executeQueryStub: sinon.SinonStub; + let notifyStub: sinon.SinonStub; + let instance: controlller.KxNotebookController; + let success: boolean; beforeEach(() => { - sinon.stub(vscode.notebooks, "createNotebookController").returns(< - vscode.NotebookController - >({ - createNotebookCellExecution() { - return executor; - }, - dispose() {}, - })); - instance = new controlller.KxNotebookController(); + executeQueryStub = sinon.stub(serverCommand, "executeQuery"); + notifyStub = sinon.stub(notifications, "notify"); + sinon + .stub(vscode.notebooks, "createNotebookController") + .returns({}); }); afterEach(() => { - instance.dispose(); instance = undefined; + success = undefined; }); - it("should show create connection manager", async () => { - const manager = instance.createConnectionManager(); - assert.ok(manager); - }); + function createInstance() { + instance = new controlller.KxNotebookController(); + } - it("should end execution on error", async () => { - createResult({}); - const end = sinon.stub(executor, "end"); - await instance.execute( - cells, - {}, - {}, - ); - assert.ok(end.calledOnce); - }); + function createController() { + return { + createNotebookCellExecution(_) { + return { + start() {}, + end(status) { + success = status; + }, + replaceOutput(_) {}, + executionOrder: 0, + }; + }, + }; + } - it("should show error message if not connected", async () => { - const msg = sinon.stub(vscode.window, "showErrorMessage"); - await instance.execute( - cells, - {}, - {}, - ); - assert.ok(msg.calledOnce); - }); + describe("Connection Picked", () => { + beforeEach(() => { + sinon.stub(workspaceCommand, "getServerForUri").returns("picked"); + }); - it("should execute code for number result", async () => { - createResult(1); - const res = sinon.stub(executor, "replaceOutput"); - await instance.execute( - cells, - {}, - {}, - ); - assert.ok(res.calledOnce); - }); + describe("Connected", () => { + describe("Insights Connection", () => { + beforeEach(() => { + sinon + .stub( + ConnectionManagementService.prototype, + "retrieveConnectedConnection", + ) + .returns(sinon.createStubInstance(InsightsConnection)); + }); - it("should execute code for plot result", async () => { - createResult({ - count: 50046, - columns: [ - { - name: "values", - type: "bytes", - values: [ - "0x89", - "0x50", - "0x4e", - "0x47", - "0x0d", - "0x0a", - "0x1a", - "0x0a", - ..."0x00,".repeat(58).split(","), - ], - }, - ], + describe("Connection Not Exists", () => { + beforeEach(() => { + sinon + .stub(workspaceCommand, "findConnection") + .resolves(undefined); + + createInstance(); + }); + + it("should notify missing connection with error", async () => { + await instance.execute( + [createCell()], + createNotebook(), + createController(), + ); + // sinon.assert.calledOnceWithExactly( + // notifyStub, + // sinon.match.string, + // notifications.MessageKind.ERROR, + // sinon.match.any, + // ); + assert.strictEqual(success, undefined); + }); + }); + }); + + describe("Local Connection", () => { + beforeEach(() => { + sinon + .stub( + ConnectionManagementService.prototype, + "retrieveConnectedConnection", + ) + .returns(sinon.createStubInstance(LocalConnection)); + }); + + describe("Node Exists", () => { + beforeEach(() => { + sinon + .stub(workspaceCommand, "getConnectionForServer") + .resolves(sinon.createStubInstance(KdbNode)); + + createInstance(); + }); + + describe("q cell", () => { + it("should display table results", async () => { + executeQueryStub.resolves(result.table); + await instance.execute( + [createCell("q")], + createNotebook(), + createController(), + ); + sinon.assert.notCalled(notifyStub); + assert.strictEqual(success, true); + }); + + it("should display png results", async () => { + executeQueryStub.resolves(result.png); + await instance.execute( + [createCell("q")], + createNotebook(), + createController(), + ); + sinon.assert.notCalled(notifyStub); + assert.strictEqual(success, true); + }); + + it("should display text results", async () => { + executeQueryStub.resolves(result.text); + await instance.execute( + [createCell("q")], + createNotebook(), + createController(), + ); + sinon.assert.notCalled(notifyStub); + assert.strictEqual(success, true); + }); + }); + + describe("python cell", () => { + it("should display text results", async () => { + executeQueryStub.resolves(result.text); + await instance.execute( + [createCell("python")], + createNotebook(), + createController(), + ); + sinon.assert.notCalled(notifyStub); + assert.strictEqual(success, true); + }); + }); + + describe("sql cell", () => { + it("should display not supported", async () => { + executeQueryStub.resolves({}); + await instance.execute( + [createCell("sql")], + createNotebook(), + createController(), + ); + sinon.assert.calledOnceWithExactly( + notifyStub, + sinon.match.string, + notifications.MessageKind.DEBUG, + sinon.match.any, + ); + assert.strictEqual(success, false); + }); + }); + }); + + describe("Connection Not Exists", () => { + beforeEach(() => { + sinon + .stub(workspaceCommand, "findConnection") + .resolves(undefined); + + createInstance(); + }); + + it("should notify missing connection with error", async () => { + await instance.execute( + [createCell()], + createNotebook(), + createController(), + ); + // sinon.assert.calledOnceWithExactly( + // notifyStub, + // sinon.match.string, + // notifications.MessageKind.ERROR, + // sinon.match.any, + // ); + assert.strictEqual(success, undefined); + }); + }); + }); }); - const res = sinon.stub(executor, "replaceOutput"); - await instance.execute( - cells, - {}, - {}, - ); - assert.ok(res.calledOnce); }); - it("should execute code for string result", async () => { - createResult("result"); - const res = sinon.stub(executor, "replaceOutput"); - await instance.execute( - cells, - {}, - {}, - ); - assert.ok(res.calledOnce); - }); + describe("Connection Not Picked", () => { + beforeEach(() => { + sinon.stub(workspaceCommand, "getServerForUri").returns(undefined); + }); + + describe("Connected", () => { + const text = "results"; + + afterEach(() => { + ext.activeConnection = undefined; + }); + + describe("LocalConnection", () => { + beforeEach(() => { + ext.activeConnection = sinon.createStubInstance(LocalConnection); + createInstance(); + }); + + describe("q cell", () => { + it("should display results", async () => { + executeQueryStub.resolves(text); + await instance.execute( + [createCell("q")], + createNotebook(), + createController(), + ); + sinon.assert.notCalled(notifyStub); + assert.strictEqual(success, true); + }); + }); - it("should execute code for table result", async () => { - createResult({ - count: 1, - columns: [{ name: "values", type: "long", values: ["1"], order: [0] }], + describe("q cell with target", () => { + it("should error", async () => { + executeQueryStub.resolves(text); + await instance.execute( + [createCell("q", { target: "target" })], + createNotebook(), + createController(), + ); + sinon.assert.called(notifyStub); + }); + }); + + describe("q cell with variable", () => { + it("should error", async () => { + executeQueryStub.resolves(text); + await instance.execute( + [createCell("q", { variable: "variable" })], + createNotebook(), + createController(), + ); + sinon.assert.called(notifyStub); + }); + }); + }); + }); + + describe("Disconnected", () => { + beforeEach(() => { + createInstance(); + }); + + describe("LocalConnection", () => { + describe("q cell", async () => { + it("should show warning message", async () => { + await instance.execute( + [createCell("q")], + createNotebook(), + createController(), + ); + sinon.assert.calledOnceWithExactly( + notifyStub, + sinon.match.string, + notifications.MessageKind.WARNING, + sinon.match.any, + ); + assert.strictEqual(success, undefined); + }); + }); + }); }); - const res = sinon.stub(executor, "replaceOutput"); - await instance.execute( - cells, - {}, - {}, - ); - assert.ok(res.calledOnce); }); }); @@ -242,4 +440,184 @@ describe("Notebooks", () => { } }); }); + + describe("Providers", () => { + it("getCellKind should return correct kind", () => { + ["markdown", "q", "python", "sql"].map((languageId, index) => { + const res = providers.getCellKind({ + document: { languageId }, + }); + assert.strictEqual(res, index); + }); + }); + + it("updateCellMetadata should apply workspace edit", () => { + const stub = sinon.stub(vscode.workspace, "applyEdit"); + providers.updateCellMetadata( + { + index: 0, + notebook: { uri: vscode.Uri.file("test") }, + }, + { + target: "target", + variable: "variable", + }, + ); + sinon.assert.calledOnce(stub); + }); + + it("inputVariable should return input variable", async () => { + sinon.stub(vscode.window, "showInputBox").resolves("variable"); + const res = await providers.inputVariable(); + assert.strictEqual(res, "variable"); + }); + + describe("validateInput", () => { + it("should return undefined for valid input", () => { + assert.strictEqual(providers.validateInput(".ns.var"), undefined); + }); + it("should return undefined for empty input", () => { + assert.strictEqual(providers.validateInput(""), undefined); + }); + it("should return undefined for undefined input", () => { + assert.strictEqual(providers.validateInput(undefined), undefined); + }); + it("should return message for input length > 32", () => { + assert.ok(providers.validateInput("v".repeat(33))); + }); + it("should return message for input starting with a number", () => { + assert.ok(providers.validateInput("1variable")); + }); + it("should return message for input starting with an underscore", () => { + assert.ok(providers.validateInput("_variable")); + }); + it("should return message for input with invalid characters", () => { + assert.ok(providers.validateInput("\u011f")); + }); + }); + + describe("KxNotebookTargetActionProvider", () => { + const token = {}; + let instance: providers.KxNotebookTargetActionProvider; + let changeConfigCallback: any; + + function createInstance() { + instance = new providers.KxNotebookTargetActionProvider(); + } + + describe("Connection Picked", () => { + beforeEach(() => { + sinon.stub(workspaceCommand, "getServerForUri").returns("picked"); + sinon + .stub(vscode.workspace, "onDidChangeConfiguration") + .value((callback: any) => (changeConfigCallback = callback)); + + createInstance(); + }); + + it("should update on config change", () => { + let fired = false; + instance.onDidChangeCellStatusBarItems(() => (fired = true)); + assert.ok(changeConfigCallback); + changeConfigCallback({ affectsConfiguration: () => true }); + assert.ok(fired); + }); + + describe("Local Connection", () => { + beforeEach(() => { + sinon + .stub(workspaceCommand, "getConnectionForServer") + .resolves(sinon.createStubInstance(KdbNode)); + }); + + it("should return 2", async () => { + const cell = createCell("q", { + target: "target", + variable: "variable", + }); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 2); + }); + + it("should return 2", async () => { + const cell = createCell("python", { + target: "target", + variable: "variable", + }); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 2); + }); + + it("should return 1", async () => { + const cell = createCell("sql", { + target: "target", + variable: "variable", + }); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 1); + }); + }); + + describe("Insights Connection", () => { + beforeEach(() => { + sinon + .stub(workspaceCommand, "getConnectionForServer") + .resolves(sinon.createStubInstance(InsightsNode)); + }); + + it("should return only scratchpad", async () => { + const cell = createCell("q"); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 1); + assert.strictEqual(res[0].text, "scratchpad"); + }); + + it("should return scratchpad and variable", async () => { + const cell = createCell("q", { + target: "target", + variable: "variable", + }); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 2); + assert.strictEqual(res[0].text, "target"); + assert.strictEqual(res[1].text, "(variable)"); + }); + + it("should return only variable", async () => { + const cell = createCell("sql", { + variable: "variable", + }); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 1); + assert.strictEqual(res[0].text, "(variable)"); + }); + + it("should return none for markdown", async () => { + const cell = createCell("markdown"); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 0); + }); + + it("should return target for python", async () => { + const cell = createCell("python"); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 1); + }); + }); + }); + + describe("Connection Not Picked", () => { + beforeEach(() => { + sinon.stub(workspaceCommand, "getServerForUri").returns(undefined); + createInstance(); + }); + + it("should return 1", async () => { + const cell = createCell("q"); + const res = await instance.provideCellStatusBarItems(cell, token); + assert.strictEqual(res.length, 1); + }); + }); + }); + }); }); diff --git a/test/suite/panels.test.ts b/test/suite/panels.test.ts index a3a22abe3..457cf0fec 100644 --- a/test/suite/panels.test.ts +++ b/test/suite/panels.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -24,8 +24,8 @@ import { DataSourcesPanel } from "../../src/panels/datasource"; import { NewConnectionPannel } from "../../src/panels/newConnection"; import { InsightsNode, KdbNode } from "../../src/services/kdbTreeProvider"; import { KdbResultsViewProvider } from "../../src/services/resultsPanelProvider"; -import * as coreUtils from "../../src/utils/core"; import * as utils from "../../src/utils/execution"; +import * as loggers from "../../src/utils/loggers"; import * as renderer from "../../src/utils/resultsRenderer"; describe("WebPanels", () => { @@ -496,7 +496,7 @@ describe("WebPanels", () => { } as any; postMessageStub = resultsPanel["_view"].webview .postMessage as sinon.SinonStub; - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); convertToGridStub = sinon.stub(renderer, "convertToGrid"); }); @@ -509,7 +509,7 @@ describe("WebPanels", () => { resultsPanel.updateWebView("test"); sinon.assert.calledWith( kdbOutputLogStub, - "[Results Tab] No view to update", + "[resultsPanelProvider] No view to update", "ERROR", ); }); diff --git a/test/suite/parser.test.ts b/test/suite/parser.test.ts index 37d7b80be..a3a2fafce 100644 --- a/test/suite/parser.test.ts +++ b/test/suite/parser.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/suite/qLangServer.test.ts b/test/suite/qLangServer.test.ts index fed897d89..860dddfe8 100644 --- a/test/suite/qLangServer.test.ts +++ b/test/suite/qLangServer.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -23,6 +23,7 @@ import { } from "vscode-languageserver"; import { TextDocument } from "vscode-languageserver-textdocument"; +import * as linter from "../../server/src/linter"; import QLangServer from "../../server/src/qLangServer"; const context = { includeDeclaration: true }; @@ -67,6 +68,9 @@ describe("qLangServer", () => { async getWorkspaceFolders() { return []; }, + async getConfiguration() { + return undefined; + }, }, languages: { callHierarchy: { @@ -79,6 +83,7 @@ describe("qLangServer", () => { }, }, sendDiagnostics() {}, + sendNotification() {}, }); const params = { @@ -108,189 +113,192 @@ describe("qLangServer", () => { }); describe("onDocumentSymbol", () => { - it("should return symbols", () => { + it("should return symbols", async () => { const params = createDocument("a:1;b:{[c]d:c+1;e::1;d}"); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 3); }); - it("should skip table and sql", () => { + it("should skip table and sql", async () => { const params = createDocument(")([]a:1;b:2);select a:1 from("); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 0); }); - it("should account for \\d can be only one level deep", () => { + it("should account for \\d can be only one level deep", async () => { const params = createDocument("\\d .foo.bar\na:1"); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, ".foo.a"); }); - it("should account for bogus \\d", () => { + it("should account for bogus \\d", async () => { const params = createDocument("\\d\na:1"); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, "a"); }); - it("should account for bogus \\d foo", () => { + it("should account for bogus \\d foo", async () => { const params = createDocument("\\d foo\na:1"); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, "a"); }); - it('should account for bogus system"d', () => { + it('should account for bogus system"d', async () => { const params = createDocument('system"d";a:1'); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, "a"); }); - it('should account for bogus system"d', () => { + it('should account for bogus system"d', async () => { const params = createDocument("a:1;system"); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 1); }); - it('should account for only static system"d', () => { + it('should account for only static system"d', async () => { const params = createDocument('a:1;system"d .foo";b:1'); - const result = server.onDocumentSymbol(params); + const result = await server.onDocumentSymbol(params); assert.strictEqual(result.length, 2); assert.strictEqual(result[1].name, "b"); }); }); describe("onReferences", () => { - it("should return empty array for no text", () => { + it("should return empty array for no text", async () => { const params = createDocument(""); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 0); }); - it("should return empty array for bogus assignment", () => { + it("should return empty array for bogus assignment", async () => { const params = createDocument(":"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 0); }); - it("should return empty array for bogus function", () => { + it("should return empty array for bogus function", async () => { const params = createDocument("{"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 0); }); - it("should return references in anonymous functions", () => { + it("should return references in anonymous functions", async () => { const params = createDocument("{a:1;a"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 2); }); - it("should support imlplicit arguments", () => { + it("should support imlplicit arguments", async () => { const params = createDocument("x:1;y:1;z:1;{x+y+z};x"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 2); }); - it("should find globals in functions", () => { + it("should find globals in functions", async () => { const params = createDocument("a:1;{a"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 2); }); - it("should find locals in functions", () => { + it("should find locals in functions", async () => { const params = createDocument("a:1;{a:2;a"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 2); }); - it("should apply namespace to globals in functions", () => { + it("should apply namespace to globals in functions", async () => { const params = createDocument( "\\d .foo\na:1;f:{a::2};\\d .\n.foo.f[];.foo.a", ); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 3); }); - it("should return references for quke", () => { + it("should return references for quke", async () => { const params = createDocument("FEATURE\nfeature\nshould\nEXPECT\na:1;a"); - const result = server.onReferences({ ...params, context }); + const result = await server.onReferences({ ...params, context }); assert.strictEqual(result.length, 2); }); }); describe("onDefinition", () => { - it("should return definitions", () => { + it("should return definitions", async () => { const params = createDocument("a:1;b:{[c]d:c+1;d};b"); - const result = server.onDefinition(params); + const result = await server.onDefinition(params); assert.strictEqual(result.length, 1); }); - it("should return local definitions", () => { + it("should return local definitions", async () => { const params = createDocument("a:1;b:{[c]d:1;d"); - const result = server.onDefinition(params); + const result = await server.onDefinition(params); assert.strictEqual(result.length, 1); }); }); describe("onRenameRequest", () => { - it("should rename identifiers", () => { + it("should rename identifiers", async () => { const params = createDocument("a:1;b:{[c]d:c+1;d};b"); - const result = server.onRenameRequest({ ...params, newName: "newName" }); + const result = await server.onRenameRequest({ + ...params, + newName: "newName", + }); assert.ok(result); assert.strictEqual(result.changes[params.textDocument.uri].length, 2); }); }); describe("onCompletion", () => { - it("should complete identifiers", () => { + it("should complete identifiers", async () => { const params = createDocument("a:1;b:{[c]d:c+1;d};b"); - const result = server.onCompletion(params); + const result = await server.onCompletion(params); assert.strictEqual(result.length, 2); }); }); describe("onExpressionRange", () => { - it("should return the range of the expression", () => { + it("should return the range of the expression", async () => { const params = createDocument("a:1;"); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.strictEqual(result.start.line, 0); assert.strictEqual(result.start.character, 0); assert.strictEqual(result.end.line, 0); assert.strictEqual(result.end.character, 3); }); - it("should return the range of the expression", () => { + it("should return the range of the expression", async () => { const params = createDocument("a"); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.strictEqual(result.start.line, 0); assert.strictEqual(result.start.character, 0); assert.strictEqual(result.end.line, 0); assert.strictEqual(result.end.character, 1); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument(""); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.strictEqual(result, null); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument(";"); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.strictEqual(result, null); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument("/a:1"); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.strictEqual(result, null); }); }); describe("onExpressionRange", () => { - it("should return range for simple expression", () => { + it("should return range for simple expression", async () => { const params = createDocument("a:1;"); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.ok(result); assert.strictEqual(result.start.line, 0); assert.strictEqual(result.start.character, 0); assert.strictEqual(result.end.line, 0); assert.strictEqual(result.end.character, 3); }); - it("should return range for lambda", () => { + it("should return range for lambda", async () => { const params = createDocument("a:{b:1;b};"); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.ok(result); assert.strictEqual(result.start.line, 0); assert.strictEqual(result.start.character, 0); assert.strictEqual(result.end.line, 0); assert.strictEqual(result.end.character, 9); }); - it("should skip comments", () => { + it("should skip comments", async () => { const params = createDocument("a:1 /comment", 1); - const result = server.onExpressionRange(params); + const result = await server.onExpressionRange(params); assert.ok(result); assert.strictEqual(result.start.line, 0); assert.strictEqual(result.start.character, 0); @@ -300,9 +308,9 @@ describe("qLangServer", () => { }); describe("omParameterCache", () => { - it("should cache paramater", () => { + it("should cache paramater", async () => { const params = createDocument("{[a;b]}"); - const result = server.onParameterCache(params); + const result = await server.onParameterCache(params); assert.ok(result); assert.deepEqual(result.params, ["a", "b"]); assert.strictEqual(result.start.line, 0); @@ -310,9 +318,9 @@ describe("qLangServer", () => { assert.strictEqual(result.end.line, 0); assert.strictEqual(result.end.character, 6); }); - it("should cache paramater", () => { + it("should cache paramater", async () => { const params = createDocument("{[a;b]\n }"); - const result = server.onParameterCache(params); + const result = await server.onParameterCache(params); assert.ok(result); assert.deepEqual(result.params, ["a", "b"]); assert.strictEqual(result.start.line, 0); @@ -320,32 +328,32 @@ describe("qLangServer", () => { assert.strictEqual(result.end.line, 1); assert.strictEqual(result.end.character, 1); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument("{[]}"); - const result = server.onParameterCache(params); + const result = await server.onParameterCache(params); assert.strictEqual(result, null); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument("{}"); - const result = server.onParameterCache(params); + const result = await server.onParameterCache(params); assert.strictEqual(result, null); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument("a:1;"); - const result = server.onParameterCache(params); + const result = await server.onParameterCache(params); assert.strictEqual(result, null); }); - it("should return null", () => { + it("should return null", async () => { const params = createDocument(""); - const result = server.onParameterCache(params); + const result = await server.onParameterCache(params); assert.strictEqual(result, null); }); }); describe("onSelectionRanges", () => { - it("should return identifier range", () => { + it("should return identifier range", async () => { const params = createDocument(".test.ref"); - const result = server.onSelectionRanges({ + const result = await server.onSelectionRanges({ textDocument: params.textDocument, positions: [params.position], }); @@ -356,64 +364,59 @@ describe("qLangServer", () => { }); describe("onPrepareCallHierarchy", () => { - it("should prepare call hierarchy", () => { + it("should prepare call hierarchy", async () => { const params = createDocument("a:1;a"); - const result = server.onPrepareCallHierarchy(params); + const result = await server.onPrepareCallHierarchy(params); assert.strictEqual(result.length, 1); }); }); describe("onIncomingCallsCallHierarchy", () => { - it("should return incoming calls", () => { + it("should return incoming calls", async () => { const params = createDocument("a:1;a"); - const items = server.onPrepareCallHierarchy(params); - const result = server.onIncomingCallsCallHierarchy({ item: items[0] }); + const items = await server.onPrepareCallHierarchy(params); + const result = await server.onIncomingCallsCallHierarchy({ + item: items[0], + }); assert.strictEqual(result.length, 1); }); }); describe("onOutgoingCallsCallHierarchy", () => { - it("should return outgoing calls", () => { + it("should return outgoing calls", async () => { const params = createDocument("a:1;{a"); - const items = server.onPrepareCallHierarchy(params); - const result = server.onOutgoingCallsCallHierarchy({ item: items[0] }); + const items = await server.onPrepareCallHierarchy(params); + const result = await server.onOutgoingCallsCallHierarchy({ + item: items[0], + }); assert.strictEqual(result.length, 1); }); }); describe("onSemanticTokens", () => { - it("should tokenize local variables", () => { + it("should tokenize local variables", async () => { const params = createDocument("a:{[b;c]d:1;b*c*d}"); - const result = server.onSemanticTokens(params); + const result = await server.onSemanticTokens(params); assert.strictEqual(result.data.length, 35); }); - it("should ignore qualified variables", () => { + it("should ignore qualified variables", async () => { const params = createDocument("a:{.ns.b:1;.ns.b}"); - const result = server.onSemanticTokens(params); + const result = await server.onSemanticTokens(params); assert.strictEqual(result.data.length, 5); }); - it("should detect empty lists", () => { + it("should detect empty lists", async () => { const params = createDocument("a:{b:();b}"); - const result = server.onSemanticTokens(params); + const result = await server.onSemanticTokens(params); assert.strictEqual(result.data.length, 15); }); }); - describe("setSettings", () => { - const defaultSettings = { - debug: false, - linting: false, - refactoring: "Workspace", - }; - it("should use default settings for empty", () => { - server.setSettings({}); - assert.deepEqual(server["settings"], defaultSettings); - }); - it("should use default settings for empty coming from client", () => { - server.onDidChangeConfiguration({ settings: { kdb: {} } }); - assert.deepEqual(server["settings"], defaultSettings); + describe("onDidOpen", () => { + it("should add to opened", () => { + server.onDidOpen({ document: { uri: "test" } }); + assert.ok(server["opened"].has("test")); }); }); @@ -425,23 +428,103 @@ describe("qLangServer", () => { }); }); - describe("scan", () => { - it("should scan empty workspace", () => { - sinon - .stub(connection.workspace, "getWorkspaceFolders") - .value(async () => []); - server.scan(); - assert.strictEqual(server["cached"].size, 0); + describe("Settings", () => { + describe("getDebug", () => { + it("should return false", async () => { + const res = await server["getDebug"](""); + assert.strictEqual(res, false); + }); + it("should return true", async () => { + sinon + .stub(connection.workspace, "getConfiguration") + .resolves(true); + const res = await server["getDebug"](""); + assert.strictEqual(res, true); + }); + }); + describe("getLinting", () => { + it("should return false", async () => { + const res = await server["getLinting"](""); + assert.strictEqual(res, false); + }); + it("should return true", async () => { + sinon + .stub(connection.workspace, "getConfiguration") + .resolves(true); + const res = await server["getLinting"](""); + assert.strictEqual(res, true); + }); + }); + describe("getRefactoring", () => { + it("should return Workspace", async () => { + const res = await server["getRefactoring"](""); + assert.strictEqual(res, "Workspace"); + }); + it("should return Window", async () => { + sinon + .stub(connection.workspace, "getConfiguration") + .resolves("Window"); + const res = await server["getRefactoring"](""); + assert.strictEqual(res, "Window"); + }); + }); + describe("getConnectionMap", () => { + it("should return empty map", async () => { + const res = await server["getConnectionMap"](""); + assert.deepEqual(res, {}); + }); + it("should return true", async () => { + sinon + .stub(connection.workspace, "getConfiguration") + .resolves({ "test.q": "REPL" }); + const res = await server["getConnectionMap"](""); + assert.deepEqual(res, { "test.q": "REPL" }); + }); + }); + }); + + describe("notify", () => { + it("", () => { + const stub = sinon.stub(connection, "sendNotification"); + server["notify"]("test", "DEBUG", {}, true); + sinon.assert.calledOnceWithExactly(stub, "notify", { + message: "test", + kind: "DEBUG", + options: {}, + telemetry: true, + }); }); }); describe("onDidChangeWatchedFiles", () => { - it("should parse empty match", () => { + it("should remove from cached", () => { + const stub = new Map(); + stub.set("test", "test"); + sinon.stub(server, "cached").value(stub); + server["onDidChangeWatchedFiles"]({ changes: [{ uri: "test" }] }); + assert.strictEqual(stub.get("test"), undefined); + }); + }); + + describe("onDidChangeContent", () => { + it("should lint", async () => { + const stub = sinon.stub(linter, "lint").returns([]); + sinon.stub(server, "parse").returns([]); + sinon.stub(connection.workspace, "getConfiguration").resolves(true); + await server["onDidChangeContent"]({ + document: { uri: "file:///test/test.q" }, + }); + sinon.assert.calledOnce(stub); + }); + }); + + describe("related", () => { + it("should return related files", async () => { + sinon.stub(connection.workspace, "getConfiguration").resolves(true); sinon .stub(connection.workspace, "getWorkspaceFolders") - .value(async () => []); - server.onDidChangeWatchedFiles({ changes: [] }); - assert.strictEqual(server["cached"].size, 0); + .resolves([{ uri: "file:///test" }]); + await server["related"]("file:///test/test.q"); }); }); }); diff --git a/test/suite/repl.test.ts b/test/suite/repl.test.ts new file mode 100644 index 000000000..e10ab45aa --- /dev/null +++ b/test/suite/repl.test.ts @@ -0,0 +1,187 @@ +/* + * Copyright (c) 1998-2025 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 * as assert from "node:assert"; +import * as sinon from "sinon"; +import * as vscode from "vscode"; + +import * as repl from "../../src/classes/replConnection"; + +describe("REPL", () => { + let stdinChunk: string; + let stdinWriteCallback: (error: Error) => void; + let instance: repl.ReplConnection; + + const target = { + on(_: string) {}, + stdout: { on(_: string) {} }, + stderr: { on(_: string) {} }, + stdin: { + write(chunk: any, callback: (error: Error) => void) { + stdinChunk = chunk; + stdinWriteCallback = callback; + }, + }, + }; + const terminal = { show() {} }; + + beforeEach(() => { + sinon + .stub(repl.ReplConnection.prototype, "createProcess") + .returns(target); + sinon.stub(vscode.window, "createTerminal").returns(terminal); + instance = repl.ReplConnection.getOrCreateInstance(); + }); + + afterEach(() => { + sinon.restore(); + stdinChunk = undefined; + stdinWriteCallback = undefined; + instance = undefined; + }); + + describe("connect", () => { + it("should listen error on target", () => { + const stub = sinon.stub(target, "on"); + instance["connect"](); + sinon.assert.calledWithMatch(stub, "error"); + }); + it("should listen close on target", () => { + const stub = sinon.stub(target, "on"); + instance["connect"](); + sinon.assert.calledWithMatch(stub, "close"); + }); + it("should listen data on target stdout", () => { + const stub = sinon.stub(target.stdout, "on"); + instance["connect"](); + sinon.assert.calledWithMatch(stub, "data"); + }); + it("should listen error on target stdout", () => { + const stub = sinon.stub(target.stdout, "on"); + instance["connect"](); + sinon.assert.calledWithMatch(stub, "error"); + }); + it("should listen data on target stderr", () => { + const stub = sinon.stub(target.stderr, "on"); + instance["connect"](); + sinon.assert.calledWithMatch(stub, "data"); + }); + it("should listen error on target stderr", () => { + const stub = sinon.stub(target.stderr, "on"); + instance["connect"](); + sinon.assert.calledWithMatch(stub, "error"); + }); + }); + + describe("sendToProcess", () => { + it("should write data to stdin with CRLF", () => { + instance["sendToProcess"]("a:1"); + assert.strictEqual(stdinChunk, "a:1\r\n"); + }); + it("should send token with data when no error on q", () => { + instance["sendToProcess"]("a:1"); + stdinWriteCallback(null); + assert.ok(stdinChunk.endsWith(',string system"d";\r\n')); + }); + it("should send token with data when no error on k", () => { + instance["context"] = "k"; + instance["sendToProcess"]("a:1"); + stdinWriteCallback(null); + assert.ok(stdinChunk.endsWith(',$:."\\\\d";\r\n')); + }); + it("should decrease execution count on error", () => { + instance["executing"] = 0; + instance["sendToProcess"]("a:1"); + stdinWriteCallback(new Error()); + assert.strictEqual(instance["executing"], 0); + }); + }); + + describe("sendToTerminal", () => { + let data: string; + + beforeEach(() => { + sinon.stub(instance, "onDidWrite").value({ + fire(_data: string) { + data = _data; + }, + }); + }); + + afterEach(() => { + data = undefined; + }); + + it("should fire onDidWrite", () => { + instance["sendToTerminal"]("test"); + assert.strictEqual(data, "test"); + }); + }); + + describe("moveCursorToColumn", () => { + it("should return ANSİ code for moving cursor", () => { + const res = instance["moveCursorToColumn"](1); + assert.strictEqual(res, "\x1B[1G"); + }); + }); + + describe("Output", () => { + let sendToTerminalSub: sinon.SinonStub; + + beforeEach(() => { + sendToTerminalSub = sinon.stub(instance, "sendToTerminal"); + }); + + describe("showPrompt", () => { + it("should not output to terminal if exited", () => { + sinon.stub(instance, "exited").value(true); + instance["showPrompt"](); + sinon.assert.notCalled(sendToTerminalSub); + }); + }); + + describe("showOutput", () => { + it("should not output to terminal if exited", () => { + sinon.stub(instance, "executions").value(undefined); + sinon.stub(instance, "exited").value(true); + instance["showOutput"]("test"); + sinon.assert.notCalled(sendToTerminalSub); + }); + }); + }); + + describe("show", () => { + let showStub: sinon.SinonStub; + + beforeEach(() => { + showStub = sinon.stub(terminal, "show"); + }); + + it("should show REPL when autofocus is enabled", () => { + instance["show"](); + sinon.assert.calledOnce(showStub); + }); + + it("should not show REPL when autofocus is disabled", () => { + sinon.stub(vscode.workspace, "getConfiguration").value(() => { + return { + get() { + return false; + }, + }; + }); + instance["show"](); + sinon.assert.notCalled(showStub); + }); + }); +}); diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index 9e2e6a214..d7fbf56af 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -11,8 +11,8 @@ * specific language governing permissions and limitations under the License. */ +import * as assert from "assert"; import axios from "axios"; -import assert from "node:assert"; import Path from "path"; import sinon from "sinon"; import { @@ -29,7 +29,6 @@ import { import { InsightsConnection } from "../../src/classes/insightsConnection"; import { LocalConnection } from "../../src/classes/localConnection"; -import * as serverCommand from "../../src/commands/serverCommand"; import { ext } from "../../src/extensionVariables"; import { InsightsApiConfig } from "../../src/models/config"; import { @@ -71,8 +70,8 @@ import { QueryHistoryTreeItem, } from "../../src/services/queryHistoryProvider"; import { WorkspaceTreeProvider } from "../../src/services/workspaceTreeProvider"; -import * as coreUtils from "../../src/utils/core"; import * as utils from "../../src/utils/getUri"; +import * as loggers from "../../src/utils/loggers"; import AuthSettings from "../../src/utils/secretStorage"; import { Telemetry } from "../../src/utils/telemetryClient"; @@ -340,10 +339,10 @@ describe("kdbTreeProvider", () => { it("Should return a new KdbNode", () => { const kdbNode = new KdbNode( [], - "kdbnode1", + "", { serverName: "kdbservername", - serverAlias: "kdbserveralias", + serverAlias: "", serverPort: "5001", managed: false, auth: false, @@ -353,7 +352,7 @@ describe("kdbTreeProvider", () => { ); assert.strictEqual( kdbNode.label, - "kdbnode1 [kdbserveralias]", + "[kdbservername:5001]", "KdbNode node creation failed", ); }); @@ -361,7 +360,7 @@ describe("kdbTreeProvider", () => { it("Should return a new KdbNode with no static alias", () => { const kdbNode = new KdbNode( [], - "kdbnode1", + "", { serverName: "kdbservername", serverAlias: "", @@ -374,7 +373,7 @@ describe("kdbTreeProvider", () => { ); assert.strictEqual( kdbNode.label, - "kdbnode1", + "[kdbservername:5001]", "KdbNode node creation failed", ); }); @@ -382,7 +381,7 @@ describe("kdbTreeProvider", () => { it("Should return a new KdbNode with children", () => { const kdbNode = new KdbNode( ["node1", "node2", "node3", "node4"], - "kdbnode1", + "kdbserveralias", { serverName: "kdbservername", serverAlias: "kdbserveralias", @@ -395,7 +394,7 @@ describe("kdbTreeProvider", () => { ); assert.strictEqual( kdbNode.label, - "kdbnode1 [kdbserveralias]", + "kdbserveralias [kdbservername:5001]", "KdbNode node creation failed", ); }); @@ -403,7 +402,7 @@ describe("kdbTreeProvider", () => { it("Should return a new KdbNode that is connected", () => { const kdbNode = new KdbNode( [], - "kdbnode1", + "kdbserveralias", { serverName: "kdbservername", serverAlias: "kdbserveralias", @@ -417,12 +416,21 @@ describe("kdbTreeProvider", () => { ext.connectionNode = kdbNode; - const kdbNode1 = new KdbNode( + assert.strictEqual( + kdbNode.label, + "kdbserveralias [kdbservername:5001]", + "KdbNode node creation failed", + ); + }); + + it("Should add node to no tls list", () => { + ext.kdbNodesWithoutTls.length = 0; + new KdbNode( [], - "kdbnode1", + "testServer", { - serverName: "kdbservername", - serverAlias: "kdbserveralias", + serverName: "testServername", + serverAlias: "testServerAlias", serverPort: "5001", managed: false, auth: false, @@ -430,23 +438,12 @@ describe("kdbTreeProvider", () => { }, TreeItemCollapsibleState.None, ); - - assert.strictEqual( - kdbNode1.label, - "kdbnode1 [kdbserveralias]", - "KdbNode node creation failed", - ); - }); - - it("Should add node to no tls list", () => { - ext.kdbNodesWithoutTls.length = 0; - ext.kdbNodesWithoutTls.push("testServer [testServerAlias]"); assert.equal(ext.kdbNodesWithoutTls.length, 1); }); it("Should remove node from no tls list", () => { ext.kdbNodesWithoutTls.length = 0; - ext.kdbNodesWithoutTls.push("testServer [testServerAlias]"); + ext.kdbNodesWithoutTls.push("testServer [testServername:5001]"); new KdbNode( [], "testServer", @@ -465,7 +462,6 @@ describe("kdbTreeProvider", () => { it("Should add node to no auth list", () => { ext.kdbNodesWithoutAuth.length = 0; - ext.kdbNodesWithoutAuth.push("testServer [testServerAlias]"); new KdbNode( [], "testServer", @@ -484,7 +480,7 @@ describe("kdbTreeProvider", () => { it("Should remove node from no auth list", () => { ext.kdbNodesWithoutAuth.length = 0; - ext.kdbNodesWithoutAuth.push("testServer [testServerAlias]"); + ext.kdbNodesWithoutAuth.push("testServer [testServername:5001]"); new KdbNode( [], "testServer", @@ -498,8 +494,9 @@ describe("kdbTreeProvider", () => { }, TreeItemCollapsibleState.None, ); - assert.equal(ext.kdbNodesWithoutAuth, 0); + assert.equal(ext.kdbNodesWithoutAuth.length, 0); }); + it("Should retun a new InsightsNode", () => { const insightsNode = new InsightsNode( [], @@ -759,6 +756,555 @@ describe("kdbTreeProvider", () => { assert.strictEqual(result.length, 1); }); }); + + describe("KdbTreeProvider private methods", () => { + let provider: KdbTreeProvider; + let servers: Server; + let insights: Insights; + let mockLocalConn: LocalConnection; + let mockInsightsConn: InsightsConnection; + let connMngStub: sinon.SinonStub; + + const createMockServerObject = ( + id: number, + name: string, + typeNum: number, + namespace: string = ".", + isNs: boolean = false, + ): ServerObject => ({ + id, + pid: id, + name, + fname: name, + typeNum, + namespace, + context: {}, + isNs, + }); + + beforeEach(() => { + servers = { + testServer: { + serverAlias: "testServerAlias", + serverName: "testServerName", + serverPort: "5001", + tls: false, + auth: false, + managed: false, + }, + }; + insights = { + testInsight: { + alias: "testInsightsAlias", + server: "testInsightsName", + auth: false, + }, + }; + + provider = new KdbTreeProvider(servers, insights); + mockLocalConn = sinon.createStubInstance(LocalConnection); + mockInsightsConn = sinon.createStubInstance(InsightsConnection); + + // Mock ConnectionManagementService + connMngStub = sinon.stub( + ConnectionManagementService.prototype, + "retrieveConnectedConnection", + ); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("validateAndGetConnection", () => { + it("should return null when connection is not found", () => { + const serverType = new QCategoryNode( + [], + "test", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + connMngStub.returns(undefined); + + const result = (provider as any).validateAndGetConnection(serverType); + + assert.strictEqual(result, null); + }); + + it("should return null for InsightsConnection", () => { + const serverType = new QCategoryNode( + [], + "test", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + connMngStub.returns(mockInsightsConn); + + const result = (provider as any).validateAndGetConnection(serverType); + + assert.strictEqual(result, null); + }); + + it("should return LocalConnection when valid", () => { + const serverType = new QCategoryNode( + [], + "test", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + connMngStub.returns(mockLocalConn); + + const result = (provider as any).validateAndGetConnection(serverType); + + assert.strictEqual(result, mockLocalConn); + }); + + it("should extract connLabel from QCategoryNode", () => { + const serverType = new QCategoryNode( + [], + "test", + "", + ".", + TreeItemCollapsibleState.None, + "testConnLabel", + ); + connMngStub.returns(mockLocalConn); + + (provider as any).validateAndGetConnection(serverType); + + sinon.assert.calledWith(connMngStub, "testConnLabel"); + }); + + it("should handle non-QCategoryNode TreeItem", () => { + const serverType = { contextValue: "test" } as any; + connMngStub.returns(mockLocalConn); + + (provider as any).validateAndGetConnection(serverType); + + sinon.assert.calledWith(connMngStub, ""); + }); + }); + + describe("getServerObjects", () => { + it("should return empty array when serverType is undefined", async () => { + const result = await (provider as any).getServerObjects(undefined); + + assert.deepStrictEqual(result, []); + }); + + it("should return empty array when connection validation fails", async () => { + const serverType = new QCategoryNode( + [], + "test", + "", + ".", + TreeItemCollapsibleState.None, + "", + ); + connMngStub.returns(undefined); + + const result = await (provider as any).getServerObjects(serverType); + + assert.deepStrictEqual(result, []); + }); + + it("should call loadObjectsByCategory when connection is valid", async () => { + const serverType = new QCategoryNode( + [], + "Dictionaries", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + connMngStub.returns(mockLocalConn); + + const loadObjectsByCategoryStub = sinon + .stub(provider as any, "loadObjectsByCategory") + .resolves([]); + + await (provider as any).getServerObjects(serverType); + + sinon.assert.calledOnce(loadObjectsByCategoryStub); + sinon.assert.calledWith( + loadObjectsByCategoryStub, + serverType, + mockLocalConn, + ".", + "testLabel", + ); + }); + }); + + describe("loadObjectsByCategory", () => { + it("should load dictionaries for category index 0", async () => { + const serverType = new QCategoryNode( + [], + "Dictionaries", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + const loadDictionariesStub = sinon + .stub(provider as any, "loadDictionaries") + .resolves([]); + + await (provider as any).loadObjectsByCategory( + serverType, + mockLocalConn, + ".", + "testLabel", + ); + + sinon.assert.calledOnce(loadDictionariesStub); + sinon.assert.calledWith( + loadDictionariesStub, + mockLocalConn, + ".", + ".", + "testLabel", + ); + }); + + it("should load functions for category index 1", async () => { + const serverType = new QCategoryNode( + [], + "Functions", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + const loadFunctionsStub = sinon + .stub(provider as any, "loadFunctions") + .resolves([]); + + await (provider as any).loadObjectsByCategory( + serverType, + mockLocalConn, + ".", + "testLabel", + ); + + sinon.assert.calledOnce(loadFunctionsStub); + }); + + it("should load tables for category index 2", async () => { + const serverType = new QCategoryNode( + [], + "Tables", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + const loadTablesStub = sinon + .stub(provider as any, "loadTables") + .resolves([]); + + await (provider as any).loadObjectsByCategory( + serverType, + mockLocalConn, + ".", + "testLabel", + ); + + sinon.assert.calledOnce(loadTablesStub); + }); + + it("should load variables for category index 3", async () => { + const serverType = new QCategoryNode( + [], + "Variables", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + const loadVariablesStub = sinon + .stub(provider as any, "loadVariables") + .resolves([]); + + await (provider as any).loadObjectsByCategory( + serverType, + mockLocalConn, + ".", + "testLabel", + ); + + sinon.assert.calledOnce(loadVariablesStub); + }); + + it("should load views for category index 4", async () => { + const serverType = new QCategoryNode( + [], + "Views", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + const loadViewsStub = sinon + .stub(provider as any, "loadViews") + .resolves([]); + + await (provider as any).loadObjectsByCategory( + serverType, + mockLocalConn, + ".", + "testLabel", + ); + + sinon.assert.calledOnce(loadViewsStub); + }); + + it("should return empty array for unknown category", async () => { + const serverType = new QCategoryNode( + [], + "Unknown", + "", + ".", + TreeItemCollapsibleState.None, + "testLabel", + ); + + const result = await (provider as any).loadObjectsByCategory( + serverType, + mockLocalConn, + ".", + "testLabel", + ); + + assert.deepStrictEqual(result, []); + }); + }); + + describe("loadDictionaries", () => { + it("should load dictionaries and create QServerNodes", async () => { + const mockDicts = [ + createMockServerObject(1, "dict1", 99), + createMockServerObject(2, "dict2", 99), + ]; + + const kdbTreeServiceStub = sinon + .stub(KdbTreeService, "loadDictionaries") + .resolves(mockDicts); + const createQServerNodesStub = sinon + .stub(provider as any, "createQServerNodes") + .returns([]); + + await (provider as any).loadDictionaries( + mockLocalConn, + "namespace", + ".", + "connLabel", + ); + + sinon.assert.calledWith(kdbTreeServiceStub, mockLocalConn, "namespace"); + sinon.assert.calledWith( + createQServerNodesStub, + mockDicts, + ".", + "connLabel", + "dictionaries", + ); + }); + }); + + describe("loadFunctions", () => { + it("should load functions and create QServerNodes", async () => { + const mockFuncs = [ + createMockServerObject(1, "func1", 100), + createMockServerObject(2, "func2", 100), + ]; + + const kdbTreeServiceStub = sinon + .stub(KdbTreeService, "loadFunctions") + .resolves(mockFuncs); + const createQServerNodesStub = sinon + .stub(provider as any, "createQServerNodes") + .returns([]); + + await (provider as any).loadFunctions( + mockLocalConn, + "namespace", + ".", + "connLabel", + ); + + sinon.assert.calledWith(kdbTreeServiceStub, mockLocalConn, "namespace"); + sinon.assert.calledWith( + createQServerNodesStub, + mockFuncs, + ".", + "connLabel", + "functions", + ); + }); + }); + + describe("loadTables", () => { + it("should load tables and create QServerNodes", async () => { + const mockTables = [ + createMockServerObject(1, "table1", 98), + createMockServerObject(2, "table2", 98), + ]; + const kdbTreeServiceStub = sinon + .stub(KdbTreeService, "loadTables") + .resolves(mockTables); + const createQServerNodesStub = sinon + .stub(provider as any, "createQServerNodes") + .returns([]); + + await (provider as any).loadTables( + mockLocalConn, + "namespace", + ".", + "connLabel", + ); + + sinon.assert.calledWith(kdbTreeServiceStub, mockLocalConn, "namespace"); + sinon.assert.calledWith( + createQServerNodesStub, + mockTables, + ".", + "connLabel", + "tables", + ); + }); + }); + + describe("loadVariables", () => { + it("should load variables and create QServerNodes", async () => { + const mockVars = [ + createMockServerObject(1, "var1", -7), + createMockServerObject(2, "var2", 11), + ]; + const kdbTreeServiceStub = sinon + .stub(KdbTreeService, "loadVariables") + .resolves(mockVars); + const createQServerNodesStub = sinon + .stub(provider as any, "createQServerNodes") + .returns([]); + + await (provider as any).loadVariables( + mockLocalConn, + "namespace", + ".", + "connLabel", + ); + + sinon.assert.calledWith(kdbTreeServiceStub, mockLocalConn, "namespace"); + sinon.assert.calledWith( + createQServerNodesStub, + mockVars, + ".", + "connLabel", + "variables", + ); + }); + }); + + describe("loadViews", () => { + it("should load views and create QServerNodes with correct labels for root namespace", async () => { + const mockViews = ["view1", "view2"]; + const kdbTreeServiceStub = sinon + .stub(KdbTreeService, "loadViews") + .resolves(mockViews); + + const result = await (provider as any).loadViews( + mockLocalConn, + ".", + "connLabel", + ); + + sinon.assert.calledWith(kdbTreeServiceStub, mockLocalConn); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].label, "view1"); + assert.strictEqual(result[1].label, "view2"); + assert.strictEqual(result[0].coreIcon, "views"); + }); + + it("should load views and create QServerNodes with dot prefix for non-root namespace", async () => { + const mockViews = ["view1"]; + sinon.stub(KdbTreeService, "loadViews").resolves(mockViews); + + const result = await (provider as any).loadViews( + mockLocalConn, + "myns", + "connLabel", + ); + + assert.strictEqual(result[0].label, ".view1"); + }); + }); + + describe("createQServerNodes", () => { + it("should create QServerNodes correctly for root namespace", () => { + const objects = [{ name: "test1" }, { name: "test2" }]; + + const result = (provider as any).createQServerNodes( + objects, + ".", + "connLabel", + "tables", + ); + + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].label, "test1"); + assert.strictEqual(result[1].label, "test2"); + assert.strictEqual(result[0].coreIcon, "tables"); + assert.strictEqual(result[0].connLabel, "connLabel"); + }); + + it("should create QServerNodes with namespace prefix for non-root namespace", () => { + const objects = [{ name: "test1" }]; + + const result = (provider as any).createQServerNodes( + objects, + "myns", + "connLabel", + "functions", + ); + + assert.strictEqual(result[0].label, "myns.test1"); + }); + + it("should handle empty objects array", () => { + const result = (provider as any).createQServerNodes( + [], + ".", + "connLabel", + "variables", + ); + + assert.deepStrictEqual(result, []); + }); + + it("should set correct TreeItemCollapsibleState", () => { + const objects = [{ name: "test1" }]; + + const result = (provider as any).createQServerNodes( + objects, + ".", + "connLabel", + "dictionaries", + ); + + assert.strictEqual( + result[0].collapsibleState, + TreeItemCollapsibleState.None, + ); + }); + }); + }); }); describe("Code flow login service tests", () => { @@ -1361,7 +1907,7 @@ describe("connectionManagerService", () => { connMngService, "retrieveConnectedConnection", ); - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); showInformationMessageStub = sinon.stub(window, "showInformationMessage"); _showErrorMessageStub = sinon.stub(window, "showErrorMessage"); }); @@ -1375,7 +1921,7 @@ describe("connectionManagerService", () => { await connMngService.resetScratchpad(); sinon.assert.calledWith( kdbOutputLogStub, - "[RESET SCRATCHPAD] Please activate an Insights connection to use this feature.", + "[connectionManagerService] Please activate an Insights connection to use this feature.", "ERROR", ); }); @@ -1385,7 +1931,7 @@ describe("connectionManagerService", () => { await connMngService.resetScratchpad(); sinon.assert.calledWith( kdbOutputLogStub, - "[RESET SCRATCHPAD] Please activate an Insights connection to use this feature.", + "[connectionManagerService] Please activate an Insights connection to use this feature.", "ERROR", ); }); @@ -1413,8 +1959,8 @@ describe("connectionManagerService", () => { await connMngService.resetScratchpad("test"); sinon.assert.calledWith( kdbOutputLogStub, - "[RESET SCRATCHPAD] The user canceled the scratchpad reset.", - "INFO", + "[connectionManagerService] The user canceled the scratchpad reset.", + "DEBUG", ); }); @@ -1423,7 +1969,7 @@ describe("connectionManagerService", () => { await connMngService.resetScratchpad("test"); sinon.assert.calledWith( kdbOutputLogStub, - "[RESET SCRATCHPAD] Please connect to an Insights connection to use this feature.", + "[connectionManagerService] Please connect to an Insights connection to use this feature.", "ERROR", ); }); @@ -2274,7 +2820,7 @@ describe("MetaContentProvider", () => { metaContentProvider.provideTextDocumentContent(uri), content, ); - assert(spy.calledOnceWith(uri)); + assert.ok(spy.calledOnceWith(uri)); }); it("should provide text document content", () => { @@ -2288,255 +2834,294 @@ describe("MetaContentProvider", () => { }); describe("kdbTreeService", () => { - it("Should return empty ServerObjects array when none are loaded", async () => { - sinon.stub(serverCommand, "loadServerObjects").resolves(undefined); - const result = await KdbTreeService.loadNamespaces(""); - assert.strictEqual(result.length, 0, "Namespaces returned should be zero."); - sinon.restore(); - }); + const localConn = new LocalConnection("localhost:5001", "server1", []); - it("Should return a single server object that ia a namespace", async () => { - const testObject: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test1", - typeNum: 1, - namespace: ".", - context: {}, - isNs: true, - }, - { - id: 2, - pid: 2, - name: "test", - fname: "test2", - typeNum: 1, - namespace: ".", - context: {}, - isNs: true, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject); - const result = await KdbTreeService.loadNamespaces(); - assert.strictEqual( - result[0], - testObject[0], - "Single server object that is a namespace should be returned.", - ); - sinon.restore(); - }); + describe("loadNamespaces", () => { + afterEach(() => { + sinon.restore(); + }); - it("Should return a single server object that ia a namespace (reverse sort)", async () => { - const testObject0: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test", - typeNum: 1, - namespace: ".", - context: {}, - isNs: true, - }, - { - id: 0, - pid: 0, - name: "test", - fname: "test0", - typeNum: 1, - namespace: ".", - context: {}, - isNs: true, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject0); - const result = await KdbTreeService.loadNamespaces(); - assert.strictEqual( - result[0], - testObject0[0], - "Single server object that is a namespace should be returned.", - ); - sinon.restore(); - }); + it("Should return empty ServerObjects array when none are loaded", async () => { + sinon.stub(localConn, "loadServerObjects").resolves(undefined); + const result = await KdbTreeService.loadNamespaces(localConn, ""); + assert.strictEqual( + result.length, + 0, + "Namespaces returned should be zero.", + ); + }); - it("Should return a single server object that ia a namespace", async () => { - const testObject2: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test", - typeNum: 1, - namespace: ".", - context: {}, - isNs: true, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject2); - const result = await KdbTreeService.loadNamespaces("."); - assert.strictEqual( - result[0], - testObject2[0], - `Single server object that is a namespace should be returned: ${JSON.stringify( - result, - )}`, - ); - sinon.restore(); - }); + it("Should return a single server object that ia a namespace", async () => { + const testObject: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test1", + typeNum: 1, + namespace: ".", + context: {}, + isNs: true, + }, + { + id: 2, + pid: 2, + name: "test", + fname: "test2", + typeNum: 1, + namespace: ".", + context: {}, + isNs: true, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject); + const result = await KdbTreeService.loadNamespaces(localConn); + assert.strictEqual( + result[0], + testObject[0], + "Single server object that is a namespace should be returned.", + ); + }); - it("Should return empty ServerObjects array when none are loaded", async () => { - sinon.stub(serverCommand, "loadServerObjects").resolves(undefined); - const result = await KdbTreeService.loadDictionaries(""); - assert.strictEqual( - result.length, - 0, - "ServerObjects returned should be zero.", - ); - sinon.restore(); - }); + it("Should return a single server object that ia a namespace (reverse sort)", async () => { + const testObject0: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test", + typeNum: 1, + namespace: ".", + context: {}, + isNs: true, + }, + { + id: 0, + pid: 0, + name: "test", + fname: "test0", + typeNum: 1, + namespace: ".", + context: {}, + isNs: true, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject0); + const result = await KdbTreeService.loadNamespaces(localConn); + assert.strictEqual( + result[0], + testObject0[0], + "Single server object that is a namespace should be returned.", + ); + sinon.restore(); + }); - it("Should return a single server object that ia a dictionary", async () => { - const testObject: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test", - typeNum: 99, - namespace: ".", - context: {}, - isNs: false, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject); - const result = await KdbTreeService.loadDictionaries("."); - assert.strictEqual( - result[0], - testObject[0], - "Single server object that is a namespace should be returned.", - ); - sinon.restore(); + it("Should return a single server object that ia a namespace", async () => { + const testObject2: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test", + typeNum: 1, + namespace: ".", + context: {}, + isNs: true, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject2); + const result = await KdbTreeService.loadNamespaces(localConn, "."); + assert.strictEqual( + result[0], + testObject2[0], + `Single server object that is a namespace should be returned: ${JSON.stringify( + result, + )}`, + ); + sinon.restore(); + }); }); - it("Should return empty ServerObjects array when none are loaded", async () => { - sinon.stub(serverCommand, "loadServerObjects").resolves(undefined); - const result = await KdbTreeService.loadFunctions("."); - assert.strictEqual( - result.length, - 0, - "ServerObjects returned should be zero.", - ); - sinon.restore(); - }); + describe("loadDictionaries", () => { + afterEach(() => { + sinon.restore(); + }); - it("Should return a single server object that ia a function", async () => { - const testObject: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test", - typeNum: 100, - namespace: ".", - context: {}, - isNs: false, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject); - const result = await KdbTreeService.loadFunctions("."); - assert.strictEqual( - result[0], - testObject[0], - "Single server object that is a namespace should be returned.", - ); - sinon.restore(); - }); + it("Should return empty ServerObjects array when none are loaded", async () => { + sinon.stub(localConn, "loadServerObjects").resolves(undefined); + const result = await KdbTreeService.loadDictionaries(localConn, ""); + assert.strictEqual( + result.length, + 0, + "ServerObjects returned should be zero.", + ); + }); - it("Should return empty ServerObjects array when none are loaded", async () => { - sinon.stub(serverCommand, "loadServerObjects").resolves(undefined); - const result = await KdbTreeService.loadTables("."); - assert.strictEqual( - result.length, - 0, - "ServerObjects returned should be zero.", - ); - sinon.restore(); + it("Should return a single server object that ia a dictionary", async () => { + const testObject: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test", + typeNum: 99, + namespace: ".", + context: {}, + isNs: false, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject); + const result = await KdbTreeService.loadDictionaries(localConn, "."); + assert.strictEqual( + result[0], + testObject[0], + "Single server object that is a namespace should be returned.", + ); + }); }); - it("Should return a single server object that ia a table", async () => { - const testObject: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test", - typeNum: 98, - namespace: ".", - context: {}, - isNs: false, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject); - const result = await KdbTreeService.loadTables("."); - assert.strictEqual( - result[0], - testObject[0], - "Single server object that is a namespace should be returned.", - ); - sinon.restore(); - }); + describe("loadFunctions", () => { + afterEach(() => { + sinon.restore(); + }); - it("Should return empty ServerObjects array when none are loaded", async () => { - sinon.stub(serverCommand, "loadServerObjects").resolves(undefined); - const result = await KdbTreeService.loadVariables("."); - assert.strictEqual( - result.length, - 0, - "ServerObjects returned should be zero.", - ); - sinon.restore(); + it("Should return empty ServerObjects array when none are loaded", async () => { + sinon.stub(localConn, "loadServerObjects").resolves(undefined); + const result = await KdbTreeService.loadFunctions(localConn, "."); + assert.strictEqual( + result.length, + 0, + "ServerObjects returned should be zero.", + ); + }); + + it("Should return a single server object that ia a function", async () => { + const testObject: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test", + typeNum: 100, + namespace: ".", + context: {}, + isNs: false, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject); + const result = await KdbTreeService.loadFunctions(localConn, "."); + assert.strictEqual( + result[0], + testObject[0], + "Single server object that is a namespace should be returned.", + ); + }); }); - it("Should return a single server object that ia a variable", async () => { - const testObject: ServerObject[] = [ - { - id: 1, - pid: 1, - name: "test", - fname: "test", - typeNum: -7, - namespace: ".", - context: {}, - isNs: false, - }, - ]; - sinon.stub(serverCommand, "loadServerObjects").resolves(testObject); - sinon.stub(KdbTreeService, "loadViews").resolves([]); - const result = await KdbTreeService.loadVariables("."); - assert.strictEqual( - result[0], - testObject[0], - "Single server object that is a namespace should be returned.", - ); - sinon.restore(); + describe("loadTables", () => { + afterEach(() => { + sinon.restore(); + }); + + it("Should return empty ServerObjects array when none are loaded", async () => { + sinon.stub(localConn, "loadServerObjects").resolves(undefined); + const result = await KdbTreeService.loadTables(localConn, "."); + assert.strictEqual( + result.length, + 0, + "ServerObjects returned should be zero.", + ); + }); + + it("Should return a single server object that ia a table", async () => { + const testObject: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test", + typeNum: 98, + namespace: ".", + context: {}, + isNs: false, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject); + const result = await KdbTreeService.loadTables(localConn, "."); + assert.strictEqual( + result[0], + testObject[0], + "Single server object that is a namespace should be returned.", + ); + }); }); - it("Should return sorted views", async () => { - ext.activeConnection = new LocalConnection("localhost:5001", "server1", []); - sinon.stub(ext.activeConnection, "executeQuery").resolves(["vw1", "vw2"]); - const result = await KdbTreeService.loadViews(); - assert.strictEqual(result[0], "vw1", "Should return the first view"); - sinon.restore(); + describe("loadVariables", () => { + afterEach(() => { + sinon.restore(); + }); + + it("Should return empty ServerObjects array when none are loaded", async () => { + sinon.stub(localConn, "loadServerObjects").resolves(undefined); + sinon.stub(KdbTreeService, "loadViews").resolves([]); + const result = await KdbTreeService.loadVariables(localConn, "."); + assert.strictEqual( + result.length, + 0, + "ServerObjects returned should be zero.", + ); + }); + + it("Should return a single server object that ia a variable", async () => { + const testObject: ServerObject[] = [ + { + id: 1, + pid: 1, + name: "test", + fname: "test", + typeNum: -7, + namespace: ".", + context: {}, + isNs: false, + }, + ]; + sinon.stub(localConn, "loadServerObjects").resolves(testObject); + sinon.stub(KdbTreeService, "loadViews").resolves([]); + const result = await KdbTreeService.loadVariables(localConn, "."); + assert.strictEqual( + result[0], + testObject[0], + "Single server object that is a namespace should be returned.", + ); + }); }); - it("Should return sorted views (reverse order)", async () => { - ext.activeConnection = new LocalConnection("localhost:5001", "server1", []); - sinon.stub(ext.activeConnection, "executeQuery").resolves(["vw1", "vw2"]); - const result = await KdbTreeService.loadViews(); - assert.strictEqual(result[0], "vw1", "Should return the first view"); - sinon.restore(); + describe("loadViews", () => { + afterEach(() => { + sinon.restore(); + }); + + it("Should return sorted views", async () => { + ext.activeConnection = new LocalConnection( + "localhost:5001", + "server1", + [], + ); + sinon.stub(localConn, "executeQueryRaw").resolves(["vw1", "vw2"]); + const result = await KdbTreeService.loadViews(localConn); + assert.strictEqual(result[0], "vw1", "Should return the first view"); + }); + + it("Should return sorted views (reverse order)", async () => { + ext.activeConnection = new LocalConnection( + "localhost:5001", + "server1", + [], + ); + sinon.stub(localConn, "executeQueryRaw").resolves(["vw1", "vw2"]); + const result = await KdbTreeService.loadViews(localConn); + assert.strictEqual(result[0], "vw1", "Should return the first view"); + }); }); }); @@ -2550,7 +3135,7 @@ describe("HelpFeedbackProvider", () => { it("should return all help items in getChildren", () => { const children = provider.getChildren(); assert.strictEqual(children.length, 4); - assert(children[0] instanceof TreeItem); + assert.ok(children[0] instanceof TreeItem); assert.strictEqual(children[0].label, "Extension Documentation"); assert.strictEqual(children[1].label, "Suggest a Feature"); assert.strictEqual(children[2].label, "Provide Feedback"); diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index aa37c4c9f..62b728d30 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -13,6 +13,7 @@ import * as assert from "assert"; import mock from "mock-fs"; +import path from "node:path"; import { env } from "node:process"; import * as sinon from "sinon"; import * as vscode from "vscode"; @@ -55,10 +56,13 @@ import * as executionConsoleUtils from "../../src/utils/executionConsole"; import { feedbackSurveyDialog } from "../../src/utils/feedbackSurveyUtils"; import { getNonce } from "../../src/utils/getNonce"; import { getUri } from "../../src/utils/getUri"; +import * as loggers from "../../src/utils/loggers"; +import * as notifications from "../../src/utils/notifications"; import { openUrl } from "../../src/utils/openUrl"; import * as queryUtils from "../../src/utils/queryUtils"; import { showRegistrationNotification } from "../../src/utils/registration"; -import * as shared from "../../src/utils/shared"; +import * as sharedUtils from "../../src/utils/shared"; +import * as shell from "../../src/utils/shell"; import { killPid } from "../../src/utils/shell"; import * as UDAUtils from "../../src/utils/uda"; import { @@ -92,7 +96,7 @@ describe("Utils", () => { let kdbOutputLogStub: sinon.SinonStub; beforeEach(() => { tryExecuteCommandStub = sinon.stub(cpUtils, "tryExecuteCommand"); - kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + kdbOutputLogStub = sinon.stub(loggers, "kdbOutputLog"); }); afterEach(() => { @@ -117,6 +121,7 @@ describe("Utils", () => { assert.strictEqual(result, null); }); }); + describe("setOutputWordWrapper", () => { let getConfigurationStub: sinon.SinonStub; beforeEach(() => { @@ -142,46 +147,6 @@ describe("Utils", () => { }); }); - describe("getHideDetailedConsoleQueryOutput", () => { - let getConfigurationStub: sinon.SinonStub; - - beforeEach(() => { - getConfigurationStub = sinon.stub( - vscode.workspace, - "getConfiguration", - ) as sinon.SinonStub; - }); - - afterEach(() => { - getConfigurationStub.restore(); - sinon.restore(); - }); - - it("should update configuration and set hideDetailedConsoleQueryOutput to true when setting is undefined", async () => { - getConfigurationStub.returns({ - get: sinon.stub().returns(undefined), - update: sinon.stub(), - }); - - await coreUtils.getHideDetailedConsoleQueryOutput(); - - sinon.assert.calledTwice(getConfigurationStub); - assert.strictEqual(ext.hideDetailedConsoleQueryOutput, true); - }); - - it("should set hideDetailedConsoleQueryOutput to setting when setting is defined", async () => { - getConfigurationStub.returns({ - get: sinon.stub().returns(false), - update: sinon.stub(), - }); - - await coreUtils.getHideDetailedConsoleQueryOutput(); - - sinon.assert.calledOnce(getConfigurationStub); - assert.strictEqual(ext.hideDetailedConsoleQueryOutput, false); - }); - }); - describe("server alias", () => { beforeEach(() => { ext.kdbConnectionAliasList.length = 0; @@ -319,7 +284,7 @@ describe("Utils", () => { const message = "test message"; const type = "INFO"; - coreUtils.kdbOutputLog(message, type); + loggers.kdbOutputLog(message, type); appendLineSpy.calledOnce; appendLineSpy.calledWithMatch(message); @@ -330,7 +295,7 @@ describe("Utils", () => { const message = "test message"; const type = "ERROR"; - coreUtils.kdbOutputLog(message, type); + loggers.kdbOutputLog(message, type); appendLineSpy.calledOnce; showErrorMessageSpy.calledOnce; @@ -394,6 +359,660 @@ describe("Utils", () => { assert.ok(stub.calledOnce); }); }); + + describe("getServers", () => { + let workspaceStub: sinon.SinonStub; + let _getConfigurationStub: sinon.SinonStub; + let getStub: sinon.SinonStub; + + beforeEach(() => { + getStub = sinon.stub(); + _getConfigurationStub = sinon.stub().returns({ get: getStub }); + workspaceStub = sinon + .stub(vscode.workspace, "getConfiguration") + .returns({ + get: getStub, + has: function (_section: string): boolean { + throw new Error("Function not implemented."); + }, + inspect: function (_section: string): + | { + key: string; + defaultValue?: T; + globalValue?: T; + workspaceValue?: T; + workspaceFolderValue?: T; + defaultLanguageValue?: T; + globalLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; + languageIds?: string[]; + } + | undefined { + throw new Error("Function not implemented."); + }, + update: function ( + _section: string, + _value: any, + _configurationTarget?: + | vscode.ConfigurationTarget + | boolean + | null, + _overrideInLanguage?: boolean, + ): Thenable { + throw new Error("Function not implemented."); + }, + }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should return undefined when no servers are configured", () => { + getStub.returns(undefined); + + const result = coreUtils.getServers(); + + assert.strictEqual(result, undefined); + assert.ok(getStub.calledWith("kdb.servers")); + }); + + it("should return servers sorted alphabetically by serverAlias", () => { + const mockServers = { + server3: { + serverName: "localhost", + serverPort: "5003", + serverAlias: "Charlie Server", + auth: false, + managed: false, + tls: false, + }, + server1: { + serverName: "localhost", + serverPort: "5001", + serverAlias: "Alpha Server", + auth: false, + managed: false, + tls: false, + }, + server2: { + serverName: "localhost", + serverPort: "5002", + serverAlias: "Beta Server", + auth: true, + managed: true, + tls: true, + }, + }; + + getStub.returns(mockServers); + + const result = coreUtils.getServers(); + + assert.ok(result); + const serverKeys = Object.keys(result); + const serverAliases = serverKeys.map((key) => result[key].serverAlias); + + assert.deepStrictEqual(serverAliases, [ + "Alpha Server", + "Beta Server", + "Charlie Server", + ]); + + assert.strictEqual(result[serverKeys[0]].serverName, "localhost"); + assert.strictEqual(result[serverKeys[0]].serverPort, "5001"); + assert.strictEqual(result[serverKeys[0]].auth, false); + assert.strictEqual(result[serverKeys[1]].managed, true); + assert.strictEqual(result[serverKeys[2]].tls, false); + }); + + it("should handle single server correctly", () => { + const mockServers = { + onlyServer: { + serverName: "remote-host", + serverPort: "6000", + serverAlias: "Single Server", + auth: true, + managed: false, + tls: true, + }, + }; + + getStub.returns(mockServers); + + const result = coreUtils.getServers(); + + assert.ok(result); + const serverKeys = Object.keys(result); + assert.strictEqual(serverKeys.length, 1); + assert.strictEqual(result[serverKeys[0]].serverAlias, "Single Server"); + assert.strictEqual(result[serverKeys[0]].serverName, "remote-host"); + }); + + it("should handle servers with identical serverAlias", () => { + const mockServers = { + server1: { + serverName: "host1", + serverPort: "5001", + serverAlias: "Same Name", + auth: false, + managed: false, + tls: false, + }, + server2: { + serverName: "host2", + serverPort: "5002", + serverAlias: "Same Name", + auth: false, + managed: false, + tls: false, + }, + }; + + getStub.returns(mockServers); + + const result = coreUtils.getServers(); + + assert.ok(result); + const serverKeys = Object.keys(result); + assert.strictEqual(serverKeys.length, 2); + + serverKeys.forEach((key) => { + assert.strictEqual(result[key].serverAlias, "Same Name"); + }); + }); + + it("should handle empty servers object", () => { + const mockServers = {}; + + getStub.returns(mockServers); + + const result = coreUtils.getServers(); + + assert.ok(result); + assert.strictEqual(Object.keys(result).length, 0); + }); + + it("should handle servers with special characters in serverAlias", () => { + const mockServers = { + server1: { + serverName: "localhost", + serverPort: "5001", + serverAlias: "!Special Server", + auth: false, + managed: false, + tls: false, + }, + server2: { + serverName: "localhost", + serverPort: "5002", + serverAlias: "123 Numeric Server", + auth: false, + managed: false, + tls: false, + }, + server3: { + serverName: "localhost", + serverPort: "5003", + serverAlias: "Åccented Server", + auth: false, + managed: false, + tls: false, + }, + }; + + getStub.returns(mockServers); + + const result = coreUtils.getServers(); + + assert.ok(result); + const serverKeys = Object.keys(result); + const serverAliases = serverKeys.map((key) => result[key].serverAlias); + + assert.strictEqual(serverAliases[0], "!Special Server"); + assert.strictEqual(serverAliases[1], "123 Numeric Server"); + assert.strictEqual(serverAliases[2], "Åccented Server"); + }); + + it("should call workspace.getConfiguration without parameters", () => { + getStub.returns(undefined); + + coreUtils.getServers(); + + assert.ok(workspaceStub.calledOnce); + assert.ok(workspaceStub.calledWith()); + }); + + it("should call get method with correct parameter", () => { + getStub.returns(undefined); + + coreUtils.getServers(); + + assert.ok(getStub.calledOnce); + assert.ok(getStub.calledWith("kdb.servers")); + }); + }); + + describe("getInsights", () => { + let workspaceStub: sinon.SinonStub; + let _getConfigurationStub: sinon.SinonStub; + let getStub: sinon.SinonStub; + + beforeEach(() => { + getStub = sinon.stub(); + _getConfigurationStub = sinon.stub().returns({ get: getStub }); + workspaceStub = sinon + .stub(vscode.workspace, "getConfiguration") + .returns({ + get: getStub, + has: function (_section: string): boolean { + throw new Error("Function not implemented."); + }, + inspect: function (_section: string): + | { + key: string; + defaultValue?: T; + globalValue?: T; + workspaceValue?: T; + workspaceFolderValue?: T; + defaultLanguageValue?: T; + globalLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; + languageIds?: string[]; + } + | undefined { + throw new Error("Function not implemented."); + }, + update: function ( + _section: string, + _value: any, + _configurationTarget?: + | vscode.ConfigurationTarget + | boolean + | null, + _overrideInLanguage?: boolean, + ): Thenable { + throw new Error("Function not implemented."); + }, + }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should return undefined when no insights are configured", () => { + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(undefined); + getStub.withArgs("kdb.insights").returns(undefined); + + const result = coreUtils.getInsights(); + + assert.strictEqual(result, undefined); + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(getStub.calledWith("kdb.insights")); + }); + + it("should return insightsEnterpriseConnections when available and not empty", () => { + const mockInsights = { + insight3: { + alias: "Charlie Insight", + server: "https://charlie.insights.com", + auth: true, + realm: "charlie-realm", + insecure: false, + }, + insight1: { + alias: "Alpha Insight", + server: "https://alpha.insights.com", + auth: false, + insecure: true, + }, + insight2: { + alias: "Beta Insight", + server: "https://beta.insights.com", + auth: true, + realm: "beta-realm", + insecure: false, + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(mockInsights); + getStub.withArgs("kdb.insights").returns(undefined); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + const insightAliases = insightKeys.map((key) => result[key].alias); + + assert.deepStrictEqual(insightAliases, [ + "Alpha Insight", + "Beta Insight", + "Charlie Insight", + ]); + + assert.strictEqual( + result[insightKeys[0]].server, + "https://alpha.insights.com", + ); + assert.strictEqual(result[insightKeys[0]].auth, false); + assert.strictEqual(result[insightKeys[1]].realm, "beta-realm"); + assert.strictEqual(result[insightKeys[2]].insecure, false); + + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(!getStub.calledWith("kdb.insights")); + }); + + it("should fallback to kdb.insights when insightsEnterpriseConnections is empty", () => { + const mockFallbackInsights = { + fallback2: { + alias: "Fallback Beta", + server: "https://fallback-beta.com", + auth: true, + }, + fallback1: { + alias: "Fallback Alpha", + server: "https://fallback-alpha.com", + auth: false, + }, + }; + + getStub.withArgs("kdb.insightsEnterpriseConnections").returns({}); + getStub.withArgs("kdb.insights").returns(mockFallbackInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + const insightAliases = insightKeys.map((key) => result[key].alias); + + assert.deepStrictEqual(insightAliases, [ + "Fallback Alpha", + "Fallback Beta", + ]); + + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(getStub.calledWith("kdb.insights")); + }); + + it("should fallback to kdb.insights when insightsEnterpriseConnections is undefined", () => { + const mockFallbackInsights = { + single: { + alias: "Single Fallback", + server: "https://single-fallback.com", + auth: true, + realm: "single-realm", + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(undefined); + getStub.withArgs("kdb.insights").returns(mockFallbackInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + assert.strictEqual(insightKeys.length, 1); + assert.strictEqual(result[insightKeys[0]].alias, "Single Fallback"); + + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(getStub.calledWith("kdb.insights")); + }); + + it("should return undefined when both sources are empty", () => { + getStub.withArgs("kdb.insightsEnterpriseConnections").returns({}); + getStub.withArgs("kdb.insights").returns({}); + + const result = coreUtils.getInsights(); + + assert.strictEqual(result, undefined); + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(getStub.calledWith("kdb.insights")); + }); + + it("should handle single insight correctly", () => { + const mockInsights = { + onlyInsight: { + alias: "Only Insight", + server: "https://only.insights.com", + auth: true, + realm: "only-realm", + insecure: false, + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(mockInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + assert.strictEqual(insightKeys.length, 1); + assert.strictEqual(result[insightKeys[0]].alias, "Only Insight"); + assert.strictEqual( + result[insightKeys[0]].server, + "https://only.insights.com", + ); + }); + + it("should handle insights with identical aliases", () => { + const mockInsights = { + insight1: { + alias: "Same Name", + server: "https://server1.com", + auth: false, + }, + insight2: { + alias: "Same Name", + server: "https://server2.com", + auth: true, + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(mockInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + assert.strictEqual(insightKeys.length, 2); + + insightKeys.forEach((key) => { + assert.strictEqual(result[key].alias, "Same Name"); + }); + }); + + it("should handle insights with special characters in alias", () => { + const mockInsights = { + insight1: { + alias: "!Special Insight", + server: "https://special.com", + auth: false, + }, + insight2: { + alias: "123 Numeric Insight", + server: "https://numeric.com", + auth: false, + }, + insight3: { + alias: "Åccented Insight", + server: "https://accented.com", + auth: false, + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(mockInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + const insightAliases = insightKeys.map((key) => result[key].alias); + + assert.strictEqual(insightAliases[0], "!Special Insight"); + assert.strictEqual(insightAliases[1], "123 Numeric Insight"); + assert.strictEqual(insightAliases[2], "Åccented Insight"); + }); + + it("should prefer insightsEnterpriseConnections over fallback when both exist", () => { + const mockPrimaryInsights = { + primary: { + alias: "Primary Insight", + server: "https://primary.com", + auth: true, + }, + }; + + const mockFallbackInsights = { + fallback: { + alias: "Fallback Insight", + server: "https://fallback.com", + auth: false, + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(mockPrimaryInsights); + getStub.withArgs("kdb.insights").returns(mockFallbackInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + assert.strictEqual(insightKeys.length, 1); + assert.strictEqual(result[insightKeys[0]].alias, "Primary Insight"); + assert.strictEqual( + result[insightKeys[0]].server, + "https://primary.com", + ); + + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(!getStub.calledWith("kdb.insights")); + }); + + it("should call workspace.getConfiguration without parameters", () => { + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(undefined); + getStub.withArgs("kdb.insights").returns(undefined); + + coreUtils.getInsights(); + + assert.ok(workspaceStub.calledOnce); + assert.ok(workspaceStub.calledWith()); + }); + + it("should call get method with correct parameters", () => { + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(undefined); + getStub.withArgs("kdb.insights").returns(undefined); + + coreUtils.getInsights(); + + assert.ok(getStub.calledWith("kdb.insightsEnterpriseConnections")); + assert.ok(getStub.calledWith("kdb.insights")); + }); + + it("should handle insights with optional properties", () => { + const mockInsights = { + insight1: { + alias: "Basic Insight", + server: "https://basic.com", + auth: false, + }, + insight2: { + alias: "Full Insight", + server: "https://full.com", + auth: true, + realm: "full-realm", + insecure: true, + }, + }; + + getStub + .withArgs("kdb.insightsEnterpriseConnections") + .returns(mockInsights); + + const result = coreUtils.getInsights(); + + assert.ok(result); + const insightKeys = Object.keys(result); + assert.strictEqual(insightKeys.length, 2); + + const basicInsight = Object.values(result).find( + (i) => i.alias === "Basic Insight", + ); + const fullInsight = Object.values(result).find( + (i) => i.alias === "Full Insight", + ); + + assert.ok(basicInsight); + assert.strictEqual(basicInsight.realm, undefined); + assert.strictEqual(basicInsight.insecure, undefined); + + assert.ok(fullInsight); + assert.strictEqual(fullInsight.realm, "full-realm"); + assert.strictEqual(fullInsight.insecure, true); + }); + }); + + describe("getQExecutablePath", () => { + afterEach(() => { + sinon.restore(); + }); + it("should return KDB+", () => { + ext.REAL_QHOME = "QHOME"; + sinon.stub(shell, "stat").returns(false); + const res = coreUtils.getQExecutablePath(); + assert.ok(res); + }); + it("should return KDB-X", () => { + ext.REAL_QHOME = "QHOME"; + sinon.stub(shell, "stat").returns(true); + const res = coreUtils.getQExecutablePath(); + assert.strictEqual(res, path.join("QHOME", "bin", "q")); + }); + it("should return KDB-X", () => { + ext.REAL_QHOME = ""; + const target = path.join("QHOME", "bin", "q"); + sinon.stub(shell, "which").returns([target]); + const res = coreUtils.getQExecutablePath(); + assert.strictEqual(res, target); + }); + it("should return qHomeDirectory", () => { + ext.REAL_QHOME = ""; + sinon.stub(shell, "which").throws(); + sinon.stub(vscode.workspace, "getConfiguration").value(() => { + return { get: () => "QHOME" }; + }); + const res = coreUtils.getQExecutablePath(); + assert.ok(res); + }); + it("should throw if q not found", () => { + ext.REAL_QHOME = ""; + sinon.stub(shell, "which").throws(); + sinon.stub(vscode.workspace, "getConfiguration").value(() => { + return { get: () => "" }; + }); + assert.throws(() => coreUtils.getQExecutablePath()); + }); + }); }); describe("dataSource", () => { @@ -903,81 +1522,6 @@ describe("Utils", () => { ); }); }); - - it("addQueryHistory", () => { - const query = "SELECT * FROM table"; - const connectionName = "test"; - const connectionType = ServerType.KDB; - - ext.kdbQueryHistoryList.length = 0; - - queryUtils.addQueryHistory( - query, - "fileName", - connectionName, - connectionType, - true, - ); - assert.strictEqual(ext.kdbQueryHistoryList.length, 1); - }); - - it("addQueryHistory in python", () => { - const query = "SELECT * FROM table"; - const connectionName = "test"; - const connectionType = ServerType.KDB; - - ext.kdbQueryHistoryList.length = 0; - - queryUtils.addQueryHistory( - query, - connectionName, - "fileName", - connectionType, - true, - true, - ); - assert.strictEqual(ext.kdbQueryHistoryList.length, 1); - }); - - it("should format a Scratchpad stacktrace correctly", () => { - const stacktrace = [ - { name: "g", isNested: false, text: ["{a:x*2;a", "+y}"] }, - { name: "f", isNested: false, text: ["{", "g[x;2#y]}"] }, - { name: "", isNested: false, text: ["", 'f[3;"hello"]'] }, - ]; - - const formatted = queryUtils.formatScratchpadStacktrace(stacktrace); - assert.strictEqual( - formatted, - '[2] g{a:x*2;a+y}\n ^\n[1] f{g[x;2#y]}\n ^\n[0] f[3;"hello"]\n ^', - ); - }); - - it("should format a Scratchpad stacktrace with nested function correctly", () => { - const stacktrace = [ - { name: "f", isNested: true, text: ["{a:x*2;a", "+y}"] }, - { name: "f", isNested: false, text: ["{", "{a:x*2;a+y}[x;2#y]}"] }, - { name: "", isNested: false, text: ["", 'f[3;"hello"]'] }, - ]; - - const formatted = queryUtils.formatScratchpadStacktrace(stacktrace); - assert.strictEqual( - formatted, - '[2] f @ {a:x*2;a+y}\n ^\n[1] f{{a:x*2;a+y}[x;2#y]}\n ^\n[0] f[3;"hello"]\n ^', - ); - }); - }); - - describe("selectDSType", () => { - it("should return correct DataSourceTypes for given input", function () { - assert.equal(queryUtils.selectDSType("API"), DataSourceTypes.API); - assert.equal(queryUtils.selectDSType("QSQL"), DataSourceTypes.QSQL); - assert.equal(queryUtils.selectDSType("SQL"), DataSourceTypes.SQL); - }); - - it("should return undefined for unknown input", function () { - assert.equal(queryUtils.selectDSType("unknown"), undefined); - }); }); describe("getNonce", () => { @@ -1083,28 +1627,6 @@ describe("Utils", () => { }); }); - describe("generateQSqlBody", () => { - it("should use scope for 1.13", () => { - const output = queryUtils.generateQSqlBody( - "a:1", - "assembly target", - 1.13, - ); - assert.equal(output.scope.assembly, "assembly"); - assert.equal(output.scope.tier, "target"); - }); - - it("should use legacy syntax for 1.12", () => { - const output = queryUtils.generateQSqlBody( - "a:1", - "assembly target", - 1.12, - ); - assert.equal(output.assembly, "assembly"); - assert.equal(output.target, "target"); - }); - }); - describe("handleWSResults", () => { afterEach(() => { sinon.restore(); @@ -1388,6 +1910,248 @@ describe("Utils", () => { }); }); }); + + describe("addQueryHistory", () => { + it("addQueryHistory", () => { + const query = "SELECT * FROM table"; + const connectionName = "test"; + const connectionType = ServerType.KDB; + + ext.kdbQueryHistoryList.length = 0; + + queryUtils.addQueryHistory( + query, + "fileName", + connectionName, + connectionType, + true, + ); + assert.strictEqual(ext.kdbQueryHistoryList.length, 1); + }); + + it("addQueryHistory in python", () => { + const query = "SELECT * FROM table"; + const connectionName = "test"; + const connectionType = ServerType.KDB; + + ext.kdbQueryHistoryList.length = 0; + + queryUtils.addQueryHistory( + query, + connectionName, + "fileName", + connectionType, + true, + true, + ); + assert.strictEqual(ext.kdbQueryHistoryList.length, 1); + }); + }); + + describe("formatScratchpadStacktrace", () => { + it("should format a Scratchpad stacktrace correctly", () => { + const stacktrace = [ + { name: "g", isNested: false, text: ["{a:x*2;a", "+y}"] }, + { name: "f", isNested: false, text: ["{", "g[x;2#y]}"] }, + { name: "", isNested: false, text: ["", 'f[3;"hello"]'] }, + ]; + + const formatted = queryUtils.formatScratchpadStacktrace(stacktrace); + assert.strictEqual( + formatted, + '[2] g{a:x*2;a+y}\n ^\n[1] f{g[x;2#y]}\n ^\n[0] f[3;"hello"]\n ^', + ); + }); + + it("should format a Scratchpad stacktrace with nested function correctly", () => { + const stacktrace = [ + { name: "f", isNested: true, text: ["{a:x*2;a", "+y}"] }, + { name: "f", isNested: false, text: ["{", "{a:x*2;a+y}[x;2#y]}"] }, + { name: "", isNested: false, text: ["", 'f[3;"hello"]'] }, + ]; + + const formatted = queryUtils.formatScratchpadStacktrace(stacktrace); + assert.strictEqual( + formatted, + '[2] f @ {a:x*2;a+y}\n ^\n[1] f{{a:x*2;a+y}[x;2#y]}\n ^\n[0] f[3;"hello"]\n ^', + ); + }); + }); + + describe("selectDSType", () => { + it("should return correct DataSourceTypes for given input", function () { + assert.equal(queryUtils.selectDSType("API"), DataSourceTypes.API); + assert.equal(queryUtils.selectDSType("QSQL"), DataSourceTypes.QSQL); + assert.equal(queryUtils.selectDSType("SQL"), DataSourceTypes.SQL); + }); + + it("should return undefined for unknown input", function () { + assert.equal(queryUtils.selectDSType("unknown"), undefined); + }); + }); + + describe("normalizeQSQLQuery", () => { + it("should trim query", () => { + const res = queryUtils.normalizeQSQLQuery(" a:1 "); + assert.strictEqual(res, "a:1"); + }); + it("should remove block comment", () => { + let res = queryUtils.normalizeQSQLQuery("/\nBlock Comment\n\\\na:1"); + assert.strictEqual(res, "a:1"); + res = queryUtils.normalizeQSQLQuery("/\r\nBlock Comment\r\n\\\r\na:1"); + assert.strictEqual(res, "a:1"); + }); + it("should remove single line comment", () => { + let res = queryUtils.normalizeQSQLQuery("/ single line comment\na:1"); + assert.strictEqual(res, "a:1"); + res = queryUtils.normalizeQSQLQuery("/ single line comment\r\na:1"); + assert.strictEqual(res, "a:1"); + }); + it("should remove line comment", () => { + const res = queryUtils.normalizeQSQLQuery("a:1 / line comment"); + assert.strictEqual(res, "a:1"); + }); + it("should ignore line comment in a string", () => { + const res = queryUtils.normalizeQSQLQuery('a:"1 / not line comment"'); + assert.strictEqual(res, 'a:"1 / not line comment"'); + }); + it("should replace EOS with semicolon", () => { + let res = queryUtils.normalizeQSQLQuery("a:1\na"); + assert.strictEqual(res, "a:1;a"); + res = queryUtils.normalizeQSQLQuery("a:1\r\na"); + assert.strictEqual(res, "a:1;a"); + }); + it("should escpae new lines in strings", () => { + let res = queryUtils.normalizeQSQLQuery('a:"a\n \nb"'); + assert.strictEqual(res, 'a:"a\\n \\nb"'); + res = queryUtils.normalizeQSQLQuery('a:"a\r\n \r\nb"'); + assert.strictEqual(res, 'a:"a\\n \\nb"'); + }); + }); + + describe("resultToBase64", () => { + const png = [ + "0x89", + "0x50", + "0x4e", + "0x47", + "0x0d", + "0x0a", + "0x1a", + "0x0a", + ]; + const img = Array.from({ length: 59 }, () => "0x00"); + + it("should return undefined for undefined", () => { + const result = queryUtils.resultToBase64(undefined); + assert.strictEqual(result, undefined); + }); + it("should return undefined for just signature", () => { + const result = queryUtils.resultToBase64(png); + assert.strictEqual(result, undefined); + }); + it("should return undefined for bad signature", () => { + const result = queryUtils.resultToBase64([ + ...png.map((v) => parseInt(v, 16) + 1), + ...img, + ]); + assert.strictEqual(result, undefined); + }); + it("should return base64 for minimum img str", () => { + const result = queryUtils.resultToBase64([...png, ...img]); + assert.ok(result); + }); + it("should return base64 for minimum img num", () => { + const result = queryUtils.resultToBase64([ + ...png.map((v) => parseInt(v, 16)), + ...img.map((v) => parseInt(v, 16)), + ]); + assert.ok(result); + }); + it("should return base64 for minimum img str for structuredText", () => { + const result = queryUtils.resultToBase64({ + columns: { values: [...png, ...img] }, + }); + assert.ok(result); + }); + it("should return base64 for minimum img str for structuredText v2", () => { + const result = queryUtils.resultToBase64({ + columns: [{ values: [...png, ...img] }], + }); + assert.ok(result); + }); + it("should return undefined for bogus structuredText", () => { + const result = queryUtils.resultToBase64({ + columns: {}, + }); + assert.strictEqual(result, undefined); + }); + it("should return undefined for bogus structuredText v2", () => { + const result = queryUtils.resultToBase64({ + columns: [], + }); + assert.strictEqual(result, undefined); + }); + it("should return base64 from windows q server", () => { + const result = queryUtils.resultToBase64([ + ...png.map((v) => `${v}\r`), + ...img.map((v) => `${v}\r`), + ]); + assert.ok(result); + }); + }); + + describe("normalizeQuery", () => { + it("should return normalized query under query limit", () => { + const query = "1234567890".repeat(25000); + const res = queryUtils.normalizeQuery(query); + assert.strictEqual(res, query); + }); + it("should throw when limit reached", () => { + const query = "1234567890".repeat(25000) + "1"; + assert.throws(() => queryUtils.normalizeQuery(query)); + }); + }); + + describe("normalizePyQuery", () => { + it("should escape double quotes", () => { + const res = queryUtils.normalizePyQuery('a="test"'); + assert.strictEqual(res, 'a=\\"test\\"'); + }); + }); + describe("getQSQLWrapper", () => { + let queryWrappeStub: sinon.SinonStub; + + beforeEach(() => { + //queryWrappeStub = sinon.stub(queryUtils, "queryWrapper"); + }); + + it("should normalize q code", () => { + const res = queryUtils.getQSQLWrapper("a:1;\na"); + assert.strictEqual(res, "a:1;;a"); + }); + it("should normalize python code using wrapper", () => { + assert.throws(() => { + queryUtils.getQSQLWrapper(``, true); + sinon.assert.calledOnce(queryWrappeStub); + }); + }); + }); + + describe("needsScratchpad", () => { + it("should return the promise", async () => { + const res = await queryUtils.needsScratchpad( + "test", + Promise.resolve("test"), + ); + assert.strictEqual(res, "test"); + }); + it("should reset scratchpad started status", async () => { + ext.scratchpadStarted.add("test"); + queryUtils.resetScratchpadStarted("test"); + assert.strictEqual(ext.scratchpadStarted.has("test"), false); + }); + }); }); describe("Registration", () => { @@ -1753,16 +2517,40 @@ describe("Utils", () => { assert.strictEqual(ext.connLabelList[0].color.name, "Red"); }); - it("should handle empty label name", () => { + it("should handle empty label name", () => { + getConfigurationStub.returns({ + get: sinon.stub(), + update: sinon.stub(), + }); + const logStub = sinon.stub(loggers, "kdbOutputLog"); + + LabelsUtils.createNewLabel("", "red"); + + sinon.assert.calledWith( + logStub, + "[connLabel] Label name can't be empty.", + "ERROR", + ); + }); + + it("should handle with same other label name", () => { getConfigurationStub.returns({ - get: sinon.stub(), + get: sinon.stub().returns([ + { + name: "testeLabel1", + color: { name: "red", colorHex: "#FF0000" }, + }, + ]), update: sinon.stub(), }); - const logStub = sinon.stub(coreUtils, "kdbOutputLog"); - - LabelsUtils.createNewLabel("", "red"); + const logStub = sinon.stub(loggers, "kdbOutputLog"); + LabelsUtils.createNewLabel("testeLabel1", "red"); - sinon.assert.calledWith(logStub, "Label name can't be empty", "ERROR"); + sinon.assert.calledWith( + logStub, + "[connLabel] Label with this name already exists.", + "ERROR", + ); }); it("should handle no color selected", () => { @@ -1770,13 +2558,13 @@ describe("Utils", () => { get: sinon.stub(), update: sinon.stub(), }); - const logStub = sinon.stub(coreUtils, "kdbOutputLog"); + const logStub = sinon.stub(loggers, "kdbOutputLog"); LabelsUtils.createNewLabel("label1", "randomColorName"); sinon.assert.calledWith( logStub, - "No Color selected for the label", + "[connLabel] No Color selected for the label.", "ERROR", ); }); @@ -1870,13 +2658,53 @@ describe("Utils", () => { const labels: Labels[] = [ { name: "label1", color: { name: "red", colorHex: "#FF0000" } }, ]; + + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns(labels); + getStub.withArgs("kdb.labelsConnectionMap").returns([]); + getConfigurationStub.returns({ - get: sinon.stub().returns(labels), + get: getStub, update: sinon.stub().returns(Promise.resolve()), }); + LabelsUtils.renameLabel("label1", "label2"); + assert.strictEqual(ext.connLabelList.length, 1); - assert.deepStrictEqual(ext.connLabelList[0].name, "label2"); + assert.strictEqual(ext.connLabelList[0].name, "label2"); + }); + + it("should not rename a label if the name is the same of other label", () => { + getConfigurationStub.returns({ + get: sinon.stub().returns([ + { + name: "label2", + color: { name: "red", colorHex: "#FF0000" }, + }, + ]), + update: sinon.stub().returns(Promise.resolve()), + }); + + const logStub = sinon.stub(loggers, "kdbOutputLog"); + LabelsUtils.renameLabel("label1", "label2"); + sinon.assert.calledWith( + logStub, + "[connLabel] Label with this name already exists.", + "ERROR", + ); + }); + + it("should not rename a label if the name is empty or the same of original label name", () => { + getConfigurationStub.returns({ + get: sinon.stub(), + update: sinon.stub().returns(Promise.resolve()), + }); + + LabelsUtils.renameLabel("label1", ""); + sinon.assert.notCalled(getConfigurationStub); + + LabelsUtils.renameLabel("label1", "label1"); + sinon.assert.notCalled(getConfigurationStub); }); it("should set label color", () => { @@ -1967,6 +2795,247 @@ describe("Utils", () => { assert.strictEqual(stats.Magenta, 0); assert.strictEqual(stats.Cyan, 0); }); + + describe("clearWorkspaceLabels", () => { + let notifyStub: sinon.SinonStub; + let updateStub: sinon.SinonStub; + + beforeEach(() => { + notifyStub = sinon.stub(notifications, "notify"); + updateStub = sinon.stub(); + ext.connLabelList.length = 0; + ext.labelConnMapList.length = 0; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should clear all connection mappings when no labels exist", () => { + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns([]); + getStub.withArgs("kdb.labelsConnectionMap").returns([ + { labelName: "nonexistent1", connections: ["conn1"] }, + { labelName: "nonexistent2", connections: ["conn2"] }, + ]); + + getConfigurationStub.returns({ + get: getStub, + update: updateStub, + }); + + LabelsUtils.clearWorkspaceLabels(); + + sinon.assert.calledWith( + notifyStub, + "Cleaning connection mappings for nonexistent labels.", + notifications.MessageKind.DEBUG, + { + logger: "connLabel", + telemetry: "Label.Cleanup.NoLabels", + }, + ); + + sinon.assert.calledWith( + updateStub, + "kdb.labelsConnectionMap", + [], + true, + ); + }); + + it("should remove orphaned label connection mappings", () => { + const validLabels = [ + { name: "label1", color: { name: "Red", colorHex: "#FF0000" } }, + { name: "label2", color: { name: "Blue", colorHex: "#0000FF" } }, + ]; + + const connectionMappings = [ + { labelName: "label1", connections: ["conn1", "conn2"] }, + { labelName: "orphaned1", connections: ["conn3"] }, + { labelName: "label2", connections: ["conn4"] }, + { labelName: "orphaned2", connections: ["conn5", "conn6"] }, + ]; + + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns(validLabels); + getStub.withArgs("kdb.labelsConnectionMap").returns(connectionMappings); + + getConfigurationStub.returns({ + get: getStub, + update: updateStub, + }); + + LabelsUtils.clearWorkspaceLabels(); + + assert.strictEqual(ext.labelConnMapList.length, 2); + assert.deepStrictEqual(ext.labelConnMapList, [ + { labelName: "label1", connections: ["conn1", "conn2"] }, + { labelName: "label2", connections: ["conn4"] }, + ]); + + sinon.assert.calledWith( + notifyStub, + "Removed 2 orphaned label connection mappings.", + notifications.MessageKind.DEBUG, + { + logger: "connLabel", + telemetry: "Label.Cleanup.OrphanedMappings", + measurements: { removedMappings: 2 }, + }, + ); + + sinon.assert.calledWith( + updateStub, + "kdb.labelsConnectionMap", + ext.labelConnMapList, + true, + ); + }); + + it("should remove single orphaned label connection mapping", () => { + const validLabels = [ + { name: "label1", color: { name: "Red", colorHex: "#FF0000" } }, + ]; + + const connectionMappings = [ + { labelName: "label1", connections: ["conn1"] }, + { labelName: "orphaned1", connections: ["conn2"] }, + ]; + + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns(validLabels); + getStub.withArgs("kdb.labelsConnectionMap").returns(connectionMappings); + + getConfigurationStub.returns({ + get: getStub, + update: updateStub, + }); + + LabelsUtils.clearWorkspaceLabels(); + + assert.strictEqual(ext.labelConnMapList.length, 1); + assert.deepStrictEqual(ext.labelConnMapList, [ + { labelName: "label1", connections: ["conn1"] }, + ]); + + sinon.assert.calledWith( + notifyStub, + "Removed 1 orphaned label connection mapping.", + notifications.MessageKind.DEBUG, + { + logger: "connLabel", + telemetry: "Label.Cleanup.OrphanedMappings", + measurements: { removedMappings: 1 }, + }, + ); + }); + + it("should not remove anything when all mappings are valid", () => { + const validLabels = [ + { name: "label1", color: { name: "Red", colorHex: "#FF0000" } }, + { name: "label2", color: { name: "Blue", colorHex: "#0000FF" } }, + ]; + + const connectionMappings = [ + { labelName: "label1", connections: ["conn1"] }, + { labelName: "label2", connections: ["conn2"] }, + ]; + + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns(validLabels); + getStub.withArgs("kdb.labelsConnectionMap").returns(connectionMappings); + + getConfigurationStub.returns({ + get: getStub, + update: updateStub, + }); + + LabelsUtils.clearWorkspaceLabels(); + + assert.strictEqual(ext.labelConnMapList.length, 2); + assert.deepStrictEqual(ext.labelConnMapList, connectionMappings); + + sinon.assert.neverCalledWith( + notifyStub, + sinon.match.string, + notifications.MessageKind.DEBUG, + sinon.match.has("telemetry", "Label.Cleanup.OrphanedMappings"), + ); + + sinon.assert.neverCalledWith( + updateStub, + "kdb.labelsConnectionMap", + sinon.match.array, + true, + ); + }); + + it("should handle empty connection mappings", () => { + const validLabels = [ + { name: "label1", color: { name: "Red", colorHex: "#FF0000" } }, + ]; + + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns(validLabels); + getStub.withArgs("kdb.labelsConnectionMap").returns([]); + + getConfigurationStub.returns({ + get: getStub, + update: updateStub, + }); + + LabelsUtils.clearWorkspaceLabels(); + + assert.strictEqual(ext.labelConnMapList.length, 0); + + sinon.assert.neverCalledWith( + notifyStub, + sinon.match.string, + notifications.MessageKind.DEBUG, + ); + + sinon.assert.notCalled(updateStub); + }); + + it("should handle case-sensitive label name matching", () => { + const validLabels = [ + { name: "Label1", color: { name: "Red", colorHex: "#FF0000" } }, + ]; + + const connectionMappings = [ + { labelName: "Label1", connections: ["conn1"] }, + { labelName: "label1", connections: ["conn2"] }, + { labelName: "LABEL1", connections: ["conn3"] }, + ]; + + const getStub = sinon.stub(); + getStub.withArgs("kdb.connectionLabels").returns(validLabels); + getStub.withArgs("kdb.labelsConnectionMap").returns(connectionMappings); + + getConfigurationStub.returns({ + get: getStub, + update: updateStub, + }); + + LabelsUtils.clearWorkspaceLabels(); + + assert.strictEqual(ext.labelConnMapList.length, 1); + assert.strictEqual(ext.labelConnMapList[0].labelName, "Label1"); + + sinon.assert.calledWith( + notifyStub, + "Removed 2 orphaned label connection mappings.", + notifications.MessageKind.DEBUG, + { + logger: "connLabel", + telemetry: "Label.Cleanup.OrphanedMappings", + measurements: { removedMappings: 2 }, + }, + ); + }); + }); + describe("isLabelEmpty", () => { beforeEach(() => { ext.labelConnMapList.length = 0; @@ -2092,77 +3161,6 @@ describe("Utils", () => { }); }); - describe("resultToBase64", () => { - const png = [ - "0x89", - "0x50", - "0x4e", - "0x47", - "0x0d", - "0x0a", - "0x1a", - "0x0a", - ]; - const img = Array.from({ length: 59 }, () => "0x00"); - - it("should return undefined for undefined", () => { - const result = queryUtils.resultToBase64(undefined); - assert.strictEqual(result, undefined); - }); - it("should return undefined for just signature", () => { - const result = queryUtils.resultToBase64(png); - assert.strictEqual(result, undefined); - }); - it("should return undefined for bad signature", () => { - const result = queryUtils.resultToBase64([ - ...png.map((v) => parseInt(v, 16) + 1), - ...img, - ]); - assert.strictEqual(result, undefined); - }); - it("should return base64 for minimum img str", () => { - const result = queryUtils.resultToBase64([...png, ...img]); - assert.ok(result); - }); - it("should return base64 for minimum img num", () => { - const result = queryUtils.resultToBase64([ - ...png.map((v) => parseInt(v, 16)), - ...img.map((v) => parseInt(v, 16)), - ]); - assert.ok(result); - }); - it("should return base64 for minimum img str for structuredText", () => { - const result = queryUtils.resultToBase64({ - columns: { values: [...png, ...img] }, - }); - assert.ok(result); - }); - it("should return base64 for minimum img str for structuredText v2", () => { - const result = queryUtils.resultToBase64({ - columns: [{ values: [...png, ...img] }], - }); - assert.ok(result); - }); - it("should return undefined for bogus structuredText", () => { - const result = queryUtils.resultToBase64({ - columns: {}, - }); - assert.strictEqual(result, undefined); - }); - it("should return undefined for bogus structuredText v2", () => { - const result = queryUtils.resultToBase64({ - columns: [], - }); - assert.strictEqual(result, undefined); - }); - it("should return base64 from windows q server", () => { - const result = queryUtils.resultToBase64([ - ...png.map((v) => `${v}\r`), - ...img.map((v) => `${v}\r`), - ]); - assert.ok(result); - }); - }); describe("UDAUtils", () => { describe("filterUDAParamsValidTypes", () => { it("should filter valid types", () => { @@ -2466,26 +3464,14 @@ describe("Utils", () => { }); describe("getIncompatibleError", () => { - it("should return no meta error message", () => { - const result = UDAUtils.getIncompatibleError(undefined, undefined); - - assert.deepEqual(result, InvalidParamFieldErrors.NoMetadata); - }); - it("should return BadField error message", () => { - const result = UDAUtils.getIncompatibleError( - {}, - ParamFieldType.Invalid, - ); + const result = UDAUtils.getIncompatibleError(ParamFieldType.Invalid); assert.strictEqual(result, "badField"); }); it("should return undefined", () => { - const result = UDAUtils.getIncompatibleError( - {}, - ParamFieldType.Boolean, - ); + const result = UDAUtils.getIncompatibleError(ParamFieldType.Boolean); assert.strictEqual(result, undefined); }); }); @@ -2581,28 +3567,27 @@ describe("Utils", () => { api: [ { api: "testAPI", - custom: true, - metadata: { - params: [ - { - name: "param1", - type: 1, - isReq: true, - description: "", - }, - ], - return: { type: [1], description: "test" }, - description: "", - aggReturn: { - type: 0, + uda: true, + params: [ + { + name: "param1", + type: 1, + isReq: true, description: "", }, - misc: {}, + ], + return: { type: [1], description: "test" }, + description: "", + aggReturn: { + type: 0, + description: "", }, + misc: {}, kxname: [], aggFn: "", full: false, procs: [], + custom: false, }, ], rc: [], @@ -2896,6 +3881,7 @@ describe("Utils", () => { }); }); }); + describe("FeedbackSurveyUtils", () => { describe("feedbackSurveyDialog", () => { let showSurveyDialogStub: sinon.SinonStub; @@ -2949,55 +3935,214 @@ describe("Utils", () => { }); }); - describe("Shared with webview utils", () => { + describe("SharedUtils", () => { describe("normalizeAssemblyTarget", () => { - it("should return qe assembly without -qe", () => { - const res = shared.normalizeAssemblyTarget("test-assembly-qe target"); - assert.strictEqual(res, "test-assembly target"); + it("should return qe assembly -qe", () => { + const res = sharedUtils.normalizeAssemblyTarget( + "test-assembly-qe tier dapProcess", + ); + assert.strictEqual(res, "test-assembly-qe tier dapProcess"); }); + it("should return normal assembly without -qe", () => { - const res = shared.normalizeAssemblyTarget("test-assembly target"); - assert.strictEqual(res, "test-assembly target"); + const res = sharedUtils.normalizeAssemblyTarget( + "test-assembly tier dapProcess", + ); + assert.strictEqual(res, "test-assembly tier dapProcess"); + }); + + it("should return empty string for null input", () => { + const res = sharedUtils.normalizeAssemblyTarget(null as any); + assert.strictEqual(res, ""); + }); + + it("should return empty string for undefined input", () => { + const res = sharedUtils.normalizeAssemblyTarget(undefined as any); + assert.strictEqual(res, ""); + }); + + it("should return empty string for empty string", () => { + const res = sharedUtils.normalizeAssemblyTarget(""); + assert.strictEqual(res, ""); + }); + + it("should return empty string for whitespace-only string", () => { + const res = sharedUtils.normalizeAssemblyTarget(" "); + assert.strictEqual(res, ""); + }); + + it("should handle single word input", () => { + const res = sharedUtils.normalizeAssemblyTarget("assembly"); + assert.strictEqual(res, "assembly"); + }); + + it("should handle single word with -qe suffix", () => { + const res = sharedUtils.normalizeAssemblyTarget("assembly-qe"); + assert.strictEqual(res, "assembly-qe"); + }); + + it("should normalize multiple consecutive spaces", () => { + const res = sharedUtils.normalizeAssemblyTarget( + "assembly tier dap", + ); + assert.strictEqual(res, "assembly tier dap"); + }); + + it("should normalize tabs and mixed whitespace", () => { + const res = sharedUtils.normalizeAssemblyTarget( + "assembly\t\ttier\n\ndap", + ); + assert.strictEqual(res, "assembly tier dap"); + }); + + it("should trim leading and trailing spaces", () => { + const res = sharedUtils.normalizeAssemblyTarget( + " assembly tier dap ", + ); + assert.strictEqual(res, "assembly tier dap"); + }); + + it("should handle complex whitespace normalization", () => { + const res = sharedUtils.normalizeAssemblyTarget( + " \t assembly-qe \n tier \t dap \n ", + ); + assert.strictEqual(res, "assembly-qe tier dap"); + }); + + it("should handle input with only spaces between words", () => { + const res = sharedUtils.normalizeAssemblyTarget("assembly tier"); + assert.strictEqual(res, "assembly tier"); + }); + + it("should preserve -qe in middle of assembly name", () => { + const res = sharedUtils.normalizeAssemblyTarget( + "test-qe-assembly tier", + ); + assert.strictEqual(res, "test-qe-assembly tier"); + }); + + it("should handle special characters in assembly name", () => { + const res = sharedUtils.normalizeAssemblyTarget( + "test_assembly-qe:v1.0 tier dap", + ); + assert.strictEqual(res, "test_assembly-qe:v1.0 tier dap"); + }); + + it("should normalize newlines and carriage returns", () => { + const res = sharedUtils.normalizeAssemblyTarget( + "assembly\r\ntier\rdap\n", + ); + assert.strictEqual(res, "assembly tier dap"); + }); + }); + + describe("stripUnprintableChars", () => { + it("should remove control characters", () => { + const input = "abc\u0000\u0001def"; + const result = sharedUtils.stripUnprintableChars(input); + assert.strictEqual(result, "abcdef"); + }); + + it("should remove private use characters", () => { + const input = "abc\udb80\udc00def"; + const result = sharedUtils.stripUnprintableChars(input); + assert.strictEqual(result, "abcdef"); + }); + + it("should remove unassigned characters", () => { + // U+0378 is unassigned + const input = "abc\u0378def"; + const result = sharedUtils.stripUnprintableChars(input); + assert.strictEqual(result, "abcdef"); + }); + + it("should return original string if no unprintable chars", () => { + const input = "normal string"; + const result = sharedUtils.stripUnprintableChars(input); + assert.strictEqual(result, "normal string"); + }); + + it("should handle empty string", () => { + const result = sharedUtils.stripUnprintableChars(""); + assert.strictEqual(result, ""); }); }); - }); - describe("sanitizeQsqlQuery", () => { - it("should trim query", () => { - const res = queryUtils.sanitizeQsqlQuery(" a:1 "); - assert.strictEqual(res, "a:1"); - }); - it("should remove block comment", () => { - let res = queryUtils.sanitizeQsqlQuery("/\nBlock Comment\n\\a:1"); - assert.strictEqual(res, "a:1"); - res = queryUtils.sanitizeQsqlQuery("/\nBlock Comment\r\n\\a:1"); - assert.strictEqual(res, "a:1"); - }); - it("should remove single line comment", () => { - let res = queryUtils.sanitizeQsqlQuery("/ single line comment\na:1"); - assert.strictEqual(res, "a:1"); - res = queryUtils.sanitizeQsqlQuery("/ single line comment\r\na:1"); - assert.strictEqual(res, "a:1"); - }); - it("should remove line comment", () => { - const res = queryUtils.sanitizeQsqlQuery("a:1 / line comment"); - assert.strictEqual(res, "a:1"); - }); - it("should ignore line comment in a string", () => { - const res = queryUtils.sanitizeQsqlQuery('a:"1 / not line comment"'); - assert.strictEqual(res, 'a:"1 / not line comment"'); - }); - it("should replace EOS with semicolon", () => { - let res = queryUtils.sanitizeQsqlQuery("a:1\na"); - assert.strictEqual(res, "a:1;a"); - res = queryUtils.sanitizeQsqlQuery("a:1\r\na"); - assert.strictEqual(res, "a:1;a"); - }); - it("should not replace continuation with semicolon", () => { - let res = queryUtils.sanitizeQsqlQuery('a:"a\n \nb"'); - assert.strictEqual(res, 'a:"a\n \nb"'); - res = queryUtils.sanitizeQsqlQuery('a:"a\r\n \r\nb"'); - assert.strictEqual(res, 'a:"a\r\n \r\nb"'); + describe("errorMessage", () => { + it("should return error message for Error instance", () => { + const error = new Error("Test error"); + const result = sharedUtils.errorMessage(error); + assert.strictEqual(result, "Test error"); + }); + + it("should return string for string input", () => { + const result = sharedUtils.errorMessage("Some error"); + assert.strictEqual(result, "Some error"); + }); + + it("should stringify number input", () => { + const result = sharedUtils.errorMessage(123); + assert.strictEqual(result, "123"); + }); + + it("should stringify object input", () => { + const result = sharedUtils.errorMessage({ msg: "fail" }); + assert.strictEqual(result, "[object Object]"); + }); + + it("should handle null input", () => { + const result = sharedUtils.errorMessage(null); + assert.strictEqual(result, "null"); + }); + + it("should handle undefined input", () => { + const result = sharedUtils.errorMessage(undefined); + assert.strictEqual(result, "undefined"); + }); + }); + + describe("cleanDapName", () => { + it("should remove port suffix from dap name", () => { + const result = sharedUtils.cleanDapName("my-dap:1234"); + assert.strictEqual(result, "my-dap"); + }); + + it("should not change dap name without port", () => { + const result = sharedUtils.cleanDapName("my-dap"); + assert.strictEqual(result, "my-dap"); + }); + + it("should handle dap name with multiple colons", () => { + const result = sharedUtils.cleanDapName("my:dap:name:5678"); + assert.strictEqual(result, "my:dap:name"); + }); + + it("should handle empty string", () => { + const result = sharedUtils.cleanDapName(""); + assert.strictEqual(result, ""); + }); + }); + + describe("cleanAssemblyName", () => { + it("should remove -qe suffix from assembly name", () => { + const result = sharedUtils.cleanAssemblyName("assembly-qe"); + assert.strictEqual(result, "assembly"); + }); + + it("should not change assembly name without -qe", () => { + const result = sharedUtils.cleanAssemblyName("assembly"); + assert.strictEqual(result, "assembly"); + }); + + it("should remove only trailing -qe", () => { + const result = sharedUtils.cleanAssemblyName("test-qe-assembly-qe"); + assert.strictEqual(result, "test-qe-assembly"); + }); + + it("should handle empty string", () => { + const result = sharedUtils.cleanAssemblyName(""); + assert.strictEqual(result, ""); + }); }); }); }); diff --git a/test/suite/validators.test.ts b/test/suite/validators.test.ts index 864c23047..0932ca66c 100644 --- a/test/suite/validators.test.ts +++ b/test/suite/validators.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -115,6 +115,15 @@ describe("kdbValidator", () => { ); }); + it("Should return fail if using restricted keyword", () => { + const result = kdbValidators.validateServerAlias("REPL", false); + assert.strictEqual( + result, + "The server name 'REPL' is reserved for connections to the REPL", + "Input contained restricted keyword.", + ); + }); + it("Should return fail if using restricted keyword", () => { const result = kdbValidators.validateServerAlias("local", false); assert.strictEqual( diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index b51a38f3b..17f2b6844 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -58,14 +58,20 @@ describe("KdbDataSourceView", () => { selectedServer: "server", isInsights, insightsMeta: { - dap: [{}], + dap: [ + { + assembly: "test-assembly", + instance: "instance1", + dap: "dap1", + }, + ], api: [{ api: "getData" }], - assembly: [{ assembly: "assembly", tbls: ["table1"] }], + assembly: [{ assembly: "test-assembly", tbls: ["table1"] }], schema: [ { table: "table1", columns: [{ column: "column1" }], - assembly: "assembly", + assembly: "test-assembly", type: "type", }, ], @@ -166,12 +172,145 @@ describe("KdbDataSourceView", () => { }); }); + describe("renderTargetOptions", () => { + function templateResultToString(templateResult: any): string { + if (!templateResult || !templateResult.strings) { + return ""; + } + + let result = ""; + const strings = templateResult.strings; + const values = templateResult.values || []; + + for (let i = 0; i < strings.length; i++) { + result += strings[i]; + if (i < values.length) { + result += values[i]; + } + } + + return result; + } + + beforeEach(() => { + view.isInsights = true; + view.isMetaLoaded = true; + view.insightsMeta = { + dap: [ + { + assembly: "test-assembly-1", + instance: "instance1", + dap: "dap1", + startTS: "", + endTS: "", + }, + { + assembly: "test-assembly-1", + instance: "instance1", + dap: "dap2", + startTS: "", + endTS: "", + }, + { + assembly: "test-assembly-2", + instance: "instance2", + dap: "dap3", + startTS: "", + endTS: "", + }, + { + assembly: "test-assembly-3", + instance: "instance3", + startTS: "", + endTS: "", + }, + ], + api: [], + assembly: [], + schema: [], + rc: [], + agg: [], + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should group DAP processes by tier key", () => { + const result = view.renderTargetOptions(); + const resultString = result + .map((item) => templateResultToString(item)) + .join(""); + + assert.ok(resultString.includes("test-assembly-1 instance1")); + assert.ok(resultString.includes("test-assembly-2 instance2")); + }); + + it("should include DAP processes with non-empty dap values", () => { + const result = view.renderTargetOptions(); + const resultString = result + .map((item) => templateResultToString(item)) + .join(""); + + assert.ok(resultString.includes("dap1")); + assert.ok(resultString.includes("dap2")); + assert.ok(resultString.includes("dap3")); + }); + + it("should set qsqlTarget when not already set and tier options exist", () => { + view.qsqlTarget = ""; + view.renderTargetOptions(); + assert.ok(true); + }); + + it("should not set qsqlTarget when already set", () => { + const originalTarget = "existing-target"; + view.qsqlTarget = originalTarget; + view.renderTargetOptions(); + assert.strictEqual(view.qsqlTarget, originalTarget); + }); + + it("should handle empty insightsMeta.dap array", () => { + view.insightsMeta.dap = []; + + const result = view.renderTargetOptions(); + const resultString = result + .map((item) => templateResultToString(item)) + .join(""); + + assert.ok(!resultString.includes("Tiers")); + assert.ok(!resultString.includes("DAP Process")); + }); + + it("should return empty array when not insights", () => { + view.isInsights = false; + const result = view.renderTargetOptions(); + assert.deepStrictEqual(result, []); + }); + + it("should return empty array when meta not loaded", () => { + view.isMetaLoaded = false; + const result = view.renderTargetOptions(); + assert.deepStrictEqual(result, []); + }); + + it("should return empty array when both not insights and meta not loaded", () => { + view.isInsights = false; + view.isMetaLoaded = false; + const result = view.renderTargetOptions(); + assert.deepStrictEqual(result, []); + }); + }); + describe("render", () => { it("should update from message", () => { + sinon.stub(view, "renderTargetOptions").returns([]); view.message(createMessageEvent(true)); assert.ok(view.data); const result = view.render(); assert.ok(result); + sinon.restore(); }); it("should update from offline message", () => { view.message(createMessageEvent(false)); diff --git a/test/suite/workspace.test.ts b/test/suite/workspace.test.ts index 3268400fd..97fe2f2e0 100644 --- a/test/suite/workspace.test.ts +++ b/test/suite/workspace.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/ui/fixtures/basic/sample.q b/test/ui/fixtures/basic/sample.q deleted file mode 100644 index 9d3f3bff2..000000000 --- a/test/ui/fixtures/basic/sample.q +++ /dev/null @@ -1,2 +0,0 @@ -a:1 -a diff --git a/test/ui/fixtures/utils.ts b/test/ui/fixtures/utils.ts index 2aa4b1a32..1d032efea 100644 --- a/test/ui/fixtures/utils.ts +++ b/test/ui/fixtures/utils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 diff --git a/test/ui/notebook.test.ts b/test/ui/notebooks.test.ts similarity index 70% rename from test/ui/notebook.test.ts rename to test/ui/notebooks.test.ts index c46e39ae1..6a96c233d 100644 --- a/test/ui/notebook.test.ts +++ b/test/ui/notebooks.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -12,13 +12,12 @@ */ import * as assert from "assert"; -import { Editor, VSBrowser, Workbench } from "vscode-extension-tester"; +import { Editor, VSBrowser } from "vscode-extension-tester"; import { waitForEditor } from "./fixtures/utils"; describe("Notebook", () => { let code: VSBrowser; - let workbench: Workbench; before(async () => { code = VSBrowser.instance; @@ -26,7 +25,6 @@ describe("Notebook", () => { "./test/ui/fixtures/notebook", "./test/ui/fixtures/notebook/simple.kxnb", ); - workbench = new Workbench(); }); describe("Existing notebook", () => { @@ -40,17 +38,4 @@ describe("Notebook", () => { assert.ok(editor); }); }); - - describe("New notebook", () => { - let editor: Editor; - - before(async () => { - await workbench.executeCommand("KX: Create new notebook"); - editor = await waitForEditor("notebook-1.kxnb"); - }); - - it("should exist", async () => { - assert.ok(editor); - }); - }); }); diff --git a/test/ui/sample.test.ts b/test/ui/sample.test.ts deleted file mode 100644 index cfc5e4676..000000000 --- a/test/ui/sample.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 1998-2025 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 * as assert from "assert"; -import { VSBrowser, ActivityBar } from "vscode-extension-tester"; - -describe("ActivityBar", () => { - let browser: VSBrowser; - let activityBar: ActivityBar; - - before(async () => { - browser = VSBrowser.instance; - activityBar = new ActivityBar(); - }); - - it("should contain KX tab", async () => { - const controls = await activityBar.getViewControls(); - let kx = ""; - for (const control of controls) { - const id = await control.getTitle(); - if (id === "KX") { - kx = id; - break; - } - } - assert.strictEqual(kx, "KX"); - }); - - it("should open basic workspace", async () => { - await browser.openResources("./test/ui/fixtures/basic"); - }); -}); diff --git a/test/ui/startup.test.ts b/test/ui/startup.test.ts index 2403e0335..f94f5bd2f 100644 --- a/test/ui/startup.test.ts +++ b/test/ui/startup.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2025 Kx Systems Inc. + * Copyright (c) 1998-2025 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 @@ -90,4 +90,16 @@ describe("Start Up", () => { assert.ok(section); }); }); + + describe("Help and Feedback Section", () => { + let section: ViewSection; + + before(async () => { + section = await sideBar.getContent().getSection("Help and Feedback"); + }); + + it("should exist", async () => { + assert.ok(section); + }); + }); });