]> Git Repo - pico-vscode.git/blob - src/extension.mts
Fix linting errors
[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   cmakeGetSelectedToolchainAndSDKVersions,
19   configureCmakeNinja,
20 } from "./utils/cmakeUtil.mjs";
21 import Settings, {
22   SettingsKey,
23   type PackageJSON,
24   HOME_VAR,
25 } from "./settings.mjs";
26 import UI from "./ui.mjs";
27 import SwitchSDKCommand from "./commands/switchSDK.mjs";
28 import { existsSync, readFileSync } from "fs";
29 import { basename, join } from "path";
30 import CompileProjectCommand from "./commands/compileProject.mjs";
31 import LaunchTargetPathCommand from "./commands/launchTargetPath.mjs";
32 import {
33   GetPythonPathCommand,
34   GetEnvPathCommand
35 } from "./commands/getPaths.mjs";
36 import {
37   downloadAndInstallCmake,
38   downloadAndInstallNinja,
39   downloadAndInstallSDK,
40   downloadAndInstallToolchain,
41   downloadAndInstallTools,
42   downloadEmbedPython,
43 } from "./utils/download.mjs";
44 import { SDK_REPOSITORY_URL } from "./utils/githubREST.mjs";
45 import { getSupportedToolchains } from "./utils/toolchainUtil.mjs";
46 import {
47   NewProjectPanel,
48   getWebviewOptions,
49 } from "./webview/newProjectPanel.mjs";
50 import GithubApiCache from "./utils/githubApiCache.mjs";
51 import ClearGithubApiCacheCommand from "./commands/clearGithubApiCache.mjs";
52 import { ContextKeys } from "./contextKeys.mjs";
53 import { PicoProjectActivityBar } from "./webview/activityBar.mjs";
54 import ConditionalDebuggingCommand from "./commands/conditionalDebugging.mjs";
55 import DebugLayoutCommand from "./commands/debugLayout.mjs";
56 import OpenSdkDocumentationCommand from "./commands/openSdkDocumentation.mjs";
57 import ConfigureCmakeCommand from "./commands/configureCmake.mjs";
58 import ImportProjectCommand from "./commands/importProject.mjs";
59 import { homedir } from "os";
60 import VersionBundlesLoader from "./utils/versionBundles.mjs";
61 import { pyenvInstallPython, setupPyenv } from "./utils/pyenvUtil.mjs";
62 import NewExampleProjectCommand from "./commands/newExampleProject.mjs";
63
64 const CMAKE_DO_NOT_EDIT_HEADER_PREFIX =
65   // eslint-disable-next-line max-len
66   "== DO NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work ==";
67
68 export async function activate(context: ExtensionContext): Promise<void> {
69   Logger.log("Extension activated.");
70
71   const settings = Settings.createInstance(
72     context.workspaceState,
73     context.globalState,
74     context.extension.packageJSON as PackageJSON
75   );
76   GithubApiCache.createInstance(context);
77
78   const picoProjectActivityBarProvider = new PicoProjectActivityBar();
79   const ui = new UI(picoProjectActivityBarProvider);
80   ui.init();
81
82   const COMMANDS: Array<Command | CommandWithResult<string> | CommandWithArgs> =
83     [
84       new NewProjectCommand(context.extensionUri),
85       new SwitchSDKCommand(ui, context.extensionUri),
86       new LaunchTargetPathCommand(),
87       new GetPythonPathCommand(),
88       new GetEnvPathCommand(),
89       new CompileProjectCommand(),
90       new ClearGithubApiCacheCommand(),
91       new ConditionalDebuggingCommand(),
92       new DebugLayoutCommand(),
93       new OpenSdkDocumentationCommand(context.extensionUri),
94       new ConfigureCmakeCommand(),
95       new ImportProjectCommand(context.extensionUri),
96       new NewExampleProjectCommand(context.extensionUri),
97     ];
98
99   // register all command handlers
100   COMMANDS.forEach(command => {
101     context.subscriptions.push(command.register());
102   });
103
104   context.subscriptions.push(
105     window.registerWebviewPanelSerializer(NewProjectPanel.viewType, {
106       // eslint-disable-next-line @typescript-eslint/require-await
107       async deserializeWebviewPanel(webviewPanel: WebviewPanel): Promise<void> {
108         // Reset the webview options so we use latest uri for `localResourceRoots`.
109         webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
110         NewProjectPanel.revive(webviewPanel, context.extensionUri, false);
111       },
112     })
113   );
114
115   context.subscriptions.push(
116     window.registerTreeDataProvider(
117       PicoProjectActivityBar.viewType,
118       picoProjectActivityBarProvider
119     )
120   );
121
122   const workspaceFolder = workspace.workspaceFolders?.[0];
123
124   // check if is a pico project
125   if (
126     workspaceFolder === undefined ||
127     !existsSync(join(workspaceFolder.uri.fsPath, "pico_sdk_import.cmake"))
128   ) {
129     // finish activation
130     Logger.log("No workspace folder or pico project found.");
131     await commands.executeCommand(
132       "setContext",
133       ContextKeys.isPicoProject,
134       false
135     );
136
137     return;
138   }
139
140   const cmakeListsFilePath = join(workspaceFolder.uri.fsPath, "CMakeLists.txt");
141   if (!existsSync(cmakeListsFilePath)) {
142     Logger.log("No CMakeLists.txt found in workspace folder.");
143     await commands.executeCommand(
144       "setContext",
145       ContextKeys.isPicoProject,
146       false
147     );
148
149     return;
150   }
151
152   // check if it has .vscode folder and cmake donotedit header in CMakelists.txt
153   if (
154     !existsSync(join(workspaceFolder.uri.fsPath, ".vscode")) ||
155     !readFileSync(cmakeListsFilePath)
156       .toString("utf-8")
157       .includes(CMAKE_DO_NOT_EDIT_HEADER_PREFIX)
158   ) {
159     Logger.log(
160       "No .vscode folder and/or cmake " +
161         '"DO NOT EDIT"-header in CMakelists.txt found.'
162     );
163     await commands.executeCommand(
164       "setContext",
165       ContextKeys.isPicoProject,
166       false
167     );
168     const wantToImport = await window.showInformationMessage(
169       "Do you want to import this project as Raspberry Pi Pico project?",
170       "Yes",
171       "No"
172     );
173     if (wantToImport === "Yes") {
174       void commands.executeCommand(
175         `${extensionName}.${ImportProjectCommand.id}`,
176         workspaceFolder.uri
177       );
178     }
179
180     return;
181   }
182
183   await commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
184
185   // get sdk selected in the project
186   const selectedToolchainAndSDKVersions =
187     cmakeGetSelectedToolchainAndSDKVersions(
188       join(workspaceFolder.uri.fsPath, "CMakeLists.txt")
189     );
190   if (selectedToolchainAndSDKVersions === null) {
191     return;
192   }
193
194   // get all available toolchains for download link of current selected one
195   const toolchains = await getSupportedToolchains();
196   const selectedToolchain = toolchains.find(
197     toolchain => toolchain.version === selectedToolchainAndSDKVersions[1]
198   );
199
200   // install if needed
201   if (
202     !(await downloadAndInstallSDK(
203       selectedToolchainAndSDKVersions[0],
204       SDK_REPOSITORY_URL
205     )) ||
206     !(await downloadAndInstallTools(
207       selectedToolchainAndSDKVersions[0],
208       process.platform === "win32"
209     ))
210   ) {
211     Logger.log(
212       "Failed to install project SDK " +
213         `version: ${selectedToolchainAndSDKVersions[0]}.` +
214         "Make sure all requirements are met."
215     );
216
217     void window.showErrorMessage("Failed to install project SDK version.");
218
219     return;
220   } else {
221     Logger.log(
222       "Found/installed project SDK " +
223         `version: ${selectedToolchainAndSDKVersions[0]}`
224     );
225   }
226
227   // install if needed
228   if (
229     selectedToolchain === undefined ||
230     !(await downloadAndInstallToolchain(selectedToolchain))
231   ) {
232     Logger.log(
233       "Failed to install project toolchain " +
234         `version: ${selectedToolchainAndSDKVersions[1]}`
235     );
236
237     void window.showErrorMessage(
238       "Failed to install project toolchain version."
239     );
240
241     return;
242   } else {
243     Logger.log(
244       "Found/installed project toolchain " +
245         `version: ${selectedToolchainAndSDKVersions[1]}`
246     );
247   }
248
249   // TODO: move this ninja, cmake and python installs auto of extension.mts
250   const ninjaPath = settings.getString(SettingsKey.ninjaPath);
251   if (ninjaPath && ninjaPath.includes("/.pico-sdk/ninja")) {
252     // check if ninja path exists
253     if (!existsSync(ninjaPath.replace(HOME_VAR, homedir()))) {
254       Logger.log(
255         "Ninja path in settings does not exist. " +
256           "Installing ninja to default path."
257       );
258       const ninjaVersion = /\/\.pico-sdk\/ninja\/([v.0-9]+)\//.exec(
259         ninjaPath
260       )?.[1];
261       if (ninjaVersion === undefined) {
262         Logger.log("Failed to get ninja version from path.");
263         await commands.executeCommand(
264           "setContext",
265           ContextKeys.isPicoProject,
266           false
267         );
268
269         return;
270       }
271
272       let result = false;
273       await window.withProgress(
274         {
275           location: ProgressLocation.Notification,
276           title: "Download and installing Ninja. This may take a while...",
277           cancellable: false,
278         },
279         async progress => {
280           result = await downloadAndInstallNinja(ninjaVersion);
281           progress.report({
282             increment: 100,
283           });
284         }
285       );
286
287       if (!result) {
288         Logger.log("Failed to install ninja.");
289         await commands.executeCommand(
290           "setContext",
291           ContextKeys.isPicoProject,
292           false
293         );
294
295         return;
296       }
297       Logger.log("Installed selected ninja for project.");
298     }
299   }
300
301   const cmakePath = settings.getString(SettingsKey.cmakePath);
302   if (cmakePath && cmakePath.includes("/.pico-sdk/cmake")) {
303     // check if cmake path exists
304     if (!existsSync(cmakePath.replace(HOME_VAR, homedir()))) {
305       Logger.log(
306         "CMake path in settings does not exist. " +
307           "Installing cmake to default path."
308       );
309
310       const cmakeVersion = /\/\.pico-sdk\/cmake\/([v.0-9A-Za-z-]+)\//.exec(
311         cmakePath
312       )?.[1];
313       if (cmakeVersion === undefined) {
314         Logger.log("Failed to get cmake version from path.");
315         await commands.executeCommand(
316           "setContext",
317           ContextKeys.isPicoProject,
318           false
319         );
320
321         return;
322       }
323
324       let result = false;
325       await window.withProgress(
326         {
327           location: ProgressLocation.Notification,
328           title: "Download and installing CMake. This may take a while...",
329           cancellable: false,
330         },
331         async progress => {
332           result = await downloadAndInstallCmake(cmakeVersion);
333           progress.report({
334             increment: 100,
335           });
336         }
337       );
338
339       if (!result) {
340         Logger.log("Failed to install cmake.");
341         await commands.executeCommand(
342           "setContext",
343           ContextKeys.isPicoProject,
344           false
345         );
346
347         return;
348       }
349       Logger.log("Installed selected cmake for project.");
350     }
351   }
352
353   const pythonPath = settings.getString(SettingsKey.python3Path);
354   if (pythonPath && pythonPath.includes("/.pico-sdk/python")) {
355     // check if python path exists
356     if (!existsSync(pythonPath.replace(HOME_VAR, homedir()))) {
357       Logger.log(
358         "Python path in settings does not exist. " +
359           "Installing python to default path."
360       );
361       const pythonVersion = /\/\.pico-sdk\/python\/([.0-9]+)\//.exec(
362         pythonPath
363       )?.[1];
364       if (pythonVersion === undefined) {
365         Logger.log("Failed to get python version from path.");
366         await commands.executeCommand(
367           "setContext",
368           ContextKeys.isPicoProject,
369           false
370         );
371
372         return;
373       }
374
375       let result: string | undefined;
376       await window.withProgress(
377         {
378           location: ProgressLocation.Notification,
379           title:
380             "Download and installing Python. This may take a long while...",
381           cancellable: false,
382         },
383         async progress => {
384           if (process.platform === "win32") {
385             const versionBundle = await new VersionBundlesLoader(
386               context.extensionUri
387             ).getPythonWindowsAmd64Url(pythonVersion);
388
389             if (versionBundle === undefined) {
390               Logger.log("Failed to get python url from version bundle.");
391               await commands.executeCommand(
392                 "setContext",
393                 ContextKeys.isPicoProject,
394                 false
395               );
396
397               return;
398             }
399
400             // ! because data.pythonMode === 0 => versionBundle !== undefined
401             result = await downloadEmbedPython(versionBundle);
402           } else if (process.platform === "darwin") {
403             const result1 = await setupPyenv();
404             if (!result1) {
405               progress.report({
406                 increment: 100,
407               });
408
409               return;
410             }
411             const result2 = await pyenvInstallPython(pythonVersion);
412
413             if (result2 !== null) {
414               result = result2;
415             }
416           } else {
417             Logger.log(
418               "Automatic Python installation is only " +
419                 "supported on Windows and macOS."
420             );
421
422             await window.showErrorMessage(
423               "Automatic Python installation is only " +
424                 "supported on Windows and macOS."
425             );
426           }
427           progress.report({
428             increment: 100,
429           });
430         }
431       );
432
433       if (result === undefined) {
434         Logger.log("Failed to install python.");
435         await commands.executeCommand(
436           "setContext",
437           ContextKeys.isPicoProject,
438           false
439         );
440
441         return;
442       }
443     }
444   }
445
446   ui.showStatusBarItems();
447   ui.updateSDKVersion(selectedToolchainAndSDKVersions[0]);
448
449   // auto project configuration with cmake
450   if (settings.getBoolean(SettingsKey.cmakeAutoConfigure)) {
451     //run `cmake -G Ninja -B ./build ` in the root folder
452     await configureCmakeNinja(workspaceFolder.uri);
453
454     workspace.onDidChangeTextDocument(event => {
455       // Check if the changed document is the file you are interested in
456       if (basename(event.document.fileName) === "CMakeLists.txt") {
457         // File has changed, do something here
458         // TODO: rerun configure project
459         // TODO: maybe conflicts with cmake extension which also does this
460         console.log("File changed:", event.document.fileName);
461       }
462     });
463   } else {
464     Logger.log(
465       "No workspace folder for configuration found " +
466         "or cmakeAutoConfigure disabled."
467     );
468   }
469 }
470
471 export function deactivate(): void {
472   void commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
473
474   Logger.log("Extension deactivated.");
475 }
This page took 0.052498 seconds and 4 git commands to generate.