Skip to content

Commit

Permalink
fix: Bug where successive python changed events trigger creating mult…
Browse files Browse the repository at this point in the history
…iple servers (#336)
  • Loading branch information
karthiknadig authored Feb 13, 2025
1 parent 79f3d4b commit df2fbf6
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const DEBUG_SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, 't
export const PYTHON_MAJOR = 3;
export const PYTHON_MINOR = 8;
export const PYTHON_VERSION = `${PYTHON_MAJOR}.${PYTHON_MINOR}`;
export const LS_SERVER_RESTART_DELAY = 1000;
48 changes: 43 additions & 5 deletions src/common/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { commands, Disposable, Event, EventEmitter, Uri } from 'vscode';
import { traceError, traceLog } from './logging';
import { PythonExtension, ResolvedEnvironment } from '@vscode/python-extension';
import { PYTHON_MAJOR, PYTHON_MINOR, PYTHON_VERSION } from './constants';
import { getProjectRoot } from './utilities';

export interface IInterpreterDetails {
path?: string[];
resource?: Uri;
}

const onDidChangePythonInterpreterEvent = new EventEmitter<IInterpreterDetails>();
export const onDidChangePythonInterpreter: Event<IInterpreterDetails> = onDidChangePythonInterpreterEvent.event;
const onDidChangePythonInterpreterEvent = new EventEmitter<void>();
export const onDidChangePythonInterpreter: Event<void> = onDidChangePythonInterpreterEvent.event;

let _api: PythonExtension | undefined;
async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
Expand All @@ -24,19 +25,56 @@ async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
return _api;
}

function sameInterpreter(a: string[], b: string[]): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}

let serverPython: string[] | undefined;
function checkAndFireEvent(interpreter: string[] | undefined): void {
if (interpreter === undefined) {
if (serverPython) {
// Python was reset for this uri
serverPython = undefined;
onDidChangePythonInterpreterEvent.fire();
return;
} else {
return; // No change in interpreter
}
}

if (!serverPython || !sameInterpreter(serverPython, interpreter)) {
serverPython = interpreter;
onDidChangePythonInterpreterEvent.fire();
}
}

async function refreshServerPython(): Promise<void> {
const projectRoot = await getProjectRoot();
const interpreter = await getInterpreterDetails(projectRoot?.uri);
checkAndFireEvent(interpreter.path);
}

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 });
api.environments.onDidChangeActiveEnvironmentPath(async () => {
await refreshServerPython();
}),
);

traceLog('Waiting for interpreter from Python extension.');
onDidChangePythonInterpreterEvent.fire(await getInterpreterDetails());
await refreshServerPython();
}
} catch (error) {
traceError('Error initializing Python: ', error);
Expand Down
6 changes: 3 additions & 3 deletions src/common/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ export async function restartServer(
serverId: string,
serverName: string,
outputChannel: LogOutputChannel,
lsClient?: LanguageClient,
oldLsClient?: LanguageClient,
): Promise<LanguageClient | undefined> {
if (lsClient) {
if (oldLsClient) {
traceInfo(`Server: Stop requested`);
try {
await lsClient.stop();
await oldLsClient.stop();
} catch (ex) {
traceError(`Server: Stop failed: ${ex}`);
}
Expand Down
6 changes: 1 addition & 5 deletions src/common/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfigurationChangeEvent, ConfigurationScope, WorkspaceConfiguration, W
import { traceLog, traceWarn } from './logging';
import { getInterpreterDetails } from './python';
import { getConfiguration, getWorkspaceFolders } from './vscodeapi';
import { getInterpreterFromSetting } from './utilities';

/* eslint-disable @typescript-eslint/naming-convention */
const DEFAULT_SEVERITY: Record<string, string> = {
Expand Down Expand Up @@ -80,11 +81,6 @@ function getCwd(config: WorkspaceConfiguration, workspace: WorkspaceFolder): str
return resolveVariables([cwd], workspace)[0];
}

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,
Expand Down
9 changes: 7 additions & 2 deletions src/common/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import * as fs from 'fs-extra';
import * as path from 'path';
import { LogLevel, Uri, WorkspaceFolder } from 'vscode';
import { ConfigurationScope, LogLevel, Uri, WorkspaceFolder } from 'vscode';
import { Trace } from 'vscode-jsonrpc/node';
import { getWorkspaceFolders, isVirtualWorkspace } from './vscodeapi';
import { getConfiguration, getWorkspaceFolders, isVirtualWorkspace } from './vscodeapi';
import { DocumentSelector } from 'vscode-languageclient';

function logLevelToTrace(logLevel: LogLevel): Trace {
Expand Down Expand Up @@ -36,6 +36,11 @@ export function getLSClientTraceLevel(channelLogLevel: LogLevel, globalLogLevel:
return level;
}

export function getInterpreterFromSetting(namespace: string, scope?: ConfigurationScope) {
const config = getConfiguration(namespace, scope);
return config.get<string[]>('interpreter');
}

export async function getProjectRoot(): Promise<WorkspaceFolder> {
const workspaces: readonly WorkspaceFolder[] = getWorkspaceFolders();
if (workspaces.length === 0) {
Expand Down
49 changes: 29 additions & 20 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@ import { LanguageClient } from 'vscode-languageclient/node';
import { registerLogger, traceError, traceLog, traceVerbose } from './common/logging';
import { initializePython, onDidChangePythonInterpreter } from './common/python';
import { restartServer } from './common/server';
import {
checkIfConfigurationChanged,
getInterpreterFromSetting,
getWorkspaceSettings,
logLegacySettings,
} from './common/settings';
import { checkIfConfigurationChanged, getWorkspaceSettings, logLegacySettings } from './common/settings';
import { loadServerDefaults } from './common/setup';
import { getLSClientTraceLevel, getProjectRoot } from './common/utilities';
import { getInterpreterFromSetting, getLSClientTraceLevel, getProjectRoot } from './common/utilities';
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from './common/vscodeapi';
import { registerLanguageStatusItem, updateStatus } from './common/status';
import { PYTHON_VERSION } from './common/constants';
import { LS_SERVER_RESTART_DELAY, PYTHON_VERSION } from './common/constants';

let lsClient: LanguageClient | undefined;
export async function activate(context: vscode.ExtensionContext): Promise<void> {
Expand Down Expand Up @@ -48,19 +43,33 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
traceLog(`Module: ${serverInfo.module}`);
traceVerbose(`Configuration: ${JSON.stringify(serverInfo)}`);

let isRestarting = false;
let restartTimer: NodeJS.Timeout | undefined;
const runServer = async () => {
const projectRoot = await getProjectRoot();
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true);
if (workspaceSetting.interpreter.length === 0) {
updateStatus(vscode.l10n.t('Please select a Python interpreter.'), vscode.LanguageStatusSeverity.Error);
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 ${PYTHON_VERSION} or greater.`,
);
} else {
lsClient = await restartServer(workspaceSetting, serverId, serverName, outputChannel, lsClient);
if (isRestarting) {
if (restartTimer) {
clearTimeout(restartTimer);
}
restartTimer = setTimeout(runServer, LS_SERVER_RESTART_DELAY);
return;
}
isRestarting = true;
try {
const projectRoot = await getProjectRoot();
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true);
if (workspaceSetting.interpreter.length === 0) {
updateStatus(vscode.l10n.t('Please select a Python interpreter.'), vscode.LanguageStatusSeverity.Error);
traceError(
'Python interpreter missing:\r\n' +
'[Option 1] Select python interpreter using the ms-python.python (select interpreter command).\r\n' +
`[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n`,
`Please use Python ${PYTHON_VERSION} or greater.`,
);
} else {
lsClient = await restartServer(workspaceSetting, serverId, serverName, outputChannel, lsClient);
}
} finally {
isRestarting = false;
}
};

Expand Down

0 comments on commit df2fbf6

Please sign in to comment.