Skip to content

Commit c1296f2

Browse files
committed
Fix #215 and #209, Failed to switch stubs / use stubs on external drive
Signed-off-by: paulober <[email protected]>
1 parent 825324f commit c1296f2

File tree

4 files changed

+143
-61
lines changed

4 files changed

+143
-61
lines changed

src/activator.mts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default class Activator {
6666
);
6767

6868
this.stubs = new Stubs();
69-
await this.stubs.update();
69+
await this.stubs.update(settings);
7070

7171
this.comDevice = await settings.getComDevice();
7272

@@ -354,6 +354,14 @@ export default class Activator {
354354
frozen = true;
355355
}
356356
if (data.includes("!!ERR!!")) {
357+
this.logger.error(
358+
"Exception occured (maybe a connection loss). " +
359+
`Message dump: ${data}`
360+
);
361+
console.log(
362+
"Exception occured (maybe a connection loss). " +
363+
`Message dump: ${data}`
364+
);
357365
// write red text into terminal
358366
terminal?.write(
359367
"\x1b[31mException occured (maybe a connection loss)\x1b[0m\r\n"
@@ -1113,7 +1121,7 @@ export default class Activator {
11131121
}
11141122

11151123
if (version.toLowerCase() === "included") {
1116-
await installIncludedStubs();
1124+
await installIncludedStubs(settings);
11171125

11181126
void vscode.window.showInformationMessage("Included stubs selected.");
11191127
} else {
@@ -1131,7 +1139,8 @@ export default class Activator {
11311139
// TODO: implement cancellation
11321140
const result = await installStubsByVersion(
11331141
versionParts[1],
1134-
displayStringToStubPort(versionParts[0])
1142+
displayStringToStubPort(versionParts[0]),
1143+
settings
11351144
);
11361145

11371146
if (result) {

src/api.mts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { homedir } from "os";
22
import { join } from "path";
3+
import { join as joinPosix } from "path/posix";
34
import { TextDecoder } from "util";
45
import {
56
commands,
@@ -75,6 +76,34 @@ export function getVsCodeUserPath(): string {
7576
return join(folder, "Code", "User");
7677
}
7778

79+
export function getStubsBasePath(): string {
80+
return join(homedir(), ".micropico-stubs");
81+
}
82+
83+
export function getStubsBasePathPosix(): string {
84+
return joinPosix(homedir(), ".micropico-stubs");
85+
}
86+
87+
export function getIncludedStubsPath(): string {
88+
return join(getStubsBasePath(), "included");
89+
}
90+
91+
export function settingsStubsBasePath(): string {
92+
return joinPosix("~", ".micropico-stubs");
93+
}
94+
95+
export function getStubsPathForVersion(version: string): string {
96+
return join(getStubsBasePath(), version);
97+
}
98+
99+
export function getStubsPathForVersionPosix(version: string): string {
100+
return joinPosix(getStubsBasePathPosix(), version);
101+
}
102+
103+
export function settingsStubsPathForVersion(version: string): string {
104+
return join(settingsStubsBasePath(), version);
105+
}
106+
78107
/**
79108
* Returns the path to the currently opened project (aka first workspace folder)
80109
*

src/settings.mts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Memento, Uri, WorkspaceConfiguration } from "vscode";
22
import { window, workspace as vsWorkspace } from "vscode";
33
import { PyboardRunner } from "@paulober/pyboard-serial-com";
4-
import { extName, getProjectPath } from "./api.mjs";
4+
import { extName, getProjectPath, settingsStubsBasePath } from "./api.mjs";
55
import { join, relative } from "path";
66

77
export enum SettingsKey {
@@ -24,10 +24,12 @@ export type Setting = string | boolean | string[] | null | undefined;
2424

2525
export default class Settings {
2626
private config: WorkspaceConfiguration;
27+
private pythonConfig: WorkspaceConfiguration;
2728
public context: Memento;
2829

2930
constructor(context: Memento) {
3031
this.config = vsWorkspace.getConfiguration(extName);
32+
this.pythonConfig = vsWorkspace.getConfiguration("python.analysis");
3133

3234
this.context = context;
3335
}
@@ -36,7 +38,11 @@ export default class Settings {
3638
this.config = vsWorkspace.getConfiguration(extName);
3739
}
3840

39-
public get(key: SettingsKey): Setting {
41+
public reloadPython(): void {
42+
this.pythonConfig = vsWorkspace.getConfiguration("python.analysis");
43+
}
44+
45+
public get(key: SettingsKey | string): Setting {
4046
return this.config.get(key);
4147
}
4248

@@ -58,10 +64,20 @@ export default class Settings {
5864
return Array.isArray(value) ? value : undefined;
5965
}
6066

61-
public update<T>(key: SettingsKey, value: T): Thenable<void> {
67+
public getArrayPython(key: string): string[] | undefined {
68+
const value = this.pythonConfig.get(key);
69+
70+
return Array.isArray(value) ? value : undefined;
71+
}
72+
73+
public update<T>(key: SettingsKey | string, value: T): Thenable<void> {
6274
return this.config.update(key, value, true);
6375
}
6476

77+
public updatePython<T>(key: string, value: T): Thenable<void> {
78+
return this.pythonConfig.update(key, value, null);
79+
}
80+
6581
// helpers
6682
/**
6783
* Get the COM port to connect to.
@@ -200,6 +216,40 @@ export default class Settings {
200216
public getIngoredSyncItems(): string[] {
201217
return this.getArray(SettingsKey.pyIgnore) || [];
202218
}
219+
220+
public async updateStubsPath(newStubs: string): Promise<boolean> {
221+
// catch if stubs where updated before after starting the extension
222+
this.reloadPython();
223+
224+
const typeshedPaths = this.getArrayPython("typeshedPaths");
225+
const extraPaths = this.getArrayPython("extraPaths");
226+
227+
if (typeshedPaths === undefined || extraPaths === undefined) {
228+
return false;
229+
}
230+
231+
// Remove paths starting with '~/.micropico-stubs'
232+
const filteredTypeshedPaths = typeshedPaths.filter(
233+
path =>
234+
!path.startsWith(settingsStubsBasePath()) &&
235+
!path.includes("Pico-W-Stub")
236+
);
237+
const filteredExtraPaths = extraPaths.filter(
238+
path =>
239+
!path.startsWith(settingsStubsBasePath()) &&
240+
!path.includes("Pico-W-Stub")
241+
);
242+
243+
// Add newStubs to both arrays
244+
filteredTypeshedPaths.push(newStubs);
245+
filteredExtraPaths.push(newStubs);
246+
247+
// Update the settings with the modified arrays
248+
await this.updatePython("typeshedPaths", filteredTypeshedPaths);
249+
await this.updatePython("extraPaths", filteredExtraPaths);
250+
251+
return true;
252+
}
203253
}
204254

205255
/**

src/stubs.mts

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { commands, window } from "vscode";
2-
import { join, resolve } from "path";
2+
import { join } from "path";
33
import {
44
getProjectPath,
5-
getVsCodeUserPath,
5+
getStubsPathForVersion,
6+
getStubsPathForVersionPosix,
67
recommendedExtensions,
8+
settingsStubsPathForVersion,
79
shouldRecommendExtensions,
810
} from "./api.mjs";
9-
import { mkdir, readdir, symlink } from "fs/promises";
11+
import { mkdir, readdir } from "fs/promises";
1012
import {
1113
pathExists,
1214
readJsonFile,
@@ -19,6 +21,7 @@ import _ from "lodash";
1921
import which from "which";
2022
import { execSync } from "child_process";
2123
import axios, { HttpStatusCode } from "axios";
24+
import type Settings from "./settings.mjs";
2225

2326
export default class Stubs {
2427
private logger: Logger;
@@ -27,13 +30,17 @@ export default class Stubs {
2730
this.logger = new Logger("Stubs");
2831
}
2932

30-
public async update(): Promise<void> {
31-
const configFolder = getVsCodeUserPath();
32-
const installedStubsFolder = join(configFolder, "Pico-W-Stub");
33+
/**
34+
* Update localy installed included stubs to the latest version.
35+
*
36+
* @returns
37+
*/
38+
public async update(settings: Settings): Promise<void> {
39+
const installedStubsFolder = getStubsPathForVersion("included");
3340

3441
if (!(await pathExists(join(installedStubsFolder, "version.json")))) {
3542
// ensure config folder exists
36-
await mkdir(configFolder, { recursive: true });
43+
await mkdir(installedStubsFolder, { recursive: true });
3744

3845
let installedVersion = "";
3946
let currentVersion = "";
@@ -64,6 +71,12 @@ export default class Stubs {
6471
if (installedVersion === currentVersion) {
6572
this.logger.info("Installed stubs are already up to date!");
6673

74+
// TODO: remove in future versions, only to convert legacy projects
75+
const workspace = getProjectPath();
76+
if (workspace) {
77+
await this.removeLegacyStubs(join(workspace, ".vscode"), settings);
78+
}
79+
6780
return;
6881
}
6982
}
@@ -74,6 +87,12 @@ export default class Stubs {
7487
await copy(join(__dirname, "..", "mpy_stubs"), installedStubsFolder);
7588

7689
this.logger.info("Updated stubs successfully!");
90+
91+
// TODO: remove in future versions, only to convert legacy projects
92+
const workspace = getProjectPath();
93+
if (workspace) {
94+
await this.removeLegacyStubs(join(workspace, ".vscode"), settings);
95+
}
7796
} catch (error) {
7897
const msg: string =
7998
typeof error === "string" ? error : (error as Error).message;
@@ -104,7 +123,7 @@ export default class Stubs {
104123
await mkdir(vsc);
105124
}
106125

107-
await this.addStubs(vsc);
126+
await this.removeLegacyStubs(vsc);
108127
await this.addExtensions(vsc);
109128
await this.addSettings(vsc);
110129
await this.addProjectFile(workspace);
@@ -119,19 +138,21 @@ export default class Stubs {
119138
}
120139

121140
/**
122-
* Add stubs to the VS Code user folder
141+
* Remove stubs junction from the VS Code workspace folder
123142
*
124143
* @param vsc The path to the vscode config folder in current workspace
125144
*/
126-
private async addStubs(vsc: string): Promise<void> {
145+
private async removeLegacyStubs(
146+
vsc: string,
147+
settings: Settings | undefined = undefined
148+
): Promise<void> {
127149
const stubsPath = join(vsc, "Pico-W-Stub");
128-
if (!(await pathExists(stubsPath))) {
129-
const configFolder = getVsCodeUserPath();
130-
await symlink(
131-
resolve(join(configFolder, "Pico-W-Stub")),
132-
stubsPath,
133-
"junction"
134-
);
150+
if (await pathExists(stubsPath)) {
151+
await removeJunction(stubsPath);
152+
153+
if (settings) {
154+
await installIncludedStubs(settings);
155+
}
135156
}
136157
}
137158

@@ -155,7 +176,7 @@ export default class Stubs {
155176
justUpdate: boolean = false
156177
): Promise<void> {
157178
const settingsFilePath = join(vsc, "settings.json");
158-
const stubsPath = join(".vscode", "Pico-W-Stub");
179+
const stubsPath = settingsStubsPathForVersion("included");
159180
const defaultSettings: { [key: string]: string | boolean | object } = {
160181
// eslint-disable-next-line @typescript-eslint/naming-convention
161182
"python.linting.enabled": true,
@@ -214,22 +235,8 @@ export default class Stubs {
214235
*
215236
* @returns
216237
*/
217-
export async function installIncludedStubs(): Promise<void> {
218-
const workspaceFolder = getProjectPath();
219-
if (workspaceFolder === undefined) {
220-
return;
221-
}
222-
const vsc = join(workspaceFolder, ".vscode");
223-
const stubsPath = join(vsc, "Pico-W-Stub");
224-
if (await pathExists(stubsPath)) {
225-
await removeJunction(stubsPath);
226-
}
227-
const configFolder = getVsCodeUserPath();
228-
await symlink(
229-
resolve(join(configFolder, "Pico-W-Stub")),
230-
stubsPath,
231-
"junction"
232-
);
238+
export async function installIncludedStubs(settings: Settings): Promise<void> {
239+
await settings.updateStubsPath(settingsStubsPathForVersion("included"));
233240
}
234241

235242
enum StubPorts {
@@ -269,7 +276,8 @@ export function displayStringToStubPort(displayString: string): string {
269276
// TODO: option to choose stubs distrbution
270277
export async function installStubsByVersion(
271278
version: string,
272-
port: string
279+
port: string,
280+
settings: Settings
273281
): Promise<boolean> {
274282
// check if pip is available
275283
const pip: string | null = await which("pip", { nothrow: true });
@@ -283,35 +291,21 @@ export async function installStubsByVersion(
283291
return false;
284292
}
285293

286-
const configFolder = getVsCodeUserPath();
287-
const target = resolve(
288-
join(configFolder, "MicroPico-Stubs", `${port}==${version}`)
289-
);
294+
const folderName = `${port}==${version}`;
295+
const target = getStubsPathForVersionPosix(folderName);
290296
mkdirpSync(target);
291297

292298
// install stubs with pip vscode user directory
293299
const result = execSync(
294-
`&"${pip}" install ${port}==${version} ` + `--target "${target}" --no-user`,
300+
`${
301+
process.platform === "win32" ? "&" : ""
302+
}"${pip}" install ${port}==${version} ` + `--target "${target}" --no-user`,
295303
process.platform === "win32" ? { shell: "powershell" } : {}
296304
);
297305

298306
// check result
299307
if (result.toString("utf-8").includes("Successfully installed")) {
300-
const workspaceFolder = getProjectPath();
301-
if (workspaceFolder === undefined) {
302-
return false;
303-
}
304-
const vsc = join(workspaceFolder, ".vscode");
305-
const stubsPath = join(vsc, "Pico-W-Stub");
306-
// delete stubsPath folder if it exists
307-
if (await pathExists(stubsPath)) {
308-
await removeJunction(stubsPath);
309-
}
310-
311-
// relink
312-
await symlink(target, stubsPath, "junction");
313-
314-
return true;
308+
return settings.updateStubsPath(settingsStubsPathForVersion(folderName));
315309
}
316310

317311
return false;

0 commit comments

Comments
 (0)