]> Git Repo - pico-vscode.git/blob - src/webview/newProjectPanel.mts
Merge branch 'main' into main
[pico-vscode.git] / src / webview / newProjectPanel.mts
1 /* eslint-disable max-len */
2 import {
3   Uri,
4   window,
5   workspace,
6   type WebviewOptions,
7   type OpenDialogOptions,
8   type WebviewPanel,
9   type Disposable,
10   ViewColumn,
11   type Webview,
12   commands,
13   ColorThemeKind,
14   ProgressLocation,
15   type Progress,
16 } from "vscode";
17 import { type ExecOptions, exec } from "child_process";
18 import { HOME_VAR } from "../settings.mjs";
19 import Settings from "../settings.mjs";
20 import Logger from "../logger.mjs";
21 import { dirname, join } from "path";
22 import { join as joinPosix } from "path/posix";
23 import {
24   type SupportedToolchainVersion,
25   getSupportedToolchains,
26 } from "../utils/toolchainUtil.mjs";
27 import {
28   SDK_REPOSITORY_URL,
29   getCmakeReleases,
30   getNinjaReleases,
31   getPicotoolReleases,
32   getSDKReleases,
33 } from "../utils/githubREST.mjs";
34 import {
35   buildCMakePath,
36   buildNinjaPath,
37   buildSDKPath,
38   buildToolchainPath,
39   downloadAndInstallCmake,
40   downloadAndInstallNinja,
41   downloadAndInstallOpenOCD,
42   downloadAndInstallSDK,
43   downloadAndInstallToolchain,
44   downloadAndInstallTools,
45   downloadAndInstallPicotool,
46   downloadEmbedPython,
47   getScriptsRoot,
48 } from "../utils/download.mjs";
49 import { compare } from "../utils/semverUtil.mjs";
50 import VersionBundlesLoader, {
51   type VersionBundle,
52 } from "../utils/versionBundles.mjs";
53 import which from "which";
54 import { homedir } from "os";
55 import { readFile } from "fs/promises";
56 import { pyenvInstallPython, setupPyenv } from "../utils/pyenvUtil.mjs";
57 import { existsSync, readdirSync } from "fs";
58 import {
59   type Example,
60   loadExamples,
61   setupExample,
62 } from "../utils/examplesUtil.mjs";
63 import { unknownErrorToString } from "../utils/errorHelper.mjs";
64
65 export const NINJA_AUTO_INSTALL_DISABLED = false;
66 // process.platform === "linux" && process.arch === "arm64";
67
68 export const openOCDVersion = "0.12.0+dev";
69
70 interface ImportProjectMessageValue {
71   selectedSDK: string;
72   selectedToolchain: string;
73   selectedPicotool: string;
74   ninjaMode: number;
75   ninjaPath: string;
76   ninjaVersion: string;
77   cmakeMode: number;
78   cmakePath: string;
79   cmakeVersion: string;
80   pythonMode: number;
81   pythonPath: string;
82
83   // debugger
84   debugger: number;
85 }
86
87 interface SubmitExampleMessageValue extends ImportProjectMessageValue {
88   example: string;
89   boardType: string;
90 }
91
92 interface SubmitMessageValue extends ImportProjectMessageValue {
93   projectName: string;
94   boardType: string;
95
96   // features (libraries)
97   spiFeature: boolean;
98   pioFeature: boolean;
99   i2cFeature: boolean;
100   dmaFeature: boolean;
101   hwwatchdogFeature: boolean;
102   hwclocksFeature: boolean;
103   hwinterpolationFeature: boolean;
104   hwtimerFeature: boolean;
105
106   // stdio support
107   uartStdioSupport: boolean;
108   usbStdioSupport: boolean;
109
110   // pico wireless options
111   picoWireless: number;
112
113   // code generation options
114   addExamples: boolean;
115   runFromRAM: boolean;
116   cpp: boolean;
117   cppRtti: boolean;
118   cppExceptions: boolean;
119 }
120
121 interface WebviewMessage {
122   command: string;
123   value: object | string | SubmitMessageValue;
124 }
125
126 enum BoardType {
127   pico = "pico",
128   picoW = "pico_w",
129   pico2 = "pico2",
130   other = "other",
131 }
132
133 enum ConsoleOption {
134   consoleOverUART = "Console over UART",
135   consoleOverUSB = "Console over USB (disables other USB use)",
136 }
137
138 enum Library {
139   spi = "SPI",
140   i2c = "I2C",
141   dma = "DMA support",
142   pio = "PIO",
143   interp = "HW interpolation",
144   timer = "HW timer",
145   watch = "HW watchdog",
146   clocks = "HW clocks",
147 }
148
149 enum PicoWirelessOption {
150   none = "None",
151   picoWLed = "Pico W onboard LED",
152   picoWPoll = "Polled lwIP",
153   picoWBackground = "Background lwIP",
154 }
155
156 enum CodeOption {
157   addExamples = "Add examples from Pico library",
158   runFromRAM = "Run the program from RAM rather than flash",
159   cpp = "Generate C++ code",
160   cppRtti = "Enable C++ RTTI (Uses more memory)",
161   cppExceptions = "Enable C++ exceptions (Uses more memory)",
162 }
163
164 enum Debugger {
165   debugProbe = "DebugProbe (CMSIS-DAP) [Default]",
166   swd = "SWD (Pi host)",
167 }
168
169 async function enumToBoard(e: BoardType, sdkPath: string): Promise<string> {
170   const quickPickItems: string[] = [];
171   let board;
172   switch (e) {
173     case BoardType.pico:
174       return "-board pico";
175     case BoardType.picoW:
176       return "-board pico_w";
177     case BoardType.pico2:
178       return "-board pico2";
179     case BoardType.other:
180       readdirSync(`${sdkPath}/src/boards/include/boards`).forEach(file => {
181         quickPickItems.push(file.split(".")[0]);
182       });
183
184       // show quick pick for board type
185       board = await window.showQuickPick(quickPickItems, {
186         placeHolder: "Select Board",
187       });
188
189       return `-board ${board}`;
190     default:
191       // TODO: maybe just return an empty string
192       throw new Error(`Unknown enum value: ${e as string}`);
193   }
194 }
195
196 function enumToParam(
197   e:
198     | BoardType
199     | ConsoleOption
200     | Library
201     | PicoWirelessOption
202     | CodeOption
203     | Debugger
204 ): string {
205   switch (e) {
206     case ConsoleOption.consoleOverUART:
207       return "-uart";
208     case ConsoleOption.consoleOverUSB:
209       return "-usb";
210     case Library.spi:
211       return "-f spi";
212     case Library.i2c:
213       return "-f i2c";
214     case Library.dma:
215       return "-f dma";
216     case Library.pio:
217       return "-f pio";
218     case Library.interp:
219       return "-f interp";
220     case Library.timer:
221       return "-f timer";
222     case Library.watch:
223       return "-f watchdog";
224     case Library.clocks:
225       return "-f clocks";
226     case PicoWirelessOption.none:
227       return "-f picow_none";
228     case PicoWirelessOption.picoWLed:
229       return "-f picow_led";
230     case PicoWirelessOption.picoWPoll:
231       return "-f picow_poll";
232     case PicoWirelessOption.picoWBackground:
233       return "-f picow_background";
234     case CodeOption.addExamples:
235       return "-x";
236     case CodeOption.runFromRAM:
237       return "-r";
238     case CodeOption.cpp:
239       return "-cpp";
240     case CodeOption.cppRtti:
241       return "-cpprtti";
242     case CodeOption.cppExceptions:
243       return "-cppex";
244     case Debugger.debugProbe:
245       return "-d 0";
246     case Debugger.swd:
247       return "-d 1";
248     default:
249       // TODO: maybe just return an empty string
250       throw new Error(`Unknown enum value: ${e as string}`);
251   }
252 }
253
254 interface ImportProjectOptions {
255   projectRoot: string;
256   toolchainAndSDK: {
257     toolchainVersion: string;
258     toolchainPath: string;
259     sdkVersion: string;
260     sdkPath: string;
261     picotoolVersion: string;
262     openOCDVersion: string;
263   };
264   ninjaExecutable: string;
265   cmakeExecutable: string;
266   debugger: Debugger;
267 }
268
269 interface NewExampleBasedProjectOptions extends ImportProjectOptions {
270   name: string;
271   libNames: [string];
272   boardType: BoardType;
273 }
274
275 interface NewProjectOptions extends ImportProjectOptions {
276   name: string;
277   boardType: BoardType;
278   consoleOptions: ConsoleOption[];
279   libraries: Array<Library | PicoWirelessOption>;
280   codeOptions: CodeOption[];
281 }
282
283 export function getWebviewOptions(extensionUri: Uri): WebviewOptions {
284   return {
285     enableScripts: true,
286     localResourceRoots: [Uri.joinPath(extensionUri, "web")],
287   };
288 }
289
290 export function getProjectFolderDialogOptions(
291   projectRoot?: Uri,
292   forImport: boolean = false
293 ): OpenDialogOptions {
294   return {
295     canSelectFiles: false,
296     canSelectFolders: true,
297     canSelectMany: false,
298     openLabel: "Select",
299     title: forImport
300       ? "Select a project folder to import"
301       : "Select a project root to create the new project folder in",
302     defaultUri: projectRoot,
303   };
304 }
305
306 export class NewProjectPanel {
307   public static currentPanel: NewProjectPanel | undefined;
308
309   public static readonly viewType = "newPicoProject";
310
311   private readonly _panel: WebviewPanel;
312   private readonly _extensionUri: Uri;
313   private readonly _settings: Settings;
314   private readonly _logger: Logger = new Logger("NewProjectPanel");
315   private _disposables: Disposable[] = [];
316
317   private _projectRoot?: Uri;
318   private _supportedToolchains?: SupportedToolchainVersion[];
319   private _versionBundlesLoader?: VersionBundlesLoader;
320   private _versionBundle: VersionBundle | undefined;
321   private _isProjectImport: boolean;
322   private _examples: Example[] = [];
323   private _isCreateFromExampleOnly: boolean = false;
324
325   public static createOrShow(
326     extensionUri: Uri,
327     isProjectImport: boolean = false,
328     createFromExample: boolean = false,
329     projectUri?: Uri
330   ): void {
331     const column = window.activeTextEditor
332       ? window.activeTextEditor.viewColumn
333       : undefined;
334
335     if (NewProjectPanel.currentPanel) {
336       if (
337         NewProjectPanel.currentPanel._isProjectImport === isProjectImport &&
338         (NewProjectPanel.currentPanel._isCreateFromExampleOnly
339           ? createFromExample
340           : true) &&
341         (!createFromExample || !isProjectImport)
342       ) {
343         NewProjectPanel.currentPanel._panel.reveal(column);
344         // update already exiting panel with new project root
345         if (projectUri) {
346           NewProjectPanel.currentPanel._projectRoot = projectUri;
347           // update webview
348           void NewProjectPanel.currentPanel._panel.webview.postMessage({
349             command: "changeLocation",
350             value: projectUri?.fsPath,
351           });
352         }
353
354         if (createFromExample) {
355           // update webview
356           void NewProjectPanel.currentPanel._panel.webview.postMessage({
357             command: "createFromExample",
358           });
359         } else if (!isProjectImport) {
360           void NewProjectPanel.currentPanel._panel.webview.postMessage({
361             command: "notCreateFromExample",
362           });
363         }
364
365         return;
366       } else {
367         // replace with new one
368         NewProjectPanel.currentPanel.dispose();
369       }
370     }
371
372     const panel = window.createWebviewPanel(
373       NewProjectPanel.viewType,
374       isProjectImport ? "Import Pico Project" : "New Pico Project",
375       column || ViewColumn.One,
376       getWebviewOptions(extensionUri)
377     );
378
379     const settings = Settings.getInstance();
380     if (settings === undefined) {
381       // TODO: maybe add restart button
382       void window.showErrorMessage(
383         "Failed to load settings. Please restart VSCode."
384       );
385
386       return;
387     }
388
389     NewProjectPanel.currentPanel = new NewProjectPanel(
390       panel,
391       settings,
392       extensionUri,
393       isProjectImport,
394       createFromExample,
395       projectUri
396     );
397   }
398
399   public static revive(
400     panel: WebviewPanel,
401     extensionUri: Uri,
402     isProjectImport: boolean = false,
403     createFromExample: boolean = false
404   ): void {
405     const settings = Settings.getInstance();
406     if (settings === undefined) {
407       // TODO: maybe add restart button
408       void window.showErrorMessage(
409         "Failed to load settings. Please restart VSCode."
410       );
411
412       return;
413     }
414
415     // TODO: reload if it was import panel maybe in state
416     NewProjectPanel.currentPanel = new NewProjectPanel(
417       panel,
418       settings,
419       extensionUri,
420       isProjectImport,
421       createFromExample
422     );
423   }
424
425   private constructor(
426     panel: WebviewPanel,
427     settings: Settings,
428     extensionUri: Uri,
429     isProjectImport: boolean = false,
430     createFromExample: boolean = false,
431     projectUri?: Uri
432   ) {
433     this._panel = panel;
434     this._extensionUri = extensionUri;
435     this._settings = settings;
436     this._isProjectImport = isProjectImport;
437     // set local property as it's an indicator for initial projectRoot update
438     // later during webview initialization
439     projectUri = projectUri ?? this._settings.getLastProjectRoot();
440     this._projectRoot = projectUri;
441     this._isCreateFromExampleOnly = createFromExample;
442
443     void this._update(createFromExample);
444
445     this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
446
447     // Update the content based on view changes
448     this._panel.onDidChangeViewState(
449       async () => {
450         if (this._panel.visible) {
451           await this._update(createFromExample);
452         }
453       },
454       null,
455       this._disposables
456     );
457
458     workspace.onDidChangeConfiguration(
459       async () => {
460         await this._updateTheme();
461       },
462       null,
463       this._disposables
464     );
465
466     // Handle messages from the webview
467     this._panel.webview.onDidReceiveMessage(
468       async (message: WebviewMessage) => {
469         switch (message.command) {
470           case "changeLocation":
471             {
472               const newLoc = await window.showOpenDialog(
473                 getProjectFolderDialogOptions(
474                   this._projectRoot,
475                   this._isProjectImport
476                 )
477               );
478
479               if (newLoc && newLoc[0]) {
480                 // overwrite preview folderUri
481                 this._projectRoot = newLoc[0];
482                 await this._settings.setLastProjectRoot(newLoc[0]);
483
484                 // update webview
485                 await this._panel.webview.postMessage({
486                   command: "changeLocation",
487                   value: newLoc[0].fsPath,
488                 });
489               }
490             }
491             break;
492           case "versionBundleAvailableTest":
493             {
494               // test if versionBundle for sdk version is available
495               const versionBundle =
496                 await this._versionBundlesLoader?.getModuleVersion(
497                   message.value as string
498                 );
499               // change toolchain version on arm64 linux, as Core-V not available for that
500               const riscvToolchain = versionBundle?.riscvToolchain;
501               // return result in message of command versionBundleAvailableTest
502               await this._panel.webview.postMessage({
503                 command: "versionBundleAvailableTest",
504                 value: {
505                   result: versionBundle !== undefined,
506                   toolchainVersion: versionBundle?.toolchain,
507                   riscvToolchainVersion: riscvToolchain,
508                 },
509               });
510             }
511             break;
512           case "cancel":
513             this.dispose();
514             break;
515           case "error":
516             void window.showErrorMessage(message.value as string);
517             break;
518           case "importProject":
519             {
520               if (
521                 this._projectRoot === undefined ||
522                 this._projectRoot.fsPath === ""
523               ) {
524                 void window.showErrorMessage(
525                   "No project root selected. Please select a project root."
526                 );
527                 await this._panel.webview.postMessage({
528                   command: "submitDenied",
529                 });
530
531                 return;
532               }
533
534               const posixProjectRoot = this._projectRoot.fsPath.replaceAll(
535                 "\\",
536                 "/"
537               );
538               const projectFolderName = posixProjectRoot.substring(
539                 posixProjectRoot.lastIndexOf("/") + 1
540               );
541               // a restriction since the folder name is passed as a parameter to the python script for project name
542               // if a project is imported
543               if (projectFolderName.includes(" ")) {
544                 void window.showErrorMessage(
545                   "Project folder name cannot contain spaces."
546                 );
547                 await this._panel.webview.postMessage({
548                   command: "submitDenied",
549                 });
550
551                 return;
552               }
553
554               const data = message.value as ImportProjectMessageValue;
555
556               // check the project to import exists
557               if (!existsSync(this._projectRoot.fsPath)) {
558                 void window.showErrorMessage(
559                   "The project you are trying to import does not exist."
560                 );
561                 await this._panel.webview.postMessage({
562                   command: "submitDenied",
563                 });
564
565                 return;
566               }
567
568               // close panel before generating project
569               this.dispose();
570
571               await window.withProgress(
572                 {
573                   location: ProgressLocation.Notification,
574                   title: `Importing project ${projectFolderName} from ${this._projectRoot?.fsPath}, this may take a while...`,
575                 },
576                 async progress =>
577                   this._generateProjectOperation(progress, data, message)
578               );
579             }
580             break;
581           case "submitExample":
582             {
583               if (
584                 this._projectRoot === undefined ||
585                 this._projectRoot.fsPath === ""
586               ) {
587                 void window.showErrorMessage(
588                   "No project root selected. Please select a project root."
589                 );
590                 await this._panel.webview.postMessage({
591                   command: "submitDenied",
592                 });
593
594                 return;
595               }
596
597               const data = message.value as SubmitExampleMessageValue;
598
599               // check if projectRoot/projectName folder already exists
600               if (existsSync(join(this._projectRoot.fsPath, data.example))) {
601                 void window.showErrorMessage(
602                   "Project already exists. Please select a different project root or example."
603                 );
604                 await this._panel.webview.postMessage({
605                   command: "submitDenied",
606                 });
607
608                 return;
609               }
610
611               if (this._examples.length === 0) {
612                 this._examples = await loadExamples();
613               }
614
615               const example = this._examples.find(
616                 example => example.searchKey === data.example
617               );
618               if (example === undefined) {
619                 await window.showErrorMessage(
620                   "Failed to find example. Try reinstalling the extension."
621                 );
622
623                 return;
624               }
625
626               // close panel before generating project
627               this.dispose();
628
629               const result = await setupExample(
630                 example,
631                 // required to support backslashes in macOS/Linux folder names
632                 process.platform !== "win32"
633                   ? this._projectRoot.fsPath
634                   : this._projectRoot.fsPath.replaceAll("\\", "/")
635               );
636
637               if (!result) {
638                 await window.showErrorMessage("Failed to setup example.");
639
640                 return;
641               }
642
643               await window.withProgress(
644                 {
645                   location: ProgressLocation.Notification,
646                   title: `Generating project based on the ${
647                     data.example ?? "undefined"
648                   } example in ${
649                     this._projectRoot?.fsPath
650                   }, this may take a while...`,
651                 },
652                 async progress =>
653                   this._generateProjectOperation(
654                     progress,
655                     data,
656                     message,
657                     example
658                   )
659               );
660             }
661             break;
662           case "submit":
663             {
664               const data = message.value as SubmitMessageValue;
665
666               if (
667                 this._projectRoot === undefined ||
668                 this._projectRoot.fsPath === ""
669               ) {
670                 void window.showErrorMessage(
671                   "No project root selected. Please select a project root."
672                 );
673                 await this._panel.webview.postMessage({
674                   command: "submitDenied",
675                 });
676
677                 return;
678               }
679
680               // check if projectRoot/projectName folder already exists
681               if (
682                 existsSync(join(this._projectRoot.fsPath, data.projectName))
683               ) {
684                 void window.showErrorMessage(
685                   "Project already exists. Please select a different project name or root."
686                 );
687                 await this._panel.webview.postMessage({
688                   command: "submitDenied",
689                 });
690
691                 return;
692               }
693
694               // close panel before generating project
695               this.dispose();
696
697               await window.withProgress(
698                 {
699                   location: ProgressLocation.Notification,
700                   title: `Generating project ${
701                     data.projectName ?? "undefined"
702                   } in ${this._projectRoot?.fsPath}, this may take a while...`,
703                 },
704                 async progress =>
705                   this._generateProjectOperation(progress, data, message)
706               );
707             }
708             break;
709         }
710       },
711       null,
712       this._disposables
713     );
714
715     // if project uri is defined on construction then also tell the webview
716     if (projectUri !== undefined) {
717       // update webview
718       void this._panel.webview.postMessage({
719         command: "changeLocation",
720         value: projectUri?.fsPath,
721       });
722     }
723   }
724
725   private async _generateProjectOperation(
726     progress: Progress<{ message?: string; increment?: number }>,
727     data:
728       | SubmitMessageValue
729       | SubmitExampleMessageValue
730       | ImportProjectMessageValue,
731     message: WebviewMessage,
732     example?: Example
733   ): Promise<void> {
734     const projectPath = this._projectRoot?.fsPath ?? "";
735
736     // check if message is valid
737     // TODO: add an else block if error handling
738     if (
739       typeof message.value === "object" &&
740       message.value !== null &&
741       projectPath !== "" &&
742       this._versionBundlesLoader !== undefined
743     ) {
744       const selectedSDK = data.selectedSDK.slice(0);
745       const selectedToolchain = this._supportedToolchains?.find(
746         tc => tc.version === data.selectedToolchain.replaceAll(".", "_")
747       );
748       const selectedPicotool = data.selectedPicotool.slice(0);
749
750       if (!selectedToolchain) {
751         void window.showErrorMessage("Failed to find selected toolchain.");
752
753         return;
754       }
755
756       // update to new selected sdk);
757       this._versionBundle = await this._versionBundlesLoader.getModuleVersion(
758         selectedSDK
759       );
760
761       if (
762         this._versionBundle === undefined &&
763         // if no versionBundle then all version options the could be dependent on it must be custom (=> independent of versionBundle)
764         (data.pythonMode === 0 || data.ninjaMode === 0 || data.cmakeMode === 0)
765       ) {
766         progress.report({
767           message: "Failed",
768           increment: 100,
769         });
770         await window.showErrorMessage("Failed to find selected SDK version.");
771
772         return;
773       }
774
775       // install python (if necessary)
776       let python3Path: string | undefined;
777       if (process.platform === "darwin" || process.platform === "win32") {
778         switch (data.pythonMode) {
779           case 0: {
780             const versionBundle = this._versionBundle;
781             await window.withProgress(
782               {
783                 location: ProgressLocation.Notification,
784                 title:
785                   "Download and installing Python. This may take a while...",
786                 cancellable: false,
787               },
788               async progress2 => {
789                 if (process.platform === "win32") {
790                   // ! because data.pythonMode === 0 => versionBundle !== undefined
791                   python3Path = await downloadEmbedPython(versionBundle!);
792                 } else if (process.platform === "darwin") {
793                   const result1 = await setupPyenv();
794                   if (!result1) {
795                     progress2.report({
796                       increment: 100,
797                     });
798
799                     return;
800                   }
801                   const result = await pyenvInstallPython(
802                     versionBundle!.python.version
803                   );
804
805                   if (result !== null) {
806                     python3Path = result;
807                   }
808                 } else {
809                   this._logger.error(
810                     "Automatic Python installation is only supported on Windows and macOS."
811                   );
812
813                   await window.showErrorMessage(
814                     "Automatic Python installation is only supported on Windows and macOS."
815                   );
816                 }
817                 progress2.report({
818                   increment: 100,
819                 });
820               }
821             );
822             break;
823           }
824           case 1:
825             python3Path = process.platform === "win32" ? "python" : "python3";
826             break;
827           case 2:
828             python3Path = data.pythonPath;
829             break;
830         }
831
832         if (python3Path === undefined) {
833           progress.report({
834             message: "Failed",
835             increment: 100,
836           });
837           await window.showErrorMessage("Failed to find python3 executable.");
838
839           return;
840         }
841       } else {
842         python3Path = "python3";
843       }
844
845       // install selected sdk and toolchain if necessary
846       // show user feedback as downloads can take a while
847       let installedSuccessfully = false;
848       await window.withProgress(
849         {
850           location: ProgressLocation.Notification,
851           title: "Downloading SDK and Toolchain",
852           cancellable: false,
853         },
854         async progress2 => {
855           // download both
856           if (
857             !(await downloadAndInstallSDK(
858               selectedSDK,
859               SDK_REPOSITORY_URL,
860               // python3Path is only possible undefined if downloaded and there is already checked and returned if this happened
861               python3Path!.replace(HOME_VAR, homedir().replaceAll("\\", "/"))
862             )) ||
863             !(await downloadAndInstallToolchain(selectedToolchain)) ||
864             !(await downloadAndInstallTools(selectedSDK)) ||
865             !(await downloadAndInstallPicotool(selectedPicotool))
866           ) {
867             this._logger.error(
868               `Failed to download and install toolchain and SDK.`
869             );
870
871             progress2.report({
872               message: "Failed - Make sure all requirements are met.",
873               increment: 100,
874             });
875
876             installedSuccessfully = false;
877           } else {
878             installedSuccessfully = true;
879             if (!(await downloadAndInstallOpenOCD(openOCDVersion))) {
880               this._logger.error(`Failed to download and install openocd.`);
881             } else {
882               this._logger.info(
883                 `Successfully downloaded and installed openocd.`
884               );
885             }
886           }
887         }
888       );
889
890       if (!installedSuccessfully) {
891         progress.report({
892           message: "Failed",
893           increment: 100,
894         });
895         await window.showErrorMessage(
896           "Failed to download and install SDK and/or toolchain."
897         );
898
899         return;
900       }
901
902       let ninjaExecutable: string;
903       let cmakeExecutable: string;
904       switch (data.ninjaMode) {
905         case 0:
906           if (this._versionBundle !== undefined) {
907             data.ninjaVersion = this._versionBundle.ninja;
908           } else {
909             this._logger.error("Failed to get version bundle for ninja.");
910             progress.report({
911               message: "Failed",
912               increment: 100,
913             });
914             await window.showErrorMessage(
915               "Failed to get ninja version for the selected Pico SDK version."
916             );
917
918             return;
919           }
920         // eslint-disable-next-line no-fallthrough
921         case 2:
922           installedSuccessfully = false;
923           await window.withProgress(
924             {
925               location: ProgressLocation.Notification,
926               title: "Download and install ninja",
927               cancellable: false,
928             },
929             async progress2 => {
930               if (await downloadAndInstallNinja(data.ninjaVersion)) {
931                 progress2.report({
932                   message: "Successfully downloaded and installed ninja.",
933                   increment: 100,
934                 });
935
936                 installedSuccessfully = true;
937               } else {
938                 installedSuccessfully = false;
939                 progress2.report({
940                   message: "Failed",
941                   increment: 100,
942                 });
943               }
944             }
945           );
946
947           if (!installedSuccessfully) {
948             progress.report({
949               message: "Failed",
950               increment: 100,
951             });
952             await window.showErrorMessage(
953               "Failed to download and install ninja. Make sure all requirements are met."
954             );
955
956             return;
957           } else {
958             ninjaExecutable = joinPosix(
959               buildNinjaPath(data.ninjaVersion),
960               "ninja"
961             );
962           }
963           break;
964         case 1:
965           ninjaExecutable = "ninja";
966           break;
967         case 3:
968           // normalize path returned by the os selector to posix path for the settings json
969           // and cross platform compatibility
970           ninjaExecutable =
971             process.platform === "win32"
972               ? joinPosix(...data.ninjaPath.split("\\"))
973               : data.ninjaPath;
974           break;
975
976         default:
977           progress.report({
978             message: "Failed",
979             increment: 100,
980           });
981           await window.showErrorMessage("Unknown ninja selection.");
982           this._logger.error("Unknown ninja selection.");
983
984           return;
985       }
986
987       switch (data.cmakeMode) {
988         case 0:
989           if (this._versionBundle !== undefined) {
990             data.cmakeVersion = this._versionBundle.cmake;
991           }
992         // eslint-disable-next-line no-fallthrough
993         case 2:
994           installedSuccessfully = false;
995           await window.withProgress(
996             {
997               location: ProgressLocation.Notification,
998               title: "Download and install cmake",
999               cancellable: false,
1000             },
1001             async progress2 => {
1002               if (await downloadAndInstallCmake(data.cmakeVersion)) {
1003                 progress.report({
1004                   message: "Successfully downloaded and installed cmake.",
1005                   increment: 100,
1006                 });
1007
1008                 installedSuccessfully = true;
1009               } else {
1010                 installedSuccessfully = false;
1011                 progress2.report({
1012                   message: "Failed",
1013                   increment: 100,
1014                 });
1015               }
1016             }
1017           );
1018
1019           if (!installedSuccessfully) {
1020             progress.report({
1021               message: "Failed",
1022               increment: 100,
1023             });
1024             await window.showErrorMessage(
1025               "Failed to download and install cmake. Make sure all requirements are met."
1026             );
1027
1028             return;
1029           } else {
1030             const cmakeVersionBasePath = buildCMakePath(data.cmakeVersion);
1031
1032             cmakeExecutable = joinPosix(cmakeVersionBasePath, "bin", "cmake");
1033           }
1034           break;
1035         case 1:
1036           cmakeExecutable = "cmake";
1037           break;
1038         case 3:
1039           // normalize path returned by the os selector to posix path for the settings json
1040           // and cross platform compatibility
1041           cmakeExecutable =
1042             process.platform === "win32"
1043               ? // TODO: maybe use path.sep for split
1044                 joinPosix(...data.cmakePath.split("\\"))
1045               : data.cmakePath;
1046           break;
1047         default:
1048           progress.report({
1049             message: "Failed",
1050             increment: 100,
1051           });
1052           await window.showErrorMessage("Unknown cmake selection.");
1053           this._logger.error("Unknown cmake selection.");
1054
1055           return;
1056       }
1057
1058       if (example === undefined && !this._isProjectImport) {
1059         const theData = data as SubmitMessageValue;
1060         const args: NewProjectOptions = {
1061           name: theData.projectName,
1062           projectRoot: projectPath,
1063           boardType: theData.boardType as BoardType,
1064           consoleOptions: [
1065             theData.uartStdioSupport ? ConsoleOption.consoleOverUART : null,
1066             theData.usbStdioSupport ? ConsoleOption.consoleOverUSB : null,
1067           ].filter(option => option !== null),
1068           libraries: [
1069             theData.spiFeature ? Library.spi : null,
1070             theData.i2cFeature ? Library.i2c : null,
1071             theData.dmaFeature ? Library.dma : null,
1072             theData.pioFeature ? Library.pio : null,
1073             theData.hwinterpolationFeature ? Library.interp : null,
1074             theData.hwtimerFeature ? Library.timer : null,
1075             theData.hwwatchdogFeature ? Library.watch : null,
1076             theData.hwclocksFeature ? Library.clocks : null,
1077             theData.boardType === "pico-w"
1078               ? Object.values(PicoWirelessOption)[theData.picoWireless]
1079               : null,
1080           ].filter(option => option !== null) as Library[],
1081           codeOptions: [
1082             theData.addExamples ? CodeOption.addExamples : null,
1083             theData.runFromRAM ? CodeOption.runFromRAM : null,
1084             theData.cpp ? CodeOption.cpp : null,
1085             theData.cppRtti ? CodeOption.cppRtti : null,
1086             theData.cppExceptions ? CodeOption.cppExceptions : null,
1087           ].filter(option => option !== null),
1088           debugger: data.debugger === 1 ? Debugger.swd : Debugger.debugProbe,
1089           toolchainAndSDK: {
1090             toolchainVersion: selectedToolchain.version,
1091             toolchainPath: buildToolchainPath(selectedToolchain.version),
1092             sdkVersion: selectedSDK,
1093             sdkPath: buildSDKPath(selectedSDK),
1094             picotoolVersion: selectedPicotool,
1095             openOCDVersion: openOCDVersion,
1096           },
1097           ninjaExecutable,
1098           cmakeExecutable,
1099         };
1100
1101         await this._executePicoProjectGenerator(
1102           args,
1103           python3Path.replace(HOME_VAR, homedir().replaceAll("\\", "/"))
1104         );
1105       } else if (example !== undefined && !this._isProjectImport) {
1106         const theData = data as SubmitExampleMessageValue;
1107         const args: NewExampleBasedProjectOptions = {
1108           name: theData.example,
1109           libNames: example.libNames,
1110           projectRoot: projectPath,
1111           boardType: theData.boardType as BoardType,
1112           debugger: data.debugger === 1 ? Debugger.swd : Debugger.debugProbe,
1113           toolchainAndSDK: {
1114             toolchainVersion: selectedToolchain.version,
1115             toolchainPath: buildToolchainPath(selectedToolchain.version),
1116             sdkVersion: selectedSDK,
1117             sdkPath: buildSDKPath(selectedSDK),
1118             picotoolVersion: selectedPicotool,
1119             openOCDVersion: openOCDVersion,
1120           },
1121           ninjaExecutable,
1122           cmakeExecutable,
1123         };
1124
1125         await this._executePicoProjectGenerator(
1126           args,
1127           python3Path.replace(HOME_VAR, homedir().replaceAll("\\", "/")),
1128           true
1129         );
1130       } else if (this._isProjectImport && example === undefined) {
1131         const args: ImportProjectOptions = {
1132           projectRoot: projectPath,
1133           toolchainAndSDK: {
1134             toolchainVersion: selectedToolchain.version,
1135             toolchainPath: buildToolchainPath(selectedToolchain.version),
1136             sdkVersion: selectedSDK,
1137             sdkPath: buildSDKPath(selectedSDK),
1138             picotoolVersion: selectedPicotool,
1139             openOCDVersion: openOCDVersion,
1140           },
1141           ninjaExecutable,
1142           cmakeExecutable,
1143           debugger: data.debugger === 1 ? Debugger.swd : Debugger.debugProbe,
1144         };
1145
1146         await this._executePicoProjectGenerator(
1147           args,
1148           python3Path.replace(HOME_VAR, homedir().replaceAll("\\", "/"))
1149         );
1150       } else {
1151         progress.report({
1152           message: "Failed",
1153           increment: 100,
1154         });
1155         await window.showErrorMessage("Unknown project type.");
1156
1157         return;
1158       }
1159     }
1160   }
1161
1162   private async _update(forceCreateFromExample: boolean): Promise<void> {
1163     this._panel.title = this._isProjectImport
1164       ? "Import Pico Project"
1165       : forceCreateFromExample
1166       ? "New Example Pico Project"
1167       : "New Pico Project";
1168     this._panel.iconPath = Uri.joinPath(
1169       this._extensionUri,
1170       "web",
1171       "raspberry-128.png"
1172     );
1173     const html = await this._getHtmlForWebview(
1174       this._panel.webview,
1175       forceCreateFromExample
1176     );
1177
1178     if (html !== "") {
1179       try {
1180         this._panel.webview.html = html;
1181       } catch (error) {
1182         this._logger.error(
1183           "Failed to set webview html. Webview might have been disposed. Error: ",
1184           unknownErrorToString(error)
1185         );
1186         // properly dispose panel
1187         this.dispose();
1188
1189         return;
1190       }
1191       await this._updateTheme();
1192     } else {
1193       void window.showErrorMessage(
1194         "Failed to load available Pico SDKs and/or supported toolchains. This may be due to an outdated personal access token for GitHub or a exceeded rate limit."
1195       );
1196       this.dispose();
1197     }
1198   }
1199
1200   private async _updateTheme(): Promise<void> {
1201     await this._panel.webview.postMessage({
1202       command: "setTheme",
1203       theme:
1204         window.activeColorTheme.kind === ColorThemeKind.Dark ||
1205         window.activeColorTheme.kind === ColorThemeKind.HighContrast
1206           ? "dark"
1207           : "light",
1208     });
1209   }
1210
1211   public dispose(): void {
1212     NewProjectPanel.currentPanel = undefined;
1213
1214     this._panel.dispose();
1215
1216     while (this._disposables.length) {
1217       const x = this._disposables.pop();
1218
1219       if (x) {
1220         x.dispose();
1221       }
1222     }
1223   }
1224
1225   private async _getHtmlForWebview(
1226     webview: Webview,
1227     forceCreateFromExample: boolean
1228   ): Promise<string> {
1229     // TODO: store in memory so on future update static stuff doesn't need to be read again
1230     const mainScriptUri = webview.asWebviewUri(
1231       Uri.joinPath(this._extensionUri, "web", "main.js")
1232     );
1233     const navScriptUri = webview.asWebviewUri(
1234       Uri.joinPath(this._extensionUri, "web", "nav.js")
1235     );
1236     const stateScriptUri = webview.asWebviewUri(
1237       Uri.joinPath(this._extensionUri, "web", "state.js")
1238     );
1239     const tailwindcssScriptUri = webview.asWebviewUri(
1240       Uri.joinPath(this._extensionUri, "web", "tailwindcss-3_3_5.js")
1241     );
1242
1243     const mainStyleUri = webview.asWebviewUri(
1244       Uri.joinPath(this._extensionUri, "web", "main.css")
1245     );
1246
1247     // images
1248     const navHeaderSvgUri = webview.asWebviewUri(
1249       Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header.svg")
1250     );
1251
1252     const navHeaderDarkSvgUri = webview.asWebviewUri(
1253       Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header-dark.svg")
1254     );
1255
1256     const riscvWhiteSvgUri = webview.asWebviewUri(
1257       Uri.joinPath(
1258         this._extensionUri,
1259         "web",
1260         "riscv",
1261         "RISC-V_Horizontal_White.svg"
1262       )
1263     );
1264     const riscvWhiteYellowSvgUri = webview.asWebviewUri(
1265       Uri.joinPath(
1266         this._extensionUri,
1267         "web",
1268         "riscv",
1269         "RISC-V_Horizontal_White_Yellow.svg"
1270       )
1271     );
1272     const riscvBlackSvgUri = webview.asWebviewUri(
1273       Uri.joinPath(
1274         this._extensionUri,
1275         "web",
1276         "riscv",
1277         "RISC-V_Horizontal_Black.svg"
1278       )
1279     );
1280     const riscvColorSvgUri = webview.asWebviewUri(
1281       Uri.joinPath(
1282         this._extensionUri,
1283         "web",
1284         "riscv",
1285         "RISC-V_Horizontal_Color.svg"
1286       )
1287     );
1288
1289     this._versionBundlesLoader = new VersionBundlesLoader(this._extensionUri);
1290
1291     // construct auxiliar html
1292     // TODO: add offline handling - only load installed ones
1293     let toolchainsHtml = "";
1294     let picoSDKsHtml = "";
1295     let picotoolsHtml = "";
1296     let ninjasHtml = "";
1297     let cmakesHtml = "";
1298
1299     //const installedSDKs = detectInstalledSDKs();
1300     //const installedToolchains = detectInstalledToolchains();
1301     //installedSDKs.sort((a, b) =>
1302     //compare(a.version.replace("v", ""), b.version.replace("v", ""))
1303     //);
1304
1305     try {
1306       const availableSDKs = await getSDKReleases();
1307       const supportedToolchains = await getSupportedToolchains();
1308       const ninjaReleases = await getNinjaReleases();
1309       const cmakeReleases = await getCmakeReleases();
1310       const picotoolReleases = await getPicotoolReleases();
1311
1312       if (availableSDKs.length === 0 || supportedToolchains.length === 0) {
1313         this._logger.error(
1314           "Failed to load toolchains or SDKs. This may be due to an outdated personal access token for GitHub."
1315         );
1316
1317         throw new Error("Failed to load toolchains or SDKs.");
1318       }
1319
1320       this._versionBundle = await this._versionBundlesLoader.getModuleVersion(
1321         availableSDKs[0]
1322       );
1323
1324       availableSDKs
1325         .sort((a, b) => compare(b, a))
1326         .forEach(sdk => {
1327           picoSDKsHtml += `<option ${
1328             picoSDKsHtml.length === 0 ? "selected " : ""
1329           }value="${sdk}" ${
1330             compare(sdk, "1.5.0") < 0
1331               ? `class="advanced-option-2" disabled`
1332               : ""
1333           }>v${sdk}</option>`;
1334         });
1335
1336       picotoolReleases
1337         .sort((a, b) => compare(b, a))
1338         .forEach(picotool => {
1339           picotoolsHtml += `<option ${
1340             picotoolsHtml.length === 0 ? "selected " : ""
1341           }value="${picotool}">${picotool}</option>`;
1342         });
1343
1344       supportedToolchains.forEach(toolchain => {
1345         toolchainsHtml += `<option ${
1346           toolchainsHtml.length === 0 ? "selected " : ""
1347         }value="${toolchain.version}">${toolchain.version.replaceAll(
1348           "_",
1349           "."
1350         )}</option>`;
1351       });
1352
1353       ninjaReleases
1354         .sort((a, b) => compare(b.replace("v", ""), a.replace("v", "")))
1355         .forEach(ninja => {
1356           ninjasHtml += `<option ${
1357             ninjasHtml.length === 0 ? "selected " : ""
1358           }value="${ninja}">${ninja}</option>`;
1359         });
1360
1361       cmakeReleases.forEach(cmake => {
1362         cmakesHtml += `<option ${
1363           cmakesHtml.length === 0 ? "selected " : ""
1364         }value="${cmake}">${cmake}</option>`;
1365       });
1366
1367       if (
1368         toolchainsHtml.length === 0 ||
1369         picoSDKsHtml.length === 0 ||
1370         (process.platform !== "linux" && ninjasHtml.length === 0)
1371       ) {
1372         this._logger.error(
1373           "Failed to load toolchains or SDKs. This may be due to an outdated personal access token for GitHub."
1374         );
1375
1376         throw new Error("Failed to load toolchains or SDKs.");
1377       }
1378       this._supportedToolchains = supportedToolchains;
1379       // toolchains should be sorted and cant be sorted by compare because
1380       // of their alphanumeric structure
1381     } catch (error) {
1382       this._logger.error(
1383         `Error while retrieving SDK and toolchain versions: ${
1384           error instanceof Error ? error.message : (error as string)
1385         }`
1386       );
1387
1388       this.dispose();
1389       void window.showErrorMessage(
1390         "Error while retrieving SDK and toolchain versions."
1391       );
1392
1393       return "";
1394     }
1395
1396     const isNinjaSystemAvailable =
1397       (await which("ninja", { nothrow: true })) !== null;
1398     const isCmakeSystemAvailable =
1399       (await which("cmake", { nothrow: true })) !== null;
1400     // TODO: check python version, workaround, ownly allow python3 commands on unix
1401     const isPythonSystemAvailable =
1402       (await which("python3", { nothrow: true })) !== null ||
1403       (await which("python", { nothrow: true })) !== null;
1404
1405     if (!isNinjaSystemAvailable && NINJA_AUTO_INSTALL_DISABLED) {
1406       this.dispose();
1407       await window.showErrorMessage(
1408         "Not all requirements are met. Automatic ninja installation is currently not supported on aarch64 Linux systems. Please install ninja manually."
1409       );
1410
1411       return "";
1412     }
1413
1414     this._examples = await loadExamples();
1415     this._logger.info(`Loaded ${this._examples.length} examples.`);
1416
1417     // Restrict the webview to only load specific scripts
1418     const nonce = getNonce();
1419     const isWindows = process.platform === "win32";
1420
1421     return `<!DOCTYPE html>
1422     <html lang="en">
1423       <head>
1424         <meta charset="UTF-8">
1425         
1426         <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${
1427           webview.cspSource
1428         } 'unsafe-inline'; img-src ${
1429       webview.cspSource
1430     } https:; script-src 'nonce-${nonce}';">
1431         <meta name="viewport" content="width=device-width, initial-scale=1.0">
1432
1433         <link href="${mainStyleUri.toString()}" rel="stylesheet">
1434
1435         <title>${
1436           this._isProjectImport
1437             ? "Import Pico Project"
1438             : forceCreateFromExample
1439             ? "New Example Pico Project"
1440             : "New Pico Project"
1441         }</title>
1442
1443         <script nonce="${nonce}" src="${tailwindcssScriptUri.toString()}"></script>
1444         <script nonce="${nonce}">
1445           tailwind.config = {
1446             darkMode: 'class',
1447           }
1448           ${
1449             !this._isProjectImport && this._examples.length > 0
1450               ? `
1451           var examples = {${this._examples
1452             .map(
1453               e =>
1454                 `"${e.searchKey}": {"boards": [${e.boards
1455                   .map(b => `"${b}"`)
1456                   .join(", ")}], "supportRiscV": ${e.supportRiscV}}`
1457             )
1458             .join(", ")}};`
1459               : ""
1460           }
1461           var doProjectImport = ${this._isProjectImport};
1462           var forceCreateFromExample = ${forceCreateFromExample};
1463
1464           // riscv logos
1465           const riscvWhiteSvgUri = "${riscvWhiteSvgUri.toString()}";
1466           const riscvWhiteYellowSvgUri = "${riscvWhiteYellowSvgUri.toString()}";
1467           const riscvBlackSvgUri = "${riscvBlackSvgUri.toString()}";
1468           const riscvColorSvgUri = "${riscvColorSvgUri.toString()}";
1469         </script>
1470       </head>
1471       <body class="scroll-smooth w-screen">
1472         <div id="above-nav" class="container max-w-6xl mx-auto flex justify-between items-center w-full sticky top-0 z-10 pl-5 h-5">
1473         </div>
1474         <div id="nav-overlay" class="overlay hidden md:hidden inset-y-0 right-0 w-auto z-50 overflow-y-auto ease-out bg-slate-400 dark:bg-slate-800 drop-shadow-lg">
1475           <!-- Navigation links go here -->
1476           <ul class="overlay-menu">
1477             <li id="ov-nav-basic" class="overlay-item text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md">Basic Settings</li>
1478             ${
1479               !this._isProjectImport
1480                 ? `
1481             <li id="ov-nav-features" class="overlay-item project-options text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md">Features</li>
1482             <li id="ov-nav-stdio" class="overlay-item project-options text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md">Stdio support</li>
1483             <li id="ov-nav-pico-wireless" class="overlay-item project-options hidden text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md">Pico wireless options</li>
1484             <li id="ov-nav-code-gen" class="overlay-item project-options text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md">Code generation options</li>
1485             `
1486                 : ""
1487             }
1488             <li id="ov-nav-debugger" class="overlay-item text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md">Debugger</li>
1489           </ul>
1490         </div>
1491         <nav id="top-navbar" class="container max-w-6xl mx-auto flex justify-between items-center w-full sticky top-5 z-10 pl-5 pr-5 h-24 bg-opacity-95 bg-slate-400 dark:bg-slate-800 rounded-md">
1492             <div class="inline-flex h-32 align-middle">
1493                 <img src="${navHeaderSvgUri.toString()}" alt="raspberry pi logo" class="h-32 rounded cursor-not-allowed hidden dark:block mb-2"/>
1494                 <img src="${navHeaderDarkSvgUri.toString()}" alt="raspberry pi logo" class="h-32 rounded cursor-not-allowed block dark:hidden mb-2"/>
1495             </div>
1496             <ul class="pl-3 pr-3 space-x-4 h-auto align-middle hidden md:flex">
1497                 <li class="nav-item text-black dark:text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md" id="nav-basic">
1498                     Basic Settings
1499                 </li>
1500                 ${
1501                   !this._isProjectImport
1502                     ? `
1503                 <li class="nav-item project-options text-black dark:text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md" id="nav-features">
1504                     Features
1505                 </li>
1506                 <li class="nav-item project-options text-black dark:text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md" id="nav-stdio">
1507                     Stdio support
1508                 </li>
1509                 <li class="nav-item project-options hidden text-black dark:text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md" id="nav-pico-wireless">
1510                     Pico wireless options
1511                 </li>
1512                 <li class="nav-item project-options text-black dark:text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md" id="nav-code-gen">
1513                     Code generation options
1514                 </li>
1515                 `
1516                     : ""
1517                 }
1518                 <li class="nav-item text-black dark:text-white max-h-14 text-lg flex items-center cursor-pointer p-2 hover:bg-slate-500 hover:bg-opacity-50 dark:hover:bg-slate-600 hover:shadow-md transition-colors motion-reduce:transition-none ease-in-out rounded-md" id="nav-debugger">
1519                     Debugger
1520                 </li>
1521             </ul>
1522             <div id="burger-menu" class="flex md:hidden cursor-pointer h-auto me-7">
1523               <div class="bar bg-black dark:bg-white"></div>
1524               <div class="bar bg-black dark:bg-white"></div>
1525               <div class="bar bg-black dark:bg-white"></div>
1526             </div>
1527         </nav>
1528         <main class="container max-w-3xl xl:max-w-5xl mx-auto relative top-14 snap-y mb-20">
1529             <div id="section-basic" class="snap-start">
1530                 <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Basic Settings</h3>
1531                 <form>
1532                   ${
1533                     !this._isProjectImport
1534                       ? `<div id="project-name-grid" class="grid gap-6 ${
1535                           // removed/added dynamic in nav.js
1536                           !forceCreateFromExample ? "md:grid-cols-2" : ""
1537                         }">
1538                         <div>
1539                           <label for="inp-project-name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
1540                           <div class="flex">
1541                             <div class="relative inline-flex w-full">
1542                                 <input type="text" id="inp-project-name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 invalid:border-pink-500 invalid:text-pink-600 focus:invalid:border-pink-500 focus:invalid:ring-pink-500" placeholder="${
1543                                   forceCreateFromExample
1544                                     ? "Select an example"
1545                                     : "Project name"
1546                                 }" required/> <!-- without this required the webview will crash every time you hit the examples button -->
1547                                 <button id="project-name-dropdown-button" class="absolute inset-y-0 right-0 flex items-center px-2 border bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white rounded-r-lg border-gray-300 dark:border-gray-600 ${
1548                                   !forceCreateFromExample ? "hidden" : ""
1549                                 }">&#9660;</button>
1550                                 ${
1551                                   this._examples.length > 0
1552                                     ? `   
1553                                     <ul id="examples-list" class="bg-gray-50 border-gray-300 dark:bg-gray-700 dark:border-gray-600 rounded-b-lg"></ul>                          
1554                                 <!--<datalist id="examples-list">
1555                                   <option value="\${this._examples
1556                                     .map(e => e.searchKey)
1557                                     .join(
1558                                       '">example project</option>\n<option value="'
1559                                     )}">example project</option>
1560                                 </datalist>-->`
1561                                     : ""
1562                                 }
1563                             </div>
1564                             <button id="btn-create-from-example" class="focus:outline-none bg-transparent ring-2 focus:ring-3 ring-blue-400 dark:ring-blue-700 font-medium rounded-lg px-4 ml-4 hover:bg-blue-500 dark:hover:bg-blue-700 focus:ring-blue-600 dark:focus:ring-blue-800" tooltip="Create from example">Example</button>
1565                           </div>
1566
1567                           <p id="inp-project-name-error" class="mt-2 text-sm text-red-600 dark:text-red-500" hidden>
1568                               <span class="font-medium">Error</span> Please enter a valid project name.
1569                           </p>
1570                         </div>
1571                 
1572                         <div id="board-type-riscv-grid" class="grid gap-6 grid-cols-2">
1573                           <div>
1574                             <label for="sel-board-type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Board type</label>
1575                             <select id="sel-board-type" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
1576                              <option id="option-board-type-${
1577                                BoardType.pico2
1578                              }" value="${BoardType.pico2}">Pico 2</option>    
1579                               <option id="option-board-type-${
1580                                 BoardType.pico
1581                               }" value="${BoardType.pico}">Pico</option>
1582                               <option id="option-board-type-${
1583                                 BoardType.picoW
1584                               }" value="${BoardType.picoW}">Pico W</option>
1585                               <option id="option-board-type-${
1586                                 BoardType.other
1587                               }" value="${BoardType.other}">Other</option>
1588                             </select>
1589                           </div>
1590                           <div class="use-riscv text-sm font-medium text-gray-900 dark:text-white" hidden>
1591                             <label for="riscvToggle" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Architecture (Pico 2)</label>
1592                             <div class="flex items-center justify-between p-2 bg-gray-100 rounded-lg dark:bg-gray-700">
1593                               <input type="checkbox" id="sel-riscv" class="ms-2" />
1594                               <img id="riscvIcon" src="${riscvColorSvgUri.toString()}" alt="RISC-V Logo" class="h-6 mx-auto w-28">
1595                             </div>
1596                           </div>
1597                         </div>
1598                       </div>`
1599                       : `<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
1600                             Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import
1601                         </h3>`
1602                   }
1603                     <div class="mt-6 mb-4">
1604                         <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="file_input">Location</label>
1605                         <div class="flex">
1606                             <div class="w-full left-0 flex">
1607                                 <span class="inline-flex items-center px-3 text-lg text-gray-900 bg-gray-200 border border-r-0 border-gray-300 rounded-l-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600">
1608                                 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" width="24" height="24">
1609                                 <path style="fill:#027372;" d="M471.149,51.745L438.468,503.83h32.681c17.974,0,32.681-14.706,32.681-32.681V84.426L471.149,51.745z
1610                                   "/>
1611                                 <path style="fill:#02ACAB;" d="M471.149,51.745v419.404c0,17.974-14.706,32.681-32.681,32.681h-10.894L256,405.787L84.426,503.83
1612                                   H40.851c-17.974,0-32.681-14.706-32.681-32.681V40.851C8.17,22.877,22.877,8.17,40.851,8.17h76.255L256,122.553L394.894,8.17h32.681
1613                                   L471.149,51.745z"/>
1614                                 <path style="fill:#D5F6F5;" d="M362.213,155.234V40.851h-65.362v114.383H362.213z"/>
1615                                 <polygon style="fill:#ABECEC;" points="394.894,8.17 394.894,187.915 362.213,187.915 285.957,98.043 362.213,8.17 "/>
1616                                 <polygon style="fill:#D5F6F5;" points="362.213,8.17 362.213,40.851 329.532,98.041 362.213,155.231 362.213,187.915 
1617                                   117.106,187.915 117.106,8.17 "/>
1618                                 <polygon style="fill:#EEEEEE;" points="427.574,329.532 427.574,503.83 394.894,503.83 340.426,285.957 "/>
1619                                 <polygon style="fill:#FFFFFF;" points="394.894,285.957 394.894,503.83 84.426,503.83 84.426,329.532 "/>
1620                                 <polygon style="fill:#4D4D4D;" points="362.213,40.851 362.213,155.234 340.426,155.234 318.638,98.043 340.426,40.851 "/>
1621                                 <rect x="296.851" y="40.851" style="fill:#737373;" width="43.574" height="114.383"/>
1622                                 <path style="fill:#FEA680;" d="M394.894,253.277h-32.681l32.681,76.255h32.681v-43.574
1623                                   C427.574,267.983,412.868,253.277,394.894,253.277z"/>
1624                                 <path style="fill:#FFC1A6;" d="M394.894,285.957v43.574H84.426v-43.574c0-17.974,14.706-32.681,32.681-32.681h245.106
1625                                   C380.187,253.277,394.894,267.983,394.894,285.957z"/>
1626                                 <path d="M509.607,78.648L433.351,2.392C431.82,0.861,429.741,0,427.574,0H40.851C18.325,0,0,18.325,0,40.851v430.298
1627                                   C0,493.675,18.325,512,40.851,512h430.298C493.675,512,512,493.675,512,471.149V84.426C512,82.259,511.139,80.181,509.607,78.648z
1628                                   M125.277,16.34h261.447v163.404H125.277V16.34z M495.66,471.149c0,13.515-10.995,24.511-24.511,24.511H40.851
1629                                   c-13.516,0-24.511-10.996-24.511-24.511V40.851c0-13.515,10.995-24.511,24.511-24.511h68.085v171.574c0,4.513,3.658,8.17,8.17,8.17
1630                                   h277.787c4.512,0,8.17-3.657,8.17-8.17V16.34h21.127l71.469,71.469V471.149z"/>
1631                                 <path d="M362.213,32.681h-65.362c-4.512,0-8.17,3.657-8.17,8.17v114.383c0,4.513,3.658,8.17,8.17,8.17h65.362
1632                                   c4.512,0,8.17-3.657,8.17-8.17V40.851C370.383,36.338,366.725,32.681,362.213,32.681z M354.043,147.064h-49.021V49.021h49.021
1633                                   V147.064z"/>
1634                                 <path d="M394.894,245.106H117.106c-22.526,0-40.851,18.325-40.851,40.851v185.191c0,4.513,3.658,8.17,8.17,8.17
1635                                   s8.17-3.657,8.17-8.17V337.702h57.191c4.512,0,8.17-3.657,8.17-8.17c0-4.513-3.658-8.17-8.17-8.17H92.596v-35.404
1636                                   c0-13.515,10.995-24.511,24.511-24.511h277.787c13.516,0,24.511,10.996,24.511,24.511v35.404H182.468c-4.512,0-8.17,3.657-8.17,8.17
1637                                   c0,4.513,3.658,8.17,8.17,8.17h236.936v133.447c0,4.513,3.658,8.17,8.17,8.17c4.512,0,8.17-3.657,8.17-8.17V285.957
1638                                   C435.745,263.432,417.419,245.106,394.894,245.106z"/>
1639                                 <path d="M340.426,386.723H171.574c-4.512,0-8.17,3.657-8.17,8.17c0,4.513,3.658,8.17,8.17,8.17h168.851
1640                                   c4.512,0,8.17-3.657,8.17-8.17C348.596,390.38,344.938,386.723,340.426,386.723z"/>
1641                                 <path d="M284.141,430.298H171.574c-4.512,0-8.17,3.657-8.17,8.17c0,4.513,3.658,8.17,8.17,8.17h112.567
1642                                   c4.512,0,8.17-3.657,8.17-8.17C292.312,433.955,288.655,430.298,284.141,430.298z"/>
1643                                 </svg>
1644                                 </span>
1645                                 <input type="text" id="inp-project-location" class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-r-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-500 dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="${
1646                                   !this._isProjectImport
1647                                     ? isWindows
1648                                       ? "C:\\MyProject"
1649                                       : "/home/user/MyProject"
1650                                     : isWindows
1651                                     ? "C:\\Project\\To\\Import"
1652                                     : "/home/user/Project/To/Import"
1653                                 }" disabled value="${
1654       // support folder names with backslashes on linux and macOS
1655       this._projectRoot !== undefined
1656         ? process.platform === "win32"
1657           ? this._projectRoot.fsPath.replaceAll("\\", "/")
1658           : this._projectRoot.fsPath
1659         : ""
1660     }"/>
1661                             </div>
1662                             <button 
1663                                 id="btn-change-project-location"
1664                                 type="button"
1665                                 class="relative inline-flex items-center justify-center standard-button-size p-1 ml-4 overflow-hidden text-sm font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-pink-500 to-orange-400 group-hover:from-pink-500 group-hover:to-orange-400 hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-pink-200 dark:focus:ring-pink-800">
1666                                 <span class="relative px-4 py-2 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-md group-hover:bg-opacity-0">
1667                                     Change
1668                                 </span>
1669                             </button>
1670                         </div>
1671                     </div>
1672                     <div class="grid gap-6 md:grid-cols-2 mt-6">
1673                       <div>
1674                         <label for="sel-pico-sdk" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select Pico SDK version</label>
1675                         <select id="sel-pico-sdk" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
1676                             ${picoSDKsHtml}
1677                         </select>
1678                       </div>
1679                       
1680                       <div class="advanced-option" hidden>
1681                         <label for="sel-toolchain" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select ARM/RISCV Embeded Toolchain version</label>
1682                         <select id="sel-toolchain" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
1683                             ${toolchainsHtml}
1684                         </select>
1685                       </div>
1686                       <div hidden>
1687                         <label for="sel-picotool" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select picotool version</label>
1688                         <select id="sel-picotool" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
1689                             ${picotoolsHtml}
1690                         </select>
1691                       </div>
1692                     </div>
1693                     <div class="grid gap-6 md:grid-cols-${
1694                       process.platform === "darwin" ||
1695                       process.platform === "win32"
1696                         ? "6"
1697                         : "4"
1698                     } mt-6 advanced-option" hidden>
1699                       ${
1700                         !NINJA_AUTO_INSTALL_DISABLED
1701                           ? `<div class="col-span-2">
1702                         <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Ninja Version:</label>
1703
1704                         ${
1705                           // TODO: use versionBundleAvailableTest instead of this._versionBundle !== undefined cause if a version with bundle is later selected this section wouldn't be present
1706                           this._versionBundle !== undefined
1707                             ? `<div class="flex items-center mb-2">
1708                                 <input type="radio" id="ninja-radio-default-version" name="ninja-version-radio" value="0" class="mr-1 text-blue-500 requires-version-bundle">
1709                                 <label for="ninja-radio-default-version" class="text-gray-900 dark:text-white">Default version</label>
1710                               </div>`
1711                             : ""
1712                         }
1713
1714                         ${
1715                           isNinjaSystemAvailable
1716                             ? `<div class="flex items-center mb-2" >
1717                                 <input type="radio" id="ninja-radio-system-version" name="ninja-version-radio" value="1" class="mr-1 text-blue-500">
1718                                 <label for="ninja-radio-system-version" class="text-gray-900 dark:text-white">Use system version</label>
1719                               </div>`
1720                             : ""
1721                         }
1722
1723                         <div class="flex items-center mb-2">
1724                           <input type="radio" id="ninja-radio-select-version" name="ninja-version-radio" value="2" class="mr-1 text-blue-500">
1725                           <label for="ninja-radio-select-version" class="text-gray-900 dark:text-white">Select version:</label>
1726                           <select id="sel-ninja" class="ml-2 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
1727                             ${ninjasHtml}
1728                           </select>
1729                         </div>
1730
1731                         <div class="flex items-center mb-2">
1732                           <input type="radio" id="ninja-radio-path-executable" name="ninja-version-radio" value="3" class="mr-1 text-blue-500">
1733                           <label for="ninja-radio-path-executable" class="text-gray-900 dark:text-white">Path to executable:</label>
1734                           <input type="file" id="ninja-path-executable" multiple="false" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 ms-2">
1735                         </div>
1736                       </div>`
1737                           : ""
1738                       }
1739                     
1740                       <div class="col-span-2">
1741                         <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">CMake Version:</label>
1742
1743                         ${
1744                           this._versionBundle !== undefined
1745                             ? `<div class="flex items-center mb-2">
1746                                 <input type="radio" id="cmake-radio-default-version" name="cmake-version-radio" value="0" class="mr-1 text-blue-500 requires-version-bundle">
1747                                 <label for="cmake-radio-default-version" class="text-gray-900 dark:text-white">Default version</label>
1748                               </div>`
1749                             : ""
1750                         }
1751
1752                         ${
1753                           isCmakeSystemAvailable
1754                             ? `<div class="flex items-center mb-2" >
1755                                 <input type="radio" id="cmake-radio-system-version" name="cmake-version-radio" value="1" class="mr-1 text-blue-500">
1756                                 <label for="cmake-radio-system-version" class="text-gray-900 dark:text-white">Use system version</label>
1757                               </div>`
1758                             : ""
1759                         }
1760
1761                         <div class="flex items-center mb-2">
1762                           <input type="radio" id="cmake-radio-select-version" name="cmake-version-radio" value="2" class="mr-1 text-blue-500">
1763                           <label for="cmake-radio-select-version" class="text-gray-900 dark:text-white">Select version:</label>
1764                           <select id="sel-cmake" class="ml-2 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
1765                             ${cmakesHtml}
1766                           </select>
1767                         </div>
1768
1769                         <div class="flex items-center mb-2">
1770                           <input type="radio" id="cmake-radio-path-executable" name="cmake-version-radio" value="3" class="mr-1 text-blue-500">
1771                           <label for="cmake-radio-path-executable" class="text-gray-900 dark:text-white">Path to executable:</label>
1772                           <input type="file" id="cmake-path-executable" multiple="false" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 ms-2">
1773                         </div>
1774                       </div>
1775
1776                       ${
1777                         process.platform === "darwin" ||
1778                         process.platform === "win32"
1779                           ? `
1780                           <div class="col-span-2">
1781                             <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Python Version:</label>
1782
1783                             ${
1784                               this._versionBundle !== undefined
1785                                 ? `<div class="flex items-center mb-2">
1786                                     <input type="radio" id="python-radio-default-version" name="python-version-radio" value="0" class="mr-1 text-blue-500 requires-version-bundle">
1787                                     <label for="python-radio-default-version" class="text-gray-900 dark:text-white">Default version</label>
1788                                   </div>`
1789                                 : ""
1790                             }
1791
1792                             ${
1793                               isPythonSystemAvailable
1794                                 ? `<div class="flex items-center mb-2" >
1795                                     <input type="radio" id="python-radio-system-version" name="python-version-radio" value="1" class="mr-1 text-blue-500">
1796                                     <label for="python-radio-system-version" class="text-gray-900 dark:text-white">Use system version</label>
1797                                   </div>`
1798                                 : ""
1799                             }
1800
1801                             <div class="flex items-center mb-2">
1802                               <input type="radio" id="python-radio-path-executable" name="python-version-radio" value="2" class="mr-1 text-blue-500">
1803                               <label for="python-radio-path-executable" class="text-gray-900 dark:text-white">Path to executable:</label>
1804                               <input type="file" id="python-path-executable" multiple="false" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 ms-2">
1805                             </div>
1806                           </div>`
1807                           : ""
1808                       }
1809                     </div>
1810                 </form>
1811             </div>
1812             ${
1813               !this._isProjectImport
1814                 ? `<div id="section-features" class="snap-start mt-10 project-options">
1815                 <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-8">Features</h3>
1816                 <ul class="mb-2 items-center w-full text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg sm:flex dark:bg-gray-700 dark:border-gray-600 dark:text-white">
1817                     <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1818                         <div class="flex items-center pl-3">
1819                             <input id="spi-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1820                             <label for="spi-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">SPI</label>
1821                         </div>
1822                     </li>
1823                     <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1824                         <div class="flex items-center pl-3">
1825                             <input id="pio-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1826                             <label for="pio-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">PIO interface</label>
1827                         </div>
1828                     </li>
1829                     <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1830                         <div class="flex items-center pl-3">
1831                             <input id="i2c-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1832                             <label for="i2c-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">I2C interface</label>
1833                         </div>
1834                     </li>
1835                     <li class="w-full dark:border-gray-600">
1836                         <div class="flex items-center pl-3">
1837                             <input id="dma-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1838                             <label for="dma-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">DMA support</label>
1839                         </div>
1840                     </li>
1841                 </ul>
1842                 <ul class="items-center w-full text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg sm:flex dark:bg-gray-700 dark:border-gray-600 dark:text-white">
1843                     <li class="w-full dark:border-gray-600">
1844                         <div class="flex items-center pl-3">
1845                             <input id="hwwatchdog-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1846                             <label for="hwwatchdog-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">HW watchdog</label>
1847                         </div>
1848                     </li>
1849                     <li class="w-full dark:border-gray-600">
1850                         <div class="flex items-center pl-3">
1851                             <input id="hwclocks-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1852                             <label for="hwclocks-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">HW clocks</label>
1853                         </div>
1854                     </li>
1855                     <li class="w-full dark:border-gray-600">
1856                         <div class="flex items-center pl-3">
1857                             <input id="hwinterpolation-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1858                             <label for="hwinterpolation-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">HW interpolation</label>
1859                         </div>
1860                     </li>
1861                     <li class="w-full dark:border-gray-600">
1862                         <div class="flex items-center pl-3">
1863                             <input id="hwtimer-features-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1864                             <label for="hwtimer-features-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">HW timer</label>
1865                         </div>
1866                     </li>
1867                 </ul>
1868             </div>
1869             `
1870                 : ""
1871             }
1872             ${
1873               !this._isProjectImport
1874                 ? `<div id="section-stdio" class="snap-start mt-10 project-options">
1875                 <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-8">Stdio support</h3>
1876                 <ul class="mb-2 items-center w-full text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg sm:flex dark:bg-gray-700 dark:border-gray-600 dark:text-white">
1877                   <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1878                     <div class="flex items-center pl-3">
1879                       <input id="uart-stdio-support-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1880                       <label for="uart-stdio-support-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Console over UART</label>
1881                     </div>
1882                   </li>
1883                   <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1884                     <div class="flex items-center pl-3">
1885                       <input id="usb-stdio-support-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1886                       <label for="usb-stdio-support-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Console over USB (disables other USB use)</label>
1887                     </div>
1888                   </li>
1889                 </ul>
1890             </div>
1891             <div id="section-pico-wireless" class="snap-start mt-10 project-options" hidden>
1892                 <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-8">Pico wireless options</h3>
1893                 <div class="flex items-stretch space-x-4">
1894                     <div class="flex items-center px-4 py-2 border border-gray-200 rounded dark:border-gray-700">
1895                         <input checked id="pico-wireless-radio-none" type="radio" value="0" name="pico-wireless-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 outline-none focus:ring-0 focus:ring-offset-5 dark:bg-gray-700 dark:border-gray-600">
1896                         <label for="pico-wireless-radio-none" class="w-full py-4 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">None</label>
1897                     </div>
1898                     <div class="flex items-center px-4 py-2 border border-gray-200 rounded dark:border-gray-700">
1899                         <input id="pico-wireless-radio-led" type="radio" value="1" name="pico-wireless-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 outline-none focus:ring-0 focus:ring-offset-5 dark:bg-gray-700 dark:border-gray-600">
1900                         <label for="pico-wireless-radio-led" class="w-full py-4 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Pico W onboard LED</label>
1901                     </div>
1902                     <div class="flex items-center px-4 py-2 border border-gray-200 rounded dark:border-gray-700">
1903                         <input id="pico-wireless-radio-pool" type="radio" value="2" name="pico-wireless-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 outline-none focus:ring-0 focus:ring-offset-5 dark:bg-gray-700 dark:border-gray-600">
1904                         <label for="pico-wireless-radio-pool" class="w-full py-4 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Polled lwIP</label>
1905                     </div>
1906                     <div class="flex items-center px-4 py-2 border border-gray-200 rounded dark:border-gray-700">
1907                         <input id="pico-wireless-radio-background" type="radio" value="3" name="pico-wireless-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 outline-none focus:ring-0 focus:ring-offset-5 dark:bg-gray-700 dark:border-gray-600">
1908                         <label for="pico-wireless-radio-background" class="w-full py-4 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Background lwIP</label>
1909                     </div>
1910                 </div>
1911             </div>
1912             <div id="section-code-gen" class="snap-start mt-10 project-options">
1913                 <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-8">Code generation options</h3>
1914                 <ul class="mb-2 items-center w-full text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg sm:flex dark:bg-gray-700 dark:border-gray-600 dark:text-white">
1915                     <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1916                         <div class="flex items-center pl-3">
1917                             <input id="add-examples-code-gen-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1918                             <label for="add-examples-code-gen-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Add examples from Pico library</label>
1919                         </div>
1920                     </li>
1921                     <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1922                         <div class="flex items-center pl-3">
1923                             <input id="run-from-ram-code-gen-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1924                             <label for="run-from-ram-code-gen-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Run the program from RAM rather than flash</label>
1925                         </div>
1926                     </li>
1927                 </ul>
1928                 <ul class="items-center w-full text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg sm:flex dark:bg-gray-700 dark:border-gray-600 dark:text-white">
1929                   <li class="w-full border-b border-gray-200 sm:border-b-0 sm:border-r dark:border-gray-600">
1930                     <div class="flex items-center pl-3">
1931                       <input id="cpp-code-gen-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1932                       <label for="cpp-code-gen-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Generate C++ code</label>
1933                     </div>
1934                   </li>
1935                   <li class="w-full dark:border-gray-600">
1936                     <div class="flex items-center pl-3">
1937                       <input id="cpp-rtti-code-gen-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1938                       <label for="cpp-rtti-code-gen-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Enable C++ RTTI (Uses more memory)</label>
1939                     </div>
1940                   </li>    
1941                   <li class="w-full dark:border-gray-600">
1942                     <div class="flex items-center pl-3">
1943                       <input id="cpp-exceptions-code-gen-cblist" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500">
1944                       <label for="cpp-exceptions-code-gen-cblist" class="w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">Enable C++ exceptions (Uses more memory)</label>
1945                     </div>
1946                   </li>
1947                 </ul>
1948             </div>`
1949                 : ""
1950             }
1951             <div id="section-debugger" class="snap-start mt-10">
1952                 <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-8">Debugger</h3>
1953                 <div class="flex items-stretch space-x-4">
1954                     <div class="flex items-center px-4 py-2 border border-gray-200 rounded dark:border-gray-700">
1955                         <input checked id="debugger-radio-debug-probe" type="radio" value="0" name="debugger-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 outline-none focus:ring-0 focus:ring-offset-5 dark:bg-gray-700 dark:border-gray-600">
1956                         <label for="debugger-radio-debug-probe" class="w-full py-4 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">DebugProbe (CMSIS-DAP) [Default]</label>
1957                     </div>
1958                     <div class="flex items-center px-4 py-2 border border-gray-200 rounded dark:border-gray-700">
1959                         <input id="debugger-radio-swd" type="radio" value="1" name="debugger-radio" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 outline-none focus:ring-0 focus:ring-offset-5 dark:bg-gray-700 dark:border-gray-600">
1960                         <label for="debugger-radio-swd" class="w-full py-4 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">SWD (Pi host)</label>
1961                     </div>
1962                 </div>
1963             </div>
1964             <div class="bottom-3 mt-8 mb-12 w-full flex justify-end">
1965                 <button id="btn-advanced-options" class="focus:outline-none bg-transparent ring-2 focus:ring-4 ring-yellow-400 dark:ring-yellow-700 font-medium rounded-lg text-lg px-4 py-2 mr-4 hover:bg-yellow-500 dark:hover:bg-yellow-700 focus:ring-yellow-600 dark:focus:ring-yellow-800">Show Advanced Options</button>
1966                 <button id="btn-cancel" class="focus:outline-none bg-transparent ring-2 focus:ring-4 ring-red-400 dark:ring-red-700 font-medium rounded-lg text-lg px-4 py-2 mr-4 hover:bg-red-500 dark:hover:bg-red-700 focus:ring-red-600 dark:focus:ring-red-800">Cancel</button>
1967                 <button id="btn-create" class="focus:outline-none bg-transparent ring-2 focus:ring-4 ring-green-400 dark:ring-green-700 font-medium rounded-lg text-lg px-4 py-2 mr-2 hover:bg-green-500 dark:hover:bg-green-700 focus:ring-green-600 dark:focus:ring-green-800">
1968                   ${this._isProjectImport ? "Import" : "Create"}
1969                 </button>
1970             </div>
1971         </main>
1972
1973         <script nonce="${nonce}" src="${navScriptUri.toString()}"></script>
1974         <script nonce="${nonce}" src="${stateScriptUri.toString()}"></script>
1975         <script nonce="${nonce}" src="${mainScriptUri.toString()}"></script>
1976       </body>
1977     </html>`;
1978   }
1979
1980   private _runGenerator(
1981     command: string,
1982     options: ExecOptions
1983   ): Promise<number | null> {
1984     return new Promise<number | null>(resolve => {
1985       const generatorProcess = exec(
1986         command,
1987         options,
1988         (error, stdout, stderr) => {
1989           this._logger.debug(stdout);
1990           this._logger.info(stderr);
1991           if (error) {
1992             this._logger.error(`Generator Process error: ${error.message}`);
1993             resolve(null); // indicate error
1994           }
1995         }
1996       );
1997
1998       generatorProcess.on("exit", code => {
1999         // Resolve with exit code or -1 if code is undefined
2000         resolve(code);
2001       });
2002     });
2003   }
2004
2005   // TODO: move out of NewProjectPanel class
2006   /**
2007    * Executes the Pico Project Generator with the given options.
2008    *
2009    * @param options {@link NewProjectOptions} to pass to the Pico Project Generator
2010    */
2011   private async _executePicoProjectGenerator(
2012     options:
2013       | NewProjectOptions
2014       | NewExampleBasedProjectOptions
2015       | ImportProjectOptions,
2016     pythonExe: string,
2017     isExampleBased: boolean = false
2018   ): Promise<void> {
2019     const customEnv: { [key: string]: string } = {
2020       ...(process.env as { [key: string]: string }),
2021       // set PICO_SDK_PATH
2022       ["PICO_SDK_PATH"]: options.toolchainAndSDK.sdkPath,
2023       // set PICO_TOOLCHAIN_PATH
2024       ["PICO_TOOLCHAIN_PATH"]: options.toolchainAndSDK.toolchainPath,
2025     };
2026     // add compiler to PATH
2027     const isWindows = process.platform === "win32";
2028     /*customEnv["PYTHONHOME"] = pythonExe.includes("/")
2029       ? resolve(join(dirname(pythonExe), ".."))
2030       : "";*/
2031     customEnv[isWindows ? "Path" : "PATH"] = `${join(
2032       options.toolchainAndSDK.toolchainPath,
2033       "bin"
2034     )}${
2035       options.cmakeExecutable.includes("/")
2036         ? `${isWindows ? ";" : ":"}${dirname(options.cmakeExecutable)}`
2037         : ""
2038     }${
2039       options.ninjaExecutable.includes("/")
2040         ? `${isWindows ? ";" : ":"}${dirname(options.ninjaExecutable)}`
2041         : ""
2042     }${isWindows ? ";" : ":"}${customEnv[isWindows ? "Path" : "PATH"]}`;
2043
2044     // convert the selected board type to a vaild option
2045     // for the project generator
2046     let boardTypeFromEnum = "";
2047     if ("boardType" in options) {
2048       try {
2049         boardTypeFromEnum = await enumToBoard(
2050           options.boardType,
2051           options.toolchainAndSDK.sdkPath
2052         );
2053       } catch {
2054         await window.showErrorMessage(
2055           "Unknown board type: " + options.boardType
2056         );
2057
2058         return;
2059       }
2060     }
2061
2062     const basicNewProjectOptions: string[] =
2063       "consoleOptions" in options
2064         ? [
2065             boardTypeFromEnum,
2066             ...options.consoleOptions.map(option => enumToParam(option)),
2067             !options.consoleOptions.includes(ConsoleOption.consoleOverUART)
2068               ? "-nouart"
2069               : "",
2070           ]
2071         : "boardType" in options
2072         ? [boardTypeFromEnum]
2073         : [];
2074     if (!("boardType" in options) && this._isProjectImport) {
2075       try {
2076         const cmakel = await readFile(
2077           this._isProjectImport
2078             ? join(options.projectRoot, "CMakeLists.txt")
2079             : join(
2080                 options.projectRoot,
2081                 (options as NewExampleBasedProjectOptions).name,
2082                 "CMakeLists.txt"
2083               ),
2084           "utf-8"
2085         );
2086         const match = /set\(PICO_BOARD ([A-Za-z_0-9]+) /.exec(cmakel);
2087         if (match && match[1]) {
2088           basicNewProjectOptions.push(`-board ${match[1]}`);
2089         }
2090       } catch {
2091         /* ignore */
2092       }
2093     } else if (
2094       "boardType" in options &&
2095       isExampleBased &&
2096       "name" in options &&
2097       "libNames" in options
2098     ) {
2099       for (const libName of options.libNames) {
2100         basicNewProjectOptions.push(`-examLibs ${libName}`);
2101       }
2102     }
2103
2104     const libraryAndCodeGenerationOptions: string[] =
2105       "libraries" in options && "codeOptions" in options
2106         ? [
2107             ...options.libraries.map(option => enumToParam(option)),
2108             ...options.codeOptions.map(option => enumToParam(option)),
2109           ]
2110         : "name" in options // no libraries and codeOptions but name means it's an example based project
2111         ? ["-exam"]
2112         : // no libraries and codeOptions and no name means it's an imported project
2113           ["-conv"];
2114
2115     const projectName =
2116       "name" in options
2117         ? options.name
2118         : // no name in options means importing project so the folder name is assumed to be the project name
2119           options.projectRoot.substring(
2120             options.projectRoot.lastIndexOf("/") + 1
2121           );
2122
2123     const command: string = [
2124       `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`,
2125       `"${joinPosix(getScriptsRoot(), "pico_project.py")}"`,
2126       ...basicNewProjectOptions,
2127       ...libraryAndCodeGenerationOptions,
2128       enumToParam(options.debugger),
2129       // generate .vscode config
2130       "--project",
2131       "vscode",
2132       "--projectRoot",
2133       // cause import project not the project root but the project folder is selected
2134       `"${
2135         this._isProjectImport
2136           ? options.projectRoot.substring(
2137               0,
2138               options.projectRoot.lastIndexOf("/")
2139             )
2140           : isWindows
2141           ? options.projectRoot
2142           : options.projectRoot.replaceAll("\\", "\\\\")
2143       }"`,
2144       "--sdkVersion",
2145       options.toolchainAndSDK.sdkVersion,
2146       "--toolchainVersion",
2147       options.toolchainAndSDK.toolchainVersion,
2148       "--picotoolVersion",
2149       options.toolchainAndSDK.picotoolVersion,
2150       "--openOCDVersion",
2151       options.toolchainAndSDK.openOCDVersion,
2152       "--ninjaPath",
2153       `"${options.ninjaExecutable}"`,
2154       "--cmakePath",
2155       `"${options.cmakeExecutable}"`,
2156
2157       // set custom python executable path used flag if python executable is not in PATH
2158       pythonExe.includes("/") ? "-cupy" : "",
2159       `"${projectName}"`,
2160     ].join(" ");
2161
2162     this._logger.debug(`Executing project generator command: ${command}`);
2163
2164     // execute command
2165     // TODO: use exit codes to determine why the project generator failed (if it did)
2166     // to be able to show the user a more detailed error message
2167     const generatorExitCode = await this._runGenerator(command, {
2168       env: customEnv,
2169       cwd: getScriptsRoot(),
2170       windowsHide: true,
2171     });
2172     if (
2173       (process.platform === "linux" && generatorExitCode === null) ||
2174       generatorExitCode === 0
2175     ) {
2176       void window.showInformationMessage(
2177         `Successfully generated new project: ${projectName}`
2178       );
2179
2180       const folderAlreadyOpen = workspace.workspaceFolders?.some(
2181         f => f.uri.fsPath === options.projectRoot
2182       );
2183
2184       // open new folder
2185       void commands.executeCommand(
2186         "vscode.openFolder",
2187         Uri.file(
2188           this._isProjectImport
2189             ? options.projectRoot
2190             : join(options.projectRoot, projectName)
2191         ),
2192         (workspace.workspaceFolders?.length ?? 0) > 0
2193       );
2194
2195       // restart the extension if the folder was already open cause then vscode won't
2196       // automatically reload the window
2197       if (folderAlreadyOpen) {
2198         void commands.executeCommand("workbench.action.reloadWindow");
2199       }
2200     } else {
2201       this._logger.error(
2202         `Generator Process exited with code: ${generatorExitCode ?? "null"}`
2203       );
2204
2205       void window.showErrorMessage(
2206         `Could not create new project: ${projectName}`
2207       );
2208     }
2209   }
2210 }
2211
2212 export function getNonce(): string {
2213   let text = "";
2214   const possible =
2215     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2216   for (let i = 0; i < 32; i++) {
2217     text += possible.charAt(Math.floor(Math.random() * possible.length));
2218   }
2219
2220   return text;
2221 }
This page took 0.212507 seconds and 4 git commands to generate.