python server
This commit is contained in:
Generated
+56
-3
@@ -9,6 +9,8 @@
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vscode/python-extension": "^1.0.5",
|
||||
"fs-extra": "^11.3.0",
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -36,6 +38,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vscode/python-extension": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/python-extension/-/python-extension-1.0.5.tgz",
|
||||
"integrity": "sha512-uYhXUrL/gn92mfqhjAwH2+yGOpjloBxj9ekoL4BhUsKcyJMpEg6WlNf3S3si+5x9zlbHHe7FYQNjZEbz1ymI9Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.17.1",
|
||||
"vscode": "^1.78.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -43,14 +55,46 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
|
||||
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
@@ -82,6 +126,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"vscode": "^1.96.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/python-extension": "^1.0.5",
|
||||
"fs-extra": "^11.3.0",
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as path from "path"
|
||||
|
||||
const folderName = path.basename(__dirname)
|
||||
export const EXTENSION_ROOT_DIR =
|
||||
folderName === "common"
|
||||
? path.dirname(path.dirname(path.dirname(__dirname)))
|
||||
: path.dirname(path.dirname(__dirname))
|
||||
export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, "server")
|
||||
export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, "src", `lsp_server.py`)
|
||||
export const DEBUG_SERVER_SCRIPT_PATH = path.join(
|
||||
BUNDLED_PYTHON_SCRIPTS_DIR,
|
||||
"src",
|
||||
`_debug_server.py`
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import * as util from "util"
|
||||
import { Disposable, LogOutputChannel } from "vscode"
|
||||
|
||||
type Arguments = unknown[]
|
||||
class OutputChannelLogger {
|
||||
constructor(private readonly channel: LogOutputChannel) {}
|
||||
|
||||
public traceLog(...data: Arguments): void {
|
||||
this.channel.appendLine(util.format(...data))
|
||||
}
|
||||
|
||||
public traceError(...data: Arguments): void {
|
||||
this.channel.error(util.format(...data))
|
||||
}
|
||||
|
||||
public traceWarn(...data: Arguments): void {
|
||||
this.channel.warn(util.format(...data))
|
||||
}
|
||||
|
||||
public traceInfo(...data: Arguments): void {
|
||||
this.channel.info(util.format(...data))
|
||||
}
|
||||
|
||||
public traceVerbose(...data: Arguments): void {
|
||||
this.channel.debug(util.format(...data))
|
||||
}
|
||||
}
|
||||
|
||||
let channel: OutputChannelLogger | undefined
|
||||
export function registerLogger(logChannel: LogOutputChannel): Disposable {
|
||||
channel = new OutputChannelLogger(logChannel)
|
||||
return {
|
||||
dispose: () => {
|
||||
channel = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function traceLog(...args: Arguments): void {
|
||||
channel?.traceLog(...args)
|
||||
}
|
||||
|
||||
export function traceError(...args: Arguments): void {
|
||||
channel?.traceError(...args)
|
||||
}
|
||||
|
||||
export function traceWarn(...args: Arguments): void {
|
||||
channel?.traceWarn(...args)
|
||||
}
|
||||
|
||||
export function traceInfo(...args: Arguments): void {
|
||||
channel?.traceInfo(...args)
|
||||
}
|
||||
|
||||
export function traceVerbose(...args: Arguments): void {
|
||||
channel?.traceVerbose(...args)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { commands, Disposable, Event, EventEmitter, Uri } from "vscode"
|
||||
import { traceError, traceLog } from "./log/logging"
|
||||
import { PythonExtension, ResolvedEnvironment } from "@vscode/python-extension"
|
||||
|
||||
export interface IInterpreterDetails {
|
||||
path?: string[]
|
||||
resource?: Uri
|
||||
}
|
||||
|
||||
const onDidChangePythonInterpreterEvent = new EventEmitter<IInterpreterDetails>()
|
||||
export const onDidChangePythonInterpreter: Event<IInterpreterDetails> =
|
||||
onDidChangePythonInterpreterEvent.event
|
||||
|
||||
let _api: PythonExtension | undefined
|
||||
async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
|
||||
if (_api) {
|
||||
return _api
|
||||
}
|
||||
_api = await PythonExtension.api()
|
||||
return _api
|
||||
}
|
||||
|
||||
export async function initializePython(disposables: Disposable[]): Promise<void> {
|
||||
try {
|
||||
const api = await getPythonExtensionAPI()
|
||||
|
||||
if (api) {
|
||||
disposables.push(
|
||||
api.environments.onDidChangeActiveEnvironmentPath((e) => {
|
||||
onDidChangePythonInterpreterEvent.fire({
|
||||
path: [e.path],
|
||||
resource: e.resource?.uri
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
traceLog("Waiting for interpreter from python extension.")
|
||||
onDidChangePythonInterpreterEvent.fire(await getInterpreterDetails())
|
||||
}
|
||||
} catch (error) {
|
||||
traceError("Error initializing python: ", error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveInterpreter(
|
||||
interpreter: string[]
|
||||
): Promise<ResolvedEnvironment | undefined> {
|
||||
const api = await getPythonExtensionAPI()
|
||||
return api?.environments.resolveEnvironment(interpreter[0])
|
||||
}
|
||||
|
||||
export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
|
||||
const api = await getPythonExtensionAPI()
|
||||
const environment = await api?.environments.resolveEnvironment(
|
||||
api?.environments.getActiveEnvironmentPath(resource)
|
||||
)
|
||||
if (environment?.executable.uri && checkVersion(environment)) {
|
||||
return { path: [environment?.executable.uri.fsPath], resource }
|
||||
}
|
||||
return { path: undefined, resource }
|
||||
}
|
||||
|
||||
export async function getDebuggerPath(): Promise<string | undefined> {
|
||||
const api = await getPythonExtensionAPI()
|
||||
return api?.debug.getDebuggerPackagePath()
|
||||
}
|
||||
|
||||
export async function runPythonExtensionCommand(command: string, ...rest: any[]) {
|
||||
await getPythonExtensionAPI()
|
||||
return await commands.executeCommand(command, ...rest)
|
||||
}
|
||||
|
||||
export function checkVersion(resolved: ResolvedEnvironment | undefined): boolean {
|
||||
const version = resolved?.version
|
||||
if (version?.major === 3 && version?.minor >= 11) {
|
||||
return true
|
||||
}
|
||||
traceError(`Python version ${version?.major}.${version?.minor} is not supported.`)
|
||||
traceError(`Selected python path: ${resolved?.executable.uri?.fsPath}`)
|
||||
traceError("Supported versions are 3.11 and above.")
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import * as fsapi from "fs-extra"
|
||||
import { Disposable, env, LogOutputChannel } 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 }
|
||||
|
||||
async function createServer(
|
||||
settings: ISettings,
|
||||
serverId: string,
|
||||
serverName: string,
|
||||
outputChannel: LogOutputChannel,
|
||||
initializationOptions: IInitOptions
|
||||
): Promise<LanguageClient> {
|
||||
const command = settings.interpreter[0]
|
||||
const cwd = settings.cwd
|
||||
|
||||
// Set debugger path needed for debugging python code.
|
||||
const newEnv = { ...process.env }
|
||||
const debuggerPath = await getDebuggerPath()
|
||||
const isDebugScript = await fsapi.pathExists(DEBUG_SERVER_SCRIPT_PATH)
|
||||
if (newEnv.USE_DEBUGPY && 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 args =
|
||||
newEnv.USE_DEBUGPY === "False" || !isDebugScript
|
||||
? settings.interpreter.slice(1).concat([SERVER_SCRIPT_PATH])
|
||||
: settings.interpreter.slice(1).concat([DEBUG_SERVER_SCRIPT_PATH])
|
||||
traceInfo(`Server run command: ${[command, ...args].join(" ")}`)
|
||||
|
||||
const serverOptions: ServerOptions = {
|
||||
command,
|
||||
args,
|
||||
options: { cwd, env: newEnv }
|
||||
}
|
||||
|
||||
// Options to control the language client
|
||||
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,
|
||||
initializationOptions
|
||||
}
|
||||
|
||||
return new LanguageClient(serverId, serverName, serverOptions, clientOptions)
|
||||
}
|
||||
|
||||
let _disposables: Disposable[] = []
|
||||
export async function restartServer(
|
||||
serverId: string,
|
||||
serverName: string,
|
||||
outputChannel: LogOutputChannel,
|
||||
lsClient?: LanguageClient
|
||||
): Promise<LanguageClient | undefined> {
|
||||
if (lsClient) {
|
||||
traceInfo(`Server: Stop requested`)
|
||||
await lsClient.stop()
|
||||
_disposables.forEach((d) => d.dispose())
|
||||
_disposables = []
|
||||
}
|
||||
const projectRoot = await getProjectRoot()
|
||||
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true)
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
)
|
||||
try {
|
||||
await newLSClient.start()
|
||||
} catch (ex) {
|
||||
traceError(`Server: Start failed: ${ex}`)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel)
|
||||
await newLSClient.setTrace(level)
|
||||
return newLSClient
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import {
|
||||
ConfigurationChangeEvent,
|
||||
ConfigurationScope,
|
||||
WorkspaceConfiguration,
|
||||
WorkspaceFolder
|
||||
} from "vscode"
|
||||
import { getInterpreterDetails } from "./python"
|
||||
import { getConfiguration, getWorkspaceFolders } from "./vscodeapi"
|
||||
|
||||
export interface ISettings {
|
||||
cwd: string
|
||||
workspace: string
|
||||
args: string[]
|
||||
path: string[]
|
||||
interpreter: string[]
|
||||
importStrategy: string
|
||||
showNotifications: string
|
||||
}
|
||||
|
||||
export function getExtensionSettings(
|
||||
namespace: string,
|
||||
includeInterpreter?: boolean
|
||||
): Promise<ISettings[]> {
|
||||
return Promise.all(
|
||||
getWorkspaceFolders().map((w) => getWorkspaceSettings(namespace, w, includeInterpreter))
|
||||
)
|
||||
}
|
||||
|
||||
function resolveVariables(value: string[], workspace?: WorkspaceFolder): string[] {
|
||||
const substitutions = new Map<string, string>()
|
||||
const home = process.env.HOME || process.env.USERPROFILE
|
||||
if (home) {
|
||||
substitutions.set("${userHome}", home)
|
||||
}
|
||||
if (workspace) {
|
||||
substitutions.set("${workspaceFolder}", workspace.uri.fsPath)
|
||||
}
|
||||
substitutions.set("${cwd}", process.cwd())
|
||||
getWorkspaceFolders().forEach((w) => {
|
||||
substitutions.set("${workspaceFolder:" + w.name + "}", w.uri.fsPath)
|
||||
})
|
||||
|
||||
return value.map((s) => {
|
||||
for (const [key, value] of substitutions) {
|
||||
s = s.replace(key, value)
|
||||
}
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
export function getInterpreterFromSetting(namespace: string, scope?: ConfigurationScope) {
|
||||
const config = getConfiguration(namespace, scope)
|
||||
return config.get<string[]>("interpreter")
|
||||
}
|
||||
|
||||
export async function getWorkspaceSettings(
|
||||
namespace: string,
|
||||
workspace: WorkspaceFolder,
|
||||
includeInterpreter?: boolean
|
||||
): Promise<ISettings> {
|
||||
const config = getConfiguration(namespace, workspace.uri)
|
||||
|
||||
let interpreter: string[] = []
|
||||
if (includeInterpreter) {
|
||||
interpreter = getInterpreterFromSetting(namespace, workspace) ?? []
|
||||
if (interpreter.length === 0) {
|
||||
interpreter = (await getInterpreterDetails(workspace.uri)).path ?? []
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceSetting = {
|
||||
cwd: workspace.uri.fsPath,
|
||||
workspace: workspace.uri.toString(),
|
||||
args: resolveVariables(config.get<string[]>(`args`) ?? [], workspace),
|
||||
path: resolveVariables(config.get<string[]>(`path`) ?? [], workspace),
|
||||
interpreter: resolveVariables(interpreter, workspace),
|
||||
importStrategy: config.get<string>(`importStrategy`) ?? "useBundled",
|
||||
showNotifications: config.get<string>(`showNotifications`) ?? "off"
|
||||
}
|
||||
return workspaceSetting
|
||||
}
|
||||
|
||||
function getGlobalValue<T>(config: WorkspaceConfiguration, key: string, defaultValue: T): T {
|
||||
const inspect = config.inspect<T>(key)
|
||||
return inspect?.globalValue ?? inspect?.defaultValue ?? defaultValue
|
||||
}
|
||||
|
||||
export async function getGlobalSettings(
|
||||
namespace: string,
|
||||
includeInterpreter?: boolean
|
||||
): Promise<ISettings> {
|
||||
const config = getConfiguration(namespace)
|
||||
|
||||
let interpreter: string[] = []
|
||||
if (includeInterpreter) {
|
||||
interpreter = getGlobalValue<string[]>(config, "interpreter", [])
|
||||
if (interpreter === undefined || interpreter.length === 0) {
|
||||
interpreter = (await getInterpreterDetails()).path ?? []
|
||||
}
|
||||
}
|
||||
|
||||
const setting = {
|
||||
cwd: process.cwd(),
|
||||
workspace: process.cwd(),
|
||||
args: getGlobalValue<string[]>(config, "args", []),
|
||||
path: getGlobalValue<string[]>(config, "path", []),
|
||||
interpreter: interpreter,
|
||||
importStrategy: getGlobalValue<string>(config, "importStrategy", "useBundled"),
|
||||
showNotifications: getGlobalValue<string>(config, "showNotifications", "off")
|
||||
}
|
||||
return setting
|
||||
}
|
||||
|
||||
export function checkIfConfigurationChanged(
|
||||
e: ConfigurationChangeEvent,
|
||||
namespace: string
|
||||
): boolean {
|
||||
const settings = [
|
||||
`${namespace}.args`,
|
||||
`${namespace}.path`,
|
||||
`${namespace}.interpreter`,
|
||||
`${namespace}.importStrategy`,
|
||||
`${namespace}.showNotifications`
|
||||
]
|
||||
const changed = settings.map((s) => e.affectsConfiguration(s))
|
||||
return changed.includes(true)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import * as path from "path"
|
||||
import * as fs from "fs-extra"
|
||||
import { EXTENSION_ROOT_DIR } from "./constants"
|
||||
|
||||
export interface IServerInfo {
|
||||
name: string
|
||||
module: string
|
||||
}
|
||||
|
||||
export function loadServerDefaults(): IServerInfo {
|
||||
const packageJson = path.join(EXTENSION_ROOT_DIR, "package.json")
|
||||
const content = fs.readFileSync(packageJson).toString()
|
||||
const config = JSON.parse(content)
|
||||
return config.serverInfo as IServerInfo
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import * as fs from "fs-extra"
|
||||
import * as path from "path"
|
||||
import { LogLevel, Uri, WorkspaceFolder } from "vscode"
|
||||
import { Trace } from "vscode-jsonrpc/node"
|
||||
import { getWorkspaceFolders } from "./vscodeapi"
|
||||
|
||||
function logLevelToTrace(logLevel: LogLevel): Trace {
|
||||
switch (logLevel) {
|
||||
case LogLevel.Error:
|
||||
case LogLevel.Warning:
|
||||
case LogLevel.Info:
|
||||
return Trace.Messages
|
||||
|
||||
case LogLevel.Debug:
|
||||
case LogLevel.Trace:
|
||||
return Trace.Verbose
|
||||
|
||||
case LogLevel.Off:
|
||||
default:
|
||||
return Trace.Off
|
||||
}
|
||||
}
|
||||
|
||||
export function getLSClientTraceLevel(channelLogLevel: LogLevel, globalLogLevel: LogLevel): Trace {
|
||||
if (channelLogLevel === LogLevel.Off) {
|
||||
return logLevelToTrace(globalLogLevel)
|
||||
}
|
||||
if (globalLogLevel === LogLevel.Off) {
|
||||
return logLevelToTrace(channelLogLevel)
|
||||
}
|
||||
const level = logLevelToTrace(
|
||||
channelLogLevel <= globalLogLevel ? channelLogLevel : globalLogLevel
|
||||
)
|
||||
return level
|
||||
}
|
||||
|
||||
export async function getProjectRoot(): Promise<WorkspaceFolder> {
|
||||
const workspaces: readonly WorkspaceFolder[] = getWorkspaceFolders()
|
||||
if (workspaces.length === 0) {
|
||||
return {
|
||||
uri: Uri.file(process.cwd()),
|
||||
name: path.basename(process.cwd()),
|
||||
index: 0
|
||||
}
|
||||
} else if (workspaces.length === 1) {
|
||||
return workspaces[0]
|
||||
} else {
|
||||
let rootWorkspace = workspaces[0]
|
||||
let root = undefined
|
||||
for (const w of workspaces) {
|
||||
if (await fs.pathExists(w.uri.fsPath)) {
|
||||
root = w.uri.fsPath
|
||||
rootWorkspace = w
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (const w of workspaces) {
|
||||
if (root && root.length > w.uri.fsPath.length && (await fs.pathExists(w.uri.fsPath))) {
|
||||
root = w.uri.fsPath
|
||||
rootWorkspace = w
|
||||
}
|
||||
}
|
||||
return rootWorkspace
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
commands,
|
||||
ConfigurationScope,
|
||||
Disposable,
|
||||
LogOutputChannel,
|
||||
Uri,
|
||||
window,
|
||||
workspace,
|
||||
WorkspaceConfiguration,
|
||||
WorkspaceFolder
|
||||
} from "vscode"
|
||||
|
||||
export function createOutputChannel(name: string): LogOutputChannel {
|
||||
return window.createOutputChannel(name, { log: true })
|
||||
}
|
||||
|
||||
export function getConfiguration(
|
||||
config: string,
|
||||
scope?: ConfigurationScope
|
||||
): WorkspaceConfiguration {
|
||||
return workspace.getConfiguration(config, scope)
|
||||
}
|
||||
|
||||
export function registerCommand(
|
||||
command: string,
|
||||
callback: (...args: any[]) => any,
|
||||
thisArg?: any
|
||||
): Disposable {
|
||||
return commands.registerCommand(command, callback, thisArg)
|
||||
}
|
||||
|
||||
export const { onDidChangeConfiguration } = workspace
|
||||
|
||||
export function isVirtualWorkspace(): boolean {
|
||||
const isVirtual =
|
||||
workspace.workspaceFolders &&
|
||||
workspace.workspaceFolders.every((f) => f.uri.scheme !== "file")
|
||||
return !!isVirtual
|
||||
}
|
||||
|
||||
export function getWorkspaceFolders(): readonly WorkspaceFolder[] {
|
||||
return workspace.workspaceFolders ?? []
|
||||
}
|
||||
|
||||
export function getWorkspaceFolder(uri: Uri): WorkspaceFolder | undefined {
|
||||
return workspace.getWorkspaceFolder(uri)
|
||||
}
|
||||
+92
-30
@@ -14,43 +14,105 @@ import {
|
||||
diagnosticHandler,
|
||||
tclDocumentSymbolProvider
|
||||
} from "./common/handlers"
|
||||
import * as path from "path"
|
||||
import { registerLogger, traceError, traceLog, traceVerbose } from "./common/log/logging"
|
||||
import {
|
||||
checkVersion,
|
||||
getInterpreterDetails,
|
||||
initializePython,
|
||||
onDidChangePythonInterpreter,
|
||||
resolveInterpreter
|
||||
} from "./common/python"
|
||||
import { restartServer } from "./common/server"
|
||||
import { checkIfConfigurationChanged, getInterpreterFromSetting } from "./common/settings"
|
||||
import { loadServerDefaults } from "./common/setup"
|
||||
import { getLSClientTraceLevel } from "./common/utilities"
|
||||
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from "./common/vscodeapi"
|
||||
|
||||
let client: LanguageClient
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// The server is implemented in node
|
||||
const serverModule = context.asAbsolutePath(path.join("out", "server", "src", "server.js"))
|
||||
console.log(context.asAbsolutePath(path.join("server", "src", "server.js")))
|
||||
// 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 to control the language client
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
// Register the server for plain text documents
|
||||
documentSelector: [{ scheme: "file", language: "tcl" }],
|
||||
synchronize: {
|
||||
// Notify the server about file changes to '.clientrc files contained in the workspace
|
||||
fileEvents: vscode.workspace.createFileSystemWatcher("**/.clientrc")
|
||||
}
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
// This is required to get server name and module. This should be
|
||||
// the first thing that we do in this extension.
|
||||
const serverInfo = loadServerDefaults()
|
||||
const serverName = serverInfo.name
|
||||
const serverId = serverInfo.module
|
||||
|
||||
// Setup logging
|
||||
const outputChannel = createOutputChannel(serverName)
|
||||
context.subscriptions.push(outputChannel, registerLogger(outputChannel))
|
||||
|
||||
const changeLogLevel = async (c: vscode.LogLevel, g: vscode.LogLevel) => {
|
||||
const level = getLSClientTraceLevel(c, g)
|
||||
await client?.setTrace(level)
|
||||
}
|
||||
|
||||
// Create the language client and start the client.
|
||||
client = new LanguageClient(
|
||||
"languageServerExample",
|
||||
"Language Server Example",
|
||||
serverOptions,
|
||||
clientOptions
|
||||
context.subscriptions.push(
|
||||
outputChannel.onDidChangeLogLevel(async (e) => {
|
||||
await changeLogLevel(e, vscode.env.logLevel)
|
||||
}),
|
||||
vscode.env.onDidChangeLogLevel(async (e) => {
|
||||
await changeLogLevel(outputChannel.logLevel, e)
|
||||
})
|
||||
)
|
||||
|
||||
// Start the client. This will also launch the server
|
||||
client.start()
|
||||
// Log Server information
|
||||
traceLog(`Name: ${serverInfo.name}`)
|
||||
traceLog(`Module: ${serverInfo.module}`)
|
||||
traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`)
|
||||
|
||||
const runServer = async () => {
|
||||
const interpreter = getInterpreterFromSetting(serverId)
|
||||
if (interpreter && interpreter.length > 0) {
|
||||
if (checkVersion(await resolveInterpreter(interpreter))) {
|
||||
traceVerbose(
|
||||
`Using interpreter from ${serverInfo.module}.interpreter: ${interpreter.join(" ")}`
|
||||
)
|
||||
client = await restartServer(serverId, serverName, outputChannel, client)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const interpreterDetails = await getInterpreterDetails()
|
||||
if (interpreterDetails.path) {
|
||||
traceVerbose(
|
||||
`Using interpreter from Python extension: ${interpreterDetails.path.join(" ")}`
|
||||
)
|
||||
client = await restartServer(serverId, serverName, outputChannel, client)
|
||||
return
|
||||
}
|
||||
|
||||
traceError(
|
||||
"Python interpreter missing:\r\n" +
|
||||
"[Option 1] Select python interpreter using the ms-python.python.\r\n" +
|
||||
`[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n` +
|
||||
"Please use Python 3.8 or greater."
|
||||
)
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
onDidChangePythonInterpreter(async () => {
|
||||
await runServer()
|
||||
}),
|
||||
onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
|
||||
if (checkIfConfigurationChanged(e, serverId)) {
|
||||
await runServer()
|
||||
}
|
||||
}),
|
||||
registerCommand(`${serverId}.restart`, async () => {
|
||||
await runServer()
|
||||
})
|
||||
)
|
||||
|
||||
setImmediate(async () => {
|
||||
const interpreter = getInterpreterFromSetting(serverId)
|
||||
if (interpreter === undefined || interpreter.length === 0) {
|
||||
traceLog(`Python extension loading`)
|
||||
await initializePython(context.subscriptions)
|
||||
traceLog(`Python extension loaded`)
|
||||
} else {
|
||||
await runServer()
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider(
|
||||
|
||||
Reference in New Issue
Block a user