From 0e88d5d97c026ad851f1da9aa806b7ed8d7a4389 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Fri, 27 Dec 2024 10:19:51 -0800 Subject: [PATCH 1/5] introduce import selector in vscode --- library/Registry.md | 22 -------- vscode/package.json | 9 ++++ vscode/src/createProject.ts | 101 ++++++++++++++++++++++++++++++++++++ vscode/src/registry.json | 52 +++++++++++++++++++ 4 files changed, 162 insertions(+), 22 deletions(-) delete mode 100644 library/Registry.md create mode 100644 vscode/src/registry.json diff --git a/library/Registry.md b/library/Registry.md deleted file mode 100644 index 7440e4379e..0000000000 --- a/library/Registry.md +++ /dev/null @@ -1,22 +0,0 @@ -# Q# Library Registry - -This document is a list of notable Q# library projects. If you have a library that you would like to add to this list, please open a pull request. If you have a Q# project that you'd like to share as a library, please see the [documentation on how to do this](https://learn.microsoft.com/en-us/azure/quantum/how-to-work-with-qsharp-projects?tabs=tabid-qsharp%2Ctabid-qsharp-run#configuring-q-projects-as-external-dependencies). - -## Unstable - -[link](https://github.com/microsoft/qsharp/tree/main/library/unstable) - -A general staging ground for some useful APIs that have not yet been stabilized into the standard library. - -## Signed - -[link](https://github.com/microsoft/qsharp/tree/main/library/signed) - -Defines types and functions to work with signed qubit-based integers. - -## FixedPoint - -[link](https://github.com/microsoft/qsharp/tree/main/library/fixed_point) - -Types and functions for fixed-point arithmetic with qubits. - diff --git a/vscode/package.json b/vscode/package.json index a96876a704..e54bb3da2d 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -223,6 +223,10 @@ "command": "qsharp-vscode.populateFilesList", "when": "resourceFilename == qsharp.json" }, + { + "command": "qsharp-vscode.importExternalPackage", + "when": "resourceFilename == qsharp.json" + }, { "command": "qsharp-vscode.addProjectReference", "when": "resourceFilename == qsharp.json" @@ -415,6 +419,11 @@ "category": "Q#", "title": "Populate qsharp.json files list" }, + { + "command": "qsharp-vscode.importExternalPackage", + "category": "Q#", + "title": "Import external Q# package" + }, { "command": "qsharp-vscode.addProjectReference", "category": "Q#", diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts index 2d6f59e48f..7f8c4d7b6c 100644 --- a/vscode/src/createProject.ts +++ b/vscode/src/createProject.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode"; import { log, samples } from "qsharp-lang"; import { EventType, sendTelemetryEvent } from "./telemetry"; import { qsharpExtensionId } from "./common"; +import registryJson from "./registry.json"; export async function initProjectCreator(context: vscode.ExtensionContext) { context.subscriptions.push( @@ -167,6 +168,106 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { ), ); + context.subscriptions.push( + vscode.commands.registerCommand( + `${qsharpExtensionId}.importExternalPackage`, + async (qsharpJsonUri: vscode.Uri | undefined) => { + // If called from the content menu qsharpJsonUri will be the full qsharp.json uri + // If called from the command palette is will be undefined, so use the active editor + log.info("importExternalPackage called with", qsharpJsonUri); + + qsharpJsonUri = + qsharpJsonUri ?? vscode.window.activeTextEditor?.document.uri; + if (!qsharpJsonUri) { + log.error( + "populateFilesList called, but argument or active editor is not qsharp.json", + ); + return; + } + + // First, verify the qsharp.json can be opened and is a valid json file + const qsharpJsonDoc = + await vscode.workspace.openTextDocument(qsharpJsonUri); + if (!qsharpJsonDoc) { + log.error("Unable to open the qsharp.json file at ", qsharpJsonDoc); + return; + } + + let manifestObj: any = {}; + try { + manifestObj = JSON.parse(qsharpJsonDoc.getText()); + } catch { + await vscode.window.showErrorMessage( + `Unable to parse the contents of ${qsharpJsonUri.path}`, + ); + return; + } + + // ask the user to pick a package to import + const packageChoice = await vscode.window.showQuickPick( + registryJson.knownPackages.map( + (pkg: { + name: string; + description: string; + dependency: object; + }) => ({ + label: pkg.name, + description: pkg.description, + }), + ), + { placeHolder: "Pick a package to import" }, + ); + + if (!packageChoice) { + log.info("User cancelled package choice"); + return; + } + + const chosenPackage = registryJson.knownPackages.find( + (pkg: { name: string; description: string; dependency: object }) => + pkg.name === packageChoice.label, + )!; + + const versionChoice = await vscode.window.showQuickPick( + chosenPackage.dependency.github.refs, + { placeHolder: "Pick a version to import" }, + ); + + if (!versionChoice) { + log.info("User cancelled version choice"); + return; + } + + // Update the dependencies property of the qsharp.json and write back to the document + if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; + manifestObj["dependencies"][packageChoice.label] = { + github: { + ref: versionChoice, + ...chosenPackage.dependency.github, + refs: undefined, + }, + }; + + // Apply the edits to the qsharp.json + const edit = new vscode.WorkspaceEdit(); + edit.replace( + qsharpJsonUri, + new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0), + JSON.stringify(manifestObj, null, 2), + ); + if (!(await vscode.workspace.applyEdit(edit))) { + vscode.window.showErrorMessage( + "Unable to update the qsharp.json file. Check the file is writable", + ); + return; + } + + // Bring the qsharp.json to the front for the user to save + await vscode.window.showTextDocument(qsharpJsonDoc); + }, + ), + ); + type LocalProjectRef = { path: string; // Absolute or relative path to the project dir }; diff --git a/vscode/src/registry.json b/vscode/src/registry.json new file mode 100644 index 0000000000..94a4ffd95c --- /dev/null +++ b/vscode/src/registry.json @@ -0,0 +1,52 @@ +{ + "knownPackages": [ + { + "name": "Signed", + "description": "Defines types and functions to work with signed qubit-based integers.", + "dependency": { + "github": { + "owner": "Microsoft", + "repo": "qsharp", + "refs": ["546b053"], + "path": "library/signed" + } + } + }, + { + "name": "FixedPoint", + "description": "Types and functions for fixed-point arithmetic with qubits.", + "dependency": { + "github": { + "owner": "Microsoft", + "repo": "qsharp", + "refs": ["546b053"], + "path": "library/signed" + } + } + }, + { + "name": "Rotations", + "description": "Defines types and functions to work with rotations.", + "dependency": { + "github": { + "owner": "Microsoft", + "repo": "qsharp", + "refs": ["546b053"], + "path": "library/rotations" + } + } + }, + { + "name": "Qtest", + "description": "Utilities for writing and running Q# tests.", + "dependency": { + "github": { + "owner": "Microsoft", + "repo": "qsharp", + "refs": ["546b053"], + "path": "library/qtest" + } + } + } + ] +} From e4806f820dba00e78a2465f679980d4c202467d3 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Fri, 27 Dec 2024 10:38:39 -0800 Subject: [PATCH 2/5] add ref descriptors --- vscode/src/createProject.ts | 5 ++++- vscode/src/registry.json | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts index 7f8c4d7b6c..07bd5acdc4 100644 --- a/vscode/src/createProject.ts +++ b/vscode/src/createProject.ts @@ -229,7 +229,10 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { )!; const versionChoice = await vscode.window.showQuickPick( - chosenPackage.dependency.github.refs, + chosenPackage.dependency.github.refs.map(({ ref, notes }) => ({ + label: ref, + description: notes, + })), { placeHolder: "Pick a version to import" }, ); diff --git a/vscode/src/registry.json b/vscode/src/registry.json index 94a4ffd95c..3d00fa1708 100644 --- a/vscode/src/registry.json +++ b/vscode/src/registry.json @@ -7,7 +7,10 @@ "github": { "owner": "Microsoft", "repo": "qsharp", - "refs": ["546b053"], + "refs": [ + { "ref": "546b053", "notes": "latest stable" }, + { "ref": "main", "notes": "nightly, unstable" } + ], "path": "library/signed" } } @@ -19,7 +22,10 @@ "github": { "owner": "Microsoft", "repo": "qsharp", - "refs": ["546b053"], + "refs": [ + { "ref": "546b053", "notes": "latest stable" }, + { "ref": "main", "notes": "nightly, unstable" } + ], "path": "library/signed" } } @@ -31,7 +37,10 @@ "github": { "owner": "Microsoft", "repo": "qsharp", - "refs": ["546b053"], + "refs": [ + { "ref": "546b053", "notes": "latest stable" }, + { "ref": "main", "notes": "nightly, unstable" } + ], "path": "library/rotations" } } @@ -43,7 +52,10 @@ "github": { "owner": "Microsoft", "repo": "qsharp", - "refs": ["546b053"], + "refs": [ + { "ref": "546b053", "notes": "latest stable" }, + { "ref": "main", "notes": "nightly, unstable" } + ], "path": "library/qtest" } } From 2a4bd6630f99cde1ee4bd18d3a0d29c097b5f2a7 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Fri, 27 Dec 2024 10:41:38 -0800 Subject: [PATCH 3/5] whoops need to update label --- vscode/src/createProject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts index 07bd5acdc4..d737fd7380 100644 --- a/vscode/src/createProject.ts +++ b/vscode/src/createProject.ts @@ -245,7 +245,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; manifestObj["dependencies"][packageChoice.label] = { github: { - ref: versionChoice, + ref: versionChoice.label, ...chosenPackage.dependency.github, refs: undefined, }, From 53917ed107eb5533a58f648f6fdd58939f3b1bb2 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 31 Dec 2024 12:59:54 -0800 Subject: [PATCH 4/5] it works: refactor done --- library/fixed_point/qsharp.json | 2 +- vscode/package.json | 9 -- vscode/src/createProject.ts | 233 ++++++++++++++------------------ 3 files changed, 101 insertions(+), 143 deletions(-) diff --git a/library/fixed_point/qsharp.json b/library/fixed_point/qsharp.json index 976c27aa25..ce625648fa 100644 --- a/library/fixed_point/qsharp.json +++ b/library/fixed_point/qsharp.json @@ -24,4 +24,4 @@ "src/Tests.qs", "src/Types.qs" ] -} +} \ No newline at end of file diff --git a/vscode/package.json b/vscode/package.json index e54bb3da2d..a96876a704 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -223,10 +223,6 @@ "command": "qsharp-vscode.populateFilesList", "when": "resourceFilename == qsharp.json" }, - { - "command": "qsharp-vscode.importExternalPackage", - "when": "resourceFilename == qsharp.json" - }, { "command": "qsharp-vscode.addProjectReference", "when": "resourceFilename == qsharp.json" @@ -419,11 +415,6 @@ "category": "Q#", "title": "Populate qsharp.json files list" }, - { - "command": "qsharp-vscode.importExternalPackage", - "category": "Q#", - "title": "Import external Q# package" - }, { "command": "qsharp-vscode.addProjectReference", "category": "Q#", diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts index d737fd7380..70972c41a3 100644 --- a/vscode/src/createProject.ts +++ b/vscode/src/createProject.ts @@ -168,108 +168,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { ), ); - context.subscriptions.push( - vscode.commands.registerCommand( - `${qsharpExtensionId}.importExternalPackage`, - async (qsharpJsonUri: vscode.Uri | undefined) => { - // If called from the content menu qsharpJsonUri will be the full qsharp.json uri - // If called from the command palette is will be undefined, so use the active editor - log.info("importExternalPackage called with", qsharpJsonUri); - - qsharpJsonUri = - qsharpJsonUri ?? vscode.window.activeTextEditor?.document.uri; - if (!qsharpJsonUri) { - log.error( - "populateFilesList called, but argument or active editor is not qsharp.json", - ); - return; - } - - // First, verify the qsharp.json can be opened and is a valid json file - const qsharpJsonDoc = - await vscode.workspace.openTextDocument(qsharpJsonUri); - if (!qsharpJsonDoc) { - log.error("Unable to open the qsharp.json file at ", qsharpJsonDoc); - return; - } - - let manifestObj: any = {}; - try { - manifestObj = JSON.parse(qsharpJsonDoc.getText()); - } catch { - await vscode.window.showErrorMessage( - `Unable to parse the contents of ${qsharpJsonUri.path}`, - ); - return; - } - - // ask the user to pick a package to import - const packageChoice = await vscode.window.showQuickPick( - registryJson.knownPackages.map( - (pkg: { - name: string; - description: string; - dependency: object; - }) => ({ - label: pkg.name, - description: pkg.description, - }), - ), - { placeHolder: "Pick a package to import" }, - ); - - if (!packageChoice) { - log.info("User cancelled package choice"); - return; - } - - const chosenPackage = registryJson.knownPackages.find( - (pkg: { name: string; description: string; dependency: object }) => - pkg.name === packageChoice.label, - )!; - - const versionChoice = await vscode.window.showQuickPick( - chosenPackage.dependency.github.refs.map(({ ref, notes }) => ({ - label: ref, - description: notes, - })), - { placeHolder: "Pick a version to import" }, - ); - - if (!versionChoice) { - log.info("User cancelled version choice"); - return; - } - - // Update the dependencies property of the qsharp.json and write back to the document - if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; - manifestObj["dependencies"][packageChoice.label] = { - github: { - ref: versionChoice.label, - ...chosenPackage.dependency.github, - refs: undefined, - }, - }; - - // Apply the edits to the qsharp.json - const edit = new vscode.WorkspaceEdit(); - edit.replace( - qsharpJsonUri, - new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0), - JSON.stringify(manifestObj, null, 2), - ); - if (!(await vscode.workspace.applyEdit(edit))) { - vscode.window.showErrorMessage( - "Unable to update the qsharp.json file. Check the file is writable", - ); - return; - } - // Bring the qsharp.json to the front for the user to save - await vscode.window.showTextDocument(qsharpJsonDoc); - }, - ), - ); type LocalProjectRef = { path: string; // Absolute or relative path to the project dir @@ -286,18 +185,6 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { type Dependency = LocalProjectRef | GitHubProjectRef; - // TODO: Replace with a list of legitimate known Q# projects on GitHub - const githubProjects: { [name: string]: GitHubProjectRef } = { - // Add a template to the end of the list users can use to easily add their own - "": { - github: { - owner: "", - repo: "", - ref: "", - }, - }, - }; - // Given two directory paths, return the relative path from the first to the second function getRelativeDirPath(from: string, to: string): string { // Ensure we have something @@ -369,13 +256,34 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { return; } + // ask the user if they want to import from github, or locally on their computer + const importChoice = await vscode.window.showQuickPick( + ["Import from GitHub", "Import from local directory"], + { placeHolder: "Pick a source to import from" }, + ); + + if (!importChoice) { + log.info("User cancelled import choice"); + return; + } + + if (importChoice === "Import from GitHub") { + await importExternalPackage(qsharpJsonDoc, qsharpJsonUri, manifestObj); + } else { + await importLocalPackage(qsharpJsonDoc, qsharpJsonDir, qsharpJsonUri, manifestObj); + } + + })); + + + async function importLocalPackage(qsharpJsonDoc: vscode.TextDocument, qsharpJsonDir: vscode.Uri, qsharpJsonUri: vscode.Uri, manifestObj: any) { // Find all the other Q# projects in the workspace const projectFiles = ( await vscode.workspace.findFiles("**/qsharp.json") ).filter((file) => file.toString() !== qsharpJsonUri.toString()); - + const projectChoices: Array<{ name: string; ref: Dependency }> = []; - + projectFiles.forEach((file) => { const dirName = file.path.slice(0, -"/qsharp.json".length); const relPath = getRelativeDirPath(qsharpJsonDir.path, dirName); @@ -386,14 +294,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { }, }); }); - - Object.keys(githubProjects).forEach((name) => { - projectChoices.push({ - name: name, - ref: githubProjects[name], - }); - }); - + // Convert any spaces, dashes, dots, tildes, or quotes in project names // to underscores. (Leave more 'exotic' non-identifier patterns to the user to fix) // @@ -403,10 +304,10 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { (val, idx, arr) => (arr[idx].name = val.name.replace(/[- "'.~]/g, "_")), ); - + const folderIcon = new vscode.ThemeIcon("folder"); const githubIcon = new vscode.ThemeIcon("github"); - + // Ask the user to pick a project to add as a reference const projectChoice = await vscode.window.showQuickPick( projectChoices.map((choice) => { @@ -428,17 +329,17 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { }), { placeHolder: "Pick a project to add as a reference" }, ); - + if (!projectChoice) { log.info("User cancelled project choice"); return; } - + log.info("User picked project: ", projectChoice); - + if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; manifestObj["dependencies"][projectChoice.label] = projectChoice.ref; - + // Apply the edits to the qsharp.json const edit = new vscode.WorkspaceEdit(); edit.replace( @@ -452,10 +353,76 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { ); return; } - + // Bring the qsharp.json to the front for the user to save await vscode.window.showTextDocument(qsharpJsonDoc); - }, - ), - ); + } + + async function importExternalPackage(qsharpJsonDoc: vscode.TextDocument, qsharpJsonUri: vscode.Uri, manifestObj: any) { + // ask the user to pick a package to import + const packageChoice = await vscode.window.showQuickPick( + registryJson.knownPackages.map( + (pkg: { + name: string; + description: string; + dependency: object; + }) => ({ + label: pkg.name, + description: pkg.description, + }), + ), + { placeHolder: "Pick a package to import" }, + ); + + if (!packageChoice) { + log.info("User cancelled package choice"); + return; + } + + const chosenPackage = registryJson.knownPackages.find( + (pkg: { name: string; description: string; dependency: object }) => + pkg.name === packageChoice.label, + )!; + + const versionChoice = await vscode.window.showQuickPick( + chosenPackage.dependency.github.refs.map(({ ref, notes }) => ({ + label: ref, + description: notes, + })), + { placeHolder: "Pick a version to import" }, + ); + + if (!versionChoice) { + log.info("User cancelled version choice"); + return; + } + + // Update the dependencies property of the qsharp.json and write back to the document + if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; + manifestObj["dependencies"][packageChoice.label] = { + github: { + ref: versionChoice.label, + ...chosenPackage.dependency.github, + refs: undefined, + }, + }; + + // Apply the edits to the qsharp.json + const edit = new vscode.WorkspaceEdit(); + edit.replace( + qsharpJsonUri, + new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0), + JSON.stringify(manifestObj, null, 2), + ); + if (!(await vscode.workspace.applyEdit(edit))) { + vscode.window.showErrorMessage( + "Unable to update the qsharp.json file. Check the file is writable", + ); + return; + } + + // Bring the qsharp.json to the front for the user to save + await vscode.window.showTextDocument(qsharpJsonDoc); + } + } From 12df50ae15e01dc7708a0a34b6beb748cbd8c87b Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 31 Dec 2024 13:05:31 -0800 Subject: [PATCH 5/5] finish combining new flag into old one --- vscode/src/createProject.ts | 310 ++++++++++++++++++------------------ 1 file changed, 155 insertions(+), 155 deletions(-) diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts index 70972c41a3..4c253c5e64 100644 --- a/vscode/src/createProject.ts +++ b/vscode/src/createProject.ts @@ -168,8 +168,6 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { ), ); - - type LocalProjectRef = { path: string; // Absolute or relative path to the project dir }; @@ -268,161 +266,163 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { } if (importChoice === "Import from GitHub") { - await importExternalPackage(qsharpJsonDoc, qsharpJsonUri, manifestObj); - } else { - await importLocalPackage(qsharpJsonDoc, qsharpJsonDir, qsharpJsonUri, manifestObj); - } - - })); - - - async function importLocalPackage(qsharpJsonDoc: vscode.TextDocument, qsharpJsonDir: vscode.Uri, qsharpJsonUri: vscode.Uri, manifestObj: any) { - // Find all the other Q# projects in the workspace - const projectFiles = ( - await vscode.workspace.findFiles("**/qsharp.json") - ).filter((file) => file.toString() !== qsharpJsonUri.toString()); - - const projectChoices: Array<{ name: string; ref: Dependency }> = []; - - projectFiles.forEach((file) => { - const dirName = file.path.slice(0, -"/qsharp.json".length); - const relPath = getRelativeDirPath(qsharpJsonDir.path, dirName); - projectChoices.push({ - name: dirName.slice(dirName.lastIndexOf("/") + 1), - ref: { - path: relPath, - }, - }); - }); - - // Convert any spaces, dashes, dots, tildes, or quotes in project names - // to underscores. (Leave more 'exotic' non-identifier patterns to the user to fix) - // - // Note: At some point we may want to detect/avoid duplicate names, e.g. if the user already - // references a project via 'foo', and they add a reference to a 'foo' on GitHub or in another dir. - projectChoices.forEach( - (val, idx, arr) => - (arr[idx].name = val.name.replace(/[- "'.~]/g, "_")), - ); - - const folderIcon = new vscode.ThemeIcon("folder"); - const githubIcon = new vscode.ThemeIcon("github"); - - // Ask the user to pick a project to add as a reference - const projectChoice = await vscode.window.showQuickPick( - projectChoices.map((choice) => { - if ("github" in choice.ref) { - return { - label: choice.name, - detail: `github://${choice.ref.github.owner}/${choice.ref.github.repo}#${choice.ref.github.ref}`, - iconPath: githubIcon, - ref: choice.ref, - }; - } else { - return { - label: choice.name, - detail: choice.ref.path, - iconPath: folderIcon, - ref: choice.ref, - }; - } - }), - { placeHolder: "Pick a project to add as a reference" }, - ); - - if (!projectChoice) { - log.info("User cancelled project choice"); - return; - } - - log.info("User picked project: ", projectChoice); - - if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; - manifestObj["dependencies"][projectChoice.label] = projectChoice.ref; - - // Apply the edits to the qsharp.json - const edit = new vscode.WorkspaceEdit(); - edit.replace( - qsharpJsonUri, - new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0), - JSON.stringify(manifestObj, null, 2), - ); - if (!(await vscode.workspace.applyEdit(edit))) { - vscode.window.showErrorMessage( - "Unable to update the qsharp.json file. Check the file is writable", + await importExternalPackage( + qsharpJsonDoc, + qsharpJsonUri, + manifestObj, ); - return; - } - - // Bring the qsharp.json to the front for the user to save - await vscode.window.showTextDocument(qsharpJsonDoc); - } - - async function importExternalPackage(qsharpJsonDoc: vscode.TextDocument, qsharpJsonUri: vscode.Uri, manifestObj: any) { - // ask the user to pick a package to import - const packageChoice = await vscode.window.showQuickPick( - registryJson.knownPackages.map( - (pkg: { - name: string; - description: string; - dependency: object; - }) => ({ - label: pkg.name, - description: pkg.description, - }), - ), - { placeHolder: "Pick a package to import" }, - ); - - if (!packageChoice) { - log.info("User cancelled package choice"); - return; - } - - const chosenPackage = registryJson.knownPackages.find( - (pkg: { name: string; description: string; dependency: object }) => - pkg.name === packageChoice.label, - )!; - - const versionChoice = await vscode.window.showQuickPick( - chosenPackage.dependency.github.refs.map(({ ref, notes }) => ({ - label: ref, - description: notes, - })), - { placeHolder: "Pick a version to import" }, - ); - - if (!versionChoice) { - log.info("User cancelled version choice"); - return; - } - - // Update the dependencies property of the qsharp.json and write back to the document - if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; - manifestObj["dependencies"][packageChoice.label] = { - github: { - ref: versionChoice.label, - ...chosenPackage.dependency.github, - refs: undefined, - }, - }; - - // Apply the edits to the qsharp.json - const edit = new vscode.WorkspaceEdit(); - edit.replace( - qsharpJsonUri, - new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0), - JSON.stringify(manifestObj, null, 2), - ); - if (!(await vscode.workspace.applyEdit(edit))) { - vscode.window.showErrorMessage( - "Unable to update the qsharp.json file. Check the file is writable", + } else { + await importLocalPackage( + qsharpJsonDoc, + qsharpJsonDir, + qsharpJsonUri, + manifestObj, ); - return; } - - // Bring the qsharp.json to the front for the user to save - await vscode.window.showTextDocument(qsharpJsonDoc); - } + }, + ), + ); + async function importLocalPackage( + qsharpJsonDoc: vscode.TextDocument, + qsharpJsonDir: vscode.Uri, + qsharpJsonUri: vscode.Uri, + manifestObj: any, + ) { + // Find all the other Q# projects in the workspace + const projectFiles = ( + await vscode.workspace.findFiles("**/qsharp.json") + ).filter((file) => file.toString() !== qsharpJsonUri.toString()); + + const projectChoices: Array<{ name: string; ref: LocalProjectRef }> = []; + + projectFiles.forEach((file) => { + const dirName = file.path.slice(0, -"/qsharp.json".length); + const relPath = getRelativeDirPath(qsharpJsonDir.path, dirName); + projectChoices.push({ + name: dirName.slice(dirName.lastIndexOf("/") + 1), + ref: { + path: relPath, + }, + }); + }); + + // Convert any spaces, dashes, dots, tildes, or quotes in project names + // to underscores. (Leave more 'exotic' non-identifier patterns to the user to fix) + // + // Note: At some point we may want to detect/avoid duplicate names, e.g. if the user already + // references a project via 'foo', and they add a reference to a 'foo' on GitHub or in another dir. + projectChoices.forEach( + (val, idx, arr) => (arr[idx].name = val.name.replace(/[- "'.~]/g, "_")), + ); + + const folderIcon = new vscode.ThemeIcon("folder"); + + // Ask the user to pick a project to add as a reference + const projectChoice = await vscode.window.showQuickPick( + projectChoices.map((choice) => ({ + label: choice.name, + detail: choice.ref.path, + iconPath: folderIcon, + ref: choice.ref, + })), + { placeHolder: "Pick a project to add as a reference" }, + ); + + if (!projectChoice) { + log.info("User cancelled project choice"); + return; + } + + await updateManifestAndSave( + qsharpJsonDoc, + qsharpJsonUri, + manifestObj, + projectChoice.label, + projectChoice.ref, + ); + } + + async function importExternalPackage( + qsharpJsonDoc: vscode.TextDocument, + qsharpJsonUri: vscode.Uri, + manifestObj: any, + ) { + // ask the user to pick a package to import + const packageChoice = await vscode.window.showQuickPick( + registryJson.knownPackages.map( + (pkg: { name: string; description: string; dependency: object }) => ({ + label: pkg.name, + description: pkg.description, + }), + ), + { placeHolder: "Pick a package to import" }, + ); + + if (!packageChoice) { + log.info("User cancelled package choice"); + return; + } + + const chosenPackage = registryJson.knownPackages.find( + (pkg: { name: string; description: string; dependency: object }) => + pkg.name === packageChoice.label, + )!; + + const versionChoice = await vscode.window.showQuickPick( + chosenPackage.dependency.github.refs.map(({ ref, notes }) => ({ + label: ref, + description: notes, + })), + { placeHolder: "Pick a version to import" }, + ); + + if (!versionChoice) { + log.info("User cancelled version choice"); + return; + } + const dependencyRef = { + github: { + ref: versionChoice.label, + ...chosenPackage.dependency.github, + refs: undefined, + }, + }; + + await updateManifestAndSave( + qsharpJsonDoc, + qsharpJsonUri, + manifestObj, + packageChoice.label, + dependencyRef, + ); + } + + async function updateManifestAndSave( + qsharpJsonDoc: vscode.TextDocument, + qsharpJsonUri: vscode.Uri, + manifestObj: any, + label: string, + ref: Dependency, + ) { + if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {}; + manifestObj["dependencies"][label] = ref; + + // Apply the edits to the qsharp.json + const edit = new vscode.WorkspaceEdit(); + edit.replace( + qsharpJsonUri, + new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0), + JSON.stringify(manifestObj, null, 2), + ); + if (!(await vscode.workspace.applyEdit(edit))) { + vscode.window.showErrorMessage( + "Unable to update the qsharp.json file. Check the file is writable", + ); + return; + } + + // Bring the qsharp.json to the front for the user to save + await vscode.window.showTextDocument(qsharpJsonDoc); + } }