1 import { Uri, commands, window } from "vscode";
2 import { Command } from "./command.mjs";
3 import Logger from "../logger.mjs";
4 import { dirname, join } from "path";
5 import { fileURLToPath } from "url";
6 import { type ExecOptions, exec } from "child_process";
7 import { detectInstalledSDKs } from "../utils/picoSDKUtil.mjs";
8 import type Settings from "../settings.mjs";
11 showRquirementsNotMetErrorMessage,
12 } from "../utils/requirementsUtil.mjs";
13 import { redirectVSCodeConfig } from "../utils/vscodeConfigUtil.mjs";
14 import { compare } from "semver";
15 import { SettingsKey } from "../settings.mjs";
23 consoleOverUART = "Console over UART",
24 consoleOverUSB = "Console over USB (disables other USB use)",
32 interp = "HW interpolation",
34 watch = "HW watchdog",
38 function enumToParam(e: BoardType | ConsoleOptions | Libraries): string {
43 return "-board pico_w";
44 case ConsoleOptions.consoleOverUART:
46 case ConsoleOptions.consoleOverUSB:
56 case Libraries.interp:
62 case Libraries.clocks:
65 throw new Error(`Unknown enum value: ${e as string}`);
69 interface NewProjectOptions {
73 consoleOptions: ConsoleOptions[];
74 libraries: Libraries[];
77 function getScriptsRoot(): string {
78 return join(dirname(fileURLToPath(import.meta.url)), "..", "scripts");
81 export default class NewProjectCommand extends Command {
82 private readonly _logger: Logger = new Logger("NewProjectCommand");
83 private readonly _settings: Settings;
85 constructor(settings: Settings) {
88 this._settings = settings;
91 async execute(): Promise<void> {
92 // check if all requirements are met
93 if (!(await checkForRequirements(this._settings))) {
94 void showRquirementsNotMetErrorMessage();
99 // TODO: maybe make it posible to also select a folder and
100 // not always create a new one with selectedName
101 const projectRoot: Uri[] | undefined = await window.showOpenDialog({
102 canSelectFiles: false,
103 canSelectFolders: true,
104 canSelectMany: false,
105 openLabel: "Select project root",
108 if (!projectRoot || projectRoot.length !== 1) {
113 const selectedName: string | undefined = await window.showInputBox({
114 placeHolder: "Enter a project name",
115 title: "New Pico Project",
122 // get board type (single selection)
123 const selectedBoardType: BoardType | undefined =
124 (await window.showQuickPick(Object.values(BoardType), {
125 placeHolder: "Select a board type",
126 title: "New Pico Project",
127 })) as BoardType | undefined;
129 if (!selectedBoardType) {
133 // [optional] get console options (multi selection)
134 const selectedConsoleOptions: ConsoleOptions[] | undefined =
135 (await window.showQuickPick(Object.values(ConsoleOptions), {
136 placeHolder: "Would you like to enable the USB console?",
137 title: "New Pico Project",
139 })) as ConsoleOptions[] | undefined;
141 if (!selectedConsoleOptions) {
145 // [optional] get libraries (multi selection)
146 const selectedLibraries: Libraries[] | undefined =
147 (await window.showQuickPick(Object.values(Libraries), {
148 placeHolder: "Select libraries to include",
149 title: "New Pico Project",
151 })) as Libraries[] | undefined;
153 if (!selectedLibraries) {
157 await this.executePicoProjectGenerator({
159 projectRoot: projectRoot[0].fsPath,
160 boardType: selectedBoardType,
161 consoleOptions: selectedConsoleOptions,
162 libraries: selectedLibraries,
166 private runGenerator(
169 ): Promise<number | null> {
170 return new Promise<number | null>(resolve => {
171 const generatorProcess = exec(command, options, error => {
173 console.error(`Error: ${error.message}`);
174 resolve(null); // Indicate error
178 generatorProcess.on("exit", code => {
179 // Resolve with exit code or -1 if code is undefined
186 * Executes the Pico Project Generator with the given options
188 * @param options {@link NewProjectOptions} to pass to the Pico Project Generator
190 private async executePicoProjectGenerator(
191 options: NewProjectOptions
193 /*const [PICO_SDK_PATH, COMPILER_PATH] = (await getSDKAndToolchainPath(
196 const installedSDKs = detectInstalledSDKs().sort((a, b) =>
197 compare(a.version, b.version)
201 installedSDKs.length === 0 ||
202 // "protection" against empty settings
203 installedSDKs[0].sdkPath === "" ||
204 installedSDKs[0].toolchainPath === ""
206 void window.showErrorMessage(
207 "Could not find Pico SDK or Toolchain. Please check the wiki."
213 const PICO_SDK_PATH = installedSDKs[0].sdkPath;
214 const TOOLCHAIN_PATH = installedSDKs[0].toolchainPath;
216 const customEnv: { [key: string]: string } = {
218 // eslint-disable-next-line @typescript-eslint/naming-convention
219 PICO_SDK_PATH: PICO_SDK_PATH,
220 // eslint-disable-next-line @typescript-eslint/naming-convention
221 PICO_TOOLCHAIN_PATH: TOOLCHAIN_PATH,
224 process.platform === "win32" ? "Path" : "PATH"
225 ] = `${TOOLCHAIN_PATH}:${
226 customEnv[process.platform === "win32" ? "Path" : "PATH"]
229 this._settings.getString(SettingsKey.python3Path) ||
230 process.platform === "win32"
234 const command: string = [
236 join(getScriptsRoot(), "pico_project.py"),
237 enumToParam(options.boardType),
238 ...options.consoleOptions.map(option => enumToParam(option)),
239 !options.consoleOptions.includes(ConsoleOptions.consoleOverUART)
242 ...options.libraries.map(option => enumToParam(option)),
243 // generate .vscode config
247 `"${options.projectRoot}"`,
251 this._logger.debug(`Executing project generator command: ${command}`);
254 // TODO: use exit codes to determine why the project generator failed (if it did)
255 // to be able to show the user a more detailed error message
256 const generatorExitCode = await this.runGenerator(command, {
258 cwd: getScriptsRoot(),
262 if (generatorExitCode === 0) {
263 await redirectVSCodeConfig(
264 join(options.projectRoot, options.name, ".vscode"),
265 this._settings.getExtensionId(),
266 this._settings.getExtensionName(),
269 installedSDKs[0].version
271 void window.showInformationMessage(
272 `Successfully created project: ${options.name}`
275 void commands.executeCommand(
277 Uri.file(join(options.projectRoot, options.name)),
282 `Generator Process exited with code: ${generatorExitCode ?? "null"}`
285 void window.showErrorMessage(`Could not create project ${options.name}`);