171 lines
5.8 KiB
TypeScript
171 lines
5.8 KiB
TypeScript
// 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
|
|
// Folder for the server's persistent index cache; omitted without a workspace.
|
|
indexCachePath?: string
|
|
}
|
|
|
|
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<LanguageClient> {
|
|
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,psc,def}")
|
|
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,
|
|
indexCachePath?: string
|
|
): Promise<LanguageClient | undefined> {
|
|
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),
|
|
indexCachePath
|
|
}
|
|
)
|
|
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
|
|
}
|
|
}
|