Skip to content

Commit d015c54

Browse files
committed
Improve quickpick categories
QP items now grouped into - recommended - virtual - global - Changed various functions into arrow functions to pass `'this'
1 parent fe235bd commit d015c54

File tree

5 files changed

+145
-73
lines changed

5 files changed

+145
-73
lines changed

src/executables/service/locator/shared.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { normaliseRPathString } from '../../../util';
66
export function getRDetailsFromPath(rPath: string): {version: string, arch: string} {
77
try {
88
const path = normaliseRPathString(rPath);
9-
const child = execSync(`${path} --version`).toString();
9+
const child = execSync(`${path} --version`)?.toString();
1010
const versionRegex = /(?<=R version\s)[0-9.]*/g;
1111
const archRegex = /[0-9]*-bit/g;
1212
const out = {

src/executables/service/locator/unix.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,18 @@ export class UnixExecLocator extends AbstractLocatorService {
4949
];
5050
for (const dir of conda_dirs) {
5151
if (fs.existsSync(dir)) {
52-
const lines = fs.readFileSync(dir).toString();
53-
for (const line of lines.split('\n')) {
54-
if (line) {
55-
const potential_dirs = [
56-
`${line}/lib64/R/bin/R`,
57-
`${line}/lib/R/bin/R`
58-
];
59-
for (const dir of potential_dirs) {
60-
if (fs.existsSync(dir)) {
61-
dirBins.push(dir);
52+
const lines = fs.readFileSync(dir)?.toString();
53+
if (lines) {
54+
for (const line of lines.split('\n')) {
55+
if (line) {
56+
const potential_dirs = [
57+
`${line}/lib64/R/bin/R`,
58+
`${line}/lib/R/bin/R`
59+
];
60+
for (const dir of potential_dirs) {
61+
if (fs.existsSync(dir)) {
62+
dirBins.push(dir);
63+
}
6264
}
6365
}
6466
}

src/executables/service/pathStorage.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ export class RExecutablePathStorage {
4040
}
4141

4242
private mapToString(map: Map<string, string>): string {
43-
return JSON.stringify([...map]);
43+
try {
44+
return JSON.stringify([...map]);
45+
} catch (error) {
46+
return '';
47+
}
4448
}
4549

4650
private stringToMap(str: string): Map<string, string> {

src/executables/service/renv.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,28 @@ interface IRenvLock {
88
};
99
}
1010

11-
export function getRenvVersion(workspacePath: string): string {
12-
try {
13-
const lockPath = path.join(workspacePath, 'renv.lock');
14-
if (!fs.existsSync(lockPath)) {
11+
export function getRenvVersion(workspacePath: string): string | undefined {
12+
if (isRenvWorkspace(workspacePath)) {
13+
try {
14+
const lockPath = path.join(workspacePath, 'renv.lock');
15+
if (!fs.existsSync(lockPath)) {
16+
return '';
17+
}
18+
const lockContent = fs.readJSONSync(lockPath) as IRenvLock;
19+
return lockContent?.R?.Version ?? '';
20+
} catch (error) {
1521
return '';
1622
}
17-
const lockContent = fs.readJSONSync(lockPath) as IRenvLock;
18-
return lockContent?.R?.Version ?? '';
23+
} else {
24+
return undefined;
25+
}
26+
}
27+
28+
export function isRenvWorkspace(workspacePath: string): boolean {
29+
try {
30+
const renvPath = path.join(workspacePath, 'renv');
31+
return fs.existsSync(renvPath);
1932
} catch (error) {
20-
return '';
33+
return false;
2134
}
2235
}

src/executables/ui/quickpick.ts

Lines changed: 107 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@ import { RExecutableService } from '../service';
88
import { getRenvVersion } from '../service/renv';
99

1010
class ExecutableQuickPickItem implements vscode.QuickPickItem {
11+
public recommended: boolean;
12+
public category: string;
1113
public label: string;
1214
public description: string;
1315
public detail?: string;
1416
public picked?: boolean;
1517
public alwaysShow?: boolean;
1618
private _executable: ExecutableType;
1719

18-
constructor(executable: ExecutableType, recommended?: boolean) {
20+
constructor(executable: ExecutableType, recommended: boolean) {
1921
this._executable = executable;
2022
this.description = executable.rBin;
2123

24+
if (isVirtual(executable)) {
25+
this.category = 'Virtual';
26+
} else {
27+
this.category = 'Global';
28+
}
29+
30+
this.recommended = recommended;
31+
2232
if (recommended) {
2333
this.label = `$(star) ${executable.tooltip}`;
24-
this.detail = 'ffffffffffff';
2534
} else {
2635
this.label = executable.tooltip;
2736
}
@@ -53,17 +62,6 @@ export class ExecutableQuickPick implements vscode.Disposable {
5362
}
5463

5564
private setItems(): void {
56-
function sortBins(bins: ExecutableType[]) {
57-
return bins.sort((a, b) => {
58-
if (!isVirtual(a) && isVirtual(b)) {
59-
return 1;
60-
} else if (!isVirtual(b) && isVirtual(a)) {
61-
return -1;
62-
} else {
63-
return a.rVersion.localeCompare(b.rVersion, undefined, { numeric: true, sensitivity: 'base' });
64-
}
65-
});
66-
}
6765
const qpItems: vscode.QuickPickItem[] = [];
6866
const configPath = config().get<string>(getRPathConfigEntry());
6967
qpItems.push(
@@ -83,47 +81,87 @@ export class ExecutableQuickPick implements vscode.Disposable {
8381
});
8482
}
8583

84+
const renvVersion = getRenvVersion(this.currentFolder.uri.fsPath) ?? undefined;
85+
const recommendedItems: vscode.QuickPickItem[] = [
86+
{
87+
label: 'Recommended',
88+
kind: vscode.QuickPickItemKind.Separator
89+
}
90+
];
91+
const virtualItems: vscode.QuickPickItem[] = [
92+
{
93+
label: 'Virtual',
94+
kind: vscode.QuickPickItemKind.Separator
95+
}
96+
];
97+
const globalItems: vscode.QuickPickItem[] = [
98+
{
99+
label: 'Global',
100+
kind: vscode.QuickPickItemKind.Separator
101+
}
102+
];
86103

87-
88-
sortBins([...this.service.executables]).forEach((bin: ExecutableType) => {
89-
qpItems.push(new ExecutableQuickPickItem(bin, recommendPath(bin, this.currentFolder)));
104+
[...this.service.executables].forEach((v) => {
105+
const item = new ExecutableQuickPickItem(v, recommendPath(v, this.currentFolder, renvVersion));
106+
if (item.recommended) {
107+
recommendedItems.push(item);
108+
} else {
109+
switch (item.category) {
110+
case 'Virtual': {
111+
virtualItems.push(item);
112+
break;
113+
}
114+
case 'Global': {
115+
globalItems.push(item);
116+
break;
117+
}
118+
}
119+
}
90120
});
91-
92-
this.quickpick.items = qpItems;
93-
121+
this.quickpick.items = [...qpItems, ...recommendedItems, ...virtualItems, ...globalItems];
94122
for (const item of this.quickpick.items) {
95123
if (item.description === this.service.getWorkspaceExecutable(this.currentFolder?.uri?.fsPath)?.rBin) {
96124
this.quickpick.activeItems = [item];
97125
}
98126
}
99127
}
100128

129+
/**
130+
* @description
131+
* Basic display of the quickpick is:
132+
* - Manual executable selection
133+
* - Configuration path (may be hidden)
134+
* - Recommended paths (may be hidden)
135+
* - Virtual paths
136+
* - Global paths
137+
* @returns {*} {Promise<void>}
138+
* @memberof ExecutableQuickPick
139+
*/
101140
public async showQuickPick(): Promise<void> {
102-
function setupQuickpickOpts(self: ExecutableQuickPick): void {
103-
self.quickpick = vscode.window.createQuickPick();
104-
self.quickpick.title = 'Select R executable path';
105-
self.quickpick.canSelectMany = false;
106-
self.quickpick.ignoreFocusOut = true;
107-
self.quickpick.matchOnDescription = true;
108-
self.quickpick.placeholder = '';
109-
self.quickpick.buttons = [
141+
const setupQuickpickOpts = () => {
142+
this.quickpick = vscode.window.createQuickPick();
143+
this.quickpick.title = 'Select R executable path';
144+
this.quickpick.canSelectMany = false;
145+
this.quickpick.ignoreFocusOut = true;
146+
this.quickpick.matchOnDescription = true;
147+
this.quickpick.buttons = [
110148
{ iconPath: new vscode.ThemeIcon('clear-all'), tooltip: 'Clear stored path' },
111149
{ iconPath: new vscode.ThemeIcon('refresh'), tooltip: 'Refresh paths' }
112150
];
113-
}
151+
};
114152

115-
function setupQuickpickListeners(self: ExecutableQuickPick, resolver: () => void): void {
116-
self.quickpick.onDidTriggerButton(async (item: vscode.QuickInputButton) => {
153+
const setupQuickpickListeners = (resolver: () => void) => {
154+
this.quickpick.onDidTriggerButton(async (item: vscode.QuickInputButton) => {
117155
if (item.tooltip === 'Refresh paths') {
118-
await self.service.executablePathLocator.refreshPaths();
119-
self.setItems();
120-
self.quickpick.show();
156+
await this.service.executablePathLocator.refreshPaths();
157+
this.setItems();
158+
this.quickpick.show();
121159
} else {
122-
self.service.setWorkspaceExecutable(self.currentFolder?.uri?.fsPath, undefined);
123-
self.quickpick.hide();
160+
this.service.setWorkspaceExecutable(this.currentFolder?.uri?.fsPath, undefined);
161+
this.quickpick.hide();
124162
}
125163
});
126-
self.quickpick.onDidChangeSelection((items: vscode.QuickPickItem[]) => {
164+
this.quickpick.onDidChangeSelection((items: vscode.QuickPickItem[]) => {
127165
const qpItem = items[0];
128166
if (qpItem.label) {
129167
switch (qpItem.label) {
@@ -136,59 +174,74 @@ export class ExecutableQuickPick implements vscode.Disposable {
136174
};
137175
void vscode.window.showOpenDialog(opts).then((execPath) => {
138176
if (execPath?.[0].fsPath && validateRExecutablePath(execPath[0].fsPath)) {
139-
const rExec = self.service.executableFactory.create(execPath[0].fsPath);
140-
self.service.setWorkspaceExecutable(self.currentFolder?.uri?.fsPath, rExec);
177+
const rExec = this.service.executableFactory.create(execPath[0].fsPath);
178+
this.service.setWorkspaceExecutable(this.currentFolder?.uri?.fsPath, rExec);
141179
} else {
142180
void vscode.window.showErrorMessage(ExecutableNotifications.badFolder);
143-
self.service.setWorkspaceExecutable(self.currentFolder?.uri?.fsPath, undefined);
181+
this.service.setWorkspaceExecutable(this.currentFolder?.uri?.fsPath, undefined);
144182
}
145183
});
146184
break;
147185
}
148186
case PathQuickPickMenu.configuration: {
149187
const configPath = config().get<string>(getRPathConfigEntry());
150188
if (configPath && validateRExecutablePath(configPath)) {
151-
const rExec = self.service.executableFactory.create(configPath);
152-
self.service.setWorkspaceExecutable(self.currentFolder?.uri?.fsPath, rExec);
189+
const rExec = this.service.executableFactory.create(configPath);
190+
this.service.setWorkspaceExecutable(this.currentFolder?.uri?.fsPath, rExec);
153191
} else {
154192
void vscode.window.showErrorMessage(ExecutableNotifications.badConfig);
155-
self.service.setWorkspaceExecutable(self.currentFolder?.uri?.fsPath, undefined);
193+
this.service.setWorkspaceExecutable(this.currentFolder?.uri?.fsPath, undefined);
156194
}
157195
break;
158196
}
159197
default: {
160-
self.service.setWorkspaceExecutable(self.currentFolder?.uri?.fsPath, (qpItem as ExecutableQuickPickItem).executable);
198+
this.service.setWorkspaceExecutable(this.currentFolder?.uri?.fsPath, (qpItem as ExecutableQuickPickItem).executable);
161199
break;
162200
}
163201
}
164202
}
165-
self.quickpick.hide();
203+
this.quickpick.hide();
166204
resolver();
167205
});
168-
}
206+
};
169207

170208
return await new Promise((res) => {
171-
setupQuickpickOpts(this);
172-
setupQuickpickListeners(this, res);
209+
setupQuickpickOpts();
210+
setupQuickpickListeners(res);
173211
void showWorkspaceFolderQP().then((folder: vscode.WorkspaceFolder) => {
174212
this.currentFolder = folder;
213+
const currentExec = this.service.getWorkspaceExecutable(folder?.uri?.fsPath);
214+
if (currentExec) {
215+
this.quickpick.placeholder = `Current path: ${currentExec.rBin}`;
216+
} else {
217+
this.quickpick.placeholder = '';
218+
}
175219
this.setItems();
176220
this.quickpick.show();
177221
});
178222
});
179223
}
180224
}
181225

182-
async function showWorkspaceFolderQP(): Promise<vscode.WorkspaceFolder> {
226+
async function showWorkspaceFolderQP(): Promise<vscode.WorkspaceFolder | undefined> {
183227
const opts: vscode.WorkspaceFolderPickOptions = {
184228
ignoreFocusOut: true,
185229
placeHolder: 'Select a workspace folder to define an R path for'
186230
};
231+
const currentDocument = vscode?.window?.activeTextEditor?.document?.uri;
187232
if (isMultiRoot()) {
188-
return await vscode.window.showWorkspaceFolderPick(opts);
233+
const workspaceFolder = await vscode.window.showWorkspaceFolderPick(opts);
234+
if (workspaceFolder) {
235+
return workspaceFolder;
236+
} else if (currentDocument) {
237+
return {
238+
index: 0,
239+
uri: currentDocument,
240+
name: 'untitled'
241+
};
242+
}
189243
}
190244

191-
const currentDocument = vscode?.window?.activeTextEditor?.document?.uri;
192245
if (currentDocument) {
193246
const folder = vscode.workspace.getWorkspaceFolder(currentDocument);
194247
if (folder) {
@@ -201,12 +254,12 @@ async function showWorkspaceFolderQP(): Promise<vscode.WorkspaceFolder> {
201254
};
202255
}
203256
}
257+
258+
return undefined;
204259
}
205260

206-
function recommendPath(executable: ExecutableType, workspaceFolder: vscode.WorkspaceFolder): boolean {
207-
const renvVersion = getRenvVersion(workspaceFolder?.uri?.fsPath);
261+
function recommendPath(executable: ExecutableType, workspaceFolder: vscode.WorkspaceFolder, renvVersion?: string): boolean {
208262
if (renvVersion) {
209-
console.log(renvVersion);
210263
const compatibleBin = renvVersion === executable.rVersion;
211264
if (compatibleBin) {
212265
return true;
@@ -215,5 +268,5 @@ function recommendPath(executable: ExecutableType, workspaceFolder: vscode.Works
215268
}
216269
const uri = vscode.Uri.file(executable.rBin);
217270
const possibleWorkspace = vscode.workspace.getWorkspaceFolder(uri);
218-
return possibleWorkspace && possibleWorkspace === workspaceFolder;
271+
return !!possibleWorkspace && possibleWorkspace === workspaceFolder;
219272
}

0 commit comments

Comments
 (0)