Skip to content

Commit c7410c4

Browse files
committed
Binary recommendations, fix undefined, revert LSP changes
- Revert language service changes - need to look at how python does it - Recommend binaries that are in the workspace or are specified by a renv lockfile - Fix possibly undefined issues - Normalise path for windows - Update vscode types and engine to 1.65 - Async init method for the manager
1 parent f071316 commit c7410c4

File tree

16 files changed

+262
-116
lines changed

16 files changed

+262
-116
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"R Markdown"
2727
],
2828
"engines": {
29-
"vscode": "^1.60.0"
29+
"vscode": "^1.65.0"
3030
},
3131
"activationEvents": [
3232
"onLanguage:r",
@@ -1916,7 +1916,7 @@
19161916
"@types/node": "^14.17.3",
19171917
"@types/node-fetch": "^2.5.10",
19181918
"@types/showdown": "^1.9.3",
1919-
"@types/vscode": "^1.60.0",
1919+
"@types/vscode": "^1.65.0",
19201920
"@types/winreg": "^1.2.31",
19211921
"@types/ws": "^7.4.4",
19221922
"@typescript-eslint/eslint-plugin": "4.25.0",

src/executables/index.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ export { ExecutableType as IRExecutable, VirtualExecutableType as IVirtualRExecu
1111

1212
// super class that manages relevant sub classes
1313
export class RExecutableManager implements vscode.Disposable {
14-
private readonly statusBar: ExecutableStatusItem;
15-
private readonly quickPick: ExecutableQuickPick;
1614
private readonly executableService: RExecutableService;
15+
private statusBar: ExecutableStatusItem;
16+
private quickPick: ExecutableQuickPick;
1717

18-
constructor() {
19-
this.executableService = new RExecutableService();
18+
private constructor(service: RExecutableService) {
19+
this.executableService = service;
2020
this.statusBar = new ExecutableStatusItem(this.executableService);
2121
this.quickPick = new ExecutableQuickPick(this.executableService);
22-
2322
extensionContext.subscriptions.push(
2423
this.onDidChangeActiveExecutable(() => {
2524
this.reload();
@@ -31,10 +30,14 @@ export class RExecutableManager implements vscode.Disposable {
3130
}),
3231
this
3332
);
34-
3533
this.reload();
3634
}
3735

36+
static async initialize(): Promise<RExecutableManager> {
37+
const executableService = await RExecutableService.initialize();
38+
return new this(executableService);
39+
}
40+
3841
public dispose(): void {
3942
this.executableService.dispose();
4043
this.statusBar.dispose();
@@ -45,19 +48,19 @@ export class RExecutableManager implements vscode.Disposable {
4548
return this.quickPick;
4649
}
4750

48-
public get activeExecutablePath(): string {
49-
return this.executableService.activeExecutable.rBin;
51+
public get activeExecutablePath(): string | undefined {
52+
return this.executableService.activeExecutable?.rBin;
5053
}
5154

52-
public getExecutablePath(workingDir: string): string {
53-
return this.executableService.getWorkspaceExecutable(workingDir).rBin;
55+
public getExecutablePath(workingDir: string): string | undefined {
56+
return this.executableService.getWorkspaceExecutable(workingDir)?.rBin;
5457
}
5558

56-
public get activeExecutable(): ExecutableType {
59+
public get activeExecutable(): ExecutableType | undefined {
5760
return this.executableService.activeExecutable;
5861
}
5962

60-
public get onDidChangeActiveExecutable(): vscode.Event<ExecutableType> {
63+
public get onDidChangeActiveExecutable(): vscode.Event<ExecutableType | undefined> {
6164
return this.executableService.onDidChangeActiveExecutable;
6265
}
6366

@@ -77,8 +80,9 @@ export class RExecutableManager implements vscode.Disposable {
7780
void this.statusBar.busy(loading);
7881
}
7982

83+
8084
private async activateEnvironment(): Promise<unknown> {
81-
if (!isVirtual(this.activeExecutable) ||
85+
if (!this.activeExecutable || !isVirtual(this.activeExecutable) ||
8286
process.env.CONDA_DEFAULT_ENV !== this.activeExecutable.name) {
8387
return Promise.resolve();
8488
}

src/executables/service/class.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class RExecutableFactory {
2121
return oldExec;
2222
} else {
2323
let executable: RExecutable;
24-
if (new RegExp('\\.conda').exec(executablePath)?.length > 0) {
24+
if (new RegExp('\\.conda')?.exec(executablePath)) {
2525
executable = new VirtualRExecutable(executablePath);
2626
} else {
2727
executable = new RExecutable(executablePath);
@@ -64,13 +64,16 @@ class RExecutable {
6464
}
6565

6666
class VirtualRExecutable extends RExecutable {
67+
private _name: string;
68+
6769
constructor(bin_path: string) {
6870
super(bin_path);
71+
const reg = new RegExp('(?<=\\/envs\\/)(.*?)(?=\\/)');
72+
this._name = reg?.exec(this.rBin)?.[0] ?? '';
6973
}
7074

7175
public get name(): string {
72-
const reg = new RegExp('(?<=\\/envs\\/)(.*?)(?=\\/)');
73-
return reg.exec(this.rBin)[0];
76+
return this._name;
7477
}
7578

7679
public get tooltip(): string {
@@ -79,6 +82,10 @@ class VirtualRExecutable extends RExecutable {
7982

8083
// todo, hardcoded
8184
public get activationCommand(): string[] {
82-
return ['activate', this.name];
85+
if (this.name) {
86+
return ['activate', this.name];
87+
} else {
88+
return ['activate'];
89+
}
8390
}
8491
}

src/executables/service/index.ts

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { config, getCurrentWorkspaceFolder, getRPathConfigEntry } from '../../ut
66
import { RExecutablePathStorage } from './pathStorage';
77
import { RExecutableRegistry } from './registry';
88
import { AbstractLocatorService, LocatorServiceFactory } from './locator';
9+
import { getRenvVersion } from './renv';
910

1011
export * from './class';
1112

@@ -15,7 +16,7 @@ export * from './class';
1516
* @interface WorkspaceExecutableEvent
1617
*/
1718
export interface WorkspaceExecutableEvent {
18-
workingFolder: vscode.WorkspaceFolder,
19+
workingFolder: vscode.WorkspaceFolder | undefined,
1920
executable: ExecutableType | undefined
2021
}
2122

@@ -26,50 +27,47 @@ export interface WorkspaceExecutableEvent {
2627
* @implements {vscode.Disposable}
2728
*/
2829
export class RExecutableService implements vscode.Disposable {
29-
public readonly executableFactory: RExecutableFactory;
30-
public readonly executablePathLocator: AbstractLocatorService;
31-
private readonly executableStorage: RExecutablePathStorage;
32-
private readonly executableRegistry: RExecutableRegistry;
33-
private executableEmitter: vscode.EventEmitter<ExecutableType>;
30+
public executableFactory: RExecutableFactory;
31+
public executablePathLocator: AbstractLocatorService;
32+
private executableStorage: RExecutablePathStorage;
33+
private executableRegistry: RExecutableRegistry;
34+
private executableEmitter: vscode.EventEmitter<ExecutableType | undefined>;
3435
private workspaceEmitter: vscode.EventEmitter<WorkspaceExecutableEvent>;
35-
private workspaceExecutables: Map<string, ExecutableType>;
36+
private workspaceExecutables: Map<string, ExecutableType | undefined>;
3637

38+
public readonly ready: Thenable<this>;
3739

3840
/**
3941
* Creates an instance of RExecutableService.
4042
* @memberof RExecutableService
4143
*/
42-
public constructor() {
43-
this.executablePathLocator = LocatorServiceFactory.getLocator();
44-
this.executablePathLocator.refreshPaths();
44+
private constructor(locator: AbstractLocatorService) {
45+
this.executablePathLocator = locator;
4546
this.executableRegistry = new RExecutableRegistry();
4647
this.executableStorage = new RExecutablePathStorage();
4748
this.executableFactory = new RExecutableFactory(this.executableRegistry);
4849
this.workspaceExecutables = new Map<string, ExecutableType>();
49-
5050
this.executableEmitter = new vscode.EventEmitter<ExecutableType>();
5151
this.workspaceEmitter = new vscode.EventEmitter<WorkspaceExecutableEvent>();
52-
53-
// create executables for all executable paths found
5452
this.executablePathLocator.binaryPaths.forEach((path) => {
5553
this.executableFactory.create(path);
5654
});
5755

58-
const confPath = config().get<string>(getRPathConfigEntry());
59-
// from storage, recreate associations between workspace paths and executable paths
60-
for (const [dirPath, execPath] of this.executableStorage.executablePaths) {
61-
if (validateRExecutablePath(execPath)) {
62-
this.workspaceExecutables.set(dirPath, this.executableFactory.create(execPath));
63-
}
64-
}
56+
this.selectViableExecutables();
57+
}
6558

66-
if (!this.executableStorage.getActiveExecutablePath() && confPath && validateRExecutablePath(confPath)) {
67-
console.log(`[RExecutableService] Executable set to configuration path: ${confPath}`);
68-
const exec = this.executableFactory.create(confPath);
69-
this.activeExecutable = exec;
70-
}
59+
static async initialize(): Promise<RExecutableService> {
60+
const locator = LocatorServiceFactory.getLocator();
61+
await locator.refreshPaths();
62+
return new this(locator);
7163
}
7264

65+
/**
66+
* @description
67+
* @readonly
68+
* @type {Set<ExecutableType>}
69+
* @memberof RExecutableService
70+
*/
7371
public get executables(): Set<ExecutableType> {
7472
return this.executableRegistry.executables;
7573
}
@@ -78,12 +76,12 @@ export class RExecutableService implements vscode.Disposable {
7876
* @description
7977
* @memberof RExecutableService
8078
*/
81-
public set activeExecutable(executable: ExecutableType) {
82-
if (executable === null) {
79+
public set activeExecutable(executable: ExecutableType | undefined) {
80+
if (executable === undefined) {
8381
this.workspaceExecutables.delete(getCurrentWorkspaceFolder().uri.fsPath);
84-
this.executableStorage.setExecutablePath(getCurrentWorkspaceFolder().uri.fsPath, null);
82+
this.executableStorage.setExecutablePath(getCurrentWorkspaceFolder().uri.fsPath, undefined);
8583
console.log('[RExecutableService] executable cleared');
86-
this.executableEmitter.fire(null);
84+
this.executableEmitter.fire(undefined);
8785
} else if (this.activeExecutable !== executable) {
8886
this.workspaceExecutables.set(getCurrentWorkspaceFolder().uri.fsPath, executable);
8987
this.executableStorage.setExecutablePath(getCurrentWorkspaceFolder().uri.fsPath, executable.rBin);
@@ -100,12 +98,17 @@ export class RExecutableService implements vscode.Disposable {
10098
* @memberof RExecutableService
10199
*/
102100
public get activeExecutable(): ExecutableType | undefined {
103-
const currWorkspacePath = getCurrentWorkspaceFolder().uri.fsPath;
101+
const currWorkspacePath = getCurrentWorkspaceFolder()?.uri?.fsPath;
104102
if (currWorkspacePath) {
105103
return this.workspaceExecutables.get(currWorkspacePath);
106-
} else {
107-
return this.workspaceExecutables.get(vscode.window.activeTextEditor.document.uri.fsPath);
108104
}
105+
106+
const currentDocument = vscode?.window?.activeTextEditor?.document?.uri?.fsPath;
107+
if (currentDocument) {
108+
return this.workspaceExecutables.get(currentDocument);
109+
}
110+
111+
return undefined;
109112
}
110113

111114
/**
@@ -115,7 +118,7 @@ export class RExecutableService implements vscode.Disposable {
115118
* @param {RExecutable} executable
116119
* @memberof RExecutableService
117120
*/
118-
public setWorkspaceExecutable(folder: string, executable: ExecutableType): void {
121+
public setWorkspaceExecutable(folder: string, executable: ExecutableType | undefined): void {
119122
if (this.workspaceExecutables.get(folder) !== executable) {
120123
if (executable === undefined) {
121124
this.executableStorage.setExecutablePath(folder, undefined);
@@ -137,7 +140,7 @@ export class RExecutableService implements vscode.Disposable {
137140
* @returns {*} {RExecutable}
138141
* @memberof RExecutableService
139142
*/
140-
public getWorkspaceExecutable(folder: string): ExecutableType | undefined{
143+
public getWorkspaceExecutable(folder: string): ExecutableType | undefined {
141144
return this.workspaceExecutables.get(folder);
142145
}
143146

@@ -149,7 +152,7 @@ export class RExecutableService implements vscode.Disposable {
149152
* @type {vscode.Event<RExecutable>}
150153
* @memberof RExecutableService
151154
*/
152-
public get onDidChangeActiveExecutable(): vscode.Event<ExecutableType> {
155+
public get onDidChangeActiveExecutable(): vscode.Event<ExecutableType | undefined> {
153156
return this.executableEmitter.event;
154157
}
155158

@@ -172,4 +175,54 @@ export class RExecutableService implements vscode.Disposable {
172175
this.executableEmitter.dispose();
173176
this.workspaceEmitter.dispose();
174177
}
178+
179+
private selectViableExecutables(): void {
180+
// from storage, recreate associations between workspace paths and executable paths
181+
for (const [dirPath, execPath] of this.executableStorage.executablePaths) {
182+
if (validateRExecutablePath(execPath)) {
183+
this.workspaceExecutables.set(dirPath, this.executableFactory.create(execPath));
184+
}
185+
}
186+
187+
const confPath = config().get<string>(getRPathConfigEntry());
188+
if (vscode.workspace.workspaceFolders) {
189+
for (const workspace of vscode.workspace.workspaceFolders) {
190+
const workspacePath = workspace.uri.path;
191+
if (!this.workspaceExecutables.has(workspacePath)) {
192+
// is there a local virtual env?
193+
// todo
194+
195+
// is there a renv-recommended version?
196+
const renvVersion = getRenvVersion(workspacePath);
197+
if (renvVersion) {
198+
const compatibleExecutables = this.executableRegistry.getExecutablesWithVersion(renvVersion);
199+
if (compatibleExecutables) {
200+
const exec = compatibleExecutables.sort((a, b) => {
201+
if (a.rBin === confPath) {
202+
return -1;
203+
}
204+
if (b.rBin === confPath) {
205+
return 1;
206+
}
207+
return 0;
208+
})[0];
209+
this.workspaceExecutables.set(workspacePath, exec);
210+
return;
211+
}
212+
}
213+
214+
// fallback to a configured path if it exists
215+
if (confPath && validateRExecutablePath(confPath)) {
216+
console.log(`[RExecutableService] Executable set to configuration path: ${confPath}`);
217+
const exec = this.executableFactory.create(confPath);
218+
this.workspaceExecutables.set(workspacePath, exec);
219+
}
220+
}
221+
}
222+
} else {
223+
// todo
224+
}
225+
}
175226
}
227+
228+

src/executables/service/locator/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { UnixExecLocator } from './unix';
44
import { WindowsExecLocator } from './windows';
55
import { AbstractLocatorService } from './shared';
66

7+
8+
79
export class LocatorServiceFactory {
810
static getLocator(): AbstractLocatorService {
911
if (process.platform === 'win32') {

src/executables/service/locator/shared.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { execSync } from 'child_process';
22
import * as fs from 'fs-extra';
33
import * as vscode from 'vscode';
4+
import { normaliseRPathString } from '../../../util';
45

56
export function getRDetailsFromPath(rPath: string): {version: string, arch: string} {
67
try {
7-
const child = execSync(`${rPath} --version`).toString();
8+
const path = normaliseRPathString(rPath);
9+
const child = execSync(`${path} --version`).toString();
810
const versionRegex = /(?<=R version\s)[0-9.]*/g;
911
const archRegex = /[0-9]*-bit/g;
1012
const out = {
@@ -59,7 +61,7 @@ export function getUniquePaths(paths: string[]): string[] {
5961
export abstract class AbstractLocatorService {
6062
protected _binaryPaths: string[];
6163
protected emitter: vscode.EventEmitter<string[]>;
62-
public abstract refreshPaths(): void;
64+
public abstract refreshPaths(): Promise<void>;
6365
public get hasPaths(): boolean {
6466
return this._binaryPaths.length > 0;
6567
}
@@ -69,4 +71,4 @@ export abstract class AbstractLocatorService {
6971
public get onDidRefreshPaths(): vscode.Event<string[]> {
7072
return this.emitter.event;
7173
}
72-
}
74+
}

0 commit comments

Comments
 (0)