Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion vs-code-extension/nagini
Submodule nagini updated 2 files
+21 −0 CHANGELOG.md
+1 −1 pyproject.toml
18 changes: 9 additions & 9 deletions vs-code-extension/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as path from 'path';
import { diagnosticCollection, logOutputChannel, stopVerificationButton } from './extensionState';
import { updateToggleModeButton, updateSelectBackendButton, updateStatusItem } from './extensionState';
import { VerificationState, VerificationSession, getNaginiCommandArgs, getNaginiClientCommandArgs } from './verificationState';
import { checkNaginiInstallation, getNaginiPathFromEditor, getPythonPath, getPythonVersion, isGlobalPythonEnvironment, getSettings, parseDurationFromOutput, parseErrorsFromOutput } from './utils';
import { checkNaginiInstallation, getNaginiPathFromEditor, getPythonPath, getPythonVersion, isGlobalPythonEnvironment, getSettings, parseDurationFromOutput, parseErrorsFromOutput, MINIMUM_NAGINI_VERSION_STRING } from './utils';

let selectEnvironmentQueue: Promise<void> = Promise.resolve();
export async function selectEnvironment(context: vscode.ExtensionContext, verificationState: VerificationState): Promise<void> {
Expand Down Expand Up @@ -43,10 +43,10 @@ export async function selectEnvironment(context: vscode.ExtensionContext, verifi
}
naginiPath = await getNaginiPathFromEditor(activeEditor);

if (await checkNaginiInstallation(naginiPath)) {
if (await checkNaginiInstallation(activeEditor.document.uri)) {
updateStatusItem('Nagini is ready', 'idle');
} else {
updateStatusItem('Nagini is not installed', 'idle');
updateStatusItem('Nagini is not installed or is outdated', 'idle');

const select: string = 'Select another environment';
const install: string = 'Install Nagini in the current environment';
Expand All @@ -68,7 +68,7 @@ export async function selectEnvironment(context: vscode.ExtensionContext, verifi
}
} while (retry);

if (verificationState.serverMode && await checkNaginiInstallation(naginiPath)) {
if (verificationState.serverMode && await checkNaginiInstallation(activeEditor.document.uri)) {
try {
await verificationState.server.ensureRunning(verificationState, naginiPath);
} catch (error: Error | unknown) {
Expand Down Expand Up @@ -176,7 +176,7 @@ export async function installNagini(context: vscode.ExtensionContext, verificati
updateStatusItem('Nagini is ready', 'idle');
logOutputChannel.info(`Nagini installation process finished. Result: Success\nstdout:\n${stdout}\nstderr:\n${stderr}`);
vscode.window.showInformationMessage('Nagini installation succeeded');
if (verificationState.serverMode && await checkNaginiInstallation(naginiPath)) {
if (verificationState.serverMode && await checkNaginiInstallation(activeEditor.document.uri)) {
try {
await verificationState.server.ensureRunning(verificationState, naginiPath);
} catch (error: Error | unknown) {
Expand Down Expand Up @@ -204,7 +204,7 @@ export async function toggleMode(verificationState: VerificationState): Promise<
return;
}
const naginiPath: string = await getNaginiPathFromEditor(activeEditor);
if (!await checkNaginiInstallation(naginiPath)) {
if (!await checkNaginiInstallation(activeEditor.document.uri)) {
logOutputChannel.info('Mode toggle cancelled. Reason: Nagini is not installed');
showErrorMessageWithAction('Mode toggle');
return;
Expand Down Expand Up @@ -240,7 +240,7 @@ export async function selectBackend(verificationState: VerificationState): Promi
return;
}
const naginiPath: string = await getNaginiPathFromEditor(activeEditor);
if (!await checkNaginiInstallation(naginiPath)) {
if (!await checkNaginiInstallation(activeEditor.document.uri)) {
logOutputChannel.info('Backend selection cancelled. Reason: Nagini is not installed');
showErrorMessageWithAction('Backend selection');
return;
Expand Down Expand Up @@ -293,7 +293,7 @@ export async function startVerification(verificationState: VerificationState, se
}
const naginiPath: string = await getNaginiPathFromEditor(activeEditor, false);
const naginiClientPath: string = await getNaginiPathFromEditor(activeEditor, true);
if (!await checkNaginiInstallation(naginiPath)) {
if (!await checkNaginiInstallation(activeEditor.document.uri)) {
logOutputChannel.info('Verification cancelled. Reason: Nagini is not installed');
showErrorMessageWithAction('Verification');
return;
Expand Down Expand Up @@ -527,7 +527,7 @@ function stdoutHasResultLine(stdout: string, marker: string): boolean {
}

async function showErrorMessageWithAction(context: string): Promise<void> {
const message: string = `${context} cancelled: Nagini is not installed. Please select another virtual environment or install Nagini in the current environment.`;
const message: string = `${context} cancelled: Nagini is not installed or is outdated (version ${MINIMUM_NAGINI_VERSION_STRING} or newer is required). Please select another virtual environment or install Nagini in the current environment.`;
const select: string = 'Select';
const install: string = 'Install';
const selection: string | undefined = await vscode.window.showErrorMessage(message, select, install);
Expand Down
7 changes: 3 additions & 4 deletions vs-code-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (activeEditor?.document.languageId === 'python') {
const naginiPath: string = await getNaginiPathFromEditor(activeEditor);
await extState.welcome(naginiPath);
await extState.welcome(activeEditor.document.uri);
syntaxHighlighter.highlight(activeEditor);
if (_verificationState.serverMode && await checkNaginiInstallation(naginiPath)) {
if (_verificationState.serverMode && await checkNaginiInstallation(activeEditor.document.uri)) {
try {
await _verificationState.server.ensureRunning(_verificationState, naginiPath);
} catch (error: Error | unknown) {
Expand All @@ -43,8 +43,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor(async (editor: vscode.TextEditor | undefined) => {
if (editor?.document.languageId === 'python') {
const naginiPath: string = await getNaginiPathFromEditor(editor);
await extState.welcome(naginiPath);
await extState.welcome(editor.document.uri);
} else {
await commands.stopVerification(_verificationState);
extState.hideStatusBar();
Expand Down
4 changes: 2 additions & 2 deletions vs-code-extension/src/extensionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ export function updateStatusItem(text: string, status?: Status): void {
}
}

export async function welcome(naginiPath: string): Promise<void> {
updateStatusItem(await checkNaginiInstallation(naginiPath) ? 'Nagini is ready' : 'Nagini is not installed', 'idle');
export async function welcome(uri: vscode.Uri): Promise<void> {
updateStatusItem(await checkNaginiInstallation(uri) ? 'Nagini is ready' : 'Nagini is not installed or is outdated', 'idle');
showStatusBar();
}

Expand Down
42 changes: 40 additions & 2 deletions vs-code-extension/src/utils/verificationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,46 @@ import * as fs from 'fs';
import * as path from 'path';
import { PythonExtension, EnvironmentPath, ResolvedEnvironment } from '@vscode/python-extension';

export async function checkNaginiInstallation(naginiPath: string): Promise<boolean> {
return fs.existsSync(naginiPath);
// Minimum Nagini version this extension requires. An installed but older Nagini is treated the
// same as no installation at all.
const MINIMUM_NAGINI_VERSION: readonly number[] = [1, 3, 1];
export const MINIMUM_NAGINI_VERSION_STRING: string = MINIMUM_NAGINI_VERSION.join('.');

export async function checkNaginiInstallation(uri: vscode.Uri): Promise<boolean> {
let pythonPath: string;
try {
pythonPath = await getPythonPath(uri);
} catch {
return false;
}
const version: string | undefined = await getNaginiVersion(pythonPath);
return version !== undefined && isVersionAtLeast(version, MINIMUM_NAGINI_VERSION);
}

// Reads the installed Nagini distribution's version via importlib.metadata (the same package
// metadata pip records), or undefined if Nagini is not installed for this interpreter.
async function getNaginiVersion(pythonPath: string): Promise<string | undefined> {
return new Promise((resolve: (value: string | undefined) => void) => {
const process: cp.ChildProcess = cp.spawn(pythonPath, ['-c', 'import importlib.metadata as m; print(m.version("nagini"))']);
let stdout: string = '';
process.stdout?.on('data', (data: Buffer) => { stdout += data.toString(); });
process.on('error', () => resolve(undefined));
process.on('close', (code: number | null) => {
const version: string = stdout.trim();
resolve(code === 0 && version.length > 0 ? version : undefined);
});
});
}

function isVersionAtLeast(version: string, minimum: readonly number[]): boolean {
const parts: number[] = version.split('.').map((part: string) => parseInt(part, 10));
for (let i: number = 0; i < minimum.length; i++) {
const raw: number | undefined = parts[i];
const value: number = (raw === undefined || Number.isNaN(raw)) ? 0 : raw;
if (value > minimum[i]) { return true; }
if (value < minimum[i]) { return false; }
}
return true;
}

export async function getNaginiPathFromEditor(editor: vscode.TextEditor, serverMode: boolean = false): Promise<string> {
Expand Down
Loading