From 99eaba0c9ebee35e6002ac1abd4c9bac3511b7af Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:19:09 -0500 Subject: [PATCH 1/2] get ready for a new release 2.0.6 --- client/src/test/completion.test.ts | 43 ------------------------------ 1 file changed, 43 deletions(-) delete mode 100644 client/src/test/completion.test.ts diff --git a/client/src/test/completion.test.ts b/client/src/test/completion.test.ts deleted file mode 100644 index 37d036e..0000000 --- a/client/src/test/completion.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - -import * as vscode from 'vscode'; -import * as assert from 'assert'; -import { getDocUri, activate } from './helper'; - -suite('Should do completion', () => { - const docUri = getDocUri('completion.txt'); - - test('Completes JS/TS in txt file', async () => { - await testCompletion(docUri, new vscode.Position(0, 0), { - items: [ - { label: 'JavaScript', kind: vscode.CompletionItemKind.Text }, - { label: 'TypeScript', kind: vscode.CompletionItemKind.Text }, - ], - }); - }); -}); - -async function testCompletion( - docUri: vscode.Uri, - position: vscode.Position, - expectedCompletionList: vscode.CompletionList, -) { - await activate(docUri); - - // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion - const actualCompletionList = (await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - docUri, - position, - )) as vscode.CompletionList; - - assert.ok(actualCompletionList.items.length >= 2); - expectedCompletionList.items.forEach((expectedItem, i) => { - const actualItem = actualCompletionList.items[i]; - assert.equal(actualItem.label, expectedItem.label); - assert.equal(actualItem.kind, expectedItem.kind); - }); -} From 83e77acd399faeb4472528fe7c625a9913aaaf96 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:19:29 -0500 Subject: [PATCH 2/2] more for the 2.0.6 release --- .gitignore | 2 - CHANGELOG.md | 10 + CONTRIBUTING.md | 12 +- client/package-lock.json | 22 +- client/package.json | 6 +- client/src/extension.ts | 348 +++++++++++++++------------- client/src/test/completion.test.ts | 40 ++++ client/src/test/diagnostics.test.ts | 67 ++---- client/src/test/helper.ts | 48 ++-- client/src/test/hover.test.ts | 32 +++ client/src/test/index.ts | 46 ++-- client/src/test/runTest.ts | 15 +- client/testFixture/completion.nu | 1 + client/testFixture/diagnostics.nu | 1 + client/testFixture/hover.nu | 1 + client/tsconfig.json | 4 +- generate-example.nu | 15 +- generate-patterns.nu | 157 +++++++------ language-configuration.json | 12 +- package-lock.json | 18 +- package.json | 31 +-- snippets/nushell.json | 45 +--- syntaxes/codeblock.json | 2 +- syntaxes/nushell.tmLanguage.json | 33 +-- tests/cases/review.nu | 11 + tests/cases/review.nu.snap | 159 +++++++++++++ tsconfig.json | 5 +- 27 files changed, 687 insertions(+), 456 deletions(-) create mode 100644 client/src/test/completion.test.ts create mode 100644 client/src/test/hover.test.ts create mode 100644 client/testFixture/completion.nu create mode 100644 client/testFixture/diagnostics.nu create mode 100644 client/testFixture/hover.nu create mode 100644 tests/cases/review.nu create mode 100644 tests/cases/review.nu.snap diff --git a/.gitignore b/.gitignore index f35f04c..14b6ad9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ dist # ignore the git mailmap file .mailmap client/out -client/testFixture/completion.txt -client/testFixture/diagnostics.txt client/tsconfig.tsbuildinfo .vscode-test out/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0754450..ac24e88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to the "vscode-nushell-lang" extension will be documented in ## Release +- 2.0.6 Release + - Update grammar command list for nushell 0.116.0 [#239](https://github.com/nushell/vscode-nushell-lang/pull/239) + - Fix grammar for filesize units, string escapes, negative timezone offsets, `table<>`/`oneof<>` types, redirection operators, `0b[]`/`0o[]` literals, and `$in` [#239](https://github.com/nushell/vscode-nushell-lang/pull/239) + - Highlight ` ```nu ` code blocks in markdown [#239](https://github.com/nushell/vscode-nushell-lang/pull/239) + - Fix language server start/stop commands, fall back to `nu` on PATH when the configured executable is missing, and remove settings that `nu --lsp` never used [#239](https://github.com/nushell/vscode-nushell-lang/pull/239) + - Remove snippets for commands that no longer exist and fix `export-env` [#239](https://github.com/nushell/vscode-nushell-lang/pull/239) + - Rewrite e2e tests against `nu --lsp` and fix `generate-patterns.nu`/`generate-example.nu` for current nushell [#239](https://github.com/nushell/vscode-nushell-lang/pull/239) + - Syntax highlighting broken for record keys containing brackets [#232](https://github.com/nushell/vscode-nushell-lang/pull/232) + - Syntax highlighting broken for variable declarations with type annotations [#234](https://github.com/nushell/vscode-nushell-lang/pull/234) + - Syntax highlighting broken for bare string values in records [#237](https://github.com/nushell/vscode-nushell-lang/pull/237) - 2.0.5 Release - Update `def` syntax [#222](https://github.com/nushell/vscode-nushell-lang/pull/222) - add `<>` to `autoClosingPairs` [#227](https://github.com/nushell/vscode-nushell-lang/pull/227) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9780216..f23d90b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ If you have all these tools already installed, you should be able to clone this 2. In repo folder `npm install` 3. Hit F5 to start debugging (or Run->Start Debugging menu item) 4. Go to settings with `Ctrl ,` or `Cmd ,` -5. In the settings tree on the left, go to Extensions->Nushell IDE Support and make sure `Nushell Executable Path` is pointing at where you have nu/nu.exe installed. (It must be version 0.79.0 or greater) +5. In the settings tree on the left, go to Extensions->Nushell IDE Support and make sure `Nushell Executable Path` is pointing at where you have nu/nu.exe installed. (The extension talks to `nu --lsp`, so it must be a version that supports it; a current release is recommended.) 6. Open a nushell script and in a moment you should see inlays and see the full functionality 7. To see the Language Server debug messages hit `Ctrl ~` 8. Go to the output tab @@ -50,7 +50,13 @@ glcraft wrote a fancy program to create regexes for the extension. Here's the st 1. clone and cargo install the tool. https://github.com/glcraft/list-to-tree 2. on windows create a set of commands. `scope commands | where type == built-in or type == keyword or type == plugin | get name | to text | save win-cmds_20240923.txt` 3. on linux create a set of commands. `scope commands | where type == built-in or type == keyword or type == plugin | get name | to text | save lin-cmds_20240923.txt` -4. combine these two files, sort, and uniq them. `open win-cmds_20230919.txt | lines | append (open lin-cmds_20230919.txt | lines) | sort | uniq | save cmds_20230919.txt` -5. run list-to-tree `list-to-tree --input cmds_20230919.txt --format regex` +4. combine these two files, sort, and uniq them. `open win-cmds_20240923.txt | lines | append (open lin-cmds_20240923.txt | lines) | sort | uniq | save cmds_20240923.txt` +5. run list-to-tree `list-to-tree --input cmds_20240923.txt --format regex` 6. copy-n-paste the results to the `nushell.tmLanguage.json` file in the appropriate place (search for "list-to-tree"). Be careful, this can be tricky. 7. test out the changes with F5 and viewing some scripts. + +The `generate-patterns.nu` script does steps 5 and 6 for you: `nu generate-patterns.nu --input cmds_20240923.txt` writes the regex into the grammar, and `--dry-run` prints it instead. Without `--input` it uses the commands of the `nu` that runs it, which includes whatever plugins you have registered. + +## Grammar tests + +`npm run test:grammar` checks the grammar against the snapshots in `tests/cases`. After an intentional grammar change, refresh a snapshot with `npx vscode-tmgrammar-snap --updateSnapshot tests/cases/.nu` and review the diff. diff --git a/client/package-lock.json b/client/package-lock.json index f197290..2dcb5c7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "nushell-lsp-client", - "version": "2.0.3", + "version": "2.0.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nushell-lsp-client", - "version": "2.0.3", + "version": "2.0.6", "license": "MIT", "dependencies": { "vscode-languageclient": "9.0.1", @@ -15,10 +15,10 @@ "devDependencies": { "@types/vscode": "1.110.0", "@types/which": "3.0.4", - "@vscode/test-electron": "2.5.2" + "@vscode/test-electron": "^3.1.0" }, "engines": { - "vscode": "^1.93.0" + "vscode": "^1.110.0" } }, "node_modules/@types/vscode": { @@ -36,9 +36,9 @@ "license": "MIT" }, "node_modules/@vscode/test-electron": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", - "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.1.0.tgz", + "integrity": "sha512-CRqv5u+YYoseuNVJ6Tyo4k0sF0mx4qnKMihRB0PjsUF8Dc0WKtCXo6CNL6nWWm5esfFQsQA/pejMj4ZbpJVLTw==", "dev": true, "license": "MIT", "dependencies": { @@ -49,7 +49,7 @@ "semver": "^7.6.2" }, "engines": { - "node": ">=16" + "node": ">=22" } }, "node_modules/agent-base": { @@ -602,9 +602,9 @@ "dev": true }, "@vscode/test-electron": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", - "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.1.0.tgz", + "integrity": "sha512-CRqv5u+YYoseuNVJ6Tyo4k0sF0mx4qnKMihRB0PjsUF8Dc0WKtCXo6CNL6nWWm5esfFQsQA/pejMj4ZbpJVLTw==", "dev": true, "requires": { "http-proxy-agent": "^7.0.2", diff --git a/client/package.json b/client/package.json index b667541..6562c72 100644 --- a/client/package.json +++ b/client/package.json @@ -3,14 +3,14 @@ "description": "VSCode part of the nushell language server", "author": "Nushell Maintainers", "license": "MIT", - "version": "2.0.3", + "version": "2.0.6", "publisher": "TheNuProjectContributors", "repository": { "type": "git", "url": "https://github.com/nushell/vscode-nushell-lang" }, "engines": { - "vscode": "^1.93.0" + "vscode": "^1.110.0" }, "dependencies": { "vscode-languageclient": "9.0.1", @@ -19,6 +19,6 @@ "devDependencies": { "@types/vscode": "1.110.0", "@types/which": "3.0.4", - "@vscode/test-electron": "2.5.2" + "@vscode/test-electron": "3.1.0" } } diff --git a/client/src/extension.ts b/client/src/extension.ts index cc5319d..a5ff9be 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ +import * as os from 'os'; +import * as path from 'path'; import * as vscode from 'vscode'; import * as which from 'which'; import { window, type OutputChannel } from 'vscode'; @@ -15,86 +17,140 @@ import { RevealOutputChannelOn, } from 'vscode-languageclient/node'; +const EXTENSION_ID = 'TheNuProjectContributors.vscode-nushell-lang'; +const CONFIG_SECTION = 'nushellLanguageServer'; + let client: LanguageClient | undefined; +let fileWatcher: vscode.FileSystemWatcher | undefined; let outputChannel: OutputChannel | undefined; // Single output channel for server logs and trace +function expandHome(p: string): string { + if (p === '~' || p.startsWith('~/') || p.startsWith('~\\')) { + return path.join(os.homedir(), p.slice(1)); + } + return p; +} + +/** + * Resolve the nushell executable: the configured path if it exists, + * otherwise `nu` on PATH. Returns null when nothing usable is found. + */ function findNushellExecutable(): string | null { try { - // Get the configured executable path from VSCode settings - // Use null for resource to get global/workspace settings - const config = vscode.workspace.getConfiguration( - 'nushellLanguageServer', - null, - ); - const configuredPath = config.get('nushellExecutablePath', 'nu'); + const config = vscode.workspace.getConfiguration(CONFIG_SECTION, null); + const configuredPath = config + .get('nushellExecutablePath', 'nu') + .trim(); - // If user configured a specific path, try to find it if (configuredPath && configuredPath !== 'nu') { - // User specified a custom path - try { - // Test if the configured path works - return which.sync(configuredPath, { nothrow: true }); - } catch { - // Fall back to searching PATH for 'nu' + const found = which.sync(expandHome(configuredPath), { nothrow: true }); + if (found) { + return found; } + void vscode.window.showWarningMessage( + `Configured nushell executable '${configuredPath}' was not found. Falling back to 'nu' on PATH.`, + ); } - // Fall back to searching PATH for 'nu' return which.sync('nu', { nothrow: true }); } catch { return null; } } -function startLanguageServer( - context: vscode.ExtensionContext, - found_nushell_path: string, -): void { - // Prevent duplicate clients/channels +function showNushellNotFound(): void { + void vscode.window + .showErrorMessage( + 'Nushell executable not found. Install Nushell or set "nushellLanguageServer.nushellExecutablePath", then run "Nushell: Start Language Server".', + 'Install from website', + ) + .then((selection) => { + if (selection) { + void vscode.env.openExternal( + vscode.Uri.parse('https://www.nushell.sh/'), + ); + } + }); +} + +function getOutputChannel(context: vscode.ExtensionContext): OutputChannel { + if (!outputChannel) { + outputChannel = window.createOutputChannel('Nushell Language Server'); + context.subscriptions.push(outputChannel); + } + return outputChannel; +} + +function log(message: string): void { + try { + outputChannel?.appendLine(`[Nushell] ${message}`); + } catch { + // ignore + } +} + +type TraceLevel = 'off' | 'messages' | 'verbose'; + +function traceLevelFromConfig(): TraceLevel { + const configured = vscode.workspace + .getConfiguration(CONFIG_SECTION) + .get('trace.server'); + return configured ?? 'messages'; +} + +function applyTraceFromConfig(): void { + const level = traceLevelFromConfig(); + const map: Record = { + off: Trace.Off, + messages: Trace.Messages, + verbose: Trace.Verbose, + }; + void client?.setTrace(map[level]); + log(`JSON-RPC tracing set to: ${level}`); +} + +/** + * Start `nu --lsp` and connect the language client to it. + * Returns true when a new client was started. + */ +function startLanguageServer(context: vscode.ExtensionContext): boolean { if (client) { - vscode.window.showInformationMessage( + void vscode.window.showInformationMessage( 'Nushell Language Server is already running.', ); - return; + return false; } - // Channel to receive both server logs and JSON-RPC trace between VS Code and the LSP server - if (outputChannel) { - try { - outputChannel.dispose(); - } catch { - // ignore - } + + // Resolve the executable on every start so a changed setting or a fresh + // install is picked up without reloading the window. + const nushellPath = findNushellExecutable(); + if (!nushellPath) { + showNushellNotFound(); + return false; } - outputChannel = window.createOutputChannel('Nushell Language Server'); - context.subscriptions.push(outputChannel); + + const channel = getOutputChannel(context); // Use Nushell's native LSP server const serverOptions: ServerOptions = { - run: { - command: found_nushell_path, - args: ['--lsp'], - }, - debug: { - command: found_nushell_path, - args: ['--lsp'], - }, + run: { command: nushellPath, args: ['--lsp'] }, + debug: { command: nushellPath, args: ['--lsp'] }, }; + fileWatcher = vscode.workspace.createFileSystemWatcher('**/*.nu'); + // Options to control the language client const clientOptions: LanguageClientOptions = { // Route general server logs to a single channel - outputChannel: outputChannel, + outputChannel: channel, // Never auto-reveal the server output channel revealOutputChannelOn: RevealOutputChannelOn.Never, // Send JSON-RPC trace to the same channel as server logs - traceOutputChannel: outputChannel, + traceOutputChannel: channel, markdown: { isTrusted: true, supportHtml: true, }, - initializationOptions: { - timeout: 10000, // 10 seconds - }, // Register the server for nushell files documentSelector: [ { scheme: 'file', language: 'nushell' }, @@ -102,75 +158,65 @@ function startLanguageServer( ], synchronize: { // Notify the server about file changes to nushell files - fileEvents: vscode.workspace.createFileSystemWatcher('**/*.nu'), + fileEvents: fileWatcher, }, }; // Create the language client and start the client. - client = new LanguageClient( - 'nushellLanguageServer', + const newClient = new LanguageClient( + CONFIG_SECTION, 'Nushell Language Server', serverOptions, clientOptions, ); + client = newClient; - // Initialize trace level from settings and react to changes - const applyTraceFromConfig = () => { - const configured = vscode.workspace - .getConfiguration('nushellLanguageServer') - .get<'off' | 'messages' | 'verbose'>('trace.server'); - const level: 'off' | 'messages' | 'verbose' = configured ?? 'messages'; - const map: Record<'off' | 'messages' | 'verbose', Trace> = { - off: Trace.Off, - messages: Trace.Messages, - verbose: Trace.Verbose, - }; - client?.setTrace(map[level]); - try { - outputChannel.appendLine(`[Nushell] JSON-RPC tracing set to: ${level}`); - } catch { - // ignore - } - }; - applyTraceFromConfig(); - const cfgDisp = vscode.workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('nushellLanguageServer.trace.server')) { - applyTraceFromConfig(); - } - }); - context.subscriptions.push(cfgDisp); // Log client lifecycle - client.onDidChangeState((e) => { - try { - outputChannel.appendLine(`[Nushell] Client state changed: ${e.newState}`); - } catch { - // ignore - } + newClient.onDidChangeState((e) => { + log(`Client state changed: ${e.newState}`); }); - // Start the language client and register a disposable that stops it when disposed - client.start().catch((error) => { - vscode.window.showErrorMessage( - `Failed to start Nushell language server: ${error.message}`, - ); - }); + log(`Starting language server: ${nushellPath} --lsp`); + applyTraceFromConfig(); - const disposable = new vscode.Disposable(() => { - if (client) { - client.stop().catch((error) => { - console.error( - 'Failed to stop Nushell Language Server on dispose:', - error, - ); - }); + newClient.start().catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + log(`Failed to start language server: ${message}`); + void vscode.window.showErrorMessage( + `Failed to start Nushell language server: ${message}`, + ); + // Leave things in a state where "Nushell: Start Language Server" can retry. + if (client === newClient) { + client = undefined; } + fileWatcher?.dispose(); + fileWatcher = undefined; }); - context.subscriptions.push(disposable); + + return true; +} + +async function stopLanguageServer(): Promise { + if (!client) { + return false; + } + const running = client; + client = undefined; + fileWatcher?.dispose(); + fileWatcher = undefined; + try { + await running.stop(); + log('Language server stopped.'); + } catch (error) { + log(`Failed to stop language server: ${error}`); + throw error; + } + return true; } export function activate(context: vscode.ExtensionContext) { - // Find Nushell executable once and reuse it - const found_nushell_path = findNushellExecutable(); + console.log(`Activating ${EXTENSION_ID}.`); + getOutputChannel(context); context.subscriptions.push( vscode.window.registerTerminalProfileProvider('nushell_default', { @@ -179,17 +225,16 @@ export function activate(context: vscode.ExtensionContext) { ): vscode.ProviderResult { // Consume token to satisfy no-unused-vars without changing behavior void token; - if (!found_nushell_path) { - void vscode.window.showErrorMessage( - 'Nushell executable not found in your PATH or configured location.', - ); + const nushellPath = findNushellExecutable(); + if (!nushellPath) { + showNushellNotFound(); return undefined; } return { options: { name: 'Nushell', - shellPath: found_nushell_path, + shellPath: nushellPath, iconPath: vscode.Uri.joinPath( context.extensionUri, 'assets/nu.svg', @@ -200,81 +245,68 @@ export function activate(context: vscode.ExtensionContext) { }), ); - // Check if Nushell was found for LSP server - if (!found_nushell_path) { - vscode.window - .showErrorMessage( - 'Nushell executable not found. Please install Nushell and restart VSCode.', - 'Install from website', - ) - .then((selection) => { - if (selection) { - vscode.env.openExternal(vscode.Uri.parse('https://www.nushell.sh/')); - } - }); - return; - } - - console.log(`Found nushell executable at: ${found_nushell_path}`); - console.log('Activating Nushell Language Server extension.'); + // React to trace level changes for the lifetime of the extension + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration(`${CONFIG_SECTION}.trace.server`)) { + applyTraceFromConfig(); + } + }), + ); - // Start the language server when the extension is activated - startLanguageServer(context, found_nushell_path); - - // Register a command to stop the language server - const stopCommand = vscode.commands.registerCommand( - 'nushell.stopLanguageServer', - async () => { - if (client) { - try { - await client.stop(); - client = undefined; - vscode.window.showInformationMessage( + // Commands are registered before the server starts so they keep working + // even when nushell was not found at activation time. + context.subscriptions.push( + vscode.commands.registerCommand('nushell.startLanguageServer', () => { + if (startLanguageServer(context)) { + void vscode.window.showInformationMessage( + 'Nushell Language Server started.', + ); + } + }), + vscode.commands.registerCommand('nushell.stopLanguageServer', async () => { + try { + if (await stopLanguageServer()) { + void vscode.window.showInformationMessage( 'Nushell Language Server stopped.', ); - } catch (error) { - vscode.window.showErrorMessage( - `Failed to stop Nushell Language Server: ${error}`, + } else { + void vscode.window.showInformationMessage( + 'Nushell Language Server is not running.', ); } - } else { - vscode.window.showInformationMessage( - 'Nushell Language Server is not running.', + } catch (error) { + void vscode.window.showErrorMessage( + `Failed to stop Nushell Language Server: ${error}`, ); } - }, - ); - context.subscriptions.push(stopCommand); - - // Register a command to open documentation - const openDocsCommand = vscode.commands.registerCommand( - 'nushell.openDocs', - async () => { + }), + vscode.commands.registerCommand('nushell.openDocs', async () => { await vscode.env.openExternal( vscode.Uri.parse('https://www.nushell.sh/book/'), ); - }, + }), ); - context.subscriptions.push(openDocsCommand); - - // Register a command to start the language server - const startCommand = vscode.commands.registerCommand( - 'nushell.startLanguageServer', - () => { - startLanguageServer(context, found_nushell_path); - if (client) { - vscode.window.showInformationMessage( - 'Nushell Language Server started.', + + // Make sure the server is stopped when the extension is disposed + context.subscriptions.push( + new vscode.Disposable(() => { + stopLanguageServer().catch((error) => { + console.error( + 'Failed to stop Nushell Language Server on dispose:', + error, ); - } - }, + }); + }), ); - context.subscriptions.push(startCommand); + + // Start the language server when the extension is activated + startLanguageServer(context); } export function deactivate(): Thenable | undefined { if (!client) { return undefined; } - return client.stop(); + return stopLanguageServer().then(() => undefined); } diff --git a/client/src/test/completion.test.ts b/client/src/test/completion.test.ts new file mode 100644 index 0000000..4dec13a --- /dev/null +++ b/client/src/test/completion.test.ts @@ -0,0 +1,40 @@ +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import { getDocUri, activate, waitFor } from './helper'; + +function labelsOf(list: vscode.CompletionList): string[] { + return list.items.map((item) => + typeof item.label === 'string' ? item.label : item.label.label, + ); +} + +suite('Completion from nu --lsp', () => { + const docUri = getDocUri('completion.nu'); + + test('completes a built-in subcommand', async () => { + await activate(docUri); + + // `"hello" | str upc` -> end of line + const position = new vscode.Position(0, 17); + let lastLabels: string[] = []; + // VS Code also offers word-based and snippet suggestions, so keep polling + // until the language server's own item shows up. + const labels = await waitFor(async () => { + const result = (await vscode.commands.executeCommand( + 'vscode.executeCompletionItemProvider', + docUri, + position, + )) as vscode.CompletionList; + lastLabels = result ? labelsOf(result) : []; + return lastLabels.some((label) => label.includes('upcase')) + ? lastLabels + : undefined; + }).catch((error) => { + throw new Error( + `expected a completion containing 'upcase', got: ${lastLabels.join(', ')} (${error})`, + ); + }); + + assert.ok(labels.some((label) => label.includes('upcase'))); + }); +}); diff --git a/client/src/test/diagnostics.test.ts b/client/src/test/diagnostics.test.ts index d262f87..c624282 100644 --- a/client/src/test/diagnostics.test.ts +++ b/client/src/test/diagnostics.test.ts @@ -1,59 +1,22 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - import * as vscode from 'vscode'; import * as assert from 'assert'; -import { getDocUri, activate } from './helper'; - -suite('Should get diagnostics', () => { - const docUri = getDocUri('diagnostics.txt'); +import { getDocUri, activate, waitFor } from './helper'; - test('Diagnoses uppercase texts', async () => { - await testDiagnostics(docUri, [ - { - message: 'ANY is all uppercase.', - range: toRange(0, 0, 0, 3), - severity: vscode.DiagnosticSeverity.Warning, - source: 'ex', - }, - { - message: 'ANY is all uppercase.', - range: toRange(0, 14, 0, 17), - severity: vscode.DiagnosticSeverity.Warning, - source: 'ex', - }, - { - message: 'OS is all uppercase.', - range: toRange(0, 18, 0, 20), - severity: vscode.DiagnosticSeverity.Warning, - source: 'ex', - }, - ]); - }); -}); +suite('Diagnostics from nu --lsp', () => { + const docUri = getDocUri('diagnostics.nu'); -function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { - const start = new vscode.Position(sLine, sChar); - const end = new vscode.Position(eLine, eChar); - return new vscode.Range(start, end); -} + test('reports a parse error for an incomplete let', async () => { + await activate(docUri); -async function testDiagnostics( - docUri: vscode.Uri, - expectedDiagnostics: vscode.Diagnostic[], -) { - await activate(docUri); + const diagnostics = await waitFor(() => { + const found = vscode.languages.getDiagnostics(docUri); + return found.length > 0 ? found : undefined; + }); - const actualDiagnostics = vscode.languages.getDiagnostics(docUri); - - assert.equal(actualDiagnostics.length, expectedDiagnostics.length); - - expectedDiagnostics.forEach((expectedDiagnostic, i) => { - const actualDiagnostic = actualDiagnostics[i]; - assert.equal(actualDiagnostic.message, expectedDiagnostic.message); - assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range); - assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity); + assert.ok( + diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error), + `expected an error diagnostic, got: ${JSON.stringify(diagnostics)}`, + ); + assert.strictEqual(diagnostics[0].range.start.line, 0); }); -} +}); diff --git a/client/src/test/helper.ts b/client/src/test/helper.ts index 5c63f66..ec9b32a 100644 --- a/client/src/test/helper.ts +++ b/client/src/test/helper.ts @@ -1,36 +1,48 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ - import * as vscode from 'vscode'; import * as path from 'path'; export let doc: vscode.TextDocument; export let editor: vscode.TextEditor; -export let documentEol: string; -export let platformEol: string; + +const EXTENSION_ID = 'TheNuProjectContributors.vscode-nushell-lang'; /** - * Activates the vscode.lsp-sample extension + * Activates the extension and opens the given document. */ export async function activate(docUri: vscode.Uri) { - // The extensionId is `publisher.name` from package.json - const ext = vscode.extensions.getExtension('vscode-samples.lsp-sample')!; - await ext.activate(); - try { - doc = await vscode.workspace.openTextDocument(docUri); - editor = await vscode.window.showTextDocument(doc); - await sleep(2000); // Wait for server activation - } catch (e) { - console.error(e); + const ext = vscode.extensions.getExtension(EXTENSION_ID); + if (!ext) { + throw new Error(`Extension ${EXTENSION_ID} not found`); } + await ext.activate(); + doc = await vscode.workspace.openTextDocument(docUri); + editor = await vscode.window.showTextDocument(doc); } -async function sleep(ms: number) { +export async function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } +/** + * Polls `check` until it returns a truthy value or the timeout elapses. + */ +export async function waitFor( + check: () => Promise | T | undefined, + timeoutMs = 20000, + intervalMs = 250, +): Promise { + const deadline = Date.now() + timeoutMs; + let last: T | undefined; + while (Date.now() < deadline) { + last = await check(); + if (last) { + return last; + } + await sleep(intervalMs); + } + throw new Error(`Timed out after ${timeoutMs}ms; last value: ${last}`); +} + export const getDocPath = (p: string) => { return path.resolve(__dirname, '../../testFixture', p); }; diff --git a/client/src/test/hover.test.ts b/client/src/test/hover.test.ts new file mode 100644 index 0000000..80cd058 --- /dev/null +++ b/client/src/test/hover.test.ts @@ -0,0 +1,32 @@ +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import { getDocUri, activate, waitFor } from './helper'; + +function hoverText(hovers: vscode.Hover[]): string { + return hovers + .flatMap((h) => h.contents) + .map((c) => (typeof c === 'string' ? c : c.value)) + .join('\n'); +} + +suite('Hover from nu --lsp', () => { + const docUri = getDocUri('hover.nu'); + + test('shows help for a built-in command', async () => { + await activate(docUri); + + // `"hello" | str upcase` -> position inside `upcase` + const position = new vscode.Position(0, 15); + const hovers = await waitFor(async () => { + const result = (await vscode.commands.executeCommand( + 'vscode.executeHoverProvider', + docUri, + position, + )) as vscode.Hover[]; + return result && result.length > 0 ? result : undefined; + }); + + const text = hoverText(hovers); + assert.match(text, /uppercase/i, `unexpected hover text: ${text}`); + }); +}); diff --git a/client/src/test/index.ts b/client/src/test/index.ts index d5cbae5..58951b0 100644 --- a/client/src/test/index.ts +++ b/client/src/test/index.ts @@ -1,10 +1,6 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - * ------------------------------------------------------------------------------------------ */ +import * as fs from 'fs'; import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as glob from 'glob'; +import Mocha = require('mocha'); export function run(): Promise { // Create the mocha test @@ -17,27 +13,25 @@ export function run(): Promise { const testsRoot = __dirname; return new Promise((resolve, reject) => { - glob('**.test.js', { cwd: testsRoot }, (err, files) => { - if (err) { - return reject(err); - } + const files = fs + .readdirSync(testsRoot) + .filter((f) => f.endsWith('.test.js')); - // Add files to the test suite - files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); - try { - // Run the mocha test - mocha.run((failures) => { - if (failures > 0) { - reject(new Error(`${failures} tests failed.`)); - } else { - resolve(); - } - }); - } catch (err) { - console.error(err); - reject(err); - } - }); + try { + // Run the mocha test + mocha.run((failures: number) => { + if (failures > 0) { + reject(new Error(`${failures} tests failed.`)); + } else { + resolve(); + } + }); + } catch (err) { + console.error(err); + reject(err); + } }); } diff --git a/client/src/test/runTest.ts b/client/src/test/runTest.ts index 339afee..247e275 100644 --- a/client/src/test/runTest.ts +++ b/client/src/test/runTest.ts @@ -1,7 +1,3 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; import { runTests } from '@vscode/test-electron'; @@ -16,10 +12,17 @@ async function main() { // Passed to --extensionTestsPath const extensionTestsPath = path.resolve(__dirname, './index'); + // Open the fixture folder as the workspace so `nu --lsp` gets a workspace root + const fixturePath = path.resolve(__dirname, '../../testFixture'); + // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); + await runTests({ + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: [fixturePath, '--disable-extensions'], + }); } catch (err) { - console.error('Failed to run tests'); + console.error('Failed to run tests', err); process.exit(1); } } diff --git a/client/testFixture/completion.nu b/client/testFixture/completion.nu new file mode 100644 index 0000000..5e73ea9 --- /dev/null +++ b/client/testFixture/completion.nu @@ -0,0 +1 @@ +"hello" | str upc diff --git a/client/testFixture/diagnostics.nu b/client/testFixture/diagnostics.nu new file mode 100644 index 0000000..acfccdf --- /dev/null +++ b/client/testFixture/diagnostics.nu @@ -0,0 +1 @@ +let x = diff --git a/client/testFixture/hover.nu b/client/testFixture/hover.nu new file mode 100644 index 0000000..832cd0b --- /dev/null +++ b/client/testFixture/hover.nu @@ -0,0 +1 @@ +"hello" | str uppercase diff --git a/client/tsconfig.json b/client/tsconfig.json index 9ea2417..4f535bb 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -5,7 +5,9 @@ "lib": ["es2020"], "outDir": "out", "rootDir": "src", - "sourceMap": true + "sourceMap": true, + "strict": true, + "skipLibCheck": true }, "include": ["src"], "exclude": ["node_modules", ".vscode-test"] diff --git a/generate-example.nu b/generate-example.nu index 421b363..39004ff 100755 --- a/generate-example.nu +++ b/generate-example.nu @@ -1,6 +1,11 @@ #!/usr/bin/env nu -$nu.scope.commands | -where is_builtin and (not $it.is_extern) | -get -i examples | -each {|r| $r.example? | append (char nl)} | -flatten | save -f example.nu \ No newline at end of file + +# Collect the examples of every built-in command into example.nu. +scope commands +| where type == built-in +| get examples +| flatten +| get example +| each {|example| $example + (char nl)} +| str join (char nl) +| save --force example.nu diff --git a/generate-patterns.nu b/generate-patterns.nu index 3326d07..3799c7d 100755 --- a/generate-patterns.nu +++ b/generate-patterns.nu @@ -1,80 +1,103 @@ #!/usr/bin/env nu -# builtin sort functions only sort alphabetically -def sort-by-length [ - column: string -] { - insert length {get $column|str length} - |sort-by -r length - |reject length -} +# Regenerate the command-name regex in syntaxes/nushell.tmLanguage.json. +# +# This follows the "list-to-tree" section of CONTRIBUTING.md: it collects the +# names of every built-in, keyword and plugin command, turns them into a +# prefix-tree regex (the same output as `list-to-tree --format regex`), and +# writes it into the grammar in place of the current list-to-tree regex. +# +# Usage: +# nu generate-patterns.nu # use the commands of this nu +# nu generate-patterns.nu --input cmds.txt # use a combined list (see CONTRIBUTING.md) +# nu generate-patterns.nu --dry-run # print the regex instead of writing it -# generate combined regex for all single word commands -def match-for-single [ - commands:record -] { - '\b(' + ($commands|where ($it.subcommands|length) == 1|sort-by-length name|get name|str collect '|'|str replace -a -s '?' '\?') + ')\b' -} +const grammar_file = 'syntaxes/nushell.tmLanguage.json' +const marker = 'Regex generated with list-to-tree' -# generate list of regexes for every two word command name -def match-for-double [ - commands: record -] { - $commands - |where ($it.subcommands|length) > 0 and ($it.subcommands.second-word|all $it != '')|each {|x| - '\b' + $x.name + '(\s' + ($x.subcommands.second-word|compact|str collect '|\s') + ')\b' - } +# Escape a literal string for use inside a regex +def escape-regex []: string -> string { + str replace --all --regex '([\\.^$|?*+()\[\]{}])' '\$1' } -# returns regexes for all commands, both single and double word single-word append is conditional because some letters only have two word commands e.g 'q' -def generate-matches [ - category: record -] { - if ($category | get category) ends-with '_sub' { - match-for-double $category.commands - } else { - match-for-single $category.commands +# Longest common prefix of a list of strings +def common-prefix []: list -> string { + let words = $in + mut prefix = ($words | first) + for word in ($words | skip 1) { + let limit = [($prefix | str length) ($word | str length)] | math min + mut i = 0 + while $i < $limit and (($prefix | str substring $i..$i) == ($word | str substring $i..$i)) { + $i += 1 } + $prefix = ($prefix | str substring 0..<$i) + } + $prefix } -let patterns = ( - $nu.scope.commands - |where is_builtin == true and is_extern == false - |get name - |split column ' ' first-word second-word - |default '' second-word - |uniq - |upsert category {|x| - let first_letter = ($x.first-word|split chars|get 0) - if $x.second-word == '' { - $"($first_letter)" - } else { - $"($first_letter)_sub" - } - } - |group-by category - |transpose category commands - |upsert commands { - get commands - |group-by first-word - |transpose name subcommands - } - |reverse - |each {|category| - generate-matches $category - |each {|match| - {name: $"keyword.other.($category.category)", match: $match} - } +# Turn a sorted, de-duplicated list of words into an alternation regex, +# factoring out common prefixes: [ls, let, lines] -> l(?:et|ines|s) +def to-tree-regex []: list -> string { + let words = $in + $words + | group-by { str substring 0..0 } + | values + | each {|group| + let prefix = $group | common-prefix + let rest = $group | each { str substring ($prefix | str length).. } + let head = $prefix | escape-regex + if ($rest | length) == 1 { + $head + ($rest | first | escape-regex) + } else { + let optional = '' in $rest + let branches = $rest | where $it != '' | to-tree-regex + $head + '(?:' + $branches + ')' + (if $optional { '?' } else { '' }) + } } - |flatten -) + | str join '|' +} -open syntaxes/nushell.tmLanguage.json -|update repository.keywords.patterns $patterns -|save syntaxes/nushell.tmLanguage.json +def main [ + --input: path # file with one command name per line; defaults to `scope commands` + --dry-run # print the regex instead of updating the grammar +] { + let names = if $input != null { + open --raw $input | lines + } else { + scope commands | where type in [built-in keyword plugin] | get name + } + let names = $names | str trim | where $it != '' | uniq | sort -# TODO: don't allow keywords that have their own definition to be included keyword.other.blah + let regex = $names | to-tree-regex + if $dry_run { + print $regex + return + } -# like: -# alias, let, let-env, for, def, def-env, extern, module, overlay -# probably some others should be added \ No newline at end of file + let match_suffix = '(?![\w-])( (.*))?' + let match = '(' + $regex + ')' + $match_suffix + let lines = open --raw $grammar_file | lines + # The marker comment appears more than once; the command regex is the one + # whose "match" line ends with the argument capture. + let target = $lines + | enumerate + | where item =~ $marker + | get index + | each {|i| $i + 1 } + | where {|i| ($lines | get $i) | str ends-with '( (.*))?",' } + | get 0? + if $target == null { + error make { msg: $"could not find the '($marker)' command regex in ($grammar_file)" } + } + let indent = $lines | get $target | parse --regex '^(?\s*)"match"' | get 0?.indent + if $indent == null { + error make { msg: $"expected a \"match\" line after the marker in ($grammar_file)" } + } + let new_line = $indent + '"match": ' + ($match | to json --raw) + ',' + $lines + | update $target $new_line + | str join (char nl) + | $in + (char nl) + | save --force --raw $grammar_file + print $"updated ($grammar_file) with ($names | length) command names" +} diff --git a/language-configuration.json b/language-configuration.json index 193a663..2a13dba 100644 --- a/language-configuration.json +++ b/language-configuration.json @@ -51,13 +51,7 @@ }, "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\?\\s]+)", "indentationRules": { - "increaseIndentPattern": "{{?(def|def-env|else|extern|for|if|let|let-env|with-env)\\b.*}}?", - "decreaseIndentPattern": "{{?/(def|def-env|for|if)\\b}}?" - }, - "onEnterRules": [ - { - "beforeText": "^\\s*(?:alias|def|def-env|else|extern|for|if|let|let-env|with-env).*?:\\s*$", - "action": { "indent": "indent" } - } - ] + "increaseIndentPattern": "^.*[\\{\\[\\(]\\s*(#.*)?$", + "decreaseIndentPattern": "^\\s*[\\}\\]\\)]" + } } diff --git a/package-lock.json b/package-lock.json index 25c9ae8..d8b684b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-nushell-lang", - "version": "2.0.5", + "version": "2.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-nushell-lang", - "version": "2.0.5", + "version": "2.0.6", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -33,7 +33,7 @@ }, "client": { "name": "nushell-lsp-client", - "version": "2.0.3", + "version": "2.0.6", "license": "MIT", "dependencies": { "vscode-languageclient": "9.0.1", @@ -42,10 +42,10 @@ "devDependencies": { "@types/vscode": "1.110.0", "@types/which": "3.0.4", - "@vscode/test-electron": "2.5.2" + "@vscode/test-electron": "^3.1.0" }, "engines": { - "vscode": "^1.93.0" + "vscode": "^1.110.0" } }, "client/node_modules/which": { @@ -1875,9 +1875,9 @@ } }, "node_modules/@vscode/test-electron": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", - "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.1.0.tgz", + "integrity": "sha512-CRqv5u+YYoseuNVJ6Tyo4k0sF0mx4qnKMihRB0PjsUF8Dc0WKtCXo6CNL6nWWm5esfFQsQA/pejMj4ZbpJVLTw==", "dev": true, "license": "MIT", "dependencies": { @@ -1888,7 +1888,7 @@ "semver": "^7.6.2" }, "engines": { - "node": ">=16" + "node": ">=22" } }, "node_modules/@vscode/vsce": { diff --git a/package.json b/package.json index 8f439e7..5424b2e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "vscode-nushell-lang", "description": "nushell language for vscode", "author": "The Nushell Project Developers", - "version": "2.0.5", + "version": "2.0.6", "preview": false, "license": "MIT", "publisher": "TheNuProjectContributors", @@ -93,12 +93,6 @@ "type": "object", "title": "Nushell IDE Support", "properties": { - "nushellLanguageServer.maxNumberOfProblems": { - "scope": "resource", - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server." - }, "nushellLanguageServer.trace.server": { "scope": "window", "type": "string", @@ -110,31 +104,11 @@ "default": "messages", "description": "Traces the communication between VS Code and the language server." }, - "nushellLanguageServer.hints.showInferredTypes": { - "scope": "resource", - "type": "boolean", - "default": true, - "description": "Display types inferred by nushell." - }, "nushellLanguageServer.nushellExecutablePath": { "scope": "resource", "type": "string", "default": "nu", "description": "The path to the nushell executable." - }, - "nushellLanguageServer.maxNushellInvocationTime": { - "scope": "resource", - "type": "number", - "default": 10000000, - "description": "The maximum time in milliseconds to wait for nushell to respond." - }, - "nushellLanguageServer.includeDirs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The directories to include in the nushell environment.", - "scope": "window" } } }, @@ -161,7 +135,8 @@ "install": "cd client && npm install && cd ..", "watch": "npm run esbuild-base -- --sourcemap --watch", "lint": "npx eslint ./client/src/extension.ts", - "test": "node ./scripts/e2e.js", + "compile:test": "tsc -p client", + "test": "npm run compile:test && node ./scripts/e2e.js", "test:grammar": "vscode-tmgrammar-snap tests/cases/*", "fmt": "prettier . --write" }, diff --git a/snippets/nushell.json b/snippets/nushell.json index 051155c..f79947c 100644 --- a/snippets/nushell.json +++ b/snippets/nushell.json @@ -371,11 +371,6 @@ "description": "\"encode hex\" invocation", "body": "${1:command} | encode hex" }, - "enter": { - "prefix": "enter", - "description": "\"enter\" invocation", - "body": "enter ${1:path/to/directory}" - }, "enumerate": { "prefix": "enumerate", "description": "\"enumerate\" invocation", @@ -444,21 +439,11 @@ "description": "\"export extern\" invocation", "body": "export extern ${1:name} [${2:param}: string${3:, }]" }, - "export old-alias": { - "prefix": "export-old-alias", - "description": "\"export old-alias\" invocation", - "body": "export old-alias ${1:name} = ${2:value}" - }, "export use": { "prefix": "export-use", "description": "\"export use\" invocation", "body": "export use ${1:module} ${2:members}" }, - "export env": { - "prefix": "export-env", - "description": "\"export env\" invocation", - "body": "export env { ${1:command ...} }" - }, "extern": { "prefix": "extern", "description": "\"extern\" invocation", @@ -537,11 +522,6 @@ "description": "\"from\" invocation", "body": "${1:command} | from ${2|csv,eml,ics,ini,json,nuon,ods,ssv,toml,tsc,url,vcf,xlsx,xml,yaml,yml|}" }, - "g": { - "prefix": "g", - "description": "\"g\" invocation", - "body": "g ${1:shell}" - }, "get": { "prefix": "get", "description": "\"get\" invocation", @@ -770,11 +750,6 @@ "description": "\"let\" invocation", "body": ["# ${1:documentation}", "let ${2:name} = ${3:value}"] }, - "let-env": { - "prefix": "let-env", - "description": "\"let-env\" invocation", - "body": "let-env ${1:name} = ${2:value}" - }, "@let-env": { "prefix": "@let-env", "description": "\"let-env\" invocation", @@ -855,11 +830,6 @@ "description": "\"nu-highlight\" invocation", "body": "${1:command} | nu-highlight" }, - "old-alias": { - "prefix": "old-alias", - "description": "\"old-alias\" invocation", - "body": "old-alias ${1:name} = ${2:value}" - }, "@old-alias": { "prefix": "@old-alias", "description": "\"old-alias\" invocation", @@ -920,11 +890,6 @@ "description": "\"prepend\" invocation", "body": "${1:command} | prepend ${2:row}" }, - "profile": { - "prefix": "profile", - "description": "\"profile\" invocation", - "body": "profile {|| ${1:command ...} }" - }, "query": { "prefix": "query", "description": "\"query\" invocation", @@ -970,11 +935,6 @@ "description": "\"reduce\" invocation", "body": "${1:command} | reduce {|${2:el}, ${3:acc}| ${4:command ...} }" }, - "register": { - "prefix": "register", - "description": "\"register\" invocation", - "body": "register ${1:path/to/plugin}" - }, "registry query": { "prefix": "registry-query", "description": "\"registry query\" invocation", @@ -1402,5 +1362,10 @@ "prefix": "warning", "description": "warning", "body": ["# > **Warning** ${1:warning}"] + }, + "export-env": { + "prefix": "export-env", + "description": "\"export-env\" invocation", + "body": "export-env { ${1:command ...} }" } } diff --git a/syntaxes/codeblock.json b/syntaxes/codeblock.json index 8a0bab3..ef589ff 100644 --- a/syntaxes/codeblock.json +++ b/syntaxes/codeblock.json @@ -8,7 +8,7 @@ ], "repository": { "nushell-code-block": { - "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(nushell)(\\s+[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(nushell|nu)(\\s+[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { diff --git a/syntaxes/nushell.tmLanguage.json b/syntaxes/nushell.tmLanguage.json index 68fa52b..344f6aa 100644 --- a/syntaxes/nushell.tmLanguage.json +++ b/syntaxes/nushell.tmLanguage.json @@ -25,7 +25,7 @@ ] }, "string-escape": { - "match": "\\\\(?:[bfrnt\\\\'\"/]|u[0-9a-fA-F]{4})", + "match": "\\\\(?:[abefnrt\\\\'\"/(){}$^#|~]|u\\{[0-9a-fA-F]{1,6}\\}|u[0-9a-fA-F]{4})", "name": "constant.character.escape.nushell" }, "string-bare": { @@ -127,7 +127,7 @@ "name": "keyword.other.nushell" }, "parameters": { - "match": "(?<=\\s)(-{1,2})[\\w-]+", + "match": "(?<=\\s)(-{1,2})[a-zA-Z_][\\w-]*", "name": "variable.parameter.nushell", "captures": { "1": { "name": "keyword.control.nushell" } @@ -155,11 +155,11 @@ "name": "variable.other.nushell" }, "internal-variables": { - "match": "\\$(?:nu|env)\\b", + "match": "\\$(?:nu|env|in)\\b", "name": "variable.language.nushell" }, "datetime": { - "match": "\\b\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:\\+\\d{2}:?\\d{2}|Z)?)?\\b", + "match": "\\b\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:[+-]\\d{2}:\\d{2}|Z)?)?\\b", "name": "constant.numeric.nushell" }, "numbers-hexa": { @@ -175,11 +175,11 @@ "name": "constant.numeric.nushell" }, "numbers": { - "match": "(?=]=?|[!=]~|\\+\\+=?)(?= |$)", "name": "keyword.control.nushell" }, + "operators-redirection": { + "match": "(?<= )(?:o\\+e|e\\+o|out\\+err|err\\+out|o|e|out|err)(?:>>?|>\\|)(?= |$)", + "name": "keyword.control.nushell" + }, "operators": { "patterns": [ { "include": "#operators-word" }, + { "include": "#operators-redirection" }, { "include": "#operators-symbols" }, { "include": "#ranges" } ] @@ -254,7 +259,7 @@ "patterns": [ { "name": "meta.list.nushell", - "begin": "\\b(list)\\s*<", + "begin": "\\b(list|oneof)\\s*<", "beginCaptures": { "1": { "name": "entity.name.type.nushell" } }, @@ -263,7 +268,7 @@ }, { "name": "meta.record.nushell", - "begin": "\\b(record)\\s*<", + "begin": "\\b(record|table)\\s*<", "beginCaptures": { "1": { "name": "entity.name.type.nushell" } }, @@ -505,7 +510,7 @@ ] }, "for-loop": { - "begin": "(for)\\s+(\\$?\\w+)\\s+(in)\\s+(.+)\\s*(\\{)", + "begin": "\\b(for)\\s+(\\$?\\w+)\\s+(in)\\s+(.+)\\s*(\\{)", "beginCaptures": { "1": { "name": "keyword.other.nushell" }, "2": { "name": "variable.other.nushell" }, @@ -613,7 +618,7 @@ ] }, "define-variable": { - "match": "(let|mut|(?:export\\s+)?const)\\s+(\\w+)(?:\\s*(:)\\s*([^=]+?))?\\s+(=)", + "match": "\\b(let|mut|(?:export\\s+)?const)\\s+(\\w+)(?:\\s*(:)\\s*([^=]+?))?\\s+(=)", "captures": { "1": { "name": "keyword.other.nushell" }, "2": { "name": "variable.other.nushell" }, @@ -663,7 +668,7 @@ }, { "comment": "Regex generated with list-to-tree (https://github.com/glcraft/list-to-tree)", - "match": "(a(?:l(?:ias|l)|n(?:si(?: (?:gradient|link|strip))?|y)|ppend|st|ttr(?: (?:c(?:ategory|omplete(?: external)?)|deprecated|example|search-terms))?)|b(?:its(?: (?:and|not|or|ro(?:l|r)|sh(?:l|r)|xor))?|reak|ytes(?: (?:a(?:dd|t)|build|collect|ends-with|index-of|length|re(?:move|place|verse)|s(?:plit|tarts-with)))?)|c(?:al|d|h(?:ar|unk(?:-by|s))|l(?:ear|ip(?: (?:copy|paste))?)|o(?:l(?:lect|umns)|m(?:mandline(?: (?:edit|get-cursor|set-cursor))?|p(?:act|lete))|n(?:fig(?: (?:env|flatten|nu|reset|use-colors))?|st|tinue))|p)|d(?:ate(?: (?:f(?:ormat|rom-human)|humanize|list-timezone|now|to-timezone))?|e(?:bug(?: (?:e(?:nv|xperimental-options)|info|profile))?|code(?: (?:base(?:32(?:hex)?|64)|hex))?|f(?:ault)?|scribe|tect(?: (?:columns|type))?)|o|rop(?: (?:column|nth))?|t(?: (?:add|diff|format|now|part|to|utcnow))?|u)|e(?:ach(?: while)?|cho|moji|n(?:code(?: (?:base(?:32(?:hex)?|64)|hex))?|umerate)|rror(?: make)?|very|x(?:ec|it|p(?:l(?:ain|ore(?: (?:config|regex))?)|ort(?: (?:alias|const|def|extern|module|use)|-env)?)|tern))|f(?:i(?:l(?:e|l|ter)|nd|rst)|latten|or(?:mat(?: (?:bits|d(?:ate|uration)|filesize|number|pattern))?)?|rom(?: (?:csv|eml|i(?:cs|ni)|json|m(?:d|sgpack(?:z)?)|nuon|ods|p(?:arquet|list)|ssv|t(?:o(?:ml|on)|sv)|url|vcf|x(?:lsx|ml)|y(?:aml|ml)))?)|g(?:e(?:nerate|t)|lob|r(?:id|oup-by)|stat)|h(?:ash(?: (?:md5|sha256))?|e(?:aders|lp(?: (?:aliases|commands|e(?:scapes|xterns)|modules|operators|pipe-and-redirect))?)|i(?:de(?:-env)?|sto(?:gram|ry(?: (?:import|session))?))|ttp(?: (?:delete|get|head|options|p(?:atch|o(?:ol|st)|ut)))?)|i(?:f|gnore|n(?:c|put(?: list(?:en)?)?|s(?:ert|pect)|t(?:erleave|o(?: (?:b(?:inary|ool)|cell-path|d(?:atetime|uration)|f(?:ilesize|loat)|glob|int|record|s(?:qlite|tring)|value))?))|s-(?:admin|empty|not-empty|terminal)|tems)|j(?:o(?:b(?: (?:describe|flush|id|kill|list|recv|s(?:end|pawn)|unfreeze))?|in)|s(?:on path|p)|walk)|k(?:eybindings(?: (?:default|list(?:en)?))?|ill)|l(?:ast|e(?:ngth|t(?:-env)?)|ines|o(?:ad-env|op))|m(?:at(?:ch|h(?: (?:a(?:bs|rc(?:cos(?:h)?|sin(?:h)?|tan(?:h)?)|vg)|c(?:eil|os(?:h)?)|exp|floor|l(?:n|og)|m(?:ax|edian|in|ode)|product|round|s(?:in(?:h)?|qrt|tddev|um)|tan(?:h)?|variance))?)|e(?:rge(?: deep)?|tadata(?: (?:access|set))?)|k(?:dir|temp)|o(?:dule|ve)|ut|v)|nu-(?:check|highlight)|o(?:pen|verlay(?: (?:hide|list|new|use))?)|p(?:a(?:nic|r(?:-each|se)|th(?: (?:basename|dirname|ex(?:ists|pand)|join|parse|relative-to|s(?:elf|plit)|type))?)|lugin(?: (?:add|list|rm|stop|use))?|o(?:lars(?: (?:a(?:gg(?:-groups)?|ll-(?:false|true)|ppend|rg-(?:m(?:ax|in)|sort|true|unique|where)|s(?:-date(?:time)?)?)|c(?:a(?:che|st)|o(?:l(?:lect|umns)?|n(?:cat(?:-str)?|tains|vert-time-zone)|unt(?:-null)?)|u(?:mulative|t))|d(?:atepart|ecimal|rop(?:-(?:duplicates|nulls))?|ummies)|e(?:ntropy|xp(?:lode|r-not))|f(?:i(?:l(?:l-n(?:an|ull)|ter(?:-with)?)|rst)|latten)|g(?:et(?:-(?:day|hour|m(?:inute|onth)|nanosecond|ordinal|second|week(?:day)?|year))?|roup-by)|horizontal|i(?:mplode|nt(?:eger|o-(?:d(?:f|type)|lazy|nu|repr|schema))|s-(?:duplicated|in|n(?:ot-null|ull)|unique))|join(?:-where)?|l(?:ast|en|i(?:st-contains|t)|owercase)|m(?:a(?:th|x)|e(?:an|dian)|in)|n(?:-unique|ot)|o(?:pen|therwise|ver)|p(?:ivot|rofile)|q(?:cut|u(?:antile|ery))|r(?:e(?:name|place(?:-time-zone)?|verse)|olling)|s(?:a(?:mple|ve)|chema|e(?:lect(?:or(?: (?:all|by-(?:dtype|name)|ends-with|f(?:irst|loat)|integer|last|matches|n(?:ot|umeric)|s(?:igned-integer|tarts-with)|unsigned-integer))?)?|t(?:-with-idx)?)|h(?:ape|ift)|lice|ort-by|t(?:d|ore-(?:get|ls|rm)|r(?:-(?:join|lengths|replace(?:-all)?|s(?:lice|plit|trip-chars))|ftime|uct-json-encode))|um(?:mary)?)|t(?:ake|runcate)|u(?:n(?:ique|nest|pivot)|ppercase)|va(?:lue-counts|r)|w(?:hen|ith-column)))?|rt)|r(?:epend|int)|s)|query(?: (?:db|git|json|web(?:page-info)?|xml))?|r(?:andom(?: (?:b(?:inary|ool)|chars|float|int|uuid))?|e(?:duce|g(?:ex|istry(?: query)?)|ject|name|turn|verse)|g|m|o(?:ll(?: (?:down|left|right|up))?|tate)|un-(?:external|internal))|s(?:ave|c(?:hema|ope(?: (?:aliases|commands|e(?:ngine-stats|xterns)|modules|variables))?)|e(?:lect|q(?: (?:char|date))?)|huffle|kip(?: (?:until|while))?|l(?:eep|ice)|o(?:rt(?:-by)?|urce(?:-env)?)|plit(?: (?:c(?:ell-path|hars|olumn)|list|row|words))?|t(?:art|or(?: (?:create|delete|export|i(?:mport|nsert)|open|reset|update))?|r(?: (?:c(?:a(?:mel-case|pitalize)|o(?:mpress|ntains))|d(?:e(?:compress|dent|unicode)|istance|owncase)|e(?:nds-with|scape-regex|xpand)|inde(?:nt|x-of)|join|kebab-case|length|pascal-case|re(?:place|verse)|s(?:creaming-snake-case|hl-(?:quote|split)|imilarity|lug|nake-case|ta(?:rts-with|ts)|ubstring)|t(?:itle-case|rim)|upcase|wrap)|ess_internals)?)|ys(?: (?:cpu|disks|host|mem|net|temp|users))?)|t(?:a(?:ble|ke(?: (?:until|while))?)|e(?:e|rm(?: (?:query|size))?)|imeit|o(?: (?:csv|gui|html|json|m(?:d|sgpack(?:z)?)|nuon|p(?:arquet|list)|t(?:ext|o(?:ml|on)|sv)|xml|y(?:aml|ml))|uch)?|r(?:anspose|ee|y)|utor)|u(?:limit|mask|n(?:ame|iq(?:-by)?|let)|p(?:date(?: cells)?|sert)|rl(?: (?:build-query|decode|encode|join|parse|split-query))?|se)|v(?:alues|ersion(?: check)?|iew(?: (?:blocks|files|ir|s(?:ource|pan)))?)|w(?:atch|h(?:ere|i(?:ch|le)|oami)|i(?:ndow|th-env)|rap)|zip)(?![\\w-])( (.*))?", + "match": "(a(?:bbr(?: list)?|l(?:ias|l)|n(?:si(?: (?:gradient|link|strip))?|y)|ppend|st|ttr(?: (?:c(?:ategory|omplete(?: external)?)|deprecated|example|interactive|search-terms))?)|b(?:its(?: (?:and|not|or|ro(?:l|r)|sh(?:l|r)|xor))?|reak|ytes(?: (?:a(?:dd|t)|build|collect|ends-with|index-of|length|re(?:move|place|verse)|s(?:plit|tarts-with)))?)|c(?:al|d|h(?:ar|unk(?:-by|s))|l(?:ear|ip(?: (?:copy|paste))?)|o(?:l(?:lect|umns)|m(?:binations|mandline(?: (?:complete|edit|get-cursor|set-(?:cursor|prompt)))?|p(?:act|lete))|n(?:fig(?: (?:env|flatten|nu|reset|use-colors))?|st|tinue))|p)|d(?:ate(?: (?:f(?:ormat|rom-human)|humanize|list-timezone|now|to-timezone))?|e(?:bug(?: (?:e(?:nv|xperimental-options)|info|profile))?|code(?: (?:base(?:32(?:hex)?|64)|hex))?|f(?:ault)?|scribe|tect(?: (?:columns|type))?)|ifference|o|rop(?: (?:column|nth))?|t(?: (?:add|diff|format|now|part|to|utcnow))?|u)|e(?:ach(?: while)?|cho|moji|n(?:code(?: (?:base(?:32(?:hex)?|64)|hex))?|umerate)|rror(?: make)?|very|x(?:ec|it|p(?:l(?:ain|ore(?: (?:config|regex))?)|ort(?: (?:alias|const|def|extern|module|use)|-env)?)|tern))|f(?:i(?:l(?:e|l|ter)|nd|rst)|latten|or(?:mat(?: (?:bits|d(?:ate|uration)|filesize|number|pattern))?)?|rom(?: (?:csv|eml|i(?:cs|ni)|json|kdl|m(?:d|sgpack(?:z)?)|nuon|ods|p(?:arquet|list)|ssv|t(?:o(?:ml|on)|sv)|url|vcf|x(?:lsx|ml)|y(?:aml|ml)))?)|g(?:e(?:nerate|t)|lob|r(?:id|oup-by)|stat)|h(?:ash(?: (?:md5|sha(?:256|512)))?|e(?:aders|lp(?: (?:aliases|commands|e(?:scapes|xterns)|modules|operators|pipe-and-redirect))?)|i(?:de(?:-env)?|sto(?:gram|ry(?: (?:import|session))?))|ttp(?: (?:delete|get|head|options|p(?:atch|o(?:ol|st)|ut)))?)|i(?:dx(?: (?:d(?:irs|rop)|fi(?:les|nd)|init|s(?:earch|tatus)|watch))?|f|gnore|n(?:c|put(?: list(?:en)?)?|s(?:ert|pect)|t(?:er(?:leave|sect)|o(?: (?:b(?:inary|ool)|cell-path|d(?:atetime|uration)|f(?:ilesize|loat)|glob|int|matrix|record|s(?:emver(?:-range)?|qlite|tring)|value))?))|s-(?:admin|empty|not-empty|redirected|terminal)|tems)|j(?:o(?:b(?: (?:describe|flush|id|kill|list|recv|s(?:end|pawn)|unfreeze))?|in)|son path|walk)|k(?:eybindings(?: (?:default|list(?:en)?))?|ill)|l(?:ast|e(?:ngth|t(?:-env)?)|ines|o(?:ad-env|op)|s)|m(?:at(?:ch|h(?: (?:a(?:bs|rc(?:cos(?:h)?|sin(?:h)?|tan(?:h)?)|vg)|c(?:brt|eil|os(?:h)?)|exp|floor|l(?:n|og)|m(?:ax|edian|in|ode)|product|round|s(?:in(?:h)?|qrt|tddev|um)|tan(?:h)?|variance))?|rix(?: (?:add|get-(?:col|row)|i(?:dentity|nto-nu)|m(?:a(?:p|x)|ean|ultiply)|re(?:duce|shape)|s(?:cale|et-(?:col|row)|u(?:btract|m))|transpose|zeros))?)|e(?:rge(?: deep)?|tadata(?: (?:access|set))?)|k(?:dir|temp)|o(?:dule|ve)|ut|v)|nu-(?:check|highlight)|o(?:pen|verlay(?: (?:hide|list|new|use))?)|p(?:a(?:nic|r(?:-each|se)|th(?: (?:basename|dirname|ex(?:ists|pand)|join|parse|relative-to|s(?:elf|plit)|type))?)|e(?:ek|rmutations)|lugin(?: (?:add|list|rm|stop|use))?|o(?:lars(?: (?:a(?:gg(?:-groups)?|ll-(?:false|true)|ppend|rg-(?:m(?:ax|in)|sort|true|unique|where)|s(?:-date(?:time)?)?)|c(?:a(?:che|st)|o(?:l(?:lect|umns)?|n(?:cat(?:-str)?|tains|vert-time-zone)|unt(?:-null)?)|u(?:mulative|t))|d(?:atepart|ecimal|rop(?:-(?:duplicates|nulls))?|ummies)|e(?:ntropy|xp(?:lode|r-not))|f(?:i(?:l(?:l-n(?:an|ull)|ter(?:-with)?)|rst)|latten)|g(?:et(?:-(?:day|hour|m(?:inute|onth)|nanosecond|ordinal|second|week(?:day)?|year))?|roup-by)|horizontal|i(?:mplode|nt(?:eger|o-(?:d(?:f|type)|lazy|nu|repr|schema))|s-(?:duplicated|in|n(?:ot-null|ull)|unique))|join(?:-where)?|l(?:ast|en|i(?:st-contains|t)|owercase)|m(?:a(?:p-batches|th(?: (?:abs|bitwise-(?:and|count-(?:ones|zeros)|leading-(?:ones|zeros)|or|trailing-(?:ones|zeros)|xor)|cos|dot|exp|log(?:1p)?|s(?:i(?:gn|n)|qrt)))?|x)|e(?:an|dian)|in)|n(?:-unique|ot)|o(?:pen|therwise|ver)|p(?:ivot|rofile)|q(?:cut|u(?:antile|ery))|r(?:e(?:name|place(?:-time-zone)?|verse)|olling)|s(?:a(?:mple|ve)|chema|e(?:lect(?:or(?: (?:a(?:l(?:l|pha(?:numeric)?)|rray)|b(?:inary|oolean|y-(?:dtype|index|name))|c(?:ategorical|ontains)|d(?:ate(?:time)?|ecimal|igit|uration)|e(?:mpty|n(?:ds-with|um)|xclude)|f(?:irst|loat)|integer|l(?:ast|ist)|matches|n(?:ested|ot|umeric)|object|s(?:igned-integer|t(?:arts-with|r(?:ing|uct)))|temporal|unsigned-integer))?)?|t(?:-with-idx)?)|h(?:ape|ift)|lice|ort-by|t(?:d|ore-(?:get|ls|rm)|r(?:-(?:join|lengths|replace(?:-all)?|s(?:lice|plit|trip-chars))|ftime|uct-json-encode))|um(?:mary)?)|t(?:ake|runcate)|u(?:n(?:ique|nest|pivot)|ppercase)|va(?:lue-counts|r)|w(?:hen|ith-column)))?|rt)|r(?:epend|int)|s)|query(?: (?:db|git|json|web(?:page-info)?|xml))?|r(?:andom(?: (?:b(?:inary|ool)|chars|float|int|pass|uuid))?|e(?:duce|g(?:ex|istry(?: query)?)|ject|name|turn|verse)|m|o(?:ll(?: (?:down|left|right|up))?|tate)|un(?:-(?:external|internal))?)|s(?:ave|c(?:hema|ope(?: (?:aliases|commands|e(?:ngine-stats|xterns)|modules|variables))?)|e(?:lect|mver(?: bump)?|q(?: (?:char|date))?)|huffle|kip(?: (?:until|while))?|l(?:eep|ice)|o(?:rt(?:-by)?|urce(?:-env)?)|plit(?: (?:c(?:ell-path|hars|olumn)|list|row|words))?|t(?:art|or(?: (?:create|delete|export|i(?:mport|nsert)|open|reset|update))?|r(?: (?:a(?:fter|lign)|be(?:fore|tween)|c(?:a(?:mel-case|pitalize)|o(?:m(?:mon-prefix|press)|ntains))|d(?:e(?:compress|dent|unicode)|istance|owncase)|e(?:nds-with|scape-regex|xpand)|in(?:crement|de(?:nt|x-of))|join|kebab-case|l(?:ength|owercase)|pascal-case|re(?:place|verse)|s(?:creaming-snake-case|hl-(?:quote|split)|imilarity|lug|nake-case|ta(?:rts-with|ts)|ubstring)|t(?:itle-case|r(?:im|uncate))|u(?:nescape|p(?:case|percase))|wrap))?)|ys(?: (?:cpu|disks|host|mem|net|temp|users))?)|t(?:a(?:ble|ke(?: (?:until|while))?)|e(?:e|rm(?: (?:query|size))?)|imeit|o(?: (?:csv|gui|html|json|kdl|m(?:d|sgpack(?:z)?)|nuon|p(?:arquet|list)|t(?:ext|o(?:ml|on)|sv|xt)|xml|y(?:aml|ml))|uch)?|r(?:anspose|ee|y)|u(?:i(?: (?:b(?:ind|ox|utton)|debug|l(?:abel|og)|menu|pr(?:eview|ogress)|run|s(?:e(?:arch|lect)|plit)|t(?:ab(?:le)?|extbox|ree)))?|tor))|u(?:limit|mask|n(?:ame|i(?:on|q(?:-by)?)|let)|p(?:date(?: cells)?|sert)|rl(?: (?:build-query|decode|encode|join|parse|split-query))?|se)|v(?:alues|ersion(?: check)?|iew(?: (?:blocks|files|ir|s(?:ource|pan)))?)|w(?:atch|h(?:ere|i(?:ch|le)|oami)|i(?:ndow|th-env)|rap)|zip)(?![\\w-])( (.*))?", "captures": { "1": { "name": "keyword.other.builtin.nushell" }, "2": { diff --git a/tests/cases/review.nu b/tests/cases/review.nu new file mode 100644 index 0000000..eb167f8 --- /dev/null +++ b/tests/cases/review.nu @@ -0,0 +1,11 @@ +let size = 10pb + 2pib +let when = 2024-01-01T00:00:00-05:00 +let esc = "a\u{1F600}b\e[0m\(x\)" +let neg = seq -1 5 +def f [x: table, y: oneof, z?: list]: [nothing -> string, int -> string] { $in } +ls | to txt +polars math abs +matrix identity 3 +cmd o> out.txt e>| lines +0b[1010] | into int +for x in [1 2] { print $x } diff --git a/tests/cases/review.nu.snap b/tests/cases/review.nu.snap new file mode 100644 index 0000000..22973f0 --- /dev/null +++ b/tests/cases/review.nu.snap @@ -0,0 +1,159 @@ +>let size = 10pb + 2pib +#^^^ source.nushell keyword.other.nushell +# ^ source.nushell +# ^^^^ source.nushell variable.other.nushell +# ^ source.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^^^^ source.nushell constant.numeric.nushell +# ^ source.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^^^^ source.nushell constant.numeric.nushell +>let when = 2024-01-01T00:00:00-05:00 +#^^^ source.nushell keyword.other.nushell +# ^ source.nushell +# ^^^^ source.nushell variable.other.nushell +# ^ source.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^^^^^^^^^^^^^^^^^^^^^^^^^ source.nushell constant.numeric.nushell +>let esc = "a\u{1F600}b\e[0m\(x\)" +#^^^ source.nushell keyword.other.nushell +# ^ source.nushell +# ^^^ source.nushell variable.other.nushell +# ^ source.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^ source.nushell string.quoted.double.nushell punctuation.definition.string.begin.nushell +# ^ source.nushell string.quoted.double.nushell +# ^^^^^^^^^ source.nushell string.quoted.double.nushell constant.character.escape.nushell +# ^ source.nushell string.quoted.double.nushell +# ^^ source.nushell string.quoted.double.nushell constant.character.escape.nushell +# ^ source.nushell string.quoted.double.nushell +# ^^ source.nushell string.quoted.double.nushell +# ^^ source.nushell string.quoted.double.nushell constant.character.escape.nushell +# ^ source.nushell string.quoted.double.nushell +# ^^ source.nushell string.quoted.double.nushell constant.character.escape.nushell +# ^ source.nushell string.quoted.double.nushell punctuation.definition.string.end.nushell +>let neg = seq -1 5 +#^^^ source.nushell keyword.other.nushell +# ^ source.nushell +# ^^^ source.nushell variable.other.nushell +# ^ source.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^^^ source.nushell meta.command.nushell keyword.other.builtin.nushell +# ^ source.nushell meta.command.nushell +# ^^ source.nushell meta.command.nushell constant.numeric.nushell +# ^ source.nushell meta.command.nushell +# ^ source.nushell meta.command.nushell constant.numeric.nushell +>def f [x: table, y: oneof, z?: list]: [nothing -> string, int -> string] { $in } +#^^^ source.nushell storage.type.function.nushell +# ^ source.nushell +# ^ source.nushell entity.name.function.nushell +# ^ source.nushell +# ^ source.nushell meta.function.parameters.nushell meta.brace.square.begin.nushell +# ^ source.nushell meta.function.parameters.nushell variable.parameter.nushell +# ^^ source.nushell meta.function.parameters.nushell +# ^^^^^ source.nushell meta.function.parameters.nushell meta.record.nushell entity.name.type.nushell +# ^ source.nushell meta.function.parameters.nushell meta.record.nushell +# ^ source.nushell meta.function.parameters.nushell meta.record.nushell variable.parameter.nushell +# ^^ source.nushell meta.function.parameters.nushell meta.record.nushell +# ^^^ source.nushell meta.function.parameters.nushell meta.record.nushell entity.name.type.nushell +# ^^ source.nushell meta.function.parameters.nushell meta.record.nushell +# ^ source.nushell meta.function.parameters.nushell meta.record.nushell variable.parameter.nushell +# ^^ source.nushell meta.function.parameters.nushell meta.record.nushell +# ^^^^^^ source.nushell meta.function.parameters.nushell meta.record.nushell entity.name.type.nushell +# ^ source.nushell meta.function.parameters.nushell meta.record.nushell +# ^^ source.nushell meta.function.parameters.nushell +# ^ source.nushell meta.function.parameters.nushell variable.parameter.nushell +# ^^ source.nushell meta.function.parameters.nushell +# ^^^^^ source.nushell meta.function.parameters.nushell meta.list.nushell entity.name.type.nushell +# ^ source.nushell meta.function.parameters.nushell meta.list.nushell +# ^^^ source.nushell meta.function.parameters.nushell meta.list.nushell entity.name.type.nushell +# ^^ source.nushell meta.function.parameters.nushell meta.list.nushell +# ^^^^^^ source.nushell meta.function.parameters.nushell meta.list.nushell entity.name.type.nushell +# ^ source.nushell meta.function.parameters.nushell meta.list.nushell +# ^^ source.nushell meta.function.parameters.nushell +# ^ source.nushell meta.function.parameters.nushell variable.parameter.nushell +# ^^^ source.nushell meta.function.parameters.nushell +# ^^^^ source.nushell meta.function.parameters.nushell meta.list.nushell entity.name.type.nushell +# ^ source.nushell meta.function.parameters.nushell meta.list.nushell +# ^^^ source.nushell meta.function.parameters.nushell meta.list.nushell entity.name.type.nushell +# ^ source.nushell meta.function.parameters.nushell meta.list.nushell +# ^ source.nushell meta.function.parameters.nushell meta.brace.square.end.nushell +# ^ source.nushell punctuation.definition.in-out.nushell +# ^ source.nushell +# ^ source.nushell meta.brace.square.begin.nushell +# ^^^^^^^ source.nushell entity.name.type.nushell +# ^ source.nushell +# ^^ source.nushell keyword.operator.nushell +# ^ source.nushell +# ^^^^^^ source.nushell entity.name.type.nushell +# ^ source.nushell punctuation.separator.nushell +# ^ source.nushell +# ^^^ source.nushell entity.name.type.nushell +# ^ source.nushell +# ^^ source.nushell keyword.operator.nushell +# ^ source.nushell +# ^^^^^^ source.nushell entity.name.type.nushell +# ^ source.nushell meta.brace.square.end.nushell +# ^ source.nushell +# ^ source.nushell meta.function.body.nushell punctuation.definition.function.begin.nushell +# ^ source.nushell meta.function.body.nushell +# ^^^ source.nushell meta.function.body.nushell variable.language.nushell +# ^ source.nushell meta.function.body.nushell +# ^ source.nushell meta.function.body.nushell punctuation.definition.function.end.nushell +>ls | to txt +#^^ source.nushell meta.command.nushell keyword.other.builtin.nushell +# ^ source.nushell meta.command.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^^^^^^ source.nushell meta.command.nushell keyword.other.builtin.nushell +>polars math abs +#^^^^^^^^^^^^^^^ source.nushell meta.command.nushell keyword.other.builtin.nushell +>matrix identity 3 +#^^^^^^^^^^^^^^^ source.nushell meta.command.nushell keyword.other.builtin.nushell +# ^ source.nushell meta.command.nushell +# ^ source.nushell meta.command.nushell constant.numeric.nushell +>cmd o> out.txt e>| lines +#^^^ source.nushell meta.command.nushell entity.name.type.external.nushell +# ^ source.nushell meta.command.nushell +# ^^ source.nushell meta.command.nushell keyword.control.nushell +# ^ source.nushell meta.command.nushell +# ^^^^^^^ source.nushell meta.command.nushell string.bare.nushell +# ^ source.nushell meta.command.nushell +# ^^^ source.nushell meta.command.nushell keyword.control.nushell +# ^ source.nushell meta.command.nushell +# ^^^^^ source.nushell meta.command.nushell string.bare.nushell +>0b[1010] | into int +#^^ source.nushell constant.binary.nushell constant.numeric.nushell +# ^ source.nushell constant.binary.nushell meta.brace.square.begin.nushell +# ^^^^ source.nushell constant.binary.nushell constant.numeric.nushell +# ^ source.nushell constant.binary.nushell meta.brace.square.begin.nushell +# ^ source.nushell +# ^ source.nushell keyword.control.nushell +# ^ source.nushell +# ^^^^^^^^ source.nushell meta.command.nushell keyword.other.builtin.nushell +>for x in [1 2] { print $x } +#^^^ source.nushell meta.for-loop.nushell keyword.other.nushell +# ^ source.nushell meta.for-loop.nushell +# ^ source.nushell meta.for-loop.nushell variable.other.nushell +# ^ source.nushell meta.for-loop.nushell +# ^^ source.nushell meta.for-loop.nushell keyword.other.nushell +# ^ source.nushell meta.for-loop.nushell +# ^ source.nushell meta.for-loop.nushell meta.table.nushell meta.brace.square.begin.nushell +# ^ source.nushell meta.for-loop.nushell meta.table.nushell constant.numeric.nushell +# ^ source.nushell meta.for-loop.nushell meta.table.nushell +# ^ source.nushell meta.for-loop.nushell meta.table.nushell constant.numeric.nushell +# ^ source.nushell meta.for-loop.nushell meta.table.nushell meta.brace.square.end.nushell +# ^ source.nushell meta.for-loop.nushell +# ^ source.nushell meta.for-loop.nushell punctuation.section.block.begin.bracket.curly.nushell +# ^ source.nushell meta.for-loop.nushell +# ^^^^^ source.nushell meta.for-loop.nushell meta.command.nushell keyword.other.builtin.nushell +# ^ source.nushell meta.for-loop.nushell meta.command.nushell +# ^^ source.nushell meta.for-loop.nushell meta.command.nushell variable.other.nushell +# ^ source.nushell meta.for-loop.nushell meta.command.nushell +# ^ source.nushell meta.for-loop.nushell punctuation.section.block.end.bracket.curly.nushell +> \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 77f1ac2..5723b6b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,9 +7,8 @@ "rootDir": "client/src", "sourceMap": true, "strict": true, - "composite": true + "skipLibCheck": true }, "include": ["client/src"], - "exclude": ["node_modules", ".vscode-test"], - "references": [{ "path": "./client" }] + "exclude": ["node_modules", ".vscode-test"] }