From 025c851f1685e20f16753e159c74e40b4ceb7eaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:09:35 +0000 Subject: [PATCH 1/9] Initial plan From d56d345bbbc5d9eabc43005be5e0bbbc33a59559 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:10:42 +0000 Subject: [PATCH 2/9] Fix multiple LSP server handlers via concurrency guard and stop error handling Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/common/server.ts | 6 +++++- src/extension.ts | 48 ++++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/common/server.ts b/src/common/server.ts index 89fcc8f..dfc4589 100644 --- a/src/common/server.ts +++ b/src/common/server.ts @@ -86,7 +86,11 @@ export async function restartServer( ): Promise { if (lsClient) { traceInfo(`Server: Stop requested`); - await lsClient.stop(); + try { + await lsClient.stop(); + } catch (ex) { + traceError(`Server: Stop failed: ${ex}`); + } _disposables.forEach((d) => d.dispose()); _disposables = []; } diff --git a/src/extension.ts b/src/extension.ts index 0bdf62e..a0b1f50 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -48,29 +48,43 @@ export async function activate(context: vscode.ExtensionContext): Promise traceLog(`Module: ${serverInfo.module}`); traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`); + let isRestarting = false; + let restartTimer: NodeJS.Timeout | undefined; 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(' ')}`); - lsClient = await restartServer(serverId, serverName, outputChannel, lsClient); + if (isRestarting) { + if (restartTimer) { + clearTimeout(restartTimer); } + restartTimer = setTimeout(runServer, 1000); return; } + isRestarting = true; + try { + const interpreter = getInterpreterFromSetting(serverId); + if (interpreter && interpreter.length > 0) { + if (checkVersion(await resolveInterpreter(interpreter))) { + traceVerbose(`Using interpreter from ${serverInfo.module}.interpreter: ${interpreter.join(' ')}`); + lsClient = await restartServer(serverId, serverName, outputChannel, lsClient); + } + return; + } - const interpreterDetails = await getInterpreterDetails(); - if (interpreterDetails.path) { - traceVerbose(`Using interpreter from Python extension: ${interpreterDetails.path.join(' ')}`); - lsClient = await restartServer(serverId, serverName, outputChannel, lsClient); - return; - } + const interpreterDetails = await getInterpreterDetails(); + if (interpreterDetails.path) { + traceVerbose(`Using interpreter from Python extension: ${interpreterDetails.path.join(' ')}`); + lsClient = await restartServer(serverId, serverName, outputChannel, lsClient); + 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.', - ); + 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.', + ); + } finally { + isRestarting = false; + } }; context.subscriptions.push( From 4f80196d7ee8d1fd7731661f8541b6e1ccf2aacf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:18:27 +0000 Subject: [PATCH 3/9] Move isRestarting/restartTimer to module scope; fix deactivate to cancel timer and handle stop errors Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/extension.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index a0b1f50..61b65b7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,6 +18,8 @@ import { getLSClientTraceLevel } from './common/utilities'; import { createOutputChannel, onDidChangeConfiguration, registerCommand } from './common/vscodeapi'; let lsClient: LanguageClient | undefined; +let isRestarting = false; +let restartTimer: NodeJS.Timeout | undefined; export async function activate(context: vscode.ExtensionContext): Promise { // This is required to get server name and module. This should be // the first thing that we do in this extension. @@ -48,8 +50,6 @@ export async function activate(context: vscode.ExtensionContext): Promise traceLog(`Module: ${serverInfo.module}`); traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`); - let isRestarting = false; - let restartTimer: NodeJS.Timeout | undefined; const runServer = async () => { if (isRestarting) { if (restartTimer) { @@ -114,7 +114,14 @@ export async function activate(context: vscode.ExtensionContext): Promise } export async function deactivate(): Promise { + if (restartTimer) { + clearTimeout(restartTimer); + } if (lsClient) { - await lsClient.stop(); + try { + await lsClient.stop(); + } catch (ex) { + traceError(`Server: Stop failed: ${ex}`); + } } } From a7cb72c905a6823207f1aa5751045a2674238660 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:22:52 +0000 Subject: [PATCH 4/9] Remove timer debounce from runServer; simplify to match vscode-isort PR pattern Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/extension.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 61b65b7..6e11ed7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,7 +19,6 @@ import { createOutputChannel, onDidChangeConfiguration, registerCommand } from ' let lsClient: LanguageClient | undefined; let isRestarting = false; -let restartTimer: NodeJS.Timeout | undefined; export async function activate(context: vscode.ExtensionContext): Promise { // This is required to get server name and module. This should be // the first thing that we do in this extension. @@ -52,10 +51,6 @@ export async function activate(context: vscode.ExtensionContext): Promise const runServer = async () => { if (isRestarting) { - if (restartTimer) { - clearTimeout(restartTimer); - } - restartTimer = setTimeout(runServer, 1000); return; } isRestarting = true; @@ -114,9 +109,6 @@ export async function activate(context: vscode.ExtensionContext): Promise } export async function deactivate(): Promise { - if (restartTimer) { - clearTimeout(restartTimer); - } if (lsClient) { try { await lsClient.stop(); From dad6036d140b2273b374f9bed1ab63d5b6327eec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:29:33 +0000 Subject: [PATCH 5/9] Add getServerEnabled from settings; wire into extension to allow disabling the server Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/common/settings.ts | 6 ++++++ src/extension.ts | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/common/settings.ts b/src/common/settings.ts index 8aaf9e5..0259aff 100644 --- a/src/common/settings.ts +++ b/src/common/settings.ts @@ -108,7 +108,13 @@ export function checkIfConfigurationChanged(e: ConfigurationChangeEvent, namespa `${namespace}.interpreter`, `${namespace}.importStrategy`, `${namespace}.showNotifications`, + `${namespace}.serverEnabled`, ]; const changed = settings.map((s) => e.affectsConfiguration(s)); return changed.includes(true); } + +export function getServerEnabled(namespace: string): boolean { + const config = getConfiguration(namespace); + return config.get('serverEnabled', true); +} diff --git a/src/extension.ts b/src/extension.ts index 6e11ed7..956624e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,7 @@ import { resolveInterpreter, } from './common/python'; import { restartServer } from './common/server'; -import { checkIfConfigurationChanged, getInterpreterFromSetting } from './common/settings'; +import { checkIfConfigurationChanged, getInterpreterFromSetting, getServerEnabled } from './common/settings'; import { loadServerDefaults } from './common/setup'; import { getLSClientTraceLevel } from './common/utilities'; import { createOutputChannel, onDidChangeConfiguration, registerCommand } from './common/vscodeapi'; @@ -55,6 +55,18 @@ export async function activate(context: vscode.ExtensionContext): Promise } isRestarting = true; try { + if (!getServerEnabled(serverId)) { + if (lsClient) { + try { + await lsClient.stop(); + } catch (ex) { + traceError(`Server: Stop failed: ${ex}`); + } + lsClient = undefined; + } + return; + } + const interpreter = getInterpreterFromSetting(serverId); if (interpreter && interpreter.length > 0) { if (checkVersion(await resolveInterpreter(interpreter))) { From 96f152594229f94b44f7da2bbbe3a9efaebf18b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:49:44 +0000 Subject: [PATCH 6/9] Add LS_SERVER_RESTART_DELAY constant and debounce timer to prevent dropped concurrent restarts Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/common/constants.ts | 1 + src/extension.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/common/constants.ts b/src/common/constants.ts index e33f333..7ec36c8 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -9,3 +9,4 @@ export const EXTENSION_ROOT_DIR = export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'bundled'); export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 'tool', `lsp_server.py`); export const DEBUG_SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 'tool', `_debug_server.py`); +export const LS_SERVER_RESTART_DELAY = 1000; // Delay before restarting the LS in case of multiple configuration change events. diff --git a/src/extension.ts b/src/extension.ts index 956624e..3305b7e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,11 +14,13 @@ import { import { restartServer } from './common/server'; import { checkIfConfigurationChanged, getInterpreterFromSetting, getServerEnabled } from './common/settings'; import { loadServerDefaults } from './common/setup'; +import { LS_SERVER_RESTART_DELAY } from './common/constants'; import { getLSClientTraceLevel } from './common/utilities'; import { createOutputChannel, onDidChangeConfiguration, registerCommand } from './common/vscodeapi'; let lsClient: LanguageClient | undefined; let isRestarting = false; +let restartTimer: NodeJS.Timeout | undefined; export async function activate(context: vscode.ExtensionContext): Promise { // This is required to get server name and module. This should be // the first thing that we do in this extension. @@ -51,6 +53,10 @@ export async function activate(context: vscode.ExtensionContext): Promise const runServer = async () => { if (isRestarting) { + if (restartTimer) { + clearTimeout(restartTimer); + } + restartTimer = setTimeout(runServer, LS_SERVER_RESTART_DELAY); return; } isRestarting = true; From 1b52c7b7ea616b04c1425bf3f24a4675b08bd6ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:57:49 +0000 Subject: [PATCH 7/9] Fix multiple server handlers registered: always clear _disposables in restartServer regardless of lsClient Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/common/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/server.ts b/src/common/server.ts index dfc4589..fb5bf3d 100644 --- a/src/common/server.ts +++ b/src/common/server.ts @@ -91,9 +91,9 @@ export async function restartServer( } catch (ex) { traceError(`Server: Stop failed: ${ex}`); } - _disposables.forEach((d) => d.dispose()); - _disposables = []; } + _disposables.forEach((d) => d.dispose()); + _disposables = []; const projectRoot = await getProjectRoot(); const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true); From 3b322a6fc7952a4d93ee926a3e7bf0f17a850ab5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 21:02:32 +0000 Subject: [PATCH 8/9] Deduplicate server-stop logic in runServer() by calling deactivate() Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/extension.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 3305b7e..107bab0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -62,14 +62,8 @@ export async function activate(context: vscode.ExtensionContext): Promise isRestarting = true; try { if (!getServerEnabled(serverId)) { - if (lsClient) { - try { - await lsClient.stop(); - } catch (ex) { - traceError(`Server: Stop failed: ${ex}`); - } - lsClient = undefined; - } + await deactivate(); + lsClient = undefined; return; } From 34d139c43e7bc7b5573f204a035ea1065f62661b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 21:08:19 +0000 Subject: [PATCH 9/9] Revert: restore original inline stop block in runServer() Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/extension.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 107bab0..3305b7e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -62,8 +62,14 @@ export async function activate(context: vscode.ExtensionContext): Promise isRestarting = true; try { if (!getServerEnabled(serverId)) { - await deactivate(); - lsClient = undefined; + if (lsClient) { + try { + await lsClient.stop(); + } catch (ex) { + traceError(`Server: Stop failed: ${ex}`); + } + lsClient = undefined; + } return; }