diff --git a/CHANGELOG.md b/CHANGELOG.md index f019c5a..04e2c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,8 @@ ## [0.2.0] -- Add DEF File Support \ No newline at end of file +- Add DEF File Support + +## [2026.6.100] + +- Fix several bugs diff --git a/client/src/common/folding.ts b/client/src/common/folding.ts deleted file mode 100644 index d415553..0000000 --- a/client/src/common/folding.ts +++ /dev/null @@ -1,70 +0,0 @@ -import * as vscode from "vscode" - -/** - * Computes folding ranges for a TCL document based on brace matching. - * - * Handles same-line opening braces (`... {`), standalone `{`, `} elseif`/`} else` - * branch continuations and line continuations (`\`). - */ -export function computeTclFoldingRanges(document: vscode.TextDocument): vscode.FoldingRange[] { - const ranges: vscode.FoldingRange[] = [] - const stack: number[] = [] - let pendingStartLine: number | null = null - - for (let i = 0; i < document.lineCount; i += 1) { - const line = document.lineAt(i).text - const trimmed = line.trim() - const branchContinuation = /^\}\s*(elseif|else)\b/.test(trimmed) - const sameLineOpen = trimmed.endsWith("{") - - if (!trimmed || trimmed.startsWith("#")) { - continue - } - - if (trimmed.startsWith("}")) { - const startLine = stack.pop() - const endLine = branchContinuation ? i - 1 : i - if (typeof startLine === "number" && startLine < endLine) { - ranges.push( - new vscode.FoldingRange(startLine, endLine, vscode.FoldingRangeKind.Region) - ) - } - - if (branchContinuation && sameLineOpen) { - stack.push(i) - pendingStartLine = null - } else if (trimmed !== "}" && trimmed.endsWith("\\")) { - pendingStartLine = i - } else { - pendingStartLine = null - } - continue - } - - if (trimmed === "{") { - const startLine = pendingStartLine ?? i - if (startLine < i) { - stack.push(startLine) - } else { - stack.push(i) - } - pendingStartLine = null - continue - } - - if (trimmed.endsWith("{")) { - stack.push(i) - pendingStartLine = null - continue - } - - if (trimmed.endsWith("\\")) { - pendingStartLine = i - continue - } - - pendingStartLine = null - } - - return ranges -} diff --git a/client/src/extension.ts b/client/src/extension.ts index e1d60eb..672f354 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -15,7 +15,6 @@ import { cdlDocumentSymbolProvider, defDocumentSymbolProvider } from "./common/handlers" -import { computeTclFoldingRanges } from "./common/folding" import { registerLogger, traceError, traceLog, traceVerbose } from "./common/log/logging" import { checkVersion, @@ -30,7 +29,7 @@ import { loadServerDefaults } from "./common/setup" import { getLSClientTraceLevel } from "./common/utilities" import { createOutputChannel, onDidChangeConfiguration, registerCommand } from "./common/vscodeapi" -let client: LanguageClient +let client: LanguageClient | undefined export async function activate(context: vscode.ExtensionContext) { // This is required to get server name and module. This should be @@ -62,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext) { traceLog(`Module: ${serverInfo.module}`) traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`) - const runServer = async () => { + const runServerImpl = async () => { const interpreter = getInterpreterFromSetting(serverId) if (interpreter && interpreter.length > 0) { if (checkVersion(await resolveInterpreter(interpreter))) { @@ -91,6 +90,16 @@ export async function activate(context: vscode.ExtensionContext) { ) } + // Serialize server (re)starts. Overlapping triggers (interpreter change, + // config change, restart command, initial activation) would otherwise each + // read the stale module-level `client`, start a new server and leave the + // previous one running orphaned -> hints/hover shown multiple times. + let runServerQueue: Promise = Promise.resolve() + const runServer = () => { + runServerQueue = runServerQueue.catch(() => undefined).then(() => runServerImpl()) + return runServerQueue + } + context.subscriptions.push( onDidChangePythonInterpreter(async () => { await runServer() @@ -116,16 +125,9 @@ export async function activate(context: vscode.ExtensionContext) { } }) - // Folding ranges for TCL (brace based) - const tclFoldingProvider = vscode.languages.registerFoldingRangeProvider( - [{ language: "tcl" }], - { - provideFoldingRanges(document: vscode.TextDocument) { - return computeTclFoldingRanges(document) - } - } - ) - context.subscriptions.push(tclFoldingProvider) + // Folding ranges for TCL are provided by the language server + // (folding_range_provider in lsp_server.py). No client-side provider here to + // avoid duplicate folding regions. // const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider( @@ -219,26 +221,27 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(diagnosticCollectionCdl, diagnosticCollectionDef) // Check if the first line of the CDL file contains "MACHINE" - vscode.workspace.onDidOpenTextDocument((document) => { - if (document.languageId === "cdl" || document.languageId === "def") { - if (document.languageId === "cdl") { - diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document)) - } else if (document.languageId === "def") { - diagnosticCollectionDef.set(document.uri, diagnosticHandler(document)) + context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument((document) => { + if (document.languageId === "cdl" || document.languageId === "def") { + if (document.languageId === "cdl") { + diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document)) + } else if (document.languageId === "def") { + diagnosticCollectionDef.set(document.uri, diagnosticHandler(document)) + } } - } - }) - - vscode.workspace.onDidChangeTextDocument((event) => { - const document = event.document - if (document.languageId === "cdl" || document.languageId === "def") { - if (document.languageId === "cdl") { - diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document)) - } else if (document.languageId === "def") { - diagnosticCollectionDef.set(document.uri, diagnosticHandler(document)) + }), + vscode.workspace.onDidChangeTextDocument((event) => { + const document = event.document + if (document.languageId === "cdl" || document.languageId === "def") { + if (document.languageId === "cdl") { + diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document)) + } else if (document.languageId === "def") { + diagnosticCollectionDef.set(document.uri, diagnosticHandler(document)) + } } - } - }) + }) + ) } export function deactivate(): Thenable | undefined { diff --git a/package.json b/package.json index 02ed441..db62c74 100644 --- a/package.json +++ b/package.json @@ -134,10 +134,5 @@ "esbuild": "^0.25.6", "prettier": "^3.4.2", "typescript": "^5.7.2" - }, - "__metadata": { - "installedTimestamp": 1776513675781, - "targetPlatform": "undefined", - "size": 3267242 } } \ No newline at end of file diff --git a/server/pyproject.toml b/server/pyproject.toml index ac1eddf..a5d7b62 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -4,9 +4,12 @@ version = "0.1.0" description = "Python language server for NX Postprocessor Support" requires-python = ">=3.8" dependencies = [ - "pygls", - "packaging", - "tclint", + # Upper bounds guard against breaking API changes in bundled deps. The code + # is written against tclint 0.8.x (see tools/semantic_tokens.py); pin it so a + # re-bundle can't silently pull an incompatible major/minor. + "pygls>=1.3,<2", + "packaging>=24,<27", + "tclint>=0.8,<0.9", ] [dependency-groups] diff --git a/server/src/_debug_server.py b/server/src/_debug_server.py index 52d3f0c..ade940e 100644 --- a/server/src/_debug_server.py +++ b/server/src/_debug_server.py @@ -27,7 +27,17 @@ if debugger_path: # 5678 is the default port, If you need to change it update it here # and in launch.json. - debugpy.connect(5678) + # Connecting requires the "Python debug server" listener (launch.json) to be + # up first. If it isn't (e.g. wrong launch config was used), don't crash the + # whole language server - just continue running without the debugger attached. + try: + debugpy.connect(5678) + except (ConnectionRefusedError, OSError) as exc: + print( + f"debugpy: could not connect to debug adapter on port 5678 " + f"({exc}); continuing without debugging.", + file=sys.stderr, + ) # 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 diff --git a/server/src/tools/semantic_tokens.py b/server/src/tools/semantic_tokens.py index 4bb37b2..1096aeb 100644 --- a/server/src/tools/semantic_tokens.py +++ b/server/src/tools/semantic_tokens.py @@ -7,6 +7,23 @@ from common.load_data import standard_items import lsprotocol.types as lsp +# Constructing a PluginManager scans entry points, and get_commands() rebuilds +# the builtin command set on every call. Semantic tokens are requested often, so +# cache the manager and the resolved commands per plugin set. +_PLUGIN_MANAGER = None +_COMMANDS_CACHE = {} + + +def _load_commands(plugins): + global _PLUGIN_MANAGER + if _PLUGIN_MANAGER is None: + _PLUGIN_MANAGER = PluginManager() + key = tuple(plugins) + if key not in _COMMANDS_CACHE: + _COMMANDS_CACHE[key] = _PLUGIN_MANAGER.get_commands(list(plugins)) + return _COMMANDS_CACHE[key] + + class TokenModifier(enum.IntFlag): deprecated = enum.auto() readonly = enum.auto() @@ -47,7 +64,7 @@ TOKEN_TYPES = [ class _Highlighter(Visitor): def __init__(self, plugins, custom_functions: dict[str : list[lsp.CompletionItem]]): - self._commands = PluginManager().get_commands(plugins) + self._commands = _load_commands(plugins) self._tokens = [] self.custom_functions = custom_functions