13 type CommandWithResult,
14 } from "./commands/command.mjs";
15 import NewProjectCommand from "./commands/newProject.mjs";
16 import Logger from "./logger.mjs";
18 cmakeGetSelectedBoard,
19 cmakeGetSelectedToolchainAndSDKVersions,
21 } from "./utils/cmakeUtil.mjs";
26 } from "./settings.mjs";
27 import UI from "./ui.mjs";
28 import SwitchSDKCommand from "./commands/switchSDK.mjs";
29 import { existsSync, readFileSync } from "fs";
30 import { basename, join } from "path";
31 import CompileProjectCommand from "./commands/compileProject.mjs";
32 import RunProjectCommand from "./commands/runProject.mjs";
33 import LaunchTargetPathCommand from "./commands/launchTargetPath.mjs";
40 GetChipUppercaseCommand,
41 } from "./commands/getPaths.mjs";
43 downloadAndInstallCmake,
44 downloadAndInstallNinja,
45 downloadAndInstallSDK,
46 downloadAndInstallToolchain,
47 downloadAndInstallTools,
48 downloadAndInstallPicotool,
49 downloadAndInstallOpenOCD,
51 } from "./utils/download.mjs";
52 import { SDK_REPOSITORY_URL } from "./utils/githubREST.mjs";
53 import { getSupportedToolchains } from "./utils/toolchainUtil.mjs";
59 } from "./webview/newProjectPanel.mjs";
60 import GithubApiCache from "./utils/githubApiCache.mjs";
61 import ClearGithubApiCacheCommand from "./commands/clearGithubApiCache.mjs";
62 import { ContextKeys } from "./contextKeys.mjs";
63 import { PicoProjectActivityBar } from "./webview/activityBar.mjs";
64 import ConditionalDebuggingCommand from "./commands/conditionalDebugging.mjs";
65 import DebugLayoutCommand from "./commands/debugLayout.mjs";
66 import OpenSdkDocumentationCommand from "./commands/openSdkDocumentation.mjs";
67 import ConfigureCmakeCommand from "./commands/configureCmake.mjs";
68 import ImportProjectCommand from "./commands/importProject.mjs";
69 import { homedir } from "os";
70 import VersionBundlesLoader from "./utils/versionBundles.mjs";
71 import { pyenvInstallPython, setupPyenv } from "./utils/pyenvUtil.mjs";
72 import NewExampleProjectCommand from "./commands/newExampleProject.mjs";
73 import SwitchBoardCommand from "./commands/switchBoard.mjs";
75 export const CMAKE_DO_NOT_EDIT_HEADER_PREFIX =
76 // eslint-disable-next-line max-len
77 "== DO NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work ==";
79 export async function activate(context: ExtensionContext): Promise<void> {
80 Logger.log("Extension activated.");
82 const settings = Settings.createInstance(
83 context.workspaceState,
85 context.extension.packageJSON as PackageJSON
87 GithubApiCache.createInstance(context);
89 const picoProjectActivityBarProvider = new PicoProjectActivityBar();
90 const ui = new UI(picoProjectActivityBarProvider);
93 const COMMANDS: Array<
95 | CommandWithResult<string>
96 | CommandWithResult<boolean>
99 new NewProjectCommand(context.extensionUri),
100 new SwitchSDKCommand(ui, context.extensionUri),
101 new SwitchBoardCommand(ui),
102 new LaunchTargetPathCommand(),
103 new GetPythonPathCommand(),
104 new GetEnvPathCommand(),
105 new GetGDBPathCommand(),
106 new GetChipCommand(),
107 new GetChipUppercaseCommand(),
108 new GetTargetCommand(),
109 new CompileProjectCommand(),
110 new RunProjectCommand(),
111 new ClearGithubApiCacheCommand(),
112 new ConditionalDebuggingCommand(),
113 new DebugLayoutCommand(),
114 new OpenSdkDocumentationCommand(context.extensionUri),
115 new ConfigureCmakeCommand(),
116 new ImportProjectCommand(context.extensionUri),
117 new NewExampleProjectCommand(context.extensionUri),
120 // register all command handlers
121 COMMANDS.forEach(command => {
122 context.subscriptions.push(command.register());
125 context.subscriptions.push(
126 window.registerWebviewPanelSerializer(NewProjectPanel.viewType, {
127 // eslint-disable-next-line @typescript-eslint/require-await
128 async deserializeWebviewPanel(webviewPanel: WebviewPanel): Promise<void> {
129 // Reset the webview options so we use latest uri for `localResourceRoots`.
130 webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
131 NewProjectPanel.revive(webviewPanel, context.extensionUri, false);
136 context.subscriptions.push(
137 window.registerTreeDataProvider(
138 PicoProjectActivityBar.viewType,
139 picoProjectActivityBarProvider
143 const workspaceFolder = workspace.workspaceFolders?.[0];
145 // check if is a pico project
147 workspaceFolder === undefined ||
148 !existsSync(join(workspaceFolder.uri.fsPath, "pico_sdk_import.cmake"))
151 Logger.log("No workspace folder or pico project found.");
152 await commands.executeCommand(
154 ContextKeys.isPicoProject,
161 const cmakeListsFilePath = join(workspaceFolder.uri.fsPath, "CMakeLists.txt");
162 if (!existsSync(cmakeListsFilePath)) {
163 Logger.log("No CMakeLists.txt found in workspace folder.");
164 await commands.executeCommand(
166 ContextKeys.isPicoProject,
173 // check if it has .vscode folder and cmake donotedit header in CMakelists.txt
175 !existsSync(join(workspaceFolder.uri.fsPath, ".vscode")) ||
176 !readFileSync(cmakeListsFilePath)
178 .includes(CMAKE_DO_NOT_EDIT_HEADER_PREFIX)
181 "No .vscode folder and/or cmake " +
182 '"DO NOT EDIT"-header in CMakelists.txt found.'
184 await commands.executeCommand(
186 ContextKeys.isPicoProject,
189 const wantToImport = await window.showInformationMessage(
190 "Do you want to import this project as Raspberry Pi Pico project?",
194 if (wantToImport === "Yes") {
195 void commands.executeCommand(
196 `${extensionName}.${ImportProjectCommand.id}`,
204 await commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
206 // get sdk selected in the project
207 const selectedToolchainAndSDKVersions =
208 await cmakeGetSelectedToolchainAndSDKVersions(
211 if (selectedToolchainAndSDKVersions === null) {
215 // get all available toolchains for download link of current selected one
216 const toolchains = await getSupportedToolchains();
217 const selectedToolchain = toolchains.find(
218 toolchain => toolchain.version === selectedToolchainAndSDKVersions[1]
223 !(await downloadAndInstallSDK(
224 selectedToolchainAndSDKVersions[0],
227 !(await downloadAndInstallTools(selectedToolchainAndSDKVersions[0])) ||
228 !(await downloadAndInstallPicotool(picotoolVersion))
231 "Failed to install project SDK " +
232 `version: ${selectedToolchainAndSDKVersions[0]}.` +
233 "Make sure all requirements are met."
236 void window.showErrorMessage("Failed to install project SDK version.");
241 "Found/installed project SDK " +
242 `version: ${selectedToolchainAndSDKVersions[0]}`
245 if (!(await downloadAndInstallOpenOCD(openOCDVersion))) {
246 Logger.log("Failed to download and install openocd.");
248 Logger.log("Successfully downloaded and installed openocd.");
254 selectedToolchain === undefined ||
255 !(await downloadAndInstallToolchain(selectedToolchain))
258 "Failed to install project toolchain " +
259 `version: ${selectedToolchainAndSDKVersions[1]}`
262 void window.showErrorMessage(
263 "Failed to install project toolchain version."
269 "Found/installed project toolchain " +
270 `version: ${selectedToolchainAndSDKVersions[1]}`
274 // TODO: move this ninja, cmake and python installs auto of extension.mts
275 const ninjaPath = settings.getString(SettingsKey.ninjaPath);
276 if (ninjaPath && ninjaPath.includes("/.pico-sdk/ninja")) {
277 // check if ninja path exists
278 if (!existsSync(ninjaPath.replace(HOME_VAR, homedir()))) {
280 "Ninja path in settings does not exist. " +
281 "Installing ninja to default path."
283 const ninjaVersion = /\/\.pico-sdk\/ninja\/([v.0-9]+)\//.exec(
286 if (ninjaVersion === undefined) {
287 Logger.log("Failed to get ninja version from path.");
288 await commands.executeCommand(
290 ContextKeys.isPicoProject,
298 await window.withProgress(
300 location: ProgressLocation.Notification,
301 title: "Download and installing Ninja. This may take a while...",
305 result = await downloadAndInstallNinja(ninjaVersion);
313 Logger.log("Failed to install ninja.");
314 await commands.executeCommand(
316 ContextKeys.isPicoProject,
322 Logger.log("Installed selected ninja for project.");
326 const cmakePath = settings.getString(SettingsKey.cmakePath);
327 if (cmakePath && cmakePath.includes("/.pico-sdk/cmake")) {
328 // check if cmake path exists
329 if (!existsSync(cmakePath.replace(HOME_VAR, homedir()))) {
331 "CMake path in settings does not exist. " +
332 "Installing cmake to default path."
335 const cmakeVersion = /\/\.pico-sdk\/cmake\/([v.0-9A-Za-z-]+)\//.exec(
338 if (cmakeVersion === undefined) {
339 Logger.log("Failed to get cmake version from path.");
340 await commands.executeCommand(
342 ContextKeys.isPicoProject,
350 await window.withProgress(
352 location: ProgressLocation.Notification,
353 title: "Download and installing CMake. This may take a while...",
357 result = await downloadAndInstallCmake(cmakeVersion);
365 Logger.log("Failed to install cmake.");
366 await commands.executeCommand(
368 ContextKeys.isPicoProject,
374 Logger.log("Installed selected cmake for project.");
378 const pythonPath = settings.getString(SettingsKey.python3Path);
379 if (pythonPath && pythonPath.includes("/.pico-sdk/python")) {
380 // check if python path exists
381 if (!existsSync(pythonPath.replace(HOME_VAR, homedir()))) {
383 "Python path in settings does not exist. " +
384 "Installing python to default path."
386 const pythonVersion = /\/\.pico-sdk\/python\/([.0-9]+)\//.exec(
389 if (pythonVersion === undefined) {
390 Logger.log("Failed to get python version from path.");
391 await commands.executeCommand(
393 ContextKeys.isPicoProject,
400 let result: string | undefined;
401 await window.withProgress(
403 location: ProgressLocation.Notification,
405 "Download and installing Python. This may take a long while...",
409 if (process.platform === "win32") {
410 const versionBundle = await new VersionBundlesLoader(
412 ).getPythonWindowsAmd64Url(pythonVersion);
414 if (versionBundle === undefined) {
415 Logger.log("Failed to get python url from version bundle.");
416 await commands.executeCommand(
418 ContextKeys.isPicoProject,
425 // ! because data.pythonMode === 0 => versionBundle !== undefined
426 result = await downloadEmbedPython(versionBundle);
427 } else if (process.platform === "darwin") {
428 const result1 = await setupPyenv();
436 const result2 = await pyenvInstallPython(pythonVersion);
438 if (result2 !== null) {
443 "Automatic Python installation is only " +
444 "supported on Windows and macOS."
447 await window.showErrorMessage(
448 "Automatic Python installation is only " +
449 "supported on Windows and macOS."
458 if (result === undefined) {
459 Logger.log("Failed to install python.");
460 await commands.executeCommand(
462 ContextKeys.isPicoProject,
471 ui.showStatusBarItems();
472 ui.updateSDKVersion(selectedToolchainAndSDKVersions[0]);
474 const selectedBoard = cmakeGetSelectedBoard(workspaceFolder.uri);
475 if (selectedBoard !== null) {
476 ui.updateBoard(selectedBoard);
478 ui.updateBoard("unknown");
481 // auto project configuration with cmake
482 if (settings.getBoolean(SettingsKey.cmakeAutoConfigure)) {
483 //run `cmake -G Ninja -B ./build ` in the root folder
484 await configureCmakeNinja(workspaceFolder.uri);
486 workspace.onDidChangeTextDocument(event => {
487 // Check if the changed document is the file you are interested in
488 if (basename(event.document.fileName) === "CMakeLists.txt") {
489 // File has changed, do something here
490 // TODO: rerun configure project
491 // TODO: maybe conflicts with cmake extension which also does this
492 console.log("File changed:", event.document.fileName);
497 "No workspace folder for configuration found " +
498 "or cmakeAutoConfigure disabled."
503 export function deactivate(): void {
504 void commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
506 Logger.log("Extension deactivated.");