From 3524eb191fa7ef6fd39dbd0e7e8ea5a34ca31f92 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:52:47 +0100 Subject: [PATCH 01/13] Added MicroPico dependency + bump min vscode version to v1.92.1 Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- .vscode/settings.json | 6 ++++-- eslint.config.mjs | 6 +++--- package.json | 13 +++++++------ yarn.lock | 29 +++++++++++++++++++---------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7070a53d..9c8aaaa3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,8 +2,10 @@ "files.exclude": { // set them to true to hide them in vscode "out": false, - "dist": false, - "**/*.vsix": true + "dist": true, + "**/*.vsix": true, + ".yarn": true, + ".pnp.*": true }, "search.exclude": { "out": true, diff --git a/eslint.config.mjs b/eslint.config.mjs index a6a184e6..97977f0e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,7 @@ import tseslint from "typescript-eslint"; import js from "@eslint/js"; import eslintConfigPrettier from "eslint-config-prettier"; -export default [ +export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommendedTypeChecked, eslintConfigPrettier, @@ -19,7 +19,7 @@ export default [ ...globals.commonjs }, parserOptions: { - project: true, + projectService: true, tsconfigRootDir: import.meta.dirname } }, @@ -55,4 +55,4 @@ export default [ "web/**/*.js", ] } -]; +); diff --git a/package.json b/package.json index bfbe05a8..86957ecc 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "url": "https://github.com/raspberrypi/pico-vscode/" }, "engines": { - "vscode": "^1.87.0", - "node": ">=18.17.1" + "vscode": "^1.92.1", + "node": ">=20.14.0" }, "os": [ "win32", @@ -46,11 +46,12 @@ "ms-vscode.cpptools", "ms-vscode.cpptools-extension-pack", "ms-vscode.vscode-serial-monitor", - "marus25.cortex-debug" + "marus25.cortex-debug", + "paulober.pico-w-go" ], "main": "./dist/extension.cjs", "markdown": "github", - "minimumNodeVersion": 18, + "minimumNodeVersion": 20, "capabilities": { "virtualWorkspaces": { "supported": false, @@ -279,9 +280,9 @@ "@rollup/plugin-typescript": "^11.1.6", "@types/adm-zip": "^0.5.5", "@types/ini": "^4.1.1", - "@types/node": "18.17.x", + "@types/node": "20.14.0", "@types/uuid": "^10.0.0", - "@types/vscode": "^1.87.0", + "@types/vscode": "^1.92.0", "@types/which": "^3.0.4", "eslint": "^9.9.0", "eslint-config-prettier": "^9.1.0", diff --git a/yarn.lock b/yarn.lock index 0ae4b2fc..dfa657fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -500,10 +500,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.17.x": - version: 18.17.19 - resolution: "@types/node@npm:18.17.19" - checksum: 10/7d83ee58e0c402d8dc8efa59151215cb8b233e5fa06630399779d46ae8a0c7cb458b36c0352fb8b06328dd446b4f02e28b9a81ceceb509ad938feec3d3c54a3d +"@types/node@npm:20.14.0": + version: 20.14.0 + resolution: "@types/node@npm:20.14.0" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10/49b332fbf8aee4dc4f61cc1f1f6e130632510f795dd7b274e55894516feaf4bec8a3d13ea764e2443e340a64ce9bbeb006d14513bf6ccdd4f21161eccc7f311e languageName: node linkType: hard @@ -521,10 +523,10 @@ __metadata: languageName: node linkType: hard -"@types/vscode@npm:^1.87.0": - version: 1.87.0 - resolution: "@types/vscode@npm:1.87.0" - checksum: 10/6f10df10d9fbe305ccd69ad5432357f05de267c72100a5f5aa54341c454b4b3505a98580d0308a5dcd9562268a22701c46e70b744080c50955d44ac415f60cf1 +"@types/vscode@npm:^1.92.0": + version: 1.92.0 + resolution: "@types/vscode@npm:1.92.0" + checksum: 10/395f3eeec345a9e2f85f82d4f7082433480a791ccd936a16d07946fa8bddfe0a399fd7341e4e124556abbb1f31920bf5974d1500444b3d5c5428510a8a4f9d87 languageName: node linkType: hard @@ -2446,9 +2448,9 @@ __metadata: "@rollup/plugin-typescript": "npm:^11.1.6" "@types/adm-zip": "npm:^0.5.5" "@types/ini": "npm:^4.1.1" - "@types/node": "npm:18.17.x" + "@types/node": "npm:20.14.0" "@types/uuid": "npm:^10.0.0" - "@types/vscode": "npm:^1.87.0" + "@types/vscode": "npm:^1.92.0" "@types/which": "npm:^3.0.4" adm-zip: "npm:^0.5.14 <0.5.15" eslint: "npm:^9.9.0" @@ -2965,6 +2967,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10/0097779d94bc0fd26f0418b3a05472410408877279141ded2bd449167be1aed7ea5b76f756562cb3586a07f251b90799bab22d9019ceba49c037c76445f7cddd + languageName: node + linkType: hard + "undici@npm:^6.19.7": version: 6.19.7 resolution: "undici@npm:6.19.7" From 52e866c430f2bca85335abe7545b4cbc804a19ca Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:37:00 +0100 Subject: [PATCH 02/13] Added basic MicroPython project creation interop Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/commands/newProject.mts | 51 +- src/extension.mts | 12 + src/webview/activityBar.mts | 24 +- src/webview/newMicroPythonProjectPanel.mts | 591 +++++++++++++++++++++ src/webview/newProjectPanel.mts | 4 +- web/main.js | 4 +- web/mpy/main.js | 181 +++++++ 7 files changed, 854 insertions(+), 13 deletions(-) create mode 100644 src/webview/newMicroPythonProjectPanel.mts create mode 100644 web/mpy/main.js diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 398e5a29..66b58fde 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -1,11 +1,24 @@ import { Command } from "./command.mjs"; import Logger from "../logger.mjs"; -import { type Uri } from "vscode"; +import { window, type Uri } from "vscode"; import { NewProjectPanel } from "../webview/newProjectPanel.mjs"; +import { NewMicroPythonProjectPanel } from "../webview/newMicroPythonProjectPanel.mjs"; + +/** + * Enum for the language of the project. + * Can be used to specify the language of the project + * in the `NewProjectCommand` class. + */ +export enum ProjectLang { + cCpp = 1, + micropython = 2, +} export default class NewProjectCommand extends Command { private readonly _logger: Logger = new Logger("NewProjectCommand"); private readonly _extensionUri: Uri; + private static readonly micropythonOption = "MicroPython"; + private static readonly cCppOption = "C/C++"; public static readonly id = "newProject"; @@ -15,8 +28,38 @@ export default class NewProjectCommand extends Command { this._extensionUri = extensionUri; } - execute(): void { - // show webview where the process of creating a new project is continued - NewProjectPanel.createOrShow(this._extensionUri); + private preSelectedTypeToStr(preSelectedType?: number): string | undefined { + return preSelectedType === ProjectLang.cCpp + ? NewProjectCommand.cCppOption + : preSelectedType === ProjectLang.micropython + ? NewProjectCommand.micropythonOption + : undefined; + } + + async execute(preSelectedType?: number): Promise { + // ask the user what language to use + const lang = + this.preSelectedTypeToStr(preSelectedType) ?? + (await window.showQuickPick( + [NewProjectCommand.cCppOption, NewProjectCommand.micropythonOption], + { + placeHolder: "Select which language to use for your new project", + canPickMany: false, + ignoreFocusOut: false, + title: "New Pico Project", + } + )); + + if (lang === undefined) { + return; + } + + if (lang === NewProjectCommand.micropythonOption) { + // create a new project with MicroPython + NewMicroPythonProjectPanel.createOrShow(this._extensionUri); + } else { + // show webview where the process of creating a new project is continued + NewProjectPanel.createOrShow(this._extensionUri); + } } } diff --git a/src/extension.mts b/src/extension.mts index 0cf6dd58..edaf0528 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -74,6 +74,7 @@ import NewExampleProjectCommand from "./commands/newExampleProject.mjs"; import SwitchBoardCommand from "./commands/switchBoard.mjs"; import UninstallPicoSDKCommand from "./commands/uninstallPicoSDK.mjs"; import FlashProjectSWDCommand from "./commands/flashProjectSwd.mjs"; +import { NewMicroPythonProjectPanel } from "./webview/newMicroPythonProjectPanel.mjs"; export async function activate(context: ExtensionContext): Promise { Logger.info(LoggerSource.extension, "Extension activation triggered"); @@ -144,6 +145,17 @@ export async function activate(context: ExtensionContext): Promise { }) ); + context.subscriptions.push( + window.registerWebviewPanelSerializer(NewMicroPythonProjectPanel.viewType, { + // eslint-disable-next-line @typescript-eslint/require-await + async deserializeWebviewPanel(webviewPanel: WebviewPanel): Promise { + // Reset the webview options so we use latest uri for `localResourceRoots`. + webviewPanel.webview.options = getWebviewOptions(context.extensionUri); + NewMicroPythonProjectPanel.revive(webviewPanel, context.extensionUri); + }, + }) + ); + context.subscriptions.push( window.registerTreeDataProvider( PicoProjectActivityBar.viewType, diff --git a/src/webview/activityBar.mts b/src/webview/activityBar.mts index c2c330b0..bf607649 100644 --- a/src/webview/activityBar.mts +++ b/src/webview/activityBar.mts @@ -9,7 +9,7 @@ import { } from "vscode"; import Logger from "../logger.mjs"; import { extensionName } from "../commands/command.mjs"; -import NewProjectCommand from "../commands/newProject.mjs"; +import NewProjectCommand, { ProjectLang } from "../commands/newProject.mjs"; import CompileProjectCommand from "../commands/compileProject.mjs"; import RunProjectCommand from "../commands/runProject.mjs"; import SwitchSDKCommand from "../commands/switchSDK.mjs"; @@ -39,7 +39,8 @@ const COMMON_COMMANDS_PARENT_LABEL = "General"; const PROJECT_COMMANDS_PARENT_LABEL = "Project"; const DOCUMENTATION_COMMANDS_PARENT_LABEL = "Documentation"; -const NEW_PROJECT_LABEL = "New Project"; +const NEW_C_CPP_PROJECT_LABEL = "New C/C++ Project"; +const NEW_MICROPYTHON_PROJECT_LABEL = "New MicroPython Project"; const IMPORT_PROJECT_LABEL = "Import Project"; const EXAMPLE_PROJECT_LABEL = "New Project From Example"; const SWITCH_SDK_LABEL = "Switch SDK"; @@ -78,10 +79,13 @@ export class PicoProjectActivityBar element: QuickAccessCommand ): TreeItem | Thenable { switch (element.label) { - case NEW_PROJECT_LABEL: + case NEW_C_CPP_PROJECT_LABEL: // alt. "new-folder" element.iconPath = new ThemeIcon("file-directory-create"); break; + case NEW_MICROPYTHON_PROJECT_LABEL: + element.iconPath = new ThemeIcon("file-directory-create"); + break; case IMPORT_PROJECT_LABEL: // alt. "repo-pull" element.iconPath = new ThemeIcon("repo-clone"); @@ -158,11 +162,21 @@ export class PicoProjectActivityBar } else if (element.label === COMMON_COMMANDS_PARENT_LABEL) { return [ new QuickAccessCommand( - NEW_PROJECT_LABEL, + NEW_C_CPP_PROJECT_LABEL, + TreeItemCollapsibleState.None, + { + command: `${extensionName}.${NewProjectCommand.id}`, + title: NEW_C_CPP_PROJECT_LABEL, + arguments: [ProjectLang.cCpp], + } + ), + new QuickAccessCommand( + NEW_MICROPYTHON_PROJECT_LABEL, TreeItemCollapsibleState.None, { command: `${extensionName}.${NewProjectCommand.id}`, - title: NEW_PROJECT_LABEL, + title: NEW_MICROPYTHON_PROJECT_LABEL, + arguments: [ProjectLang.micropython], } ), new QuickAccessCommand( diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts new file mode 100644 index 00000000..2811eb7b --- /dev/null +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -0,0 +1,591 @@ +import { + Uri, + ViewColumn, + window, + type WebviewPanel, + type Disposable, + ColorThemeKind, + Webview, + workspace, + ProgressLocation, + Progress, + commands, +} from "vscode"; +import Settings from "../settings.mjs"; +import Logger from "../logger.mjs"; +import { + getNonce, + getProjectFolderDialogOptions, + getWebviewOptions, + WebviewMessage, +} from "./newProjectPanel.mjs"; +import which from "which"; +import { existsSync } from "fs"; +import { join } from "path"; +import { pyenvInstallPython, setupPyenv } from "../utils/pyenvUtil.mts"; +import { downloadEmbedPython } from "../utils/download.mts"; + +interface SubmitMessageValue { + projectName: string; + pythonMode: number; + pythonPath: string; +} + +export class NewMicroPythonProjectPanel { + public static currentPanel: NewMicroPythonProjectPanel | undefined; + + public static readonly viewType = "newPicoMicroPythonProject"; + + private readonly _panel: WebviewPanel; + private readonly _extensionUri: Uri; + private readonly _settings: Settings; + private readonly _logger: Logger = new Logger("NewProjectPanel"); + private _disposables: Disposable[] = []; + + private _projectRoot?: Uri; + + public static createOrShow(extensionUri: Uri, projectUri?: Uri) { + const column = window.activeTextEditor + ? window.activeTextEditor.viewColumn + : undefined; + + if (NewMicroPythonProjectPanel.currentPanel) { + NewMicroPythonProjectPanel.currentPanel._panel.reveal(column); + // update already exiting panel with new project root + if (projectUri) { + NewMicroPythonProjectPanel.currentPanel._projectRoot = projectUri; + // update webview + void NewMicroPythonProjectPanel.currentPanel._panel.webview.postMessage( + { + command: "changeLocation", + value: projectUri?.fsPath, + } + ); + } + + return; + } + + const panel = window.createWebviewPanel( + NewMicroPythonProjectPanel.viewType, + "New MicroPython Pico Project", + column || ViewColumn.One, + getWebviewOptions(extensionUri) + ); + + const settings = Settings.getInstance(); + if (!settings) { + // TODO: maybe add restart button + void window.showErrorMessage( + "Failed to load settings. Please restart VSCode." + ); + + return; + } + + NewMicroPythonProjectPanel.currentPanel = new NewMicroPythonProjectPanel( + panel, + settings, + extensionUri, + projectUri + ); + } + + public static revive(panel: WebviewPanel, extensionUri: Uri): void { + const settings = Settings.getInstance(); + if (settings === undefined) { + // TODO: maybe add restart button + void window.showErrorMessage( + "Failed to load settings. Please restart VSCode." + ); + + return; + } + + // TODO: reload if it was import panel maybe in state + NewMicroPythonProjectPanel.currentPanel = new NewMicroPythonProjectPanel( + panel, + settings, + extensionUri + ); + } + + private constructor( + panel: WebviewPanel, + settings: Settings, + extensionUri: Uri, + projectUri?: Uri + ) { + this._panel = panel; + this._extensionUri = extensionUri; + this._settings = settings; + + this._projectRoot = projectUri ?? this._settings.getLastProjectRoot(); + + void this._update(); + + this._panel.onDidDispose(() => this.dispose(), null, this._disposables); + + // Update the content based on view changes + this._panel.onDidChangeViewState( + async () => { + if (this._panel.visible) { + await this._update(); + } + }, + null, + this._disposables + ); + + workspace.onDidChangeConfiguration( + async () => { + await this._updateTheme(); + }, + null, + this._disposables + ); + + this._panel.webview.onDidReceiveMessage( + async (message: WebviewMessage) => { + switch (message.command) { + case "changeLocation": + { + const newLoc = await window.showOpenDialog( + getProjectFolderDialogOptions(this._projectRoot, false) + ); + + if (newLoc && newLoc[0]) { + // overwrite preview folderUri + this._projectRoot = newLoc[0]; + await this._settings.setLastProjectRoot(newLoc[0]); + + // update webview + await this._panel.webview.postMessage({ + command: "changeLocation", + value: newLoc[0].fsPath, + }); + } + } + break; + case "cancel": + this.dispose(); + break; + case "error": + void window.showErrorMessage(message.value as string); + break; + case "submit": + { + const data = message.value as SubmitMessageValue; + + if ( + this._projectRoot === undefined || + this._projectRoot.fsPath === "" + ) { + void window.showErrorMessage( + "No project root selected. Please select a project root." + ); + await this._panel.webview.postMessage({ + command: "submitDenied", + }); + + return; + } + + // check if projectRoot/projectName folder already exists + if ( + existsSync(join(this._projectRoot.fsPath, data.projectName)) + ) { + void window.showErrorMessage( + "Project already exists. Please select a different project name or root." + ); + await this._panel.webview.postMessage({ + command: "submitDenied", + }); + + return; + } + + // close panel before generating project + this.dispose(); + + await window.withProgress( + { + location: ProgressLocation.Notification, + title: `Generating MicroPico project ${ + data.projectName ?? "undefined" + } in ${this._projectRoot?.fsPath}...`, + }, + async progress => + this._generateProjectOperation(progress, data, message) + ); + } + break; + } + }, + null, + this._disposables + ); + + if (projectUri !== undefined) { + // update webview + void this._panel.webview.postMessage({ + command: "changeLocation", + value: projectUri.fsPath, + }); + } + } + + private async _generateProjectOperation( + progress: Progress<{ message?: string; increment?: number }>, + data: SubmitMessageValue, + message: WebviewMessage + ): Promise { + const projectPath = this._projectRoot?.fsPath ?? ""; + + if ( + typeof message.value !== "object" || + message.value === null || + projectPath.length === 0 + ) { + void window.showErrorMessage( + "Failed to generate MicroPython project. Please try again and check your settings." + ); + + return; + } + + // install python (if necessary) + let python3Path: string | undefined; + if (process.platform === "darwin" || process.platform === "win32") { + switch (data.pythonMode) { + // TODO: add a simpler option, maybe to select via python extension api + case 1: + python3Path = process.platform === "win32" ? "python" : "python3"; + break; + case 2: + python3Path = data.pythonPath; + break; + } + + if (python3Path === undefined) { + progress.report({ + message: "Failed", + increment: 100, + }); + await window.showErrorMessage("Failed to find python3 executable."); + + return; + } + } else { + python3Path = "python3"; + } + + // create the folder with project name in project root + // open the folder in vscode and call micropico.initialise + + // create the project folder + const projectFolder = join(projectPath, data.projectName); + progress.report({ + message: `Creating project folder ${projectFolder}`, + increment: 10, + }); + + try { + await workspace.fs.createDirectory(Uri.file(projectFolder)); + // also create a blink.py in it with a import machine + const blinkPyCode = `# Only works on Raspberry Pi Pico not the W models +from machine import Pin +from utime import sleep + +pin = Pin("LED", Pin.OUT) + +print("LED starts flashing...") +while True: + try: + pin.toggle() + sleep(1) # sleep 1sec + except KeyboardInterrupt: + break +pin.off() +print("Finished.")`; + const filePath = join(projectFolder, "blink.py"); + await workspace.fs.writeFile( + Uri.file(filePath), + new TextEncoder().encode(blinkPyCode) + ); + } catch (err) { + progress.report({ + message: "Failed", + increment: 100, + }); + await window.showErrorMessage( + `Failed to create project folder ${projectFolder}` + ); + + return; + } + + commands.executeCommand("micropico.initialise", python3Path, projectFolder); + progress.report({ + message: "Project initialized", + increment: 90, + }); + + // wait 2 seconds to give user option to read notifications + await new Promise(resolve => setTimeout(resolve, 2000)); + + // open and call initialise + commands.executeCommand("vscode.openFolder", Uri.file(projectFolder), { + forceReuseWindow: true, + }); + } + + private async _update(): Promise { + this._panel.title = "New MicroPython Pico Project"; + + this._panel.iconPath = Uri.joinPath( + this._extensionUri, + "web", + "raspberry-128.png" + ); + const html = await this._getHtmlForWebview(this._panel.webview); + + if (html !== "") { + this._panel.webview.html = html; + await this._updateTheme(); + } else { + void window.showErrorMessage( + "Failed to load webview for new MicroPython project" + ); + this.dispose(); + } + } + + private async _updateTheme(): Promise { + await this._panel.webview.postMessage({ + command: "setTheme", + theme: + window.activeColorTheme.kind === ColorThemeKind.Dark || + window.activeColorTheme.kind === ColorThemeKind.HighContrast + ? "dark" + : "light", + }); + } + + public dispose(): void { + NewMicroPythonProjectPanel.currentPanel = undefined; + + this._panel.dispose(); + + while (this._disposables.length) { + const x = this._disposables.pop(); + + if (x) { + x.dispose(); + } + } + } + + private async _getHtmlForWebview(webview: Webview): Promise { + const mainScriptUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "mpy", "main.js") + ); + + const mainStyleUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "main.css") + ); + + const tailwindcssScriptUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "tailwindcss-3_3_5.js") + ); + + // images + const navHeaderSvgUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header.svg") + ); + + const navHeaderDarkSvgUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header-dark.svg") + ); + + // TODO: maybe use python extension api + // TODO: check python version, workaround, ownly allow python3 commands on unix + const isPythonSystemAvailable = + (await which("python3", { nothrow: true })) !== null || + (await which("python", { nothrow: true })) !== null; + + // Restrict the webview to only load specific scripts + const nonce = getNonce(); + + return ` + + + + + + + + + + New Pico MicroPython Project + + + + + +
+
+ + +
+
+

Basic Settings

+
+
+
+ +
+
+ + +
+
+ + +
+ +
+ ${ + process.platform === "darwin" || process.platform === "win32" + ? ` +
+ + + ${ + isPythonSystemAvailable + ? `
+ + +
` + : "" + } + +
+ + + +
+
` + : "" + } +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ + +
+
+ + + + `; + } +} diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 85d9bb82..d07d02d9 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -119,7 +119,7 @@ interface SubmitMessageValue extends ImportProjectMessageValue { cppExceptions: boolean; } -interface WebviewMessage { +export interface WebviewMessage { command: string; value: object | string | SubmitMessageValue; } @@ -736,7 +736,7 @@ export class NewProjectPanel { // update webview void this._panel.webview.postMessage({ command: "changeLocation", - value: projectUri?.fsPath, + value: projectUri.fsPath, }); } } diff --git a/web/main.js b/web/main.js index 3d0d4699..ae9d334c 100644 --- a/web/main.js +++ b/web/main.js @@ -210,7 +210,7 @@ var exampleSupportedBoards = []; } } - // selected cmake version + // selected python version const pythonVersionRadio = document.getElementsByName('python-version-radio'); let pythonMode = null; let pythonPath = null; @@ -225,7 +225,7 @@ var exampleSupportedBoards = []; pythonMode = 1; } - // if cmake version is null or not a number, smaller than 0 or bigger than 3, set it to 0 + // if python version is null or not a number, smaller than 0 or bigger than 3, set it to 0 if (pythonMode === null || isNaN(pythonMode) || pythonMode < 0 || pythonMode > 3) { // TODO: first check if defaul is supported pythonMode = 0; diff --git a/web/mpy/main.js b/web/mpy/main.js new file mode 100644 index 00000000..2262119e --- /dev/null +++ b/web/mpy/main.js @@ -0,0 +1,181 @@ +"use strict"; + +const CMD_CHANGE_LOCATION = 'changeLocation'; +const CMD_SUBMIT = 'submit'; +const CMD_CANCEL = 'cancel'; +const CMD_SET_THEME = 'setTheme'; +const CMD_ERROR = 'error'; +const CMD_SUBMIT_DENIED = 'submitDenied'; + +var submitted = false; + +(function () { + const vscode = acquireVsCodeApi(); + + // needed so a element isn't hidden behind the navbar on scroll + const navbarOffsetHeight = document.getElementById('top-navbar').offsetHeight; + + // returns true if project name input is valid + function projectNameFormValidation(projectNameElement) { + if (typeof examples !== 'undefined') { + return true; + } + + const projectNameError = document.getElementById('inp-project-name-error'); + const projectName = projectNameElement.value; + + var invalidChars = /[\/:*?"<>| ]/; + // check for reserved names in Windows + var reservedNames = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])$/i; + if (projectName.trim().length == 0 || invalidChars.test(projectName) || reservedNames.test(projectName)) { + projectNameError.hidden = false; + //projectNameElement.scrollIntoView({ behavior: "smooth" }); + window.scrollTo({ + top: projectNameElement.offsetTop - navbarOffsetHeight, + behavior: 'smooth' + }); + + return false; + } + + projectNameError.hidden = true; + return true; + } + + window.changeLocation = () => { + // Send a message back to the extension + vscode.postMessage({ + command: CMD_CHANGE_LOCATION, + value: null + }); + } + + window.cancelBtnClick = () => { + // close webview + vscode.postMessage({ + command: CMD_CANCEL, + value: null + }); + } + + window.submitBtnClick = () => { + /* Catch silly users who spam the submit button */ + if (submitted) { + console.error("already submitted"); + return; + } + submitted = true; + + // get all values of inputs + const projectNameElement = document.getElementById('inp-project-name'); + // if is project import then the project name element will not be rendered and does not exist in the DOM + const projectName = projectNameElement.value; + if (projectName !== undefined && !projectNameFormValidation(projectNameElement)) { + submitted = false; + return; + } + + // selected python version + const pythonVersionRadio = document.getElementsByName('python-version-radio'); + let pythonMode = null; + let pythonPath = null; + for (let i = 0; i < pythonVersionRadio.length; i++) { + if (pythonVersionRadio[i].checked) { + pythonMode = pythonVersionRadio[i].value; + break; + } + } + if (pythonVersionRadio.length == 0) { + // default to python mode 1 == System version + pythonMode = 1; + } + + // if python version is null or not a number, smaller than 0 or bigger than 3, set it to 0 + if (pythonMode === null || isNaN(pythonMode) || pythonMode < 0 || pythonMode > 3) { + // TODO: first check if defaul is supported + pythonMode = 0; + console.debug('Invalid python version value: ' + pythonMode.toString()); + vscode.postMessage({ + command: CMD_ERROR, + value: "Please select a valid python version." + }); + submitted = false; + + return; + } + if (pythonMode == 2) { + const files = document.getElementById('python-path-executable').files; + + if (files.length == 1) { + cmakePath = files[0].name; + } else { + console.debug("Please select a valid python executable file"); + vscode.postMessage({ + command: CMD_ERROR, + value: "Please select a valid python executable file." + }); + submitted = false; + + return; + } + } + + //post all data values to the extension + vscode.postMessage({ + command: CMD_SUBMIT, + value: { + projectName: projectName, + pythonMode: Number(pythonMode), + pythonPath: pythonPath + } + }); + } + + function _onMessage(event) { + // JSON data sent from the extension + const message = event.data; + + switch (message.command) { + case CMD_CHANGE_LOCATION: + // update UI + document.getElementById('inp-project-location').value = message.value; + break; + case CMD_SET_THEME: + console.log("set theme", message.theme); + // update UI + if (message.theme == "dark") { + // explicitly choose dark mode + localStorage.theme = 'dark' + document.body.classList.add('dark') + } else if (message.theme == "light") { + document.body.classList.remove('dark') + // explicitly choose light mode + localStorage.theme = 'light' + } + break; + case CMD_SUBMIT_DENIED: + submitted = false; + break; + default: + console.error('Unknown command: ' + message.command); + break; + } + } + + window.addEventListener("message", _onMessage); + + // add onclick event handlers to avoid inline handlers + document.getElementById('btn-change-project-location').addEventListener('click', changeLocation); + document.getElementById('btn-cancel').addEventListener('click', cancelBtnClick); + document.getElementById('btn-create').addEventListener('click', submitBtnClick); + + document.getElementById('inp-project-name').addEventListener('input', function () { + const projName = document.getElementById('inp-project-name').value; + console.log(`${projName} is now`); + // TODO: future examples stuff (maybe) + }); + + const pythonVersionRadio = document.getElementsByName('python-version-radio'); + if (pythonVersionRadio.length > 0) + pythonVersionRadio[0].checked = true; +}()); From 0cb3747618ebfe01fa10608468df420c3662ce32 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:07:05 +0100 Subject: [PATCH 03/13] Fix imports Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/commands/newProject.mts | 1 + src/webview/newMicroPythonProjectPanel.mts | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 66b58fde..32d1a175 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -2,6 +2,7 @@ import { Command } from "./command.mjs"; import Logger from "../logger.mjs"; import { window, type Uri } from "vscode"; import { NewProjectPanel } from "../webview/newProjectPanel.mjs"; +// eslint-disable-next-line max-len import { NewMicroPythonProjectPanel } from "../webview/newMicroPythonProjectPanel.mjs"; /** diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 2811eb7b..e9b77280 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -22,8 +22,6 @@ import { import which from "which"; import { existsSync } from "fs"; import { join } from "path"; -import { pyenvInstallPython, setupPyenv } from "../utils/pyenvUtil.mts"; -import { downloadEmbedPython } from "../utils/download.mts"; interface SubmitMessageValue { projectName: string; @@ -307,7 +305,7 @@ while True: except KeyboardInterrupt: break pin.off() -print("Finished.")`; +print("Finished.")\r\n`; const filePath = join(projectFolder, "blink.py"); await workspace.fs.writeFile( Uri.file(filePath), From 42d3dcc5bd07d630139c955ab5f7885eccc90285 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:03:08 +0100 Subject: [PATCH 04/13] Fixed linting issues Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/extension.mts | 1 + src/webview/newMicroPythonProjectPanel.mts | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/extension.mts b/src/extension.mts index edaf0528..aa9a95d5 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -74,6 +74,7 @@ import NewExampleProjectCommand from "./commands/newExampleProject.mjs"; import SwitchBoardCommand from "./commands/switchBoard.mjs"; import UninstallPicoSDKCommand from "./commands/uninstallPicoSDK.mjs"; import FlashProjectSWDCommand from "./commands/flashProjectSwd.mjs"; +// eslint-disable-next-line max-len import { NewMicroPythonProjectPanel } from "./webview/newMicroPythonProjectPanel.mjs"; export async function activate(context: ExtensionContext): Promise { diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index e9b77280..77a1fa64 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -1,3 +1,5 @@ +/* eslint-disable max-len */ +import type { Webview, Progress } from "vscode"; import { Uri, ViewColumn, @@ -5,19 +7,17 @@ import { type WebviewPanel, type Disposable, ColorThemeKind, - Webview, workspace, ProgressLocation, - Progress, commands, } from "vscode"; import Settings from "../settings.mjs"; import Logger from "../logger.mjs"; +import type { WebviewMessage } from "./newProjectPanel.mjs"; import { getNonce, getProjectFolderDialogOptions, getWebviewOptions, - WebviewMessage, } from "./newProjectPanel.mjs"; import which from "which"; import { existsSync } from "fs"; @@ -42,7 +42,7 @@ export class NewMicroPythonProjectPanel { private _projectRoot?: Uri; - public static createOrShow(extensionUri: Uri, projectUri?: Uri) { + public static createOrShow(extensionUri: Uri, projectUri?: Uri): void { const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined; @@ -194,7 +194,8 @@ export class NewMicroPythonProjectPanel { existsSync(join(this._projectRoot.fsPath, data.projectName)) ) { void window.showErrorMessage( - "Project already exists. Please select a different project name or root." + "Project already exists. " + + "Please select a different project name or root." ); await this._panel.webview.postMessage({ command: "submitDenied", @@ -246,7 +247,8 @@ export class NewMicroPythonProjectPanel { projectPath.length === 0 ) { void window.showErrorMessage( - "Failed to generate MicroPython project. Please try again and check your settings." + "Failed to generate MicroPython project. " + + "Please try again and check your settings." ); return; @@ -311,7 +313,7 @@ print("Finished.")\r\n`; Uri.file(filePath), new TextEncoder().encode(blinkPyCode) ); - } catch (err) { + } catch { progress.report({ message: "Failed", increment: 100, From 93a1221bf7cbfa4350f3f49af21ea36aa4224cf4 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:15:47 +0100 Subject: [PATCH 05/13] Remove old warning that isn't applicable anymore Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newMicroPythonProjectPanel.mts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 77a1fa64..e3126dc5 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -293,8 +293,7 @@ export class NewMicroPythonProjectPanel { try { await workspace.fs.createDirectory(Uri.file(projectFolder)); // also create a blink.py in it with a import machine - const blinkPyCode = `# Only works on Raspberry Pi Pico not the W models -from machine import Pin + const blinkPyCode = `from machine import Pin from utime import sleep pin = Pin("LED", Pin.OUT) From 47bd24876644bec81206b4dbba0ae0192bd97dd0 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:46:26 +0100 Subject: [PATCH 06/13] Fix default project location loading Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newMicroPythonProjectPanel.mts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index e3126dc5..2bc6c1e9 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -557,8 +557,10 @@ print("Finished.")\r\n`; : "/home/user/MyProject" }" disabled value="${ // support folder names with backslashes on linux and macOS - this._projectRoot !== undefined && process.platform === "win32" - ? this._projectRoot.fsPath.replaceAll("\\", "/") + this._projectRoot !== undefined + ? process.platform === "win32" + ? this._projectRoot.fsPath.replaceAll("\\", "/") + : this._projectRoot.fsPath : "" }"/> From 0aeccea354a3c253b6f747e6aaf736bfd695c23d Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:13:38 +0100 Subject: [PATCH 07/13] Update README adding MicroPython, some styling and some refactoring Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- README.md | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 60868c3c..488b0d6d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Raspberry Pi Pico Visual Studio Code extension -> NOTE: The extension is currently under development. +> **Note: The extension is currently under development.** This is the official Visual Studio Code extension for Raspberry Pi Pico development. This extension equips you with a suite of tools designed to streamline your Pico projects using Visual Studio Code and the official [Pico SDK](https://github.com/raspberrypi/pico-sdk). @@ -10,18 +10,33 @@ For comprehensive setup instructions, refer to the [Getting Started guide](https ## Features -- Project Generator: Easily create new projects targeting the Ninja build system. -- Automatic CMake Configuration: Automatically configures CMake when loading a project. -- Version Switching: Seamlessly switch between different versions of the Pico SDK and tools. -- No Manual Setup Required: The extension handles environment variables, toolchain, SDK, and tool management for you. -- One-Click Compilation: Compile projects directly from the status bar with your selected SDK and tools. -- Offline Documentation: Access Pico SDK documentation offline. -- Quick Project Setup: Quickly create new Pico projects from the Explorer view when no workspace is open. -- Includes an Uninstaller: Easily remove the extension along with all automatically installed tools and SDKs. +### Project Setup and Management + +- **Project Generator**: Easily create and configure new projects with support for advanced Pico features like I2C and PIO. The generator targets the Ninja build system and allows customization during project creation. +- **Quick Project Setup**: Initiate new Pico projects directly from the Explorer view, when no workspace is open. +- **MicroPython Support**: Create and develop MicroPython-based Pico projects with support provided through the [MicroPico](https://github.com/paulober/MicroPico) extension. + +### Configuration and Tool Management + +- **Automatic CMake Configuration**: Automatically configures CMake when loading a project. +- **Version Switching**: Seamlessly switch between different versions of the Pico SDK and tools. +- **No Manual Setup Required**: Automatically handles environment variables, toolchain, SDK, and tool management. + +### Build, Debug, and Documentation + +- **One-Click Compilation and Debugging**: Automatically configure OpenOCD, Ninja, and CMake, allowing you to compile and debug with a single click. +- **Offline Documentation**: Conveniently access Pico SDK documentation directly within the editor, even when offline. + +- **Version Switching**: Seamlessly switch between different versions of the Pico SDK and tools. +- **No Manual Setup Required**: The extension handles environment variables, toolchain, SDK, and tool management for you. +- **One-Click Compilation**: Compile projects directly from the status bar with your selected SDK and tools. +- **Offline Documentation**: Access Pico SDK documentation offline. +- **Quick Project Setup**: Quickly create new Pico projects from the Explorer view when no workspace is open. +- **MicroPython Support**: Create MicroPython-based Pico projects with support provided through the MicroPico extension. ## Requirements by OS -> Supported Platforms: Raspberry Pi OS (64-bit), Windows 10/11 (x86_64), macOS Sonoma (14.0) and newer, Linux x64 and arm64 +> **Supported Platforms: Raspberry Pi OS (64-bit), Windows 10/11 (x86_64), macOS Sonoma (14.0) and newer, Linux x64 and arm64** - Visual Studio Code v1.87.0 or later @@ -38,19 +53,19 @@ To meet the requirements for macOS, run the following command in Terminal to ins xcode-select --install ``` This command installs all of the necessary tools, including but not limited to: -- Git 2.28 or later (ensure it's in your PATH) -- Tar (ensure it's in your PATH) +- **Git 2.28 or later** (ensure it's in your PATH) +- **Tar** (ensure it's in your PATH) ### Windows No additional requirements are needed for Windows. ### Linux -- Python 3.9 or later (ensure it’s in your PATH or set in settings) -- Git 2.28 or later (ensure it’s in your PATH) -- Tar (ensure it’s in your PATH) -- \[Optional\] gdb-multiarch for debugging (x86_64 only) -- For \[Ubuntu 22.04\], install `libftdi1-2` and `libhidapi-hidraw0` packages to use OpenOCD +- **Python 3.9 or later** (ensure it’s in your PATH or set in settings) +- **Git 2.28 or later** (ensure it’s in your PATH) +- **Tar** (ensure it’s in your PATH) +- **\[Optional\]** gdb-multiarch for debugging (x86_64 only) +- For **\[Ubuntu 22.04\]**, install `libftdi1-2` and `libhidapi-hidraw0` packages to use OpenOCD ## Extension Settings From 38172ead721bce88d243e821fe84b25f93074677 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:47:21 +0100 Subject: [PATCH 08/13] Implement python extension version selection + added python ext as dependency as MicroPico already has it so this doesn't make a difference Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- package.json | 7 +- src/utils/githubREST.mts | 6 +- src/webview/newMicroPythonProjectPanel.mts | 85 +++++++++++++++------- web/mpy/main.js | 12 +-- yarn.lock | 8 ++ 5 files changed, 82 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 86957ecc..9b4a28ef 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "ms-vscode.cpptools-extension-pack", "ms-vscode.vscode-serial-monitor", "marus25.cortex-debug", - "paulober.pico-w-go" + "paulober.pico-w-go", + "ms-python.python" ], "main": "./dist/extension.cjs", "markdown": "github", @@ -64,7 +65,8 @@ }, "activationEvents": [ "workspaceContains:./pico_sdk_import.cmake", - "onWebviewPanel:newPicoProject" + "onWebviewPanel:newPicoProject", + "onWebviewPanel:newPicoMicroPythonProject" ], "contributes": { "commands": [ @@ -293,6 +295,7 @@ "typescript-eslint": "^8.1.0" }, "dependencies": { + "@vscode/python-extension": "^1.0.5", "adm-zip": "^0.5.14 <0.5.15", "got": "^14.4.2", "ini": "^4.1.3", diff --git a/src/utils/githubREST.mts b/src/utils/githubREST.mts index 1b628dba..bc86c6f7 100644 --- a/src/utils/githubREST.mts +++ b/src/utils/githubREST.mts @@ -61,15 +61,13 @@ export const PYENV_REPOSITORY_URL = "https://github.com/pyenv/pyenv.git"; export function ownerOfRepository(repository: GithubRepository): string { switch (repository) { case GithubRepository.picoSDK: + case GithubRepository.tools: + case GithubRepository.picotool: return "raspberrypi"; case GithubRepository.cmake: return "Kitware"; case GithubRepository.ninja: return "ninja-build"; - case GithubRepository.tools: - return "raspberrypi"; - case GithubRepository.picotool: - return "raspberrypi"; } } diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 2bc6c1e9..4a582f91 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -22,6 +22,7 @@ import { import which from "which"; import { existsSync } from "fs"; import { join } from "path"; +import { PythonExtension } from "@vscode/python-extension"; interface SubmitMessageValue { projectName: string; @@ -37,10 +38,11 @@ export class NewMicroPythonProjectPanel { private readonly _panel: WebviewPanel; private readonly _extensionUri: Uri; private readonly _settings: Settings; - private readonly _logger: Logger = new Logger("NewProjectPanel"); + private readonly _logger: Logger = new Logger("NewMicroPythonProjectPanel"); private _disposables: Disposable[] = []; private _projectRoot?: Uri; + private _pythonExtensionApi?: PythonExtension; public static createOrShow(extensionUri: Uri, projectUri?: Uri): void { const column = window.activeTextEditor @@ -347,6 +349,9 @@ print("Finished.")\r\n`; "web", "raspberry-128.png" ); + if (!this._pythonExtensionApi) { + this._pythonExtensionApi = await PythonExtension.api(); + } const html = await this._getHtmlForWebview(this._panel.webview); if (html !== "") { @@ -407,8 +412,13 @@ print("Finished.")\r\n`; Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header-dark.svg") ); - // TODO: maybe use python extension api - // TODO: check python version, workaround, ownly allow python3 commands on unix + // TODO: add support for onDidChangeActiveEnvironment and filter envs that don't directly point + // to an executable + const environments = this._pythonExtensionApi?.environments; + const knownEnvironments = environments?.known; + const activeEnv = environments?.getActiveEnvironmentPath(); + + // TODO: check python version, workaround, only allow python3 commands on unix const isPythonSystemAvailable = (await which("python3", { nothrow: true })) !== null || (await which("python", { nothrow: true })) !== null; @@ -484,29 +494,54 @@ print("Finished.")\r\n`;
- ${ - process.platform === "darwin" || process.platform === "win32" - ? ` -
- - - ${ - isPythonSystemAvailable - ? `
- - -
` - : "" - } - -
- - - -
+
+ + + ${ + knownEnvironments && knownEnvironments.length > 0 + ? ` +
+ + + + +
+ ` + : "" + } + + ${ + process.platform === "darwin" || + process.platform === "win32" + ? ` + ${ + isPythonSystemAvailable + ? `
+ +
` - : "" - } + : "" + } + +
+ + + +
` + : "" + } +
diff --git a/web/mpy/main.js b/web/mpy/main.js index 2262119e..2acf3f0c 100644 --- a/web/mpy/main.js +++ b/web/mpy/main.js @@ -86,13 +86,12 @@ var submitted = false; } } if (pythonVersionRadio.length == 0) { - // default to python mode 1 == System version - pythonMode = 1; + // default to python mode 0 == python ext version + pythonMode = 0; } // if python version is null or not a number, smaller than 0 or bigger than 3, set it to 0 if (pythonMode === null || isNaN(pythonMode) || pythonMode < 0 || pythonMode > 3) { - // TODO: first check if defaul is supported pythonMode = 0; console.debug('Invalid python version value: ' + pythonMode.toString()); vscode.postMessage({ @@ -103,11 +102,14 @@ var submitted = false; return; } - if (pythonMode == 2) { + if (pythonMode === 0) { + const pyenvKnownSel = document.getElementById("sel-pyenv-known"); + pythonPath = pyenvKnownSel.value; + } else if (pythonMode === 2) { const files = document.getElementById('python-path-executable').files; if (files.length == 1) { - cmakePath = files[0].name; + pythonPath = files[0].name; } else { console.debug("Please select a valid python executable file"); vscode.postMessage({ diff --git a/yarn.lock b/yarn.lock index dfa657fd..1679c7e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -653,6 +653,13 @@ __metadata: languageName: node linkType: hard +"@vscode/python-extension@npm:^1.0.5": + version: 1.0.5 + resolution: "@vscode/python-extension@npm:1.0.5" + checksum: 10/aeaa1ee9275f8679aa745c3096a68e1952328b8c7de90e1665f369e321dafb3e1271d42c32e4f8d6a3c92f6cdbb4aaef22cc45101764a623900efc4f3dfd3270 + languageName: node + linkType: hard + "abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -2452,6 +2459,7 @@ __metadata: "@types/uuid": "npm:^10.0.0" "@types/vscode": "npm:^1.92.0" "@types/which": "npm:^3.0.4" + "@vscode/python-extension": "npm:^1.0.5" adm-zip: "npm:^0.5.14 <0.5.15" eslint: "npm:^9.9.0" eslint-config-prettier: "npm:^9.1.0" From a10b3635d8872e934b1ef89b2edb82848a5b68b6 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:15:49 +0100 Subject: [PATCH 09/13] Fix using wrong interface Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/commands/newProject.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 32d1a175..45c01a74 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -1,4 +1,4 @@ -import { Command } from "./command.mjs"; +import { CommandWithArgs } from "./command.mjs"; import Logger from "../logger.mjs"; import { window, type Uri } from "vscode"; import { NewProjectPanel } from "../webview/newProjectPanel.mjs"; @@ -15,7 +15,7 @@ export enum ProjectLang { micropython = 2, } -export default class NewProjectCommand extends Command { +export default class NewProjectCommand extends CommandWithArgs { private readonly _logger: Logger = new Logger("NewProjectCommand"); private readonly _extensionUri: Uri; private static readonly micropythonOption = "MicroPython"; From b79edee848c1a45d61fdcc664277a5a3b8006a15 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:44:35 +0100 Subject: [PATCH 10/13] Added empty project name warning for MicroPython projects Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newMicroPythonProjectPanel.mts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 4a582f91..3678d5da 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -191,6 +191,20 @@ export class NewMicroPythonProjectPanel { return; } + if ( + data.projectName === undefined || + data.projectName.length === 0 + ) { + void window.showWarningMessage( + "The project name is empty. Please enter a project name." + ); + await this._panel.webview.postMessage({ + command: "submitDenied", + }); + + return; + } + // check if projectRoot/projectName folder already exists if ( existsSync(join(this._projectRoot.fsPath, data.projectName)) From fc26a5c21e8b1d879583be8edf87b720d17a10cd Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:20:35 +0100 Subject: [PATCH 11/13] Support latest MicroPico changes Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- README.md | 1 + src/webview/newMicroPythonProjectPanel.mts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 488b0d6d..d94718d3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ For comprehensive setup instructions, refer to the [Getting Started guide](https - **Automatic CMake Configuration**: Automatically configures CMake when loading a project. - **Version Switching**: Seamlessly switch between different versions of the Pico SDK and tools. - **No Manual Setup Required**: Automatically handles environment variables, toolchain, SDK, and tool management. +- **Includes an Uninstaller**: Easily remove the extension along with all automatically installed tools and SDKs. ### Build, Debug, and Documentation diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 3678d5da..25c48655 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -340,7 +340,7 @@ print("Finished.")\r\n`; return; } - commands.executeCommand("micropico.initialise", python3Path, projectFolder); + commands.executeCommand("micropico.initialise", projectFolder, python3Path); progress.report({ message: "Project initialized", increment: 90, From 9fd8600e3f350e223b9f873622c6735175f03015 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:03:25 +0100 Subject: [PATCH 12/13] Fix panels not disposed if loading of settings failed Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newMicroPythonProjectPanel.mts | 2 ++ src/webview/newProjectPanel.mts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 25c48655..3e13f77f 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -75,6 +75,8 @@ export class NewMicroPythonProjectPanel { const settings = Settings.getInstance(); if (!settings) { + panel.dispose(); + // TODO: maybe add restart button void window.showErrorMessage( "Failed to load settings. Please restart VSCode." diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index d07d02d9..c6702710 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -382,6 +382,8 @@ export class NewProjectPanel { const settings = Settings.getInstance(); if (settings === undefined) { + panel.dispose(); + // TODO: maybe add restart button void window.showErrorMessage( "Failed to load settings. Please restart VSCode." From 36d972e1c6227ffd8aaad347ee3b8dc37dec425f Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:21:20 +0100 Subject: [PATCH 13/13] Fix python selection Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newMicroPythonProjectPanel.mts | 4 +++- web/mpy/main.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webview/newMicroPythonProjectPanel.mts b/src/webview/newMicroPythonProjectPanel.mts index 3e13f77f..1c296b56 100644 --- a/src/webview/newMicroPythonProjectPanel.mts +++ b/src/webview/newMicroPythonProjectPanel.mts @@ -276,7 +276,9 @@ export class NewMicroPythonProjectPanel { let python3Path: string | undefined; if (process.platform === "darwin" || process.platform === "win32") { switch (data.pythonMode) { - // TODO: add a simpler option, maybe to select via python extension api + case 0: + python3Path = data.pythonPath; + break; case 1: python3Path = process.platform === "win32" ? "python" : "python3"; break; diff --git a/web/mpy/main.js b/web/mpy/main.js index 2acf3f0c..56331315 100644 --- a/web/mpy/main.js +++ b/web/mpy/main.js @@ -81,7 +81,7 @@ var submitted = false; let pythonPath = null; for (let i = 0; i < pythonVersionRadio.length; i++) { if (pythonVersionRadio[i].checked) { - pythonMode = pythonVersionRadio[i].value; + pythonMode = Number(pythonVersionRadio[i].value); break; } }