diff --git a/.gitignore b/.gitignore index f35f04c..5f5b5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ client/tsconfig.tsbuildinfo .vscode-test out/ server/tsconfig.tsbuildinfo -tsconfig.tsbuildinfo \ No newline at end of file +tsconfig.tsbuildinfo +.pnpm-lock.yaml diff --git a/client/src/extension.ts b/client/src/extension.ts index 360faa9..a945105 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -1,44 +1,79 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* -------------------------------------------------------------------------------------------- - * 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 { workspace, ExtensionContext } from "vscode"; -import * as vscode from 'vscode'; +import * as path from "path"; +import { + commands, + Uri, + env, + ExtensionContext, + workspace, + window, + ProviderResult, + TerminalProfile, + CancellationToken, +} from "vscode"; + +import {sync as which_sync} from "which"; +import { LanguageClientOptions } from "vscode-languageclient/node"; import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind, -} from 'vscode-languageclient/node'; + activate as activateExtension, + deactivate as deactivateExtension, +} from "./nu-ide"; +import { + activate as activateNuLsp, + deactivate as deactivateNuLsp, +} from "./nu-lsp"; + +async function startLanguageServer(context: ExtensionContext) { + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: "file", language: "nushell" }], + synchronize: { + // Notify the server about file changes to '.clientrc files contained in the workspace + fileEvents: workspace.createFileSystemWatcher("**/.clientrc"), + }, + }; + + const configuration = workspace.getConfiguration( + "nushellLanguageServer", + null, + ); + + if (configuration.implementation == "nu --lsp") { + await activateNuLsp(clientOptions); + } else { + await activateExtension(context, clientOptions); + } +} + +async function stopLanguageServers() { + await Promise.all([deactivateExtension(), deactivateNuLsp()]); +} -let client: LanguageClient; +async function handleDidChangeConfiguration(this: ExtensionContext) { + await stopLanguageServers(); + await startLanguageServer(this); +} -export function activate(context: vscode.ExtensionContext) { - console.log('Terminals: ' + (vscode.window).terminals.length); +export function activate(context: ExtensionContext): void { + console.log("Terminals: " + (window).terminals.length); context.subscriptions.push( - vscode.window.registerTerminalProfileProvider('nushell_default', { + window.registerTerminalProfileProvider("nushell_default", { provideTerminalProfile( - token: vscode.CancellationToken, - ): vscode.ProviderResult { - const which = require('which'); - const path = require('path'); - - const PATH_FROM_ENV = process.env['PATH']; + token: CancellationToken, + ): ProviderResult { + const PATH_FROM_ENV = process.env["PATH"]; const pathsToCheck = [ PATH_FROM_ENV, // cargo install location - (process.env['CARGO_HOME'] || '~/.cargo') + '/bin', + (process.env["CARGO_HOME"] || "~/.cargo") + "/bin", // winget on Windows install location - 'c:\\program files\\nu\\bin', + "c:\\program files\\nu\\bin", // just add a few other drives for fun - 'd:\\program files\\nu\\bin', - 'e:\\program files\\nu\\bin', - 'f:\\program files\\nu\\bin', + "d:\\program files\\nu\\bin", + "e:\\program files\\nu\\bin", + "f:\\program files\\nu\\bin", // SCOOP:TODO // all user installed programs and scoop itself install to @@ -60,40 +95,40 @@ export function activate(context: vscode.ExtensionContext) { // brew install location mac // intel - '/usr/local/bin', + "/usr/local/bin", // arm - '/opt/homebrew/bin', + "/opt/homebrew/bin", // native package manager install location // standard location should be in `PATH` env var //"/usr/bin/nu", ]; - const found_nushell_path = which.sync('nu', { + const found_nushell_path = which_sync("nu", { nothrow: true, path: pathsToCheck.join(path.delimiter), }); if (found_nushell_path == null) { console.log( - 'Nushell not found in env:PATH or any of the heuristic locations.', + "Nushell not found in env:PATH or any of the heuristic locations.", ); // use an async arrow funciton to use `await` inside return (async () => { if ( - (await vscode.window.showErrorMessage( - 'We cannot find a nushell executable in your path or pre-defined locations', - 'install from website', + (await window.showErrorMessage( + "We cannot find a nushell executable in your path or pre-defined locations", + "install from website", )) && - (await vscode.env.openExternal( - vscode.Uri.parse('https://www.nushell.sh/'), + (await env.openExternal( + Uri.parse("https://www.nushell.sh/"), )) && - (await vscode.window.showInformationMessage( - 'after you install nushell, you might need to reload vscode', - 'reload now', + (await window.showInformationMessage( + "after you install nushell, you might need to reload vscode", + "reload now", )) ) { - vscode.commands.executeCommand('workbench.action.reloadWindow'); + commands.executeCommand("workbench.action.reloadWindow"); } // user has already seen error messages, but they didn't click through // return a promise that never resolve to supress the confusing error @@ -103,11 +138,11 @@ export function activate(context: vscode.ExtensionContext) { return { options: { - name: 'Nushell', + name: "Nushell", shellPath: found_nushell_path, - iconPath: vscode.Uri.joinPath( + iconPath: Uri.joinPath( context.extensionUri, - 'assets/nu.svg', + "assets/nu.svg", ), }, }; @@ -115,54 +150,14 @@ export function activate(context: vscode.ExtensionContext) { }), ); - // The server is implemented in node - const serverModule = context.asAbsolutePath( - path.join('out', 'server', 'src', 'server.js'), - ); - - // The debug options for the server - // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging - const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; - - // If the extension is launched in debug mode then the debug server options are used - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { - module: serverModule, - transport: TransportKind.ipc, - options: debugOptions, - }, - }; - - // Options to control the language client - const clientOptions: LanguageClientOptions = { - // Register the server for plain text documents - documentSelector: [{ scheme: 'file', language: 'nushell' }], - synchronize: { - // Notify the server about file changes to '.clientrc files contained in the workspace - fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc'), - }, - markdown: { - isTrusted: true - } - }; - - // Create the language client and start the client. - client = new LanguageClient( - 'nushellLanguageServer', - 'Nushell Language Server', - serverOptions, - clientOptions, + startLanguageServer(context); + workspace.onDidChangeConfiguration( + handleDidChangeConfiguration, + context, + undefined, ); - - // Start the client. This will also launch the server - client.start(); } -export function deactivate(): Thenable | undefined { - if (!client) { - return undefined; - } - return client.stop(); -} +export function deactivate(): void { + stopLanguageServers(); +} \ No newline at end of file diff --git a/client/src/nu-ide.ts b/client/src/nu-ide.ts new file mode 100644 index 0000000..ed634bd --- /dev/null +++ b/client/src/nu-ide.ts @@ -0,0 +1,69 @@ +import * as path from "path"; +import { ExtensionContext, window } from "vscode"; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, +} from "vscode-languageclient/node"; + +let client: LanguageClient | null = null; +const name = "Nushell Language Server (extension)"; + +async function startClient( + context: ExtensionContext, + clientOptions: LanguageClientOptions, +) { + // The server is implemented in node + const serverModule = context.asAbsolutePath( + path.join("out", "server", "src", "server.js"), + ); + + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions, + }, + }; + + // Create the language client and start the client. + client = new LanguageClient( + "nushellLanguageServer-ide", + name, + serverOptions, + clientOptions, + ); + + return client.start().catch((reason: unknown) => { + window.showWarningMessage(`Failed to start ${name}: ${reason}`); + client = null; + }); +} + +async function stopClient(): Promise { + if (client) { + await client.stop().catch((reason: unknown) => { + console.error(`Failed to stop ${name}: ${reason}`); + }); + } + client = null; +} + +export async function activate( + context: ExtensionContext, + clientOptions: LanguageClientOptions, +) { + await startClient(context, clientOptions); +} + +export async function deactivate(): Promise { + await stopClient(); +} diff --git a/client/src/nu-lsp.ts b/client/src/nu-lsp.ts new file mode 100644 index 0000000..92e22a2 --- /dev/null +++ b/client/src/nu-lsp.ts @@ -0,0 +1,55 @@ +import { window } from "vscode"; +import * as vscode from "vscode"; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, +} from "vscode-languageclient/node"; + +let client: LanguageClient | null = null; +const name = "Nushell Language Server (nu --lsp)"; + +async function startClient(clientOptions: LanguageClientOptions) { + const configuration = vscode.workspace.getConfiguration( + "nushellLanguageServer", + null, + ); + + const serverOptions: ServerOptions = { + command: configuration.nushellExecutablePath, + args: ["--lsp"], + }; + + // Create the language client and start the client. + client = new LanguageClient( + "nushellLanguageServer-lsp", + name, + serverOptions, + clientOptions, + ); + + return client.start().catch((reason: unknown) => { + window.showWarningMessage(`Failed to start ${name}: ${reason}`); + client = null; + }); +} + +async function stopClient(): Promise { + if (client) { + await client.stop().catch((reason: unknown) => { + console.error(`Failed to stop ${name}: ${reason}`); + }); + } + client = null; +} + +export async function activate(clientOptions: LanguageClientOptions) { + // TODO: use configuration + // const configuration = workspace.getConfiguration("nushellLanguageServer", null); + + await startClient(clientOptions); +} + +export async function deactivate(): Promise { + await stopClient(); +} diff --git a/package.json b/package.json index 8ef574b..ea0f440 100644 --- a/package.json +++ b/package.json @@ -19,25 +19,15 @@ "vscode": "^1.75.0" }, "icon": "assets/nushell.ico", - "categories": [ - "Programming Languages", - "Snippets" - ], - "activationEvents": [ - "onTerminalProfile:nushell_default" - ], + "categories": ["Programming Languages", "Snippets"], + "activationEvents": ["onTerminalProfile:nushell_default"], "main": "out/client/src/extension.js", "contributes": { "languages": [ { "id": "nushell", - "aliases": [ - "nushell", - "nu" - ], - "extensions": [ - ".nu" - ], + "aliases": ["nushell", "nu"], + "extensions": [".nu"], "icon": { "light": "assets/nu.svg", "dark": "assets/nu.svg" @@ -85,6 +75,13 @@ "type": "object", "title": "Nushell IDE Support", "properties": { + "nushellLanguageServer.implementation": { + "scope": "window", + "type": "string", + "enum": ["extension", "nu --lsp"], + "default": "extension", + "description": "Switch between different language server implementations." + }, "nushellLanguageServer.maxNumberOfProblems": { "scope": "resource", "type": "number", @@ -94,11 +91,7 @@ "nushellLanguageServer.trace.server": { "scope": "window", "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], + "enum": ["off", "messages", "verbose"], "default": "off", "description": "Traces the communication between VS Code and the language server." }, @@ -165,12 +158,7 @@ "webpack": "^5.70.0", "webpack-cli": "^4.9.2" }, - "keywords": [ - "nushell", - "nu", - "shell", - "scripting" - ], + "keywords": ["nushell", "nu", "shell", "scripting"], "galleryBanner": { "color": "#008000", "theme": "light"