From eb5dc1baabd30bdfcbf1fc9a960c1e7456777322 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Thu, 21 Mar 2024 08:57:18 -0700 Subject: [PATCH] Add createProject command (#1286) --- vscode/package.json | 11 +++++++ vscode/src/createProject.ts | 66 +++++++++++++++++++++++++++++++++++++ vscode/src/extension.ts | 2 ++ vscode/src/memfs.ts | 6 ++++ vscode/src/telemetry.ts | 5 +++ 5 files changed, 90 insertions(+) create mode 100644 vscode/src/createProject.ts diff --git a/vscode/package.json b/vscode/package.json index 1793877251..2be9e503f8 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -219,6 +219,12 @@ "command": "qsharp-vscode.workspacePythonCode", "when": "view == quantum-workspaces && viewItem == workspace" } + ], + "explorer/context": [ + { + "command": "qsharp-vscode.createProject", + "when": "explorerResourceIsFolder" + } ] }, "views": { @@ -236,6 +242,11 @@ } ], "commands": [ + { + "command": "qsharp-vscode.createProject", + "title": "Create Q# project", + "category": "Q#" + }, { "command": "qsharp-vscode.webOpener", "title": "Internal web opener", diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts new file mode 100644 index 0000000000..a46df2eb9e --- /dev/null +++ b/vscode/src/createProject.ts @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from "vscode"; +import { log, samples } from "qsharp-lang"; +import { EventType, sendTelemetryEvent } from "./telemetry"; + +export async function initProjectCreator(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.commands.registerCommand( + "qsharp-vscode.createProject", + async (folderUri: vscode.Uri | undefined) => { + sendTelemetryEvent(EventType.CreateProject, {}, {}); + + if (!folderUri) { + // This was run from the command palette, not the context menu, so create the project + // in the root of an open workspace folder. + const workspaceCount = vscode.workspace.workspaceFolders?.length || 0; + if (workspaceCount === 0) { + // No workspaces open + vscode.window.showErrorMessage( + "You must have a folder open to create a Q# project in", + ); + return; + } else if (workspaceCount === 1) { + folderUri = vscode.workspace.workspaceFolders![0].uri; + } else { + const workspaceChoice = await vscode.window.showWorkspaceFolderPick( + { placeHolder: "Pick the workspace folder for the project" }, + ); + if (!workspaceChoice) return; + folderUri = workspaceChoice.uri; + } + } + + const edit = new vscode.WorkspaceEdit(); + const projUri = vscode.Uri.joinPath(folderUri, "qsharp.json"); + const mainUri = vscode.Uri.joinPath(folderUri, "src", "Main.qs"); + + const sample = samples.find((elem) => elem.title === "Minimal"); + if (!sample) { + // Should never happen. + log.error("Unable to find the Minimal sample"); + return; + } + + edit.createFile(projUri); + edit.createFile(mainUri); + + edit.set(projUri, [ + new vscode.TextEdit(new vscode.Range(0, 0, 0, 0), "{}"), + ]); + edit.set(mainUri, [ + new vscode.TextEdit(new vscode.Range(0, 0, 0, 0), sample.code), + ]); + + // This doesn't throw on failure, it just returns false + if (!(await vscode.workspace.applyEdit(edit))) { + vscode.window.showErrorMessage( + "Unable to create the project. Check the project files don't already exist and that the file system is writable", + ); + } + }, + ), + ); +} diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 83f8c21cf0..8d7ba77b0b 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -48,6 +48,7 @@ import { sendTelemetryEvent, } from "./telemetry.js"; import { registerWebViewCommands } from "./webviewPanel.js"; +import { initProjectCreator } from "./createProject.js"; export async function activate( context: vscode.ExtensionContext, @@ -88,6 +89,7 @@ export async function activate( registerCreateNotebookCommand(context); registerWebViewCommands(context); initFileSystem(context); + initProjectCreator(context); log.info("Q# extension activated."); diff --git a/vscode/src/memfs.ts b/vscode/src/memfs.ts index c553f21334..fdd7eeca5c 100644 --- a/vscode/src/memfs.ts +++ b/vscode/src/memfs.ts @@ -259,6 +259,8 @@ export class MemFS implements vscode.FileSystemProvider { newUri: vscode.Uri, options: { overwrite: boolean }, ): void { + log.debug(`rename: ${oldUri.path} to ${newUri.path}`); + if (!options.overwrite && this._lookup(newUri, true)) { throw vscode.FileSystemError.FileExists(newUri); } @@ -280,6 +282,8 @@ export class MemFS implements vscode.FileSystemProvider { } delete(uri: vscode.Uri): void { + log.debug(`delete: ${uri.path}`); + const dirName = uri.with({ path: dirname(uri.path) }); const baseName = basename(uri.path); const parent = this._lookupAsDirectory(dirName, false); @@ -296,6 +300,8 @@ export class MemFS implements vscode.FileSystemProvider { } createDirectory(uri: vscode.Uri): void { + log.debug(`createDirectory: ${uri.path}`); + const baseName = basename(uri.path); const dirName = uri.with({ path: dirname(uri.path) }); const parent = this._lookupAsDirectory(dirName, false); diff --git a/vscode/src/telemetry.ts b/vscode/src/telemetry.ts index 8b060e5e05..02db80ab92 100644 --- a/vscode/src/telemetry.ts +++ b/vscode/src/telemetry.ts @@ -42,6 +42,7 @@ export enum EventType { HistogramEnd = "Qsharp.HistogramEnd", FormatStart = "Qsharp.FormatStart", FormatEnd = "Qsharp.FormatEnd", + CreateProject = "Qsharp.CreateProject", } type Empty = { [K in any]: never }; @@ -216,6 +217,10 @@ type EventTypes = { properties: { associationId: string }; measurements: { timeToCompleteMs: number; numberOfEdits: number }; }; + [EventType.CreateProject]: { + properties: Empty; + measurements: Empty; + }; }; export enum QsharpDocumentType {