diff --git a/.README/addnewconnection.jpg b/.README/addnewconnection.jpg deleted file mode 100644 index 9ab7c4bbe..000000000 Binary files a/.README/addnewconnection.jpg and /dev/null differ diff --git a/.README/associated-file-workbook.png b/.README/associated-file-workbook.png index 8572594d2..6c0989471 100644 Binary files a/.README/associated-file-workbook.png and b/.README/associated-file-workbook.png differ diff --git a/.README/bundleq.png b/.README/bundleq.png deleted file mode 100644 index bb0f9ccc3..000000000 Binary files a/.README/bundleq.png and /dev/null differ diff --git a/.README/conn-labels-tree.png b/.README/conn-labels-tree.png index 6c7e3f2cb..cad8c9c9d 100644 Binary files a/.README/conn-labels-tree.png and b/.README/conn-labels-tree.png differ diff --git a/.README/connect-insights.jpg b/.README/connect-insights.jpg deleted file mode 100644 index 4e9b3ea8f..000000000 Binary files a/.README/connect-insights.jpg and /dev/null differ diff --git a/.README/connectserver.jpg b/.README/connectserver.jpg deleted file mode 100644 index 80744be0f..000000000 Binary files a/.README/connectserver.jpg and /dev/null differ diff --git a/.README/connecttoinsights.jpg b/.README/connecttoinsights.jpg deleted file mode 100644 index 13acdf862..000000000 Binary files a/.README/connecttoinsights.jpg and /dev/null differ diff --git a/.README/connecttoinsightscontext.jpg b/.README/connecttoinsightscontext.jpg deleted file mode 100644 index 3ef44fc03..000000000 Binary files a/.README/connecttoinsightscontext.jpg and /dev/null differ diff --git a/.README/connecttokdbserver.jpg b/.README/connecttokdbserver.jpg deleted file mode 100644 index a2d97afea..000000000 Binary files a/.README/connecttokdbserver.jpg and /dev/null differ diff --git a/.README/datasources.jpg b/.README/datasources.jpg deleted file mode 100644 index c6d25a921..000000000 Binary files a/.README/datasources.jpg and /dev/null differ diff --git a/.README/edit-bundle-q-conn-form.png b/.README/edit-bundle-q-conn-form.png deleted file mode 100644 index e853e23fe..000000000 Binary files a/.README/edit-bundle-q-conn-form.png and /dev/null differ diff --git a/.README/edit-connected-connection-dialog.png b/.README/edit-connected-connection-dialog.png deleted file mode 100644 index 867d72d47..000000000 Binary files a/.README/edit-connected-connection-dialog.png and /dev/null differ diff --git a/.README/findlicense.jpg b/.README/findlicense.jpg deleted file mode 100644 index f1605e773..000000000 Binary files a/.README/findlicense.jpg and /dev/null differ diff --git a/.README/image.png b/.README/image.png deleted file mode 100644 index a8fb7f1f3..000000000 Binary files a/.README/image.png and /dev/null differ diff --git a/.README/impex.png b/.README/impex.png index 9161b300b..9fa7055a3 100644 Binary files a/.README/impex.png and b/.README/impex.png differ diff --git a/.README/insightsconnection.png b/.README/insightsconnection.png deleted file mode 100644 index 37e3b0504..000000000 Binary files a/.README/insightsconnection.png and /dev/null differ diff --git a/.README/installationofqfound.jpg b/.README/installationofqfound.jpg deleted file mode 100644 index bed0b3395..000000000 Binary files a/.README/installationofqfound.jpg and /dev/null differ diff --git a/.README/installnewinstance.jpg b/.README/installnewinstance.jpg deleted file mode 100644 index 9d5762c6d..000000000 Binary files a/.README/installnewinstance.jpg and /dev/null differ diff --git a/.README/kdb-connection.png b/.README/kdb-connection.png deleted file mode 100644 index bcffe1a0f..000000000 Binary files a/.README/kdb-connection.png and /dev/null differ diff --git a/.README/kdbinsightsconnection.jpg b/.README/kdbinsightsconnection.jpg deleted file mode 100644 index be28c4041..000000000 Binary files a/.README/kdbinsightsconnection.jpg and /dev/null differ diff --git a/.README/kdbinsightsconnection.png b/.README/kdbinsightsconnection.png new file mode 100644 index 000000000..b61ed82ad Binary files /dev/null and b/.README/kdbinsightsconnection.png differ diff --git a/.README/localkdbconnection.jpg b/.README/localkdbconnection.jpg deleted file mode 100644 index e1e32f218..000000000 Binary files a/.README/localkdbconnection.jpg and /dev/null differ diff --git a/.README/managedqprocess.jpg b/.README/managedqprocess.jpg deleted file mode 100644 index 97e90d5c0..000000000 Binary files a/.README/managedqprocess.jpg and /dev/null differ diff --git a/.README/myq.png b/.README/myq.png deleted file mode 100644 index 29d5e5fcd..000000000 Binary files a/.README/myq.png and /dev/null differ diff --git a/.README/output-results.png b/.README/output-results.png deleted file mode 100644 index ffefbbfa5..000000000 Binary files a/.README/output-results.png and /dev/null differ diff --git a/.README/pastelicense.jpg b/.README/pastelicense.jpg deleted file mode 100644 index c478b336e..000000000 Binary files a/.README/pastelicense.jpg and /dev/null differ diff --git a/.README/qruntimeinstalled.jpg b/.README/qruntimeinstalled.jpg deleted file mode 100644 index f5875be5a..000000000 Binary files a/.README/qruntimeinstalled.jpg and /dev/null differ diff --git a/.README/query-history-details.png b/.README/query-history-details.png index 21488601c..aeed53649 100644 Binary files a/.README/query-history-details.png and b/.README/query-history-details.png differ diff --git a/.README/select-edit-connection.png b/.README/select-edit-connection.png index 4725c151f..004c4aaa8 100644 Binary files a/.README/select-edit-connection.png and b/.README/select-edit-connection.png differ diff --git a/.README/select-notebook-language.png b/.README/select-notebook-language.png deleted file mode 100644 index 75a1e59f7..000000000 Binary files a/.README/select-notebook-language.png and /dev/null differ diff --git a/.README/step1connecttoakdbserver.jpg b/.README/step1connecttoakdbserver.jpg deleted file mode 100644 index 398f78838..000000000 Binary files a/.README/step1connecttoakdbserver.jpg and /dev/null differ diff --git a/.README/subscribetoupdates.jpg b/.README/subscribetoupdates.jpg deleted file mode 100644 index 1d7d74f7c..000000000 Binary files a/.README/subscribetoupdates.jpg and /dev/null differ diff --git a/.README/unassociated-file-workbook.png b/.README/unassociated-file-workbook.png index 9dd910e5e..f611fb372 100644 Binary files a/.README/unassociated-file-workbook.png and b/.README/unassociated-file-workbook.png differ diff --git a/.README/welcome-to-kdbx.png b/.README/welcome-to-kdbx.png index d4acd420f..78891de7f 100644 Binary files a/.README/welcome-to-kdbx.png and b/.README/welcome-to-kdbx.png differ diff --git a/.README/workbookplaydropdown.png b/.README/workbookplaydropdown.png deleted file mode 100644 index 5dfbdf46f..000000000 Binary files a/.README/workbookplaydropdown.png and /dev/null differ diff --git a/.README/workbookrunlink.png b/.README/workbookrunlink.png deleted file mode 100644 index 58e97d7e1..000000000 Binary files a/.README/workbookrunlink.png and /dev/null differ diff --git a/.README/workbookstatusbarrun.png b/.README/workbookstatusbarrun.png deleted file mode 100644 index 5f368d9c0..000000000 Binary files a/.README/workbookstatusbarrun.png and /dev/null differ diff --git a/.vscodeignore b/.vscodeignore index 9235ab9d8..86d25c315 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -15,8 +15,11 @@ esbuild.js sonar-project.properties .snyk eslint.config.cjs +eslint.config.mjs release_process.md .github/ out-test/ .test-extensions/ .test-folder/ +.README +REFCARD.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dfd764b4..179d79096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to the **kdb VS Code extension** are documented in this file. +# v1.16.1 + +### Fixes + +- Updated Keycloak URL configuration to support Insights Enterprise 1.17 +- Resolved an issue with REPL system command path +- Improved clarity and layout of the welcome page +- Updated and refined documentation + # v1.16.0 ### Enhancements diff --git a/README.md b/README.md index 342f419bd..788ab7e65 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Once the **kdb VS Code extension** is installed **KX** appears in the Activity B ### Customized Authentication -Customized authentication has been implemented for the kdb VS Code extension, allowing you to add custom logic when authenticating with kdb. +Customized authentication has been implemented for the kdb VS Code extension, allowing you to add custom logic when authenticating with kdb. Refer to [customized authentication](https://github.com/KxSystems/kx-vscode-auth) for details on how to set this up. @@ -81,25 +81,21 @@ When first opening the KX extension, you are greeted by a **Welcome to KDB-X** m **Setup instructions:** 1. Log in or create an account - - When prompted, a browser window opens automatically. - Log in using your email address (verify using the code sent to you) or sign in with Google, then accept the KDB-X EULA. 2. Retrieve your KDB-X license key - - After login, navigate to the [KDB-X Welcome Page](https://developer.kx.com/products/kdb-x/install) or check your welcome email to copy your unique license key. 3. Activate KDB-X in VS Code - - Paste your license key when prompted. - The KDB-X runtime will install automatically via the terminal. 4. Start coding - - Once the installation completes, the runtime path and environment should appear in the KX Extension panel. - You can start coding immediately using KDB-X scripts and sessions. -**Note!** New users must install KDB-X before starting a REPL session in VS Code. The REPL environment requires the KDB-X runtime, which is installed during license activation. +**Note!** New users must install KDB-X before starting a REPL session in VS Code. The REPL environment requires the KDB-X runtime, which is installed during license activation. As a side note, if KDB-X is installed from within VS Code, you do not need to modify your system `PATH`. The REPL automatically remembers the installed runtime location and will use it for subsequent sessions. @@ -122,7 +118,7 @@ There are commercial and non-commercial editions available. We recommend you sta | Edition | write q | run q queries | explore results | shared kdb process with kdb Insights | | ----------------------------------------------------------------------------------------------- | ------- | ------------- | --------------- | ------------------------------------ | -| [KDB-X Community Edition](https://developer.kx.com/products/kdb-x/install) | yes | yes | yes | no | +| [KDB-X Community Edition](https://developer.kx.com/products/kdb-x/install) | yes | yes | yes | no | | [kdb+ Personal Edition](https://kx.com/kdb-personal-edition-download/) | yes | yes | yes | no | | [kdb Insights SDK Personal Edition](https://kx.com/kdb-insights-sdk-personal-edition-download/) | yes | yes | yes | no | | **kdb Insights Enterprise** | yes | yes | yes | yes | @@ -149,7 +145,7 @@ To execute a q file in REPL: 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. +Refer to the [REPL shortcuts table](https://github.com/KxSystems/kx-vscode/wiki/Reference-Card#repl) for information on the keyboard shortcuts you can use. ### Benefits of using REPL @@ -177,11 +173,11 @@ Refer to the [PyKX within REPL](https://github.com/KxSystems/kx-vscode/wiki/Use- ### Supported file types -In addition to q and Python files, SQL files are also supported for execution within REPL. Refer to the [supported execution types](https://github.com/KxSystems/kx-vscode/wiki/Supported-Execution-Types) table for more details. +In addition to q and Python files, SQL files are also supported for execution within REPL. Refer to the [supported execution types](https://github.com/KxSystems/kx-vscode/wiki/Reference-Card#execution) table for more details. ## Connections -The **kdb VS Code extension** allows you to have multiple connections open at once, enabling development and testing across different KDB-X and kdb Insights Enterprise connections using both q and Python. +The **kdb VS Code extension** allows you to have multiple connections open at once, enabling development and testing across different KDB-X and kdb Insights Enterprise connections using q, Python, and SQL. To add connections: @@ -203,17 +199,15 @@ When you select **My q** as the connection type, identify the remote location of Set the following properties: -| Property | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Property | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Server Name | The server name / alias. The server name selected cannot be **insights**, as this is reserved for use by [Insights connections](#insights-connection); e.g. dev. | -| The connection address | Set to the IP address of the kdb server; e.g. **localhost**. | -| Port | Enter the port used by the kdb server; e.g. 5001. Learn more about [setting a q port](https://code.kx.com/q/basics/ipc/) . | -| Username | If authentication is needed, fill in the username otherwise, leave **blank** | -| Password | If authentication is needed, fill in the password otherwise, leave **blank** | -| Enable TLS Encryption | Check the box is TLS is enabled. Learn more [about TLS encryption](https://code.kx.com/q/kb/ssl/). | -| Label Name | Select the label you want to assign the connection to | - -![setendpoint](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/myq.png) +| The connection address | Set to the IP address of the kdb server; e.g. **localhost**. | +| Port | Enter the port used by the kdb server; e.g. 5001. Learn more about [setting a q port](https://code.kx.com/q/basics/ipc/) . | +| Username | If authentication is needed, fill in the username otherwise, leave **blank** | +| Password | If authentication is needed, fill in the password otherwise, leave **blank** | +| Enable TLS Encryption | Check the box is TLS is enabled. Learn more [about TLS encryption](https://code.kx.com/q/kb/ssl/). | +| Label Name | Select the label you want to assign the connection to | 1. Click **Create Connection** and the connection appears under **CONNECTIONS** in the primary sidebar. @@ -229,12 +223,10 @@ Set the following properties: | Property | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| Server Name | The server name / alias. | +| Server Name | The server name / alias. | | The connection address | This is the remote address of your **kdb Insights Enterprise** deployment: e.g. `https://mykdbinsights.cloudapp.azure.com` | | Label Name | Select the label you want to assign the connection to | -![connecttoinsights](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/insightsconnection.png) - Set the following from the Advanced properties if necessary: | Property | Description | @@ -249,7 +241,7 @@ Set the following from the Advanced properties if necessary: 1. Right-click the connection, and click **Connect server**. - ![connecttoinsights](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/kdbinsightsconnection.jpg) + ![connecttoinsights](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/kdbinsightsconnection.png) 1. The kdb VS Code extension runs an authentication step with the remote **kdb Insights Enterprise** process to sign-in. @@ -281,22 +273,20 @@ To edit an existing connection, right-click the connection you wish to edit and > 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) - ### Edit My q connection When editing a **My q** connection, you can edit the following properties: -| Property | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Server Name | The server name / alias. The server name selected cannot be **insights**, as this is reserved for use by [Insights connections](#insights-connection); e.g. dev | -| The connection address | Set to the IP address of the kdb server; e.g. **localhost**. | -| Port | Enter the port used by the kdb server; e.g. 5001. Learn more about [setting a q port](https://code.kx.com/q/basics/ipc/) . | -| Edit Auth options | Check the box if you wish to change **Auth options**. If you want to **remove the Auth** for this connection, select this checkbox and leave the **Username** and **Password** fields in **blank**. | -| Username | If authentication is needed, fill in the username otherwise, leave **blank**. | -| Password | If authentication is needed, fill in the password otherwise, leave **blank**. | -| Enable TLS Encryption | Check the box is TLS is enabled. Learn more [about TLS encryption](https://code.kx.com/q/kb/ssl/). | -| Label Name | Select the label to assign the connection to. | +| Property | Description | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Server Name | The server name / alias. The server name selected cannot be **insights**, as this is reserved for use by [Insights connections](#insights-connection); e.g. dev | +| The connection address | Set to the IP address of the kdb server; e.g. **localhost**. | +| Port | Enter the port used by the kdb server; e.g. 5001. Learn more about [setting a q port](https://code.kx.com/q/basics/ipc/) . | +| Edit Auth options | Check the box if you wish to change **Auth options**. If you want to **remove the Auth** for this connection, select this checkbox and leave the **Username** and **Password** fields in **blank**. | +| Username | If authentication is needed, fill in the username otherwise, leave **blank**. | +| Password | If authentication is needed, fill in the password otherwise, leave **blank**. | +| Enable TLS Encryption | Check the box is TLS is enabled. Learn more [about TLS encryption](https://code.kx.com/q/kb/ssl/). | +| Label Name | Select the label to assign the connection to. | ![Edit My q connection](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/edit-my-q-conn-form.png) @@ -306,7 +296,7 @@ When editing a **Insights** connection, you can edit the following properties: | Property | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Server Name | The server name / alias. | +| Server Name | The server name / alias. | | The connection address | This is the remote address of your **kdb Insights Enterprise** deployment: e.g. `https://mykdbinsights.cloudapp.azure.com` | | Define Realm | Specify the Keycloak realm for authentication. Usually the realm is set to `insights`, which is the default value used by the extension. You only need to change this field if a different realm has been configured on your server. | | Label Name | Select the label you want to assign the connection to | @@ -470,8 +460,6 @@ For any file with a **.q** or **.py** extension there are additional options ava - **Execute current block** - Selects the q expression under the cursor and executes it against the active connection. Results are displayed in the [Output window and/or the KDB Results window](#view-results). -- **Run q file in new q instance** - If q is installed and executable from the terminal you can execute an entire q script on a newly launched q instance. Executing a file on a new instance is done in the terminal, and allows interrogation of the active q process from the terminal window. - When executing Python code against kdb+ connections, **note** the following: - A Python variable is defined in the remote process `_kx_execution_context`, which means you need to explicitly accept it to avoid implicit changes to the remote process. It doesn’t make any other change to the remote process. @@ -709,7 +697,7 @@ Create a Workbook using the WORKBOOKS panel and run code against a specific conn 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) + ![play dropdown](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/unassociated-file-workbook.png) 1. Right-click and choose **KX: Execute Entire File** from the menu. @@ -797,7 +785,7 @@ KX Notebooks detect [GGPlot2](#grammar-of-graphics) outputs. If the execution ge ![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). +See the [Sample KX Notebooks](https://github.com/KxSystems/kx-vscode/wiki/Sample-KX-Notebooks) for more detailed examples. ## Query History @@ -829,8 +817,6 @@ All query executions happen remotely from the **kdb VS Code extension** either a - **Output** - The **Output** window displays results as they are received by the **kdb VS Code extension**. It includes the query executed, a timestamp and the results. - ![Output view](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/output-results.png) - **Note:** You can enable/disable auto-scrolling in the VS Code settings. This setting determines whether the output view scrolls to the latest results. ![Output autoscrolling](https://raw.githubusercontent.com/KxSystems/kx-vscode/main/.README/auto-scrolling.png) @@ -889,23 +875,23 @@ The format is: 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 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 source expressions** | 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 | +| Setting | Action | +| ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| **Automatically [focus the output](#auto-focus-output-on-entry) console when running a query without an active results tab or receive log entry** | yes/no; default yes | +| **List of label names and color set** | edit JSON settings | +| **Connection map for workspace files** | edit JSON settings | +| **Hide source expressions** | yes/no; default yes | +| **Hide subscribe for registration notification** | yes/no; default yes | +| **Hide survey** | yes/no; default no | +| **kdb Insights Enterprise connections for explorer** | [edit JSON settings](#kdb-insights-enterprise-connections-for-explorer) | +| **Labels connection map** | edit JSON settings | +| **Linting** | Enable linting for q and quke files | +| **Never show q install walkthrough again** | yes/no; default no | +| **QHOME directory for q runtime** | Display location path of q installation | +| **QHOME directory for q runtime for the workspace** | Display location path of the q installation used to launch the REPL for the current workspace | +| **Refactoring** | Choose [refactoring](#refactoring) scope | +| **kdb servers for explorer** | [edit JSON settings](#servers) | +| **Target map for workspace files** | edit JSON settings | ### Refactoring @@ -1016,33 +1002,37 @@ If you choose to opt out permanently but wish to revert this, open VS Code setti ### For Windows -| Key | Action | -| --------------------- | --------------------------------- | -| F12 | Go to definition | -| Shift + F12 | Go to references | -| Ctrl + Shift + F12 | Find all references | -| Ctrl + D | Execute current selection | -| Ctrl + Shift + E | Execute current block | -| Ctrl + Shift + D | Execute entire file | -| 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 | +| Key | Action | +| ---------------------- | --------------------------------- | +| F12 | Go to definition | +| Shift + F12 | Go to references | +| Ctrl + Shift + F12 | Find all references | +| Ctrl + D | Execute current selection | +| Ctrl + Shift + E | Execute current block | +| Ctrl + Shift + D | Execute entire file | +| Ctrl + Shift + Y | Toggle parameter cache for lambda | +| Ctrl + Shift + Alt + P | Populate Scratchpad | +| Ctrl + Shift + Delete | Reset Scratchpad | +| Ctrl + Alt + T | Choose the execution target | ### For MacOS -| Key | Action | -| ------------------ | --------------------------------- | -| F12 | Go to definition | -| Shift + F12 | Go to references | -| ⌘ + Shift + F12 | Find all references | -| ⌘ + D | Execute current selection | -| ⌘ + Shift + E | Execute current block | -| ⌘ + Shift + D | Execute entire file | -| ⌘ + 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 | +| Key | Action | +| ------------------- | --------------------------------- | +| F12 | Go to definition | +| Shift + F12 | Go to references | +| ⌘ + Shift + F12 | Find all references | +| ⌘ + D | Execute current selection | +| ⌘ + Shift + E | Execute current block | +| ⌘ + Shift + D | Execute entire file | +| ⌘ + Shift + Y | Toggle parameter cache for lambda | +| ⌘ + Shift + Alt + P | Populate Scratchpad | +| ⌘ + Shift + Delete | Reset Scratchpad | +| ⌘ + Alt + T | Choose the execution target | + +## Reference card + +See the [Reference Card](https://github.com/KxSystems/kx-vscode/wiki/Reference-Card) for a list of all commands, shortcuts, and settings. ## Data and telemetry diff --git a/REFCARD.md b/REFCARD.md new file mode 100644 index 000000000..814ac75c7 --- /dev/null +++ b/REFCARD.md @@ -0,0 +1,69 @@ +## Command Palette + +| Command | Command | Command | +| :--------------------- | :------------------------ | :---------------------------------- | +| KX: Welcome to KDB-X | KX: New Connection | KX: Focus on Connections view | +| KX: Install KDB-X | KX: New Notebook | KX: Focus on Datasources view | +| KX: Start REPL | KX: New Workbook (q) | KX: Focus on Workbooks view | +| KX: Import Connections | KX: New Workbook (Python) | KX: Focus on Query History view | +| KX: Export Connections | KX: New Datasource | KX: Focus on Help and Feedback view | + +## Keybindings + +| Command | When | Shortcut | +| :---------------------------- | :-------------------- | :--------------------------- | +| KX: Execute Entire File | `q` `py` `sql` | `ctrl`/`⌘` +`shift`+`d` | +| KX: Execute Current Selection | `q` `py` `sql` | `ctrl`/`⌘`+`d` | +| KX: Execute Current Block | `q` | `ctrl`/`⌘`+`shift`+`e` | +| KX: Populate Scratchpad | `q` `py` `sql` | `ctrl`/`⌘`+`shift`+`alt`+`p` | +| KX: Reset Scratchpad | `q` `py` `sql` `kxnb` | `ctrl`/`⌘`+`shift`+`delete` | +| KX: Choose Connection | `q` `py` `sql` `kxnb` | | +| KX: Choose Execution Target | `q` `py` `sql` | `ctrl`/`⌘`+`alt`+`t` | +| KX: Toggle Parameter Cache | `q` | `ctrl`/`⌘`+`shift`+`y` | + +### REPL + +| Shortcut | Action | Shortcut | Action | +| :---------- | :--------------------- | :------------- | :-------------------------------- | +| `RETURN` | Execute command line | `END` | Move cursor to end | +| `BACKSPACE` | Delete left of cursor | `shift`+`←` | Move cursor left | +| `DEL` | Delete right of cursor | `shift`+`→` | Move cursor right | +| `←` | Move cursor left | `shift`+`↑` | Move cursor up | +| `→` | Move cursor right | `shift`+`↓` | Move cursor down | +| `↑` | History | `ctrl`/`⌘`+`v` | Paste code | +| `↓` | History | `ctrl`+`c` | Stop execution (Reset on Windows) | +| `HOME` | Move cursor to start | `ctrl`+`d` | Reset | + +## Settings + +| Setting | Scope | Type | Default | +| :---------------------------------------------------------------------------------------- | :------- | :-------- | :------------ | +| [kdb.qHomeDirectory](https://github.com/KxSystems/kx-vscode/wiki/qHomeDirectory) | machine | `string` | `""` | +| kdb.servers | machine | `object` | `{}` | +| kdb.insightsEnterpriseConnections | machine | `object` | `{}` | +| kdb.connectionLabels | machine | `array` | `[]` | +| kdb.labelsConnectionMap | machine | `array` | `[]` | +| kdb.hideSurvey | machine | `boolean` | `false` | +| kdb.hideSourceExpressions | machine | `boolean` | `true` | +| kdb.hideSubscribeRegistrationNotification | machine | `boolean` | `false` | +| kdb.neverShowQInstallAgain | machine | `boolean` | `false` | +| kdb.autoFocusOutputOnEntry | machine | `boolean` | `true` | +| [kdb.qHomeDirectoryWorkspace](https://github.com/KxSystems/kx-vscode/wiki/qHomeDirectory) | resource | `string` | `""` | +| kdb.connectionMap | resource | `object` | `{}` | +| kdb.targetMap | resource | `object` | `{}` | +| kdb.linting | resource | `boolean` | `false` | +| kdb.refactoring | resource | `string` | `"Workspace"` | + +## Execution + +| Type | REPL | My q | IE SP | IE q/SQL | IE API | IE UDA | IE Populate SP | +| :--------- | :--: | :--: | :---: | :------: | :----: | :----: | :------------: | +| File `q` | ✓ | ✓ | ✓ | ✓ | | | ✓ | +| File `py` | ✓ | ✓ | ✓ | ✓ | | | ✓ | +| File `sql` | ✓ | | | ✓ | | | ✓ | +| Cell `q` | ✓ | ✓ | ✓ | ✓ | | | ✓ | +| Cell `py` | ✓ | ✓ | ✓ | ✓ | | | ✓ | +| Cell `sql` | ✓ | | | ✓ | | | ✓ | +| Datasource | | | | ✓ | ✓ | ✓ | ✓ | + +`REPL` and `My q` requires [PyKX](https://github.com/KxSystems/kx-vscode/wiki/Use-PyKX-Within-REPL) for Python support. diff --git a/package-lock.json b/package-lock.json index 886ebf4a7..10d86171b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kdb", - "version": "1.16.0", + "version": "1.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kdb", - "version": "1.16.0", + "version": "1.16.1", "license": "MIT", "dependencies": { "@ag-grid-community/core": "^32.3.5", diff --git a/package.json b/package.json index c1f50da48..ec06fa416 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.16.0", + "version": "1.16.1", "engines": { "vscode": "^1.96.0" }, @@ -107,7 +107,7 @@ }, "kdb.linting": { "type": "boolean", - "description": "Enable linting for q and quke files", + "description": "Enable linting", "default": false, "scope": "resource" }, @@ -167,7 +167,7 @@ { "category": "KX", "command": "kdb.connections.export.all", - "title": "Export connections" + "title": "Export Connections" }, { "category": "KX", @@ -177,7 +177,7 @@ { "category": "KX", "command": "kdb.connections.import", - "title": "Import connections" + "title": "Import Connections" }, { "category": "KX", @@ -188,7 +188,7 @@ { "category": "KX", "command": "kdb.datasource.create", - "title": "New KX Datasource", + "title": "New Datasource", "icon": "$(add)" }, { @@ -201,7 +201,7 @@ { "category": "KX", "command": "kdb.scratchpad.create", - "title": "New KX Workbook (q)", + "title": "New Workbook (q)", "icon": { "dark": "./resources/dark/add-scratchpad.svg", "light": "./resources/light/add-scratchpad.svg" @@ -210,7 +210,7 @@ { "category": "KX", "command": "kdb.scratchpad.python.create", - "title": "New KX Workbook (Python)", + "title": "New Workbook (Python)", "icon": { "dark": "./resources/dark/add-scratchpad-python.svg", "light": "./resources/light/add-scratchpad-python.svg" @@ -235,14 +235,14 @@ { "category": "KX", "command": "kdb.file.pickConnection", - "title": "Chooses Connection", + "title": "Choose Connection", "shortTitle": "Connection", "icon": "$(cloud)" }, { "category": "KX", "command": "kdb.file.pickTarget", - "title": "Choose Target", + "title": "Choose Execution Target", "shortTitle": "Target", "icon": "$(target)" }, @@ -252,7 +252,6 @@ "title": "Input Variable Name" }, { - "category": "KX", "command": "kdb.file.populateScratchpad", "title": "KX: Populate Scratchpad", "icon": "$(debug-rerun)" @@ -265,17 +264,17 @@ { "category": "KX", "command": "kdb.scratchpad.reset", - "title": "Reset scratchpad" + "title": "Reset Scratchpad" }, { "category": "KX", "command": "kdb.scratchpad.editor.reset", - "title": "Reset editor connection" + "title": "Reset Scratchpad" }, { "category": "KX", "command": "kdb.connections.add", - "title": "New KX Connection", + "title": "New Connection", "icon": "$(add)" }, { @@ -316,7 +315,7 @@ { "category": "KX", "command": "kdb.connections.addAuthentication", - "title": "Add Authentication", + "title": "Add authentication", "position": "end" }, { @@ -376,25 +375,21 @@ "title": "Start REPL" }, { - "category": "KX", "command": "kdb.execute.selectedQuery", "title": "KX: Execute Current Selection", "icon": "$(run-above)" }, { - "category": "KX", "command": "kdb.execute.fileQuery", "title": "KX: Execute Entire File", "icon": "$(run)" }, { - "category": "KX", "command": "kdb.scratchpad.python.run", "title": "KX: Execute Current Selection", "icon": "$(run-above)" }, { - "category": "KX", "command": "kdb.scratchpad.python.run.file", "title": "KX: Execute Entire File", "icon": "$(run)" @@ -415,7 +410,6 @@ "title": "Delete" }, { - "category": "KX", "command": "kdb.execute.block", "title": "KX: Execute Current Block", "icon": "$(run-below)" @@ -463,7 +457,7 @@ { "category": "KX", "command": "kdb.createNotebook", - "title": "New KX Notebook" + "title": "New Notebook" } ], "keybindings": [ @@ -495,7 +489,7 @@ "command": "kdb.scratchpad.editor.reset", "key": "ctrl+shift+delete", "mac": "cmd+shift+delete", - "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" + "when": "resourceFilename =~ /\\.(?:q|py|sql|kxnb)$/i" }, { "command": "kdb.file.populateScratchpad", @@ -513,13 +507,13 @@ "command": "kdb.toggleParameterCache", "key": "ctrl+shift+y", "mac": "cmd+shift+y", - "when": "resourceFilename =~ /\\.(?:q|quke)$/i" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.file.pickTarget", "key": "ctrl+alt+t", "mac": "cmd+alt+t", - "when": "kdb.connected.active && resourceFilename =~ /\\.(?:q|py)$/i" + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" } ], "snippets": [ @@ -703,11 +697,11 @@ }, { "command": "kdb.file.pickConnection", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q|py|sql|kxnb)$/i" }, { "command": "kdb.file.pickTarget", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" }, { "command": "kdb.file.inputVariable", @@ -715,7 +709,7 @@ }, { "command": "kdb.file.populateScratchpad", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q|py|sql)$/i" }, { "command": "kdb.scratchpad.run", @@ -727,7 +721,7 @@ }, { "command": "kdb.scratchpad.editor.reset", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q|py|sql|kxnb)$/i" }, { "command": "kdb.connections.add", @@ -811,19 +805,19 @@ }, { "command": "kdb.execute.selectedQuery", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { "command": "kdb.execute.fileQuery", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q|sql)$/i" }, { "command": "kdb.scratchpad.python.run", - "when": "false" + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { "command": "kdb.scratchpad.python.run.file", - "when": "false" + "when": "resourceFilename =~ /\\.(?:py)$/i" }, { "command": "kdb.ls.q.lint", @@ -839,11 +833,11 @@ }, { "command": "kdb.execute.block", - "when": "false" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.toggleParameterCache", - "when": "true" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.connections.labels.rename", @@ -1073,7 +1067,7 @@ { "command": "kdb.execute.block", "group": "q1@2", - "when": "resourceFilename =~ /\\.(?:q|sql)$/i" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.file.populateScratchpad", @@ -1095,7 +1089,7 @@ { "command": "kdb.execute.block", "group": "q@2", - "when": "resourceFilename =~ /\\.(?:q|sql)$/i" + "when": "resourceFilename =~ /\\.(?:q)$/i" }, { "command": "kdb.file.populateScratchpad", diff --git a/src/classes/insightsConnection.ts b/src/classes/insightsConnection.ts index 741203886..30a8d14f0 100644 --- a/src/classes/insightsConnection.ts +++ b/src/classes/insightsConnection.ts @@ -76,14 +76,13 @@ export class InsightsConnection { public async connect(): Promise { ext.context.secrets.delete(this.node.details.alias); - await this.getTokens().then(async (token) => { - this.connected = token ? true : false; - if (token) { - await this.getConfig(); - await this.getApiConfig(); - await this.getMeta(); - } - }); + const token = await this.getTokens(); + if (token) { + this.connected = true; + await this.getConfig(); + await this.getApiConfig(); + await this.getMeta(); + } else this.connected = false; return this.connected; } diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index 29b34cf27..6b0820523 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -90,11 +90,9 @@ export class LocalConnection { nodeq.connect(options, (err, conn) => { if (err || !conn) { ext.serverProvider.reload(); - notify( - `Connection to server ${this.options.host}:${this.options.port} failed.`, - MessageKind.ERROR, - { logger, params: err }, - ); + this.connection = undefined; + this.connected = false; + callback(err, this); return; } diff --git a/src/classes/replConnection.ts b/src/classes/replConnection.ts index b660337e7..3ab877809 100644 --- a/src/classes/replConnection.ts +++ b/src/classes/replConnection.ts @@ -269,7 +269,7 @@ export class ReplConnection { } private createProcess() { - this.env = getEnvironment(this.workspace?.uri); + this.env = getEnvironment(this.workspace); if (!this.env.qBinPath) showSetupError(this.workspace); return spawn( diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 20002c87e..4ab4b2342 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -1157,7 +1157,7 @@ export async function runQuery( } runner.title = `Executing ${executorName} on ${connLabel || "active connection"}.`; - return (target || isSql) && !variable + return !isInsights || ((target || isSql) && !variable) ? runner.execute() : needsScratchpad(connLabel, runner.execute()); } diff --git a/src/commands/setupTools.ts b/src/commands/setupTools.ts index 7eeb4c9a1..7d8d2a2f9 100644 --- a/src/commands/setupTools.ts +++ b/src/commands/setupTools.ts @@ -73,6 +73,7 @@ export function showWelcome() { ); } }); + ext.context.subscriptions.push(panel); panel.onDidDispose(() => (panel = undefined)); } /* c8 ignore stop */ @@ -241,7 +242,7 @@ async function parseOutput(execution: vscode.TerminalShellExecution) { if (home) { for (const folder of vscode.workspace.workspaceFolders || []) { if (home.startsWith(folder.uri.fsPath)) { - await setHome(home, folder); + await setHome(vscode.workspace.asRelativePath(home), folder); return; } } @@ -250,7 +251,7 @@ async function parseOutput(execution: vscode.TerminalShellExecution) { /* c8 ignore stop */ } -async function setHome(home: string, folder?: vscode.ConfigurationScope) { +async function setHome(home: string, folder?: vscode.WorkspaceFolder) { /* c8 ignore start */ const config = vscode.workspace.getConfiguration("kdb", folder); await config.update( diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index be1950485..8e32bfeac 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -252,6 +252,10 @@ export async function pickConnection(uri: Uri) { export async function pickTarget(uri: Uri, cell?: NotebookCell) { /* c8 ignore start */ + let server = getServerForUri(uri); + if (!server) server = await pickConnection(uri); + if (!server || server === ext.REPL) return; + const conn = await findConnection(uri); const isInsights = conn instanceof InsightsConnection; diff --git a/src/services/connectionManagerService.ts b/src/services/connectionManagerService.ts index 4d8e140ce..edb0fe031 100644 --- a/src/services/connectionManagerService.ts +++ b/src/services/connectionManagerService.ts @@ -125,69 +125,77 @@ export class ConnectionManagementService { public async connect(connLabel: string): Promise { /* c8 ignore start */ - const connection = this.retrieveConnection(connLabel); - if (!connection) { - return; - } - if (connection instanceof KdbNode) { - const connectionString = this.retrieveLocalConnectionString(connection); - const authCredentials = connection.details.auth - ? await ext.secretSettings.getAuthData(connection.children[0]) - : undefined; - const localConnection = new LocalConnection( - connectionString, - connLabel, - retrieveConnLabelsNames(connection), - authCredentials ? authCredentials.split(":") : undefined, - connection.details.tls, - ); - await localConnection.connect((err, conn) => { - if (err) { - this.connectFailBehaviour(connLabel); - return; - } - if (conn) { + try { + const connection = this.retrieveConnection(connLabel); + if (!connection) { + this.connectFailBehaviour( + connLabel, + new Error(`Connection ${connLabel} not found.`), + ); + return; + } + if (connection instanceof KdbNode) { + const connectionString = this.retrieveLocalConnectionString(connection); + const authCredentials = connection.details.auth + ? await ext.secretSettings.getAuthData(connection.children[0]) + : undefined; + const localConnection = new LocalConnection( + connectionString, + connLabel, + retrieveConnLabelsNames(connection), + authCredentials ? authCredentials.split(":") : undefined, + connection.details.tls, + ); + await localConnection.connect((err, conn) => { + if (err) { + this.connectFailBehaviour(connLabel, err); + return; + } + if (conn) { + notify( + `Connection established successfully to: ${connLabel}`, + MessageKind.DEBUG, + { + logger, + telemetry: + "Connection.Connected" + + this.getTelemetryConnectionType(connLabel), + }, + ); + + ext.connectedConnectionList.push(localConnection); + + this.connectSuccessBehaviour(connection); + } + }); + } else { + ext.context.secrets.delete(connection.details.alias); + const insightsConn: InsightsConnection = new InsightsConnection( + connLabel, + connection, + ); + await insightsConn.connect(); + if (insightsConn.connected) { notify( `Connection established successfully to: ${connLabel}`, MessageKind.DEBUG, { logger, + params: { insightsVersion: insightsConn.insightsVersion }, telemetry: "Connection.Connected" + this.getTelemetryConnectionType(connLabel), }, ); - - ext.connectedConnectionList.push(localConnection); - + ext.connectedConnectionList.push(insightsConn); this.connectSuccessBehaviour(connection); + } else { + this.connectFailBehaviour(connLabel); } - }); - } else { - ext.context.secrets.delete(connection.details.alias); - const insightsConn: InsightsConnection = new InsightsConnection( - connLabel, - connection, - ); - await insightsConn.connect(); - if (insightsConn.connected) { - notify( - `Connection established successfully to: ${connLabel}`, - MessageKind.DEBUG, - { - logger, - params: { insightsVersion: insightsConn.insightsVersion }, - telemetry: - "Connection.Connected" + - this.getTelemetryConnectionType(connLabel), - }, - ); - ext.connectedConnectionList.push(insightsConn); - this.connectSuccessBehaviour(connection); - } else { - this.connectFailBehaviour(connLabel); + refreshDataSourcesPanel(); } - refreshDataSourcesPanel(); + } catch (error) { + this.connectFailBehaviour(connLabel, error); } /* c8 ignore stop */ } @@ -292,9 +300,10 @@ export class ConnectionManagementService { ext.serverProvider.reload(); } - public connectFailBehaviour(connLabel: string): void { - notify(`Connection failed to: ${connLabel}`, MessageKind.ERROR, { + public connectFailBehaviour(connLabel: string, error?: unknown): void { + notify(`Connection to ${connLabel} failed.`, MessageKind.ERROR, { logger, + params: error, telemetry: "Connection.Failed" + this.getTelemetryConnectionType(connLabel), }); diff --git a/src/services/kdbInsights/codeFlowLogin.ts b/src/services/kdbInsights/codeFlowLogin.ts index 890cdf156..93cf36829 100644 --- a/src/services/kdbInsights/codeFlowLogin.ts +++ b/src/services/kdbInsights/codeFlowLogin.ts @@ -30,23 +30,56 @@ interface IDeferred { reject: (reason: any) => void; } -function getAuthUrl(insightsUrl: string, realm: string) { +const prefixMap = new Map([ + ["https://insights.example.com", "auth/"], +]); + +async function getAuthPrefix( + insightsUrl: string, + realm: string, +): Promise { + let prefix = prefixMap.get(insightsUrl); + if (prefix === undefined) { + /* c8 ignore start */ + const res = await axios.get( + new url.URL( + `${`realms/${realm}/.well-known/openid-configuration`}`, + insightsUrl, + ).toString(), + { + maxRedirects: 0, + validateStatus: () => true, + transformResponse: (res) => res, + responseType: "json", + }, + ); + prefix = res.status === 200 ? "" : "auth/"; + prefixMap.set(insightsUrl, prefix); + /* c8 ignore stop */ + } + return prefix; +} + +async function getAuthUrl(insightsUrl: string, realm: string) { + const auth = await getAuthPrefix(insightsUrl, realm); return new url.URL( - `auth/realms/${realm}/protocol/openid-connect/auth`, + `${auth}realms/${realm}/protocol/openid-connect/auth`, insightsUrl, ); } -function getTokenUrl(insightsUrl: string, realm: string) { +async function getTokenUrl(insightsUrl: string, realm: string) { + const auth = await getAuthPrefix(insightsUrl, realm); return new url.URL( - `auth/realms/${realm}/protocol/openid-connect/token`, + `${auth}realms/${realm}/protocol/openid-connect/token`, insightsUrl, ); } -function getRevokeUrl(insightsUrl: string, realm: string) { +async function getRevokeUrl(insightsUrl: string, realm: string) { + const auth = await getAuthPrefix(insightsUrl, realm); return new url.URL( - `auth/realms/${realm}/protocol/openid-connect/revoke`, + `${auth}realms/${realm}/protocol/openid-connect/revoke`, insightsUrl, ); } @@ -85,7 +118,7 @@ export async function signIn( state: crypto.randomBytes(20).toString("hex"), }; - const authorizationUrl = getAuthUrl(insightsUrl, realm); + const authorizationUrl = await getAuthUrl(insightsUrl, realm); authorizationUrl.search = queryString(authParams); @@ -115,7 +148,8 @@ export async function signOut( headers: { "Content-Type": "application/x-www-form-urlencoded" }, httpsAgent: getHttpsAgent(insecure), }; - const requestUrl = getRevokeUrl(insightsUrl, realm); + + const requestUrl = await getRevokeUrl(insightsUrl, realm); await axios.post(requestUrl.toString(), body, headers).then((res) => { return res.data; @@ -204,7 +238,7 @@ async function tokenRequest( httpsAgent: getHttpsAgent(insecure), }; - const requestUrl = getTokenUrl(insightsUrl, realm); + const requestUrl = await getTokenUrl(insightsUrl, realm); let response; if (params.grant_type === "refresh_token") { diff --git a/src/services/notebookController.ts b/src/services/notebookController.ts index 8b3dfd955..0a241f65b 100644 --- a/src/services/notebookController.ts +++ b/src/services/notebookController.ts @@ -146,7 +146,7 @@ export class KxNotebookController { ); let results = await Promise.race([ - (target || kind === CellKind.SQL) && !variable + !isInsights || ((target || kind === CellKind.SQL) && !variable) ? executor : needsScratchpad(conn.connLabel, executor), new Promise((_, reject) => { diff --git a/src/utils/core.ts b/src/utils/core.ts index 1a892425d..240c2176e 100644 --- a/src/utils/core.ts +++ b/src/utils/core.ts @@ -16,11 +16,17 @@ import { createHash } from "crypto"; import { writeFile } from "fs/promises"; import { pathExists } from "fs-extra"; import { homedir } from "node:os"; -import path from "node:path"; +import path, { isAbsolute } from "node:path"; import { tmpdir } from "os"; import { join } from "path"; import * as semver from "semver"; -import { commands, ConfigurationTarget, Uri, workspace } from "vscode"; +import { + commands, + ConfigurationTarget, + Uri, + workspace, + WorkspaceFolder, +} from "vscode"; import { ext } from "../extensionVariables"; import { tryExecuteCommand } from "./cpUtils"; @@ -126,6 +132,7 @@ export function getPlatformFolder( } function loadEnvironment(folder: string, env: { [key: string]: string }) { + /* c8 ignore start */ const data = readTextFile(path.resolve(folder, ".env")); for (const line of data.split(/\r?\n/)) { const trimmed = line.trim(); @@ -139,22 +146,24 @@ function loadEnvironment(folder: string, env: { [key: string]: string }) { } } } + /* c8 ignore stop */ } -export function getEnvironment(resource?: Uri): { [key: string]: string } { +export function getEnvironment(resource?: WorkspaceFolder): { + [key: string]: string; +} { + /* c8 ignore start */ const env: { [key: string]: string } = { ...process.env, qBinPath: "", + qBinKdbX: "true", }; if (resource) { - const target = workspace.getWorkspaceFolder(resource); - if (target) { - try { - loadEnvironment(target.uri.fsPath, env); - } catch (error) { - notify(errorMessage(error), MessageKind.DEBUG, { logger }); - } + try { + loadEnvironment(resource.uri.fsPath, env); + } catch (error) { + notify(errorMessage(error), MessageKind.DEBUG, { logger }); } } @@ -162,17 +171,29 @@ export function getEnvironment(resource?: Uri): { [key: string]: string } { .getConfiguration("kdb", resource) .get("qHomeDirectory", ""); - const qHomeDirectoryWorkspace = workspace + let qHomeDirectoryWorkspace = workspace .getConfiguration("kdb", resource) .get("qHomeDirectoryWorkspace", ""); - const home = qHomeDirectoryWorkspace || env.QHOME || qHomeDirectory || ""; + if ( + resource && + qHomeDirectoryWorkspace && + !isAbsolute(qHomeDirectoryWorkspace) + ) { + qHomeDirectoryWorkspace = join( + resource.uri.fsPath, + qHomeDirectoryWorkspace, + ); + } + + let home = qHomeDirectoryWorkspace || env.QHOME || qHomeDirectory || ""; if (home) { let q = path.resolve(home, "bin", "q"); let exists = stat(q); if (!exists) { + env.qBinKdbX = ""; const folder = getPlatformFolder(process.platform, process.arch); if (folder) { if (!exists) { @@ -193,26 +214,33 @@ export function getEnvironment(resource?: Uri): { [key: string]: string } { } } - env.QHOME = ""; + env.qBinKdbX = "true"; - const target = join(homedir(), ".kx", "bin", "q"); + home = join(homedir(), ".kx"); + const target = join(home, "bin", "q"); - if (stat(target)) env.qBinPath = target; - else - try { - for (const target of which("q")) { - if (target.endsWith(path.join("bin", "q"))) { - if (stat(target)) { - env.qBinPath = target; - break; - } + if (stat(target)) { + env.QHOME = home; + env.qBinPath = target; + return env; + } + + try { + for (const target of which("q")) { + if (target.endsWith(path.join("bin", "q"))) { + if (stat(target)) { + env.QHOME = path.resolve(path.parse(target).dir, ".."); + env.qBinPath = target; + break; } } - } catch (error) { - notify(errorMessage(error), MessageKind.DEBUG, { logger }); } + } catch (error) { + notify(errorMessage(error), MessageKind.DEBUG, { logger }); + } return env; + /* c8 ignore stop */ } export async function getWorkspaceFolder( diff --git a/src/utils/queryUtils.ts b/src/utils/queryUtils.ts index 12629e627..8d42494d2 100644 --- a/src/utils/queryUtils.ts +++ b/src/utils/queryUtils.ts @@ -227,6 +227,12 @@ export function normalizeQuery(query: string): string { .replace(/^\\[\t ]*(?:\r\n|[\r\n])[^]*/gm, "") // Remove single line comments .replace(/^\/.+/gm, "") + // Replace system commands + .replace(/^\\([a-zA-Z_1-2\\]+)[\t ]*(.*)/gm, (matched, command, args) => + matched === "\\\\" + ? 'system"\\\\"' + : `system"${command} ${args.trim().replace(/"/gs, '\\"')}"`, + ) // Remove line comments .replace( /(?:("([^"\\]*(?:\\.[^"\\]*)*)")|([ \t]+\/.*))/gm, @@ -256,7 +262,7 @@ export function normalizeQSQLQuery(query: string): string { .replace(/^\\([a-zA-Z_1-2\\]+)[\t ]*(.*)/gm, (matched, command, args) => matched === "\\\\" ? 'system"\\\\"' - : `system"${command} ${args.trim()}"`, + : `system"${command} ${args.trim().replace(/"/gs, '\\"')}"`, ) // Trim white space .trim() diff --git a/src/webview/components/kdbWelcomeView.ts b/src/webview/components/kdbWelcomeView.ts index 265c7ed71..964b9c4e6 100644 --- a/src/webview/components/kdbWelcomeView.ts +++ b/src/webview/components/kdbWelcomeView.ts @@ -16,13 +16,12 @@ import { LitElement, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { kdbStyles, shoelaceStyles } from "./styles"; +import { shoelaceStyles } from "./styles"; @customElement("kdb-welcome-view") export class KdbWelcomeView extends LitElement { static readonly styles = [ shoelaceStyles, - kdbStyles, css` .container { padding: 1em 4em 0 4em; @@ -38,8 +37,6 @@ export class KdbWelcomeView extends LitElement { display: flex; flex-wrap: nowrap; flex-direction: row; - } - .gap-1 { gap: 1em; } .mt-1 { @@ -66,6 +63,10 @@ export class KdbWelcomeView extends LitElement { text-align: center; background-color: var(--vscode-editor-background); } + body { + margin: 0; + padding: 0; + } a { color: var(--vscode-textLink-foreground); text-decoration: none; @@ -95,7 +96,7 @@ export class KdbWelcomeView extends LitElement {

Welcome to KDB-X

-
+

KDB-X is the next generation of kdb+, optimized for modern @@ -105,11 +106,8 @@ export class KdbWelcomeView extends LitElement {

${renderIcon1(this.dark)}
- Sign in or create an account -
- A browser opens → enter email → verify with code → accept - EULA. -
+ Log in or create an account +
A browser opens → authenticate → accept EULA.
@@ -124,8 +122,7 @@ export class KdbWelcomeView extends LitElement {
Activate in VS Code
- Paste the key in the extension → terminal installs KDB-X - runtime. + Paste the key in VS Code → terminal installs KDB-X runtime.
@@ -134,7 +131,8 @@ export class KdbWelcomeView extends LitElement {
Start coding
- Installation completes → switch to KX Extension tab. + Installation completes → run command + KX: Start REPL from the command palette.
@@ -392,4 +390,4 @@ function renderIcon5() { `; } -/* c8 ignore end */ +/* c8 ignore stop */ diff --git a/test/suite/commands/commands.test.ts b/test/suite/commands/commands.test.ts new file mode 100644 index 000000000..a520d99c7 --- /dev/null +++ b/test/suite/commands/commands.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +describe("Commands", () => { + const commands = new Map(); + let palette: any; + let keybindings: any; + + before(() => { + const config = JSON.parse( + readFileSync(resolve(__dirname, "..", "..", "..", "..", "package.json"), { + encoding: "utf8", + }), + ); + config.contributes.commands.forEach((cmd: any) => + commands.set(cmd.command, cmd.title), + ); + palette = config.contributes.menus.commandPalette; + keybindings = config.contributes.keybindings; + }); + + describe("Command Palette", () => { + it("should include all commands", () => { + assert.strictEqual(palette.length, commands.size); + for (const cmd of palette) { + assert.ok(commands.get(cmd.command)); + } + }); + it("should have certain always visible commands", () => { + const visible = [ + "kdb.show.welcome", + "kdb.install.kdbx", + "kdb.connections.export.all", + "kdb.connections.import", + "kdb.datasource.create", + "kdb.scratchpad.create", + "kdb.scratchpad.python.create", + "kdb.connections.add", + "kdb.repl.start", + "kdb.createNotebook", + ]; + const shown = palette.filter((cmd: any) => cmd.when === "true"); + assert.strictEqual(shown.length, visible.length); + for (let i = 0; i < visible.length; i++) { + assert.strictEqual(visible[i], shown[i].command); + } + }); + it("should have certain visible commands depending on context", () => { + const visible = [ + "kdb.file.pickConnection", + "kdb.file.pickTarget", + "kdb.file.populateScratchpad", + "kdb.scratchpad.editor.reset", + "kdb.execute.selectedQuery", + "kdb.execute.fileQuery", + "kdb.scratchpad.python.run", + "kdb.scratchpad.python.run.file", + "kdb.execute.block", + "kdb.toggleParameterCache", + ]; + const shown = palette.filter( + (cmd: any) => cmd.when !== "true" && cmd.when !== "false", + ); + assert.strictEqual(shown.length, visible.length); + for (let i = 0; i < visible.length; i++) { + assert.strictEqual(visible[i], shown[i].command); + } + }); + }); + + describe("Keybindings", () => { + it("should have a valid command", () => { + for (const cmd of keybindings) { + assert.ok(commands.get(cmd.command)); + } + }); + it("should exist for certain commands", () => { + const bindings = [ + "kdb.execute.selectedQuery", + "kdb.execute.fileQuery", + "kdb.scratchpad.python.run", + "kdb.scratchpad.python.run.file", + "kdb.scratchpad.editor.reset", + "kdb.file.populateScratchpad", + "kdb.execute.block", + "kdb.toggleParameterCache", + "kdb.file.pickTarget", + ]; + assert.strictEqual(bindings.length, keybindings.length); + for (let i = 0; i < bindings.length; i++) { + assert.strictEqual(bindings[i], keybindings[i].command); + } + }); + }); +}); diff --git a/test/suite/docs/docs.test.ts b/test/suite/docs/docs.test.ts new file mode 100644 index 000000000..f2bf86e59 --- /dev/null +++ b/test/suite/docs/docs.test.ts @@ -0,0 +1,61 @@ +/* + * 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 { readdirSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +describe("Docs", () => { + let readme: string; + let images: string[]; + + before(() => { + const root = resolve(__dirname, "..", "..", "..", ".."); + readme = readFileSync(resolve(root, "README.md"), { + encoding: "utf8", + }); + images = readdirSync(resolve(root, ".README")); + }); + + describe("README", () => { + it("should contain secure external links", () => { + const regex = /\[.*?\]\((.*?)\)/gs; + let match: RegExpExecArray; + while ((match = regex.exec(readme))) { + if (!match[1].startsWith("#")) { + assert.ok(match[1].startsWith("https://")); + } + } + }); + it("should contain all images", () => { + for (const img of images) { + const index = readme.indexOf(`/${img}`); + assert.ok(index !== -1); + } + }); + it("should have valid links to images", () => { + const regex = /\(https:\/\/([^)]*)\/.README\/([^)]*)\)/gs; + const linked = new Set(); + let match: RegExpExecArray; + while ((match = regex.exec(readme))) { + assert.strictEqual( + match[1], + "raw.githubusercontent.com/KxSystems/kx-vscode/main", + ); + assert.ok(images.includes(match[2])); + linked.add(match[2]); + } + assert.strictEqual(linked.size, images.length); + }); + }); +}); diff --git a/test/suite/services/codeFlowLogin.test.ts b/test/suite/services/codeFlowLogin.test.ts index d70debfdb..1adafa088 100644 --- a/test/suite/services/codeFlowLogin.test.ts +++ b/test/suite/services/codeFlowLogin.test.ts @@ -65,7 +65,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); await codeFlow.signOut( - "https://insights.com", + "https://insights.example.com", "realm1", false, "token123", @@ -76,7 +76,7 @@ describe("CodeFlowLogin", () => { assert.strictEqual( url, - "https://insights.com/auth/realms/realm1/protocol/openid-connect/revoke", + "https://insights.example.com/auth/realms/realm1/protocol/openid-connect/revoke", ); assert.strictEqual( config.headers["Content-Type"], @@ -89,7 +89,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); await codeFlow.signOut( - "https://insights.com", + "https://insights.example.com", "realm1", true, "token123", @@ -112,7 +112,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); const result = await codeFlow.refreshToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "refresh_token_123", @@ -127,7 +127,7 @@ describe("CodeFlowLogin", () => { axiosStub.rejects(new Error("Token expired")); const result = await codeFlow.refreshToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "invalid_refresh_token", @@ -147,7 +147,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); await codeFlow.refreshToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "refresh_token_123", @@ -248,7 +248,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); const result = await codeFlow.getToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "auth_code_123", @@ -270,7 +270,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); await codeFlow.getToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "auth_code_123", @@ -286,7 +286,7 @@ describe("CodeFlowLogin", () => { try { await codeFlow.getToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "auth_code_123", @@ -328,7 +328,7 @@ describe("CodeFlowLogin", () => { const beforeTime = new Date(); const result = await codeFlow.getToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "code", @@ -353,7 +353,7 @@ describe("CodeFlowLogin", () => { axiosStub.resolves(mockResponse); const result = await codeFlow.getToken( - "https://insights.com", + "https://insights.example.com", "realm1", false, "code", @@ -379,7 +379,12 @@ describe("CodeFlowLogin", () => { }; axiosStub.resolves(mockResponse); - await codeFlow.getToken("https://insights.com", "realm1", false, "code"); + await codeFlow.getToken( + "https://insights.example.com", + "realm1", + false, + "code", + ); const [, body] = axiosStub.getCall(0).args; assert.ok(body.includes("client_id=insights-app")); diff --git a/test/suite/services/connectionManagementService.test.ts b/test/suite/services/connectionManagementService.test.ts index 1dcd9d063..4b3a3eedd 100644 --- a/test/suite/services/connectionManagementService.test.ts +++ b/test/suite/services/connectionManagementService.test.ts @@ -352,7 +352,7 @@ describe("ConnectionManagementService", () => { connectionManagerService.connectFailBehaviour(testLabel); sinon.assert.calledWith( showErrorMessageStub, - `Connection failed to: ${testLabel}`, + `Connection to ${testLabel} failed.`, ); sinon.assert.calledWith(sendEventStub, "Connection.Failed.KDB+"); }); diff --git a/test/suite/utils/core.test.ts b/test/suite/utils/core.test.ts index 94f8fa61b..71903f51d 100644 --- a/test/suite/utils/core.test.ts +++ b/test/suite/utils/core.test.ts @@ -891,6 +891,14 @@ describe("core", () => { afterEach(() => { sinon.restore(); }); + it("should have qBinPath variable", () => { + const env = coreUtils.getEnvironment(); + assert.notStrictEqual(env.qBinPath, undefined); + }); + it("should have qBinKdbX variable", () => { + const env = coreUtils.getEnvironment(); + assert.notStrictEqual(env.qBinKdbX, undefined); + }); }); describe("checkLocalInstall", () => {