]> Git Repo - pico-vscode.git/blob - src/extension.mts
Add getChipUppercase command, for use in launch.json
[pico-vscode.git] / src / extension.mts
1 import {
2   workspace,
3   type ExtensionContext,
4   window,
5   type WebviewPanel,
6   commands,
7   ProgressLocation,
8 } from "vscode";
9 import {
10   extensionName,
11   type Command,
12   type CommandWithArgs,
13   type CommandWithResult,
14 } from "./commands/command.mjs";
15 import NewProjectCommand from "./commands/newProject.mjs";
16 import Logger from "./logger.mjs";
17 import {
18   cmakeGetSelectedBoard,
19   cmakeGetSelectedToolchainAndSDKVersions,
20   configureCmakeNinja,
21 } from "./utils/cmakeUtil.mjs";
22 import Settings, {
23   SettingsKey,
24   type PackageJSON,
25   HOME_VAR,
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";
34 import {
35   GetPythonPathCommand,
36   GetEnvPathCommand,
37   GetGDBPathCommand,
38   GetChipCommand,
39   GetTargetCommand,
40   GetChipUppercaseCommand,
41 } from "./commands/getPaths.mjs";
42 import {
43   downloadAndInstallCmake,
44   downloadAndInstallNinja,
45   downloadAndInstallSDK,
46   downloadAndInstallToolchain,
47   downloadAndInstallTools,
48   downloadAndInstallPicotool,
49   downloadAndInstallOpenOCD,
50   downloadEmbedPython,
51 } from "./utils/download.mjs";
52 import { SDK_REPOSITORY_URL } from "./utils/githubREST.mjs";
53 import { getSupportedToolchains } from "./utils/toolchainUtil.mjs";
54 import {
55   NewProjectPanel,
56   getWebviewOptions,
57   picotoolVersion,
58   openOCDVersion,
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";
74
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 ==";
78
79 export async function activate(context: ExtensionContext): Promise<void> {
80   Logger.log("Extension activated.");
81
82   const settings = Settings.createInstance(
83     context.workspaceState,
84     context.globalState,
85     context.extension.packageJSON as PackageJSON
86   );
87   GithubApiCache.createInstance(context);
88
89   const picoProjectActivityBarProvider = new PicoProjectActivityBar();
90   const ui = new UI(picoProjectActivityBarProvider);
91   ui.init();
92
93   const COMMANDS: Array<
94     | Command
95     | CommandWithResult<string>
96     | CommandWithResult<boolean>
97     | CommandWithArgs
98   > = [
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),
118   ];
119
120   // register all command handlers
121   COMMANDS.forEach(command => {
122     context.subscriptions.push(command.register());
123   });
124
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);
132       },
133     })
134   );
135
136   context.subscriptions.push(
137     window.registerTreeDataProvider(
138       PicoProjectActivityBar.viewType,
139       picoProjectActivityBarProvider
140     )
141   );
142
143   const workspaceFolder = workspace.workspaceFolders?.[0];
144
145   // check if is a pico project
146   if (
147     workspaceFolder === undefined ||
148     !existsSync(join(workspaceFolder.uri.fsPath, "pico_sdk_import.cmake"))
149   ) {
150     // finish activation
151     Logger.log("No workspace folder or pico project found.");
152     await commands.executeCommand(
153       "setContext",
154       ContextKeys.isPicoProject,
155       false
156     );
157
158     return;
159   }
160
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(
165       "setContext",
166       ContextKeys.isPicoProject,
167       false
168     );
169
170     return;
171   }
172
173   // check if it has .vscode folder and cmake donotedit header in CMakelists.txt
174   if (
175     !existsSync(join(workspaceFolder.uri.fsPath, ".vscode")) ||
176     !readFileSync(cmakeListsFilePath)
177       .toString("utf-8")
178       .includes(CMAKE_DO_NOT_EDIT_HEADER_PREFIX)
179   ) {
180     Logger.log(
181       "No .vscode folder and/or cmake " +
182         '"DO NOT EDIT"-header in CMakelists.txt found.'
183     );
184     await commands.executeCommand(
185       "setContext",
186       ContextKeys.isPicoProject,
187       false
188     );
189     const wantToImport = await window.showInformationMessage(
190       "Do you want to import this project as Raspberry Pi Pico project?",
191       "Yes",
192       "No"
193     );
194     if (wantToImport === "Yes") {
195       void commands.executeCommand(
196         `${extensionName}.${ImportProjectCommand.id}`,
197         workspaceFolder.uri
198       );
199     }
200
201     return;
202   }
203
204   await commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
205
206   // get sdk selected in the project
207   const selectedToolchainAndSDKVersions =
208     await cmakeGetSelectedToolchainAndSDKVersions(
209       workspaceFolder.uri
210     );
211   if (selectedToolchainAndSDKVersions === null) {
212     return;
213   }
214
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]
219   );
220
221   // install if needed
222   if (
223     !(await downloadAndInstallSDK(
224       selectedToolchainAndSDKVersions[0],
225       SDK_REPOSITORY_URL
226     )) ||
227     !(await downloadAndInstallTools(selectedToolchainAndSDKVersions[0])) ||
228     !(await downloadAndInstallPicotool(picotoolVersion))
229   ) {
230     Logger.log(
231       "Failed to install project SDK " +
232         `version: ${selectedToolchainAndSDKVersions[0]}.` +
233         "Make sure all requirements are met."
234     );
235
236     void window.showErrorMessage("Failed to install project SDK version.");
237
238     return;
239   } else {
240     Logger.log(
241       "Found/installed project SDK " +
242         `version: ${selectedToolchainAndSDKVersions[0]}`
243     );
244
245     if (!(await downloadAndInstallOpenOCD(openOCDVersion))) {
246       Logger.log("Failed to download and install openocd.");
247     } else {
248       Logger.log("Successfully downloaded and installed openocd.");
249     }
250   }
251
252   // install if needed
253   if (
254     selectedToolchain === undefined ||
255     !(await downloadAndInstallToolchain(selectedToolchain))
256   ) {
257     Logger.log(
258       "Failed to install project toolchain " +
259         `version: ${selectedToolchainAndSDKVersions[1]}`
260     );
261
262     void window.showErrorMessage(
263       "Failed to install project toolchain version."
264     );
265
266     return;
267   } else {
268     Logger.log(
269       "Found/installed project toolchain " +
270         `version: ${selectedToolchainAndSDKVersions[1]}`
271     );
272   }
273
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()))) {
279       Logger.log(
280         "Ninja path in settings does not exist. " +
281           "Installing ninja to default path."
282       );
283       const ninjaVersion = /\/\.pico-sdk\/ninja\/([v.0-9]+)\//.exec(
284         ninjaPath
285       )?.[1];
286       if (ninjaVersion === undefined) {
287         Logger.log("Failed to get ninja version from path.");
288         await commands.executeCommand(
289           "setContext",
290           ContextKeys.isPicoProject,
291           false
292         );
293
294         return;
295       }
296
297       let result = false;
298       await window.withProgress(
299         {
300           location: ProgressLocation.Notification,
301           title: "Download and installing Ninja. This may take a while...",
302           cancellable: false,
303         },
304         async progress => {
305           result = await downloadAndInstallNinja(ninjaVersion);
306           progress.report({
307             increment: 100,
308           });
309         }
310       );
311
312       if (!result) {
313         Logger.log("Failed to install ninja.");
314         await commands.executeCommand(
315           "setContext",
316           ContextKeys.isPicoProject,
317           false
318         );
319
320         return;
321       }
322       Logger.log("Installed selected ninja for project.");
323     }
324   }
325
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()))) {
330       Logger.log(
331         "CMake path in settings does not exist. " +
332           "Installing cmake to default path."
333       );
334
335       const cmakeVersion = /\/\.pico-sdk\/cmake\/([v.0-9A-Za-z-]+)\//.exec(
336         cmakePath
337       )?.[1];
338       if (cmakeVersion === undefined) {
339         Logger.log("Failed to get cmake version from path.");
340         await commands.executeCommand(
341           "setContext",
342           ContextKeys.isPicoProject,
343           false
344         );
345
346         return;
347       }
348
349       let result = false;
350       await window.withProgress(
351         {
352           location: ProgressLocation.Notification,
353           title: "Download and installing CMake. This may take a while...",
354           cancellable: false,
355         },
356         async progress => {
357           result = await downloadAndInstallCmake(cmakeVersion);
358           progress.report({
359             increment: 100,
360           });
361         }
362       );
363
364       if (!result) {
365         Logger.log("Failed to install cmake.");
366         await commands.executeCommand(
367           "setContext",
368           ContextKeys.isPicoProject,
369           false
370         );
371
372         return;
373       }
374       Logger.log("Installed selected cmake for project.");
375     }
376   }
377
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()))) {
382       Logger.log(
383         "Python path in settings does not exist. " +
384           "Installing python to default path."
385       );
386       const pythonVersion = /\/\.pico-sdk\/python\/([.0-9]+)\//.exec(
387         pythonPath
388       )?.[1];
389       if (pythonVersion === undefined) {
390         Logger.log("Failed to get python version from path.");
391         await commands.executeCommand(
392           "setContext",
393           ContextKeys.isPicoProject,
394           false
395         );
396
397         return;
398       }
399
400       let result: string | undefined;
401       await window.withProgress(
402         {
403           location: ProgressLocation.Notification,
404           title:
405             "Download and installing Python. This may take a long while...",
406           cancellable: false,
407         },
408         async progress => {
409           if (process.platform === "win32") {
410             const versionBundle = await new VersionBundlesLoader(
411               context.extensionUri
412             ).getPythonWindowsAmd64Url(pythonVersion);
413
414             if (versionBundle === undefined) {
415               Logger.log("Failed to get python url from version bundle.");
416               await commands.executeCommand(
417                 "setContext",
418                 ContextKeys.isPicoProject,
419                 false
420               );
421
422               return;
423             }
424
425             // ! because data.pythonMode === 0 => versionBundle !== undefined
426             result = await downloadEmbedPython(versionBundle);
427           } else if (process.platform === "darwin") {
428             const result1 = await setupPyenv();
429             if (!result1) {
430               progress.report({
431                 increment: 100,
432               });
433
434               return;
435             }
436             const result2 = await pyenvInstallPython(pythonVersion);
437
438             if (result2 !== null) {
439               result = result2;
440             }
441           } else {
442             Logger.log(
443               "Automatic Python installation is only " +
444                 "supported on Windows and macOS."
445             );
446
447             await window.showErrorMessage(
448               "Automatic Python installation is only " +
449                 "supported on Windows and macOS."
450             );
451           }
452           progress.report({
453             increment: 100,
454           });
455         }
456       );
457
458       if (result === undefined) {
459         Logger.log("Failed to install python.");
460         await commands.executeCommand(
461           "setContext",
462           ContextKeys.isPicoProject,
463           false
464         );
465
466         return;
467       }
468     }
469   }
470
471   ui.showStatusBarItems();
472   ui.updateSDKVersion(selectedToolchainAndSDKVersions[0]);
473
474   const selectedBoard = cmakeGetSelectedBoard(workspaceFolder.uri);
475   if (selectedBoard !== null) {
476     ui.updateBoard(selectedBoard);
477   } else {
478     ui.updateBoard("unknown");
479   }
480
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);
485
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);
493       }
494     });
495   } else {
496     Logger.log(
497       "No workspace folder for configuration found " +
498         "or cmakeAutoConfigure disabled."
499     );
500   }
501 }
502
503 export function deactivate(): void {
504   void commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
505
506   Logger.log("Extension deactivated.");
507 }
This page took 0.056341 seconds and 4 git commands to generate.