python server

This commit is contained in:
2025-07-15 22:11:41 +02:00
parent 12ed7ff2bc
commit 9febcebddb
23 changed files with 900 additions and 256 deletions
+56 -3
View File
@@ -9,6 +9,8 @@
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vscode/python-extension": "^1.0.5",
"fs-extra": "^11.3.0",
"vscode-languageclient": "^9.0.1" "vscode-languageclient": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
@@ -36,6 +38,16 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -43,14 +55,46 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "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": { "node_modules/minimatch": {
"version": "5.1.6", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
@@ -82,6 +126,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/vscode-jsonrpc": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+2
View File
@@ -7,6 +7,8 @@
"vscode": "^1.96.0" "vscode": "^1.96.0"
}, },
"dependencies": { "dependencies": {
"@vscode/python-extension": "^1.0.5",
"fs-extra": "^11.3.0",
"vscode-languageclient": "^9.0.1" "vscode-languageclient": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
+14
View File
@@ -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`
)
+60
View File
@@ -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)
}
+86
View File
@@ -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
}
+131
View File
@@ -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
}
+130
View File
@@ -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)
}
+18
View File
@@ -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
}
+69
View File
@@ -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
}
}
+52
View File
@@ -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
View File
@@ -14,43 +14,105 @@ import {
diagnosticHandler, diagnosticHandler,
tclDocumentSymbolProvider tclDocumentSymbolProvider
} from "./common/handlers" } 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 let client: LanguageClient
export function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
// The server is implemented in node // This is required to get server name and module. This should be
const serverModule = context.asAbsolutePath(path.join("out", "server", "src", "server.js")) // the first thing that we do in this extension.
console.log(context.asAbsolutePath(path.join("server", "src", "server.js"))) const serverInfo = loadServerDefaults()
// If the extension is launched in debug mode then the debug server options are used const serverName = serverInfo.name
// Otherwise the run options are used const serverId = serverInfo.module
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc }, // Setup logging
debug: { const outputChannel = createOutputChannel(serverName)
module: serverModule, context.subscriptions.push(outputChannel, registerLogger(outputChannel))
transport: TransportKind.ipc
} const changeLogLevel = async (c: vscode.LogLevel, g: vscode.LogLevel) => {
} const level = getLSClientTraceLevel(c, g)
// Options to control the language client await client?.setTrace(level)
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")
}
} }
// Create the language client and start the client. context.subscriptions.push(
client = new LanguageClient( outputChannel.onDidChangeLogLevel(async (e) => {
"languageServerExample", await changeLogLevel(e, vscode.env.logLevel)
"Language Server Example", }),
serverOptions, vscode.env.onDidChangeLogLevel(async (e) => {
clientOptions await changeLogLevel(outputChannel.logLevel, e)
})
) )
// Start the client. This will also launch the server // Log Server information
client.start() 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( const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider(
+1
View File
@@ -0,0 +1 @@
3.11
View File
+26
View File
@@ -0,0 +1,26 @@
"""All the action we need during build"""
import json
import os
import pathlib
import urllib.request as url_lib
from typing import List
import nox # pylint: disable=import-error
@nox.session()
def setup(session: nox.Session) -> None:
"""Sets up the template for development."""
session.install(
"-t",
"./bundled/libs",
"--no-cache-dir",
"--implementation",
"py",
"--no-deps",
"--upgrade",
"-r",
"./requirements.txt",
)
-123
View File
@@ -1,123 +0,0 @@
{
"name": "lsp-sample-server",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lsp-sample-server",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"chevrotain": "^11.0.3",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.12"
},
"engines": {
"node": "*"
}
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"license": "Apache-2.0"
},
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/vscode-jsonrpc": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/vscode-languageserver": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
"integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
"license": "MIT",
"dependencies": {
"vscode-languageserver-protocol": "3.17.5"
},
"bin": {
"installServerIntoExtension": "bin/installServerIntoExtension"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
"license": "MIT",
"dependencies": {
"vscode-jsonrpc": "8.2.0",
"vscode-languageserver-types": "3.17.5"
}
},
"node_modules/vscode-languageserver-textdocument": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
"license": "MIT"
},
"node_modules/vscode-languageserver-types": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
"license": "MIT"
}
}
}
-16
View File
@@ -1,16 +0,0 @@
{
"name": "lsp-sample-server",
"description": "Example implementation of a language server in node.",
"version": "1.0.0",
"author": "Microsoft Corporation",
"license": "MIT",
"engines": {
"node": "*"
},
"dependencies": {
"chevrotain": "^11.0.3",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.12"
},
"scripts": {}
}
+9
View File
@@ -0,0 +1,9 @@
[project]
name = "server"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pygls","ply"
]
+39
View File
@@ -0,0 +1,39 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
"""Debugging support for LSP."""
import os
import pathlib
import runpy
import sys
def update_sys_path(path_to_add: str) -> None:
"""Add given path to `sys.path`."""
if path_to_add not in sys.path and os.path.isdir(path_to_add):
sys.path.append(path_to_add)
# Ensure debugger is loaded before we load anything else, to debug initialization.
debugger_path = os.getenv("DEBUGPY_PATH", None)
if debugger_path:
if debugger_path.endswith("debugpy"):
debugger_path = os.fspath(pathlib.Path(debugger_path).parent)
update_sys_path(debugger_path)
# pylint: disable=wrong-import-position,import-error
import debugpy
# 5678 is the default port, If you need to change it update it here
# and in launch.json.
debugpy.connect(5678)
# This will ensure that execution is paused as soon as the debugger
# connects to VS Code. If you don't want to pause here comment this
# line and set breakpoints as appropriate.
debugpy.breakpoint()
SERVER_PATH = os.fspath(pathlib.Path(__file__).parent / "lsp_server.py")
# NOTE: Set breakpoint in `lsp_server.py` before continuing.
runpy.run_path(SERVER_PATH, run_name="__main__")
+32
View File
@@ -0,0 +1,32 @@
"""Implementation of tool support over LSP."""
from __future__ import annotations
import copy
import json
import os
import pathlib
import re
import sys
import sysconfig
import traceback
from typing import Any, Optional, Sequence
# **********************************************************
# Update sys.path before importing any bundled libraries.
# **********************************************************
def update_sys_path(path_to_add: str, strategy: str) -> None:
"""Add given path to `sys.path`."""
if path_to_add not in sys.path and os.path.isdir(path_to_add):
if strategy == "useBundled":
sys.path.insert(0, path_to_add)
elif strategy == "fromEnvironment":
sys.path.append(path_to_add)
# Ensure that we can import LSP libraries, and other bundled libraries.
update_sys_path(
os.fspath(pathlib.Path(__file__).parent.parent / "libs"),
os.getenv("LS_IMPORT_STRATEGY", "useBundled"),
)
-1
View File
@@ -1 +0,0 @@
-69
View File
@@ -1,69 +0,0 @@
import {
createConnection,
TextDocuments,
ProposedFeatures,
TextDocumentSyncKind,
InitializeParams,
InitializeResult,
CompletionItemKind,
TextDocumentPositionParams,
CompletionItem,
TextEdit,
Range,
Position
} from "vscode-languageserver/node"
import { TextDocument } from "vscode-languageserver-textdocument"
// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
let connection = createConnection(ProposedFeatures.all)
// Create a simple text document manager.
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument)
connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities
// Does the client support the `workspace/configuration` request?
// If not, we fall back using global settings.
const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
// Tell the client that this server supports code completion.
completionProvider: {
resolveProvider: false
}
}
}
return result
})
// This handler provides the initial list of the completion items.
connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] => {
const doc = documents.get(params.textDocument.uri)
if (!doc) return []
const text = doc.getText()
return []
})
connection.onDocumentFormatting(async (params, token) => {
const document = documents.get(params.textDocument.uri)
if (!document) return []
const originalText = document.getText()
return []
})
documents.onDidChangeContent(async (change) => {
connection.console.log("Document has changed")
})
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection)
// Listen on the connection
connection.listen()
-14
View File
@@ -1,14 +0,0 @@
{
"compilerOptions": {
"target": "es2020",
"lib": ["es2020"],
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"strict": true,
"outDir": "out",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}
+83
View File
@@ -0,0 +1,83 @@
version = 1
requires-python = ">=3.11"
[[package]]
name = "attrs"
version = "25.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 },
]
[[package]]
name = "cattrs"
version = "25.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/57/2b/561d78f488dcc303da4639e02021311728fb7fda8006dd2835550cddd9ed/cattrs-25.1.1.tar.gz", hash = "sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c", size = 435016 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/b0/215274ef0d835bbc1056392a367646648b6084e39d489099959aefcca2af/cattrs-25.1.1-py3-none-any.whl", hash = "sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064", size = 69386 },
]
[[package]]
name = "lsprotocol"
version = "2023.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "cattrs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/f6/6e80484ec078d0b50699ceb1833597b792a6c695f90c645fbaf54b947e6f/lsprotocol-2023.0.1.tar.gz", hash = "sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d", size = 69434 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/37/2351e48cb3309673492d3a8c59d407b75fb6630e560eb27ecd4da03adc9a/lsprotocol-2023.0.1-py3-none-any.whl", hash = "sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2", size = 70826 },
]
[[package]]
name = "ply"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567 },
]
[[package]]
name = "pygls"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cattrs" },
{ name = "lsprotocol" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/b9/41d173dad9eaa9db9c785a85671fc3d68961f08d67706dc2e79011e10b5c/pygls-1.3.1.tar.gz", hash = "sha256:140edceefa0da0e9b3c533547c892a42a7d2fd9217ae848c330c53d266a55018", size = 45527 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/19/b74a10dd24548e96e8c80226cbacb28b021bc3a168a7d2709fb0d0185348/pygls-1.3.1-py3-none-any.whl", hash = "sha256:6e00f11efc56321bdeb6eac04f6d86131f654c7d49124344a9ebb968da3dd91e", size = 56031 },
]
[[package]]
name = "server"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "ply" },
{ name = "pygls" },
]
[package.metadata]
requires-dist = [
{ name = "ply" },
{ name = "pygls" },
]
[[package]]
name = "typing-extensions"
version = "4.14.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 },
]