// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import * as fsapi from "fs-extra" import { Disposable, env, LogOutputChannel, workspace } from "vscode" import { State } from "vscode-languageclient" import { LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions } from "vscode-languageclient/node" import { DEBUG_SERVER_SCRIPT_PATH, SERVER_SCRIPT_PATH } from "./constants" import { traceError, traceInfo, traceVerbose } from "./log/logging" import { getDebuggerPath } from "./python" import { getExtensionSettings, getGlobalSettings, getWorkspaceSettings, ISettings } from "./settings" import { getLSClientTraceLevel, getProjectRoot } from "./utilities" import { isVirtualWorkspace } from "./vscodeapi" export type IInitOptions = { settings: ISettings[]; globalSettings: ISettings } let _disposables: Disposable[] = [] export function disposeServerResources(): void { _disposables.forEach((disposable) => disposable.dispose()) _disposables = [] } async function createServer( settings: ISettings, serverId: string, serverName: string, outputChannel: LogOutputChannel, initializationOptions: IInitOptions ): Promise { const command = settings.interpreter[0] if (!command) { throw new Error("No Python interpreter is configured for the language server.") } const cwd = settings.cwd // Set debugger path needed for debugging python code. const newEnv = { ...process.env } const isDebugScript = await fsapi.pathExists(DEBUG_SERVER_SCRIPT_PATH) const debugRequested = newEnv.USE_DEBUGPY?.toLowerCase() === "true" if (debugRequested && !isDebugScript) { throw new Error(`Python debug bootstrap not found: ${DEBUG_SERVER_SCRIPT_PATH}`) } const debuggerPath = debugRequested ? await getDebuggerPath() : undefined if (debugRequested && !debuggerPath) { throw new Error( "Python debugging was requested, but the Python Debugger extension did not provide debugpy." ) } if (debugRequested && debuggerPath) { newEnv.DEBUGPY_PATH = debuggerPath } else { newEnv.USE_DEBUGPY = "False" } // Set import strategy newEnv.LS_IMPORT_STRATEGY = settings.importStrategy // Set notification type newEnv.LS_SHOW_NOTIFICATION = settings.showNotifications const serverScript = debugRequested ? DEBUG_SERVER_SCRIPT_PATH : SERVER_SCRIPT_PATH const interpreterArgs = settings.interpreter.slice(1) if (debugRequested && !interpreterArgs.includes("-Xfrozen_modules=off")) { interpreterArgs.push("-Xfrozen_modules=off") } const args = interpreterArgs.concat([serverScript]) traceInfo(`Python debug mode: ${debugRequested ? "enabled" : "disabled"}`) traceInfo(`Server run command: ${[command, ...args].join(" ")}`) const serverOptions: ServerOptions = { command, args, options: { cwd, env: newEnv } } // Options to control the language client const tclFileWatcher = workspace.createFileSystemWatcher("**/*.tcl") const clientOptions: LanguageClientOptions = { // Register the server for python documents documentSelector: isVirtualWorkspace() ? [{ language: "tcl" }] : [ { scheme: "file", language: "tcl" }, { scheme: "untitled", language: "tcl" }, { scheme: "vscode-notebook", language: "tcl" }, { scheme: "vscode-notebook-cell", language: "tcl" } ], outputChannel: outputChannel, traceOutputChannel: outputChannel, revealOutputChannelOn: RevealOutputChannelOn.Never, synchronize: { fileEvents: tclFileWatcher }, initializationOptions } _disposables.push(tclFileWatcher) return new LanguageClient(serverId, serverName, serverOptions, clientOptions) } export async function restartServer( serverId: string, serverName: string, outputChannel: LogOutputChannel, lsClient?: LanguageClient ): Promise { if (lsClient) { traceInfo(`Server: Stop requested`) await lsClient.stop() disposeServerResources() } const projectRoot = await getProjectRoot() const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true) try { const newLSClient = await createServer( workspaceSetting, serverId, serverName, outputChannel, { settings: await getExtensionSettings(serverId, true), globalSettings: await getGlobalSettings(serverId, false) } ) traceInfo(`Server: Start requested.`) _disposables.push( newLSClient.onDidChangeState((e) => { switch (e.newState) { case State.Stopped: traceVerbose(`Server State: Stopped`) break case State.Starting: traceVerbose(`Server State: Starting`) break case State.Running: traceVerbose(`Server State: Running`) break } }) ) await newLSClient.start() const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel) await newLSClient.setTrace(level) return newLSClient } catch (ex) { traceError(`Server: Start failed: ${ex}`) disposeServerResources() return undefined } }