Skip to content

Commit c7f6b87

Browse files
committed
Windows conda, status bar errors, refactoring
1 parent 5459d50 commit c7f6b87

File tree

14 files changed

+308
-182
lines changed

14 files changed

+308
-182
lines changed

src/executables/conda.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
2+
import { IExecutableDetails } from './service';
3+
import * as fs from 'fs-extra';
4+
import * as vscode from 'vscode';
5+
import path = require('path');
6+
import { spawn } from 'child_process';
7+
8+
export function environmentIsActive(name: string): boolean {
9+
return process.env.CONDA_DEFAULT_ENV === name ||
10+
process.env.CONDA_PREFIX === name;
11+
}
12+
13+
export function getCondaName(executablePath: string): string {
14+
return path.basename(path.dirname(getCondaMetaDir(executablePath)));
15+
}
16+
17+
export function getCondaMetaDir(executablePath: string): string {
18+
let envDir: string = executablePath;
19+
for (let index = 0; index < 4; index++) {
20+
envDir = path.dirname(envDir);
21+
}
22+
return path.join(envDir, 'conda-meta');
23+
}
24+
25+
export function getCondaHistoryPath(executablePath: string): string {
26+
return path.join(getCondaMetaDir(executablePath), 'history');
27+
}
28+
29+
export function getCondaActivationScript(executablePath: string): string {
30+
const envDir = path.dirname(getCondaMetaDir(executablePath));
31+
return path.join(path.dirname(path.dirname(envDir)), 'Scripts', 'activate');
32+
}
33+
34+
export function isCondaInstallation(executablePath: string): boolean {
35+
console.log(getCondaName(executablePath));
36+
return fs.existsSync(getCondaMetaDir(executablePath));
37+
}
38+
39+
export function getRDetailsFromMetaHistory(executablePath: string): IExecutableDetails {
40+
try {
41+
42+
const reg = new RegExp(/([0-9]{2})::r-base-([0-9.]*)/g);
43+
const historyContent = fs.readFileSync(getCondaHistoryPath(executablePath))?.toString();
44+
const res = reg.exec(historyContent);
45+
return {
46+
arch: res?.[1] ? `${res[1]}-bit` : '',
47+
version: res?.[2] ? res[2] : ''
48+
};
49+
} catch (error) {
50+
return {
51+
arch: '',
52+
version: ''
53+
};
54+
}
55+
56+
}
57+
58+
export function activateCondaEnvironment(executablePath: string): Promise<boolean> {
59+
return new Promise((resolve, reject) => {
60+
try {
61+
const opts = {
62+
env: process.env,
63+
shell: true
64+
};
65+
const activationPath = (getCondaActivationScript(executablePath));
66+
const commands = [
67+
activationPath,
68+
`conda activate ${getCondaName(executablePath)}`
69+
].join(' & ');
70+
const childProc = spawn(
71+
commands,
72+
undefined,
73+
opts
74+
);
75+
childProc.on('exit', () => resolve(true));
76+
childProc.on('error', (err) => {
77+
void vscode.window.showErrorMessage(`Error when activating conda environment: ${err.message}`);
78+
reject(false);
79+
});
80+
} catch (error) {
81+
void vscode.window.showErrorMessage(`Error when activating conda environment: ${error as string}`);
82+
reject(false);
83+
}
84+
});
85+
}

src/executables/index.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as vscode from 'vscode';
55
import { ExecutableStatusItem, ExecutableQuickPick } from './ui';
66
import { isVirtual, RExecutableService, ExecutableType, WorkspaceExecutableEvent } from './service';
77
import { extensionContext } from '../extension';
8-
import { spawnAsync } from '../util';
8+
import { activateCondaEnvironment } from './conda';
99

1010
export { ExecutableType as IRExecutable, VirtualExecutableType as IVirtualRExecutable } from './service';
1111

@@ -81,24 +81,11 @@ export class RExecutableManager implements vscode.Disposable {
8181
}
8282

8383

84-
private async activateEnvironment(): Promise<unknown> {
85-
if (!this.activeExecutable || !isVirtual(this.activeExecutable) ||
86-
process.env.CONDA_DEFAULT_ENV !== this.activeExecutable.name) {
87-
return Promise.resolve();
84+
private async activateEnvironment(): Promise<boolean> {
85+
if (!this.activeExecutable || !isVirtual(this.activeExecutable)) {
86+
return Promise.resolve(true);
8887
}
89-
90-
const opts = {
91-
env: {
92-
...process.env
93-
},
94-
};
95-
96-
return spawnAsync(
97-
'conda', // hard coded for now
98-
this.activeExecutable.activationCommand,
99-
opts,
100-
undefined
101-
);
88+
return activateCondaEnvironment(this.activeExecutable?.rBin);
10289
}
10390

10491
}

src/executables/service/class.ts

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
import { getCondaName, getRDetailsFromMetaHistory, isCondaInstallation } from '../conda';
12
import { getRDetailsFromPath } from './locator';
23
import { RExecutableRegistry } from './registry';
4+
import { IExecutableDetails, ExecutableType } from './types';
35

4-
export type ExecutableType = RExecutable;
5-
export type VirtualExecutableType = VirtualRExecutable;
6-
7-
export function isVirtual(executable: RExecutable): executable is VirtualRExecutable {
6+
export function isVirtual(executable: AbstractExecutable): executable is VirtualRExecutable {
87
return executable instanceof VirtualRExecutable;
98
}
109

@@ -16,12 +15,12 @@ export class RExecutableFactory {
1615
}
1716

1817
public create(executablePath: string): ExecutableType {
19-
const oldExec = [...this.registry.executables.values()].find((v) => v.rBin === executablePath);
20-
if (oldExec) {
21-
return oldExec;
18+
const cachedExec = [...this.registry.executables.values()].find((v) => v.rBin === executablePath);
19+
if (cachedExec) {
20+
return cachedExec;
2221
} else {
23-
let executable: RExecutable;
24-
if (new RegExp('\\.conda')?.exec(executablePath)) {
22+
let executable: AbstractExecutable;
23+
if (isCondaInstallation(executablePath)) {
2524
executable = new VirtualRExecutable(executablePath);
2625
} else {
2726
executable = new RExecutable(executablePath);
@@ -32,18 +31,10 @@ export class RExecutableFactory {
3231
}
3332
}
3433

35-
class RExecutable {
36-
private _rBin: string;
37-
private _rVersion: string;
38-
private _arch: string;
39-
40-
constructor(bin_path: string) {
41-
const details = getRDetailsFromPath(bin_path);
42-
this._rBin = bin_path;
43-
this._rVersion = details.version;
44-
this._arch = details.arch;
45-
}
46-
34+
export abstract class AbstractExecutable {
35+
protected _rBin: string;
36+
protected _rVersion: string;
37+
protected _rArch: string;
4738
public get rBin(): string {
4839
return this._rBin;
4940
}
@@ -53,39 +44,53 @@ class RExecutable {
5344
}
5445

5546
public get rArch(): string {
56-
return this._arch;
47+
return this._rArch;
48+
}
49+
public abstract tooltip: string;
50+
}
51+
52+
53+
export class RExecutable extends AbstractExecutable {
54+
constructor(executablePath: string) {
55+
super();
56+
const details = getRDetailsFromPath(executablePath);
57+
this._rBin = executablePath;
58+
this._rVersion = details.version;
59+
this._rArch = details.arch;
5760
}
5861

5962
public get tooltip(): string {
60-
const versionString = this.rVersion ? ` ${this.rVersion}` : '';
61-
const archString = this.rArch ? ` ${this.rArch}` : '';
62-
return `R${versionString}${archString}`;
63+
if (this.rVersion && this.rArch) {
64+
return `R ${this.rVersion} ${this.rArch}`;
65+
}
66+
return `$(error) R`;
67+
}
68+
69+
protected getDetailsFromPath(execPath: string): IExecutableDetails {
70+
return getRDetailsFromPath(execPath);
6371
}
6472
}
6573

66-
class VirtualRExecutable extends RExecutable {
74+
export class VirtualRExecutable extends AbstractExecutable {
6775
private _name: string;
6876

69-
constructor(bin_path: string) {
70-
super(bin_path);
71-
const reg = new RegExp('(?<=\\/envs\\/)(.*?)(?=\\/)');
72-
this._name = reg?.exec(this.rBin)?.[0] ?? '';
77+
constructor(executablePath: string) {
78+
super();
79+
this._name = getCondaName(executablePath);
80+
const details = getRDetailsFromMetaHistory(executablePath);
81+
this._rBin = executablePath;
82+
this._rVersion = details?.version ?? '';
83+
this._rArch = details?.arch ?? '';
7384
}
7485

7586
public get name(): string {
7687
return this._name;
7788
}
7889

7990
public get tooltip(): string {
80-
return `${this.name} (${super.tooltip})`;
81-
}
82-
83-
// todo, hardcoded
84-
public get activationCommand(): string[] {
85-
if (this.name) {
86-
return ['activate', this.name];
87-
} else {
88-
return ['activate'];
91+
if (this.rVersion && this.rArch) {
92+
return `${this.name} (R ${this.rVersion} ${this.rArch})`;
8993
}
94+
return `$(error) ${this.name}`;
9095
}
9196
}

src/executables/service/index.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
import * as vscode from 'vscode';
22

33
import { validateRExecutablePath } from '..';
4-
import { ExecutableType, RExecutableFactory } from './class';
4+
import { RExecutableFactory } from './class';
55
import { config, getCurrentWorkspaceFolder, getRPathConfigEntry } from '../../util';
66
import { RExecutablePathStorage } from './pathStorage';
77
import { RExecutableRegistry } from './registry';
88
import { AbstractLocatorService, LocatorServiceFactory } from './locator';
99
import { getRenvVersion } from './renv';
10+
import { ExecutableType, WorkspaceExecutableEvent } from './types';
1011

12+
export * from './types';
1113
export * from './class';
1214

13-
/**
14-
* @description
15-
* @export
16-
* @interface WorkspaceExecutableEvent
17-
*/
18-
export interface WorkspaceExecutableEvent {
19-
workingFolder: vscode.WorkspaceFolder | undefined,
20-
executable: ExecutableType | undefined
21-
}
22-
2315
/**
2416
* @description
2517
* @export
@@ -49,7 +41,7 @@ export class RExecutableService implements vscode.Disposable {
4941
this.workspaceExecutables = new Map<string, ExecutableType>();
5042
this.executableEmitter = new vscode.EventEmitter<ExecutableType>();
5143
this.workspaceEmitter = new vscode.EventEmitter<WorkspaceExecutableEvent>();
52-
this.executablePathLocator.binaryPaths.forEach((path) => {
44+
this.executablePathLocator.executablePaths.forEach((path) => {
5345
this.executableFactory.create(path);
5446
});
5547

src/executables/service/locator/shared.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ export function getUniquePaths(paths: string[]): string[] {
5959
}
6060

6161
export abstract class AbstractLocatorService {
62-
protected _binaryPaths: string[];
62+
protected _executablePaths: string[];
6363
protected emitter: vscode.EventEmitter<string[]>;
6464
public abstract refreshPaths(): Promise<void>;
6565
public get hasPaths(): boolean {
66-
return this._binaryPaths.length > 0;
66+
return this._executablePaths.length > 0;
6767
}
68-
public get binaryPaths(): string[] {
69-
return this._binaryPaths;
68+
public get executablePaths(): string[] {
69+
return this._executablePaths;
7070
}
7171
public get onDidRefreshPaths(): vscode.Event<string[]> {
7272
return this.emitter.event;

0 commit comments

Comments
 (0)