Skip to content

Commit 598fbe3

Browse files
seznabilltiminestarks
authored
"Import External Q# Package" VS Code command palette command (#2079)
This PR formalizes the "registry" md document into a JSON file, and then adds a command palette command which consumes it. This will hopefully increase discoverability of the project system, and formalizes our listing of known libraries a bit more. https://github.com/user-attachments/assets/20f8dd7b-5b53-4b03-8990-aef41d6b3da7 --------- Co-authored-by: Bill Ticehurst <billti@microsoft.com> Co-authored-by: Mine Starks <16928427+minestarks@users.noreply.github.com>
1 parent bd5a09c commit 598fbe3

File tree

4 files changed

+222
-115
lines changed

4 files changed

+222
-115
lines changed

library/Registry.md

Lines changed: 0 additions & 22 deletions
This file was deleted.

library/fixed_point/qsharp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424
"src/Tests.qs",
2525
"src/Types.qs"
2626
]
27-
}
27+
}

vscode/src/createProject.ts

Lines changed: 157 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as vscode from "vscode";
55
import { log, samples } from "qsharp-lang";
66
import { EventType, sendTelemetryEvent } from "./telemetry";
77
import { qsharpExtensionId } from "./common";
8+
import registryJson from "./registry.json";
89

910
export async function initProjectCreator(context: vscode.ExtensionContext) {
1011
context.subscriptions.push(
@@ -40,7 +41,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) {
4041

4142
const sample = samples.find((elem) => elem.title === "Minimal");
4243
if (!sample) {
43-
// Should never happen.
44+
// Should never happen, because we bake this sample in.
4445
log.error("Unable to find the Minimal sample");
4546
return;
4647
}
@@ -177,23 +178,12 @@ export async function initProjectCreator(context: vscode.ExtensionContext) {
177178
repo: string;
178179
ref: string;
179180
path?: string; // Optional, defaults to the root of the repo
181+
refs: undefined;
180182
};
181183
};
182184

183185
type Dependency = LocalProjectRef | GitHubProjectRef;
184186

185-
// TODO: Replace with a list of legitimate known Q# projects on GitHub
186-
const githubProjects: { [name: string]: GitHubProjectRef } = {
187-
// Add a template to the end of the list users can use to easily add their own
188-
"<id>": {
189-
github: {
190-
owner: "<owner>",
191-
repo: "<project>",
192-
ref: "<commit>",
193-
},
194-
},
195-
};
196-
197187
// Given two directory paths, return the relative path from the first to the second
198188
function getRelativeDirPath(from: string, to: string): string {
199189
// Ensure we have something
@@ -265,93 +255,168 @@ export async function initProjectCreator(context: vscode.ExtensionContext) {
265255
return;
266256
}
267257

268-
// Find all the other Q# projects in the workspace
269-
const projectFiles = (
270-
await vscode.workspace.findFiles("**/qsharp.json")
271-
).filter((file) => file.toString() !== qsharpJsonUri.toString());
272-
273-
const projectChoices: Array<{ name: string; ref: Dependency }> = [];
274-
275-
projectFiles.forEach((file) => {
276-
const dirName = file.path.slice(0, -"/qsharp.json".length);
277-
const relPath = getRelativeDirPath(qsharpJsonDir.path, dirName);
278-
projectChoices.push({
279-
name: dirName.slice(dirName.lastIndexOf("/") + 1),
280-
ref: {
281-
path: relPath,
282-
},
283-
});
284-
});
285-
286-
Object.keys(githubProjects).forEach((name) => {
287-
projectChoices.push({
288-
name: name,
289-
ref: githubProjects[name],
290-
});
291-
});
292-
293-
// Convert any spaces, dashes, dots, tildes, or quotes in project names
294-
// to underscores. (Leave more 'exotic' non-identifier patterns to the user to fix)
295-
//
296-
// Note: At some point we may want to detect/avoid duplicate names, e.g. if the user already
297-
// references a project via 'foo', and they add a reference to a 'foo' on GitHub or in another dir.
298-
projectChoices.forEach(
299-
(val, idx, arr) =>
300-
(arr[idx].name = val.name.replace(/[- "'.~]/g, "_")),
301-
);
302-
303-
const folderIcon = new vscode.ThemeIcon("folder");
304-
const githubIcon = new vscode.ThemeIcon("github");
305-
306-
// Ask the user to pick a project to add as a reference
307-
const projectChoice = await vscode.window.showQuickPick(
308-
projectChoices.map((choice) => {
309-
if ("github" in choice.ref) {
310-
return {
311-
label: choice.name,
312-
detail: `github://${choice.ref.github.owner}/${choice.ref.github.repo}#${choice.ref.github.ref}`,
313-
iconPath: githubIcon,
314-
ref: choice.ref,
315-
};
316-
} else {
317-
return {
318-
label: choice.name,
319-
detail: choice.ref.path,
320-
iconPath: folderIcon,
321-
ref: choice.ref,
322-
};
323-
}
324-
}),
325-
{ placeHolder: "Pick a project to add as a reference" },
258+
const importChoice = await vscode.window.showQuickPick(
259+
["Import from GitHub", "Import from local directory"],
260+
{ placeHolder: "Pick a source to import from" },
326261
);
327262

328-
if (!projectChoice) {
329-
log.info("User cancelled project choice");
263+
if (!importChoice) {
264+
log.info("User cancelled import choice");
330265
return;
331266
}
332267

333-
log.info("User picked project: ", projectChoice);
334-
335-
if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {};
336-
manifestObj["dependencies"][projectChoice.label] = projectChoice.ref;
337-
338-
// Apply the edits to the qsharp.json
339-
const edit = new vscode.WorkspaceEdit();
340-
edit.replace(
341-
qsharpJsonUri,
342-
new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0),
343-
JSON.stringify(manifestObj, null, 2),
344-
);
345-
if (!(await vscode.workspace.applyEdit(edit))) {
346-
vscode.window.showErrorMessage(
347-
"Unable to update the qsharp.json file. Check the file is writable",
268+
if (importChoice === "Import from GitHub") {
269+
await importPackage(qsharpJsonDoc, qsharpJsonUri, manifestObj, true);
270+
} else {
271+
await importPackage(
272+
qsharpJsonDoc,
273+
qsharpJsonUri,
274+
manifestObj,
275+
false,
276+
qsharpJsonDir,
348277
);
349-
return;
350278
}
351-
352-
// Bring the qsharp.json to the front for the user to save
353-
await vscode.window.showTextDocument(qsharpJsonDoc);
354279
},
355280
),
356281
);
282+
283+
async function importPackage(
284+
qsharpJsonDoc: vscode.TextDocument,
285+
qsharpJsonUri: vscode.Uri,
286+
manifestObj: any,
287+
isGitHub: boolean,
288+
qsharpJsonDir?: vscode.Uri,
289+
) {
290+
let dependencyRef: Dependency;
291+
let label: string;
292+
293+
if (isGitHub) {
294+
const packageChoice = await vscode.window.showQuickPick(
295+
registryJson.knownPackages.map(
296+
(pkg: { name: string; description: string; dependency: object }) => ({
297+
label: pkg.name,
298+
description: pkg.description,
299+
}),
300+
),
301+
{ placeHolder: "Pick a package to import" },
302+
);
303+
304+
if (!packageChoice) {
305+
log.info("User cancelled package choice");
306+
return;
307+
}
308+
309+
const chosenPackage = registryJson.knownPackages.find(
310+
(pkg: { name: string; description: string; dependency: object }) =>
311+
pkg.name === packageChoice.label,
312+
)!;
313+
314+
const versionChoice = await vscode.window.showQuickPick(
315+
chosenPackage.dependency.github.refs.map(({ ref, notes }) => ({
316+
label: ref,
317+
description: notes,
318+
})),
319+
{ placeHolder: "Pick a version to import" },
320+
);
321+
322+
if (!versionChoice) {
323+
log.info("User cancelled version choice");
324+
return;
325+
}
326+
327+
dependencyRef = {
328+
github: {
329+
ref: versionChoice.label,
330+
...chosenPackage.dependency.github,
331+
refs: undefined,
332+
},
333+
};
334+
335+
label = packageChoice.label;
336+
} else {
337+
// Find all the other Q# projects in the workspace
338+
const projectFiles = (
339+
await vscode.workspace.findFiles("**/qsharp.json")
340+
).filter((file) => file.toString() !== qsharpJsonUri.toString());
341+
342+
// Convert any spaces, dashes, dots, tildes, or quotes in project names
343+
// to underscores. (Leave more 'exotic' non-identifier patterns to the user to fix)
344+
//
345+
// Note: At some point we may want to detect/avoid duplicate names, e.g. if the user already
346+
// references a project via 'foo', and they add a reference to a 'foo' on GitHub or in another dir.
347+
348+
const projectChoices = projectFiles.map((file) => {
349+
// normalize the path using the vscode Uri API
350+
const dirUri = vscode.Uri.joinPath(file, "..");
351+
const relPath = getRelativeDirPath(qsharpJsonDir!.path, dirUri.path);
352+
return {
353+
name: dirUri.path.split("/").pop()!,
354+
ref: {
355+
path: relPath,
356+
},
357+
};
358+
});
359+
360+
projectChoices.forEach(
361+
(val, idx, arr) => (arr[idx].name = val.name.replace(/[- "'.~]/g, "_")),
362+
);
363+
364+
const folderIcon = new vscode.ThemeIcon("folder");
365+
366+
// Ask the user to pick a project to add as a reference
367+
const projectChoice = await vscode.window.showQuickPick(
368+
projectChoices.map((choice) => ({
369+
label: choice.name,
370+
detail: choice.ref.path,
371+
iconPath: folderIcon,
372+
ref: choice.ref,
373+
})),
374+
{ placeHolder: "Pick a project to add as a reference" },
375+
);
376+
377+
if (!projectChoice) {
378+
log.info("User cancelled project choice");
379+
return;
380+
}
381+
382+
dependencyRef = projectChoice.ref;
383+
label = projectChoice.label;
384+
}
385+
386+
await updateManifestAndSave(
387+
qsharpJsonDoc,
388+
qsharpJsonUri,
389+
manifestObj,
390+
label,
391+
dependencyRef,
392+
);
393+
}
394+
395+
async function updateManifestAndSave(
396+
qsharpJsonDoc: vscode.TextDocument,
397+
qsharpJsonUri: vscode.Uri,
398+
manifestObj: any,
399+
label: string,
400+
ref: Dependency,
401+
) {
402+
if (!manifestObj["dependencies"]) manifestObj["dependencies"] = {};
403+
manifestObj["dependencies"][label] = ref;
404+
405+
// Apply the edits to the qsharp.json
406+
const edit = new vscode.WorkspaceEdit();
407+
edit.replace(
408+
qsharpJsonUri,
409+
new vscode.Range(0, 0, qsharpJsonDoc.lineCount, 0),
410+
JSON.stringify(manifestObj, null, 4),
411+
);
412+
if (!(await vscode.workspace.applyEdit(edit))) {
413+
await vscode.window.showErrorMessage(
414+
"Unable to update the qsharp.json file. Check the file is writable",
415+
);
416+
return;
417+
}
418+
419+
// Bring the qsharp.json to the front for the user to save
420+
await vscode.window.showTextDocument(qsharpJsonDoc);
421+
}
357422
}

vscode/src/registry.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"knownPackages": [
3+
{
4+
"name": "Signed",
5+
"description": "Defines types and functions to work with signed qubit-based integers.",
6+
"dependency": {
7+
"github": {
8+
"owner": "microsoft",
9+
"repo": "qsharp",
10+
"refs": [
11+
{ "ref": "bd5a09c", "notes": "latest stable" },
12+
{ "ref": "main", "notes": "nightly, unstable" }
13+
],
14+
"path": "library/signed"
15+
}
16+
}
17+
},
18+
{
19+
"name": "FixedPoint",
20+
"description": "Types and functions for fixed-point arithmetic with qubits.",
21+
"dependency": {
22+
"github": {
23+
"owner": "microsoft",
24+
"repo": "qsharp",
25+
"refs": [
26+
{ "ref": "bd5a09c", "notes": "latest stable" },
27+
{ "ref": "main", "notes": "nightly, unstable" }
28+
],
29+
"path": "library/signed"
30+
}
31+
}
32+
},
33+
{
34+
"name": "Rotations",
35+
"description": "Defines types and functions to work with rotations.",
36+
"dependency": {
37+
"github": {
38+
"owner": "microsoft",
39+
"repo": "qsharp",
40+
"refs": [
41+
{ "ref": "bd5a09c", "notes": "latest stable" },
42+
{ "ref": "main", "notes": "nightly, unstable" }
43+
],
44+
"path": "library/rotations"
45+
}
46+
}
47+
},
48+
{
49+
"name": "Qtest",
50+
"description": "Utilities for writing and running Q# tests.",
51+
"dependency": {
52+
"github": {
53+
"owner": "microsoft",
54+
"repo": "qsharp",
55+
"refs": [
56+
{ "ref": "bd5a09c", "notes": "latest stable" },
57+
{ "ref": "main", "notes": "nightly, unstable" }
58+
],
59+
"path": "library/qtest"
60+
}
61+
}
62+
}
63+
]
64+
}

0 commit comments

Comments
 (0)