Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Import External Q# Package" VS Code command palette command #2079

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions library/Registry.md

This file was deleted.

2 changes: 1 addition & 1 deletion library/fixed_point/qsharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
"src/Tests.qs",
"src/Types.qs"
]
}
}
253 changes: 162 additions & 91 deletions vscode/src/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -182,18 +183,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
"<id>": {
github: {
owner: "<owner>",
repo: "<project>",
ref: "<commit>",
},
},
};

// 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
Expand Down Expand Up @@ -265,93 +254,175 @@ export async function initProjectCreator(context: vscode.ExtensionContext) {
return;
}

// 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,
},
});
});

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)
//
// 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, "_")),
// 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" },
);

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");
if (!importChoice) {
log.info("User cancelled import 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",
if (importChoice === "Import from GitHub") {
await importExternalPackage(
qsharpJsonDoc,
qsharpJsonUri,
manifestObj,
);
} 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);
}
}
64 changes: 64 additions & 0 deletions vscode/src/registry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"knownPackages": [
{
"name": "Signed",
"description": "Defines types and functions to work with signed qubit-based integers.",
"dependency": {
"github": {
"owner": "Microsoft",
"repo": "qsharp",
"refs": [
{ "ref": "546b053", "notes": "latest stable" },
{ "ref": "main", "notes": "nightly, unstable" }
],
"path": "library/signed"
}
}
},
{
"name": "FixedPoint",
"description": "Types and functions for fixed-point arithmetic with qubits.",
"dependency": {
"github": {
"owner": "Microsoft",
"repo": "qsharp",
"refs": [
{ "ref": "546b053", "notes": "latest stable" },
{ "ref": "main", "notes": "nightly, unstable" }
],
"path": "library/signed"
}
}
},
{
"name": "Rotations",
"description": "Defines types and functions to work with rotations.",
"dependency": {
"github": {
"owner": "Microsoft",
"repo": "qsharp",
"refs": [
{ "ref": "546b053", "notes": "latest stable" },
{ "ref": "main", "notes": "nightly, unstable" }
],
"path": "library/rotations"
}
}
},
{
"name": "Qtest",
"description": "Utilities for writing and running Q# tests.",
"dependency": {
"github": {
"owner": "Microsoft",
"repo": "qsharp",
"refs": [
{ "ref": "546b053", "notes": "latest stable" },
{ "ref": "main", "notes": "nightly, unstable" }
],
"path": "library/qtest"
}
}
}
]
}
Loading