diff --git a/src/VSCodeExtension/.vscodeignore b/src/VSCodeExtension/.vscodeignore
index 5231325bff..ffb0eb5ec9 100644
--- a/src/VSCodeExtension/.vscodeignore
+++ b/src/VSCodeExtension/.vscodeignore
@@ -3,10 +3,10 @@
out/test/**
out/**/*.map
src/**
-.gitignore
+**/.gitignore
tsconfig.json
vsc-extension-quickstart.md
tslint.json
Build-Dependencies.ps1
BUILDING.md
-*.v.template
+**/*.v.template
diff --git a/src/VSCodeExtension/package.json.v.template b/src/VSCodeExtension/package.json.v.template
index 57cc891af0..ddcd4518ab 100644
--- a/src/VSCodeExtension/package.json.v.template
+++ b/src/VSCodeExtension/package.json.v.template
@@ -36,7 +36,7 @@
"commands": [
{
"command": "quantum.installTemplates",
- "title": "Q#: Install project templates"
+ "title": "Q#: Install command line project templates"
},
{
@@ -114,6 +114,9 @@
"vscode-extension-telemetry": "0.0.18",
"vscode-languageclient": "5.1.0",
"which": "1.3.1",
+ "yeoman-environment": "^2.10.3",
+ "yeoman-generator": "^2.0.1",
+ "yosay": "^2.0.1",
"@types/fs-extra": "^8.0.0"
},
"devDependencies": {
@@ -121,6 +124,11 @@
"typescript": "^2.6.1",
"tslint": "^5.8.0",
"mocha": "^5.2.0",
+ "@types/yeoman-generator": "^3.1.4",
+ "@types/yosay": "^0.0.29",
+ "yeoman-test": "^1.7.0",
+ "yeoman-assert": "^3.1.0",
+ "@types/yeoman-environment": "^2.3.3",
"@types/node": "^9.6.5",
"@types/mocha": "^5.2.0",
"@types/which": "1.3.1",
diff --git a/src/VSCodeExtension/src/commands.ts b/src/VSCodeExtension/src/commands.ts
index 37c6778afb..f45b11f6a2 100644
--- a/src/VSCodeExtension/src/commands.ts
+++ b/src/VSCodeExtension/src/commands.ts
@@ -10,6 +10,9 @@ import { IPackageInfo } from './packageInfo';
import * as semver from 'semver';
import { promisify } from 'util';
import { oc } from 'ts-optchain';
+import { QSharpGenerator } from './yeoman-generator';
+
+import * as yeoman from 'yeoman-environment';
export function registerCommand(context: vscode.ExtensionContext, name: string, action: () => void) {
context.subscriptions.push(
@@ -22,107 +25,17 @@ export function registerCommand(context: vscode.ExtensionContext, name: string,
)
}
-function createNewProjectAtUri(dotNetSdk: DotnetInfo, projectType: string, uri: vscode.Uri) {
- let proc = cp.spawn(
- dotNetSdk.path,
- ["new", projectType, "-lang", "Q#", "-o", uri.fsPath]
- );
-
- let errorMessage = "";
- proc.stderr.on('data', data => {
- errorMessage = errorMessage + data;
- });
-
- proc.on(
- 'exit', (code, signal) => {
- if (code === 0) {
- const openItem = "Open new project...";
- vscode.window.showInformationMessage(
- `Successfully created new project at ${uri.fsPath}.`,
- openItem
- ).then(
- (item) => {
- if (item === openItem) {
- vscode.commands.executeCommand(
- "vscode.openFolder",
- uri
- ).then(
- (value) => {},
- (value) => {
- vscode.window.showErrorMessage("Could not open new project");
- }
- );
- }
- }
- );
- } else {
- // Check if the problem was that the project templates are missing.
- // If so, we can give a more helpful error message and offer the
- // user to install templates.
- if (errorMessage.includes("Q#") && errorMessage.includes("-lang")) {
- const installTemplatesItem = "Install project templates and retry";
- vscode.window.showErrorMessage(
- "Project creation failed. The Q# project templates may not be installed.",
- installTemplatesItem
- )
- .then(
- item => {
- if (item === installTemplatesItem) {
- vscode.commands.executeCommand(
- "quantum.installTemplates"
- ).then(
- () => createNewProjectAtUri(dotNetSdk, projectType, uri)
- );
- }
- }
- );
- } else {
- vscode.window.showErrorMessage(
- `.NET Core SDK exited with code ${code} when creating a new project:\n${errorMessage}`
- );
- }
- }
+export function createNewProject(context: vscode.ExtensionContext) {
+ let env = yeoman.createEnv();
+ env.options.extensionPath = context.extensionPath;
+ env.registerStub(QSharpGenerator, 'qsharp:app');
+ env.run('qsharp:app', (err: null | Error) => {
+ if (err) {
+ let errorMessage = err.name + ": " + err.message;
+ console.log(errorMessage);
+ vscode.window.showErrorMessage(errorMessage);
}
- );
-}
-
-export function createNewProject(dotNetSdk: DotnetInfo) {
- const projectTypes: {[key: string]: string} = {
- "Standalone console application": "console",
- "Quantum library": "classlib",
- "Unit testing project": "xunit"
- };
- vscode.window.showQuickPick(
- Object.keys(projectTypes)
- ).then(
- projectTypeSelection => {
- if (projectTypeSelection === undefined) {
- throw undefined;
- }
- let projectType = projectTypes[projectTypeSelection];
-
- vscode.window.showSaveDialog({
- saveLabel: "Create Project"
- }).then(
- (uri) => {
- if (uri !== undefined) {
- if (uri.scheme !== "file") {
- vscode.window.showErrorMessage(
- "New projects must be saved to the filesystem."
- );
- throw new Error("URI scheme was not file.");
- }
- else {
- return uri;
- }
- } else {
- throw undefined;
- }
- }
- )
- .then(uri => createNewProjectAtUri(dotNetSdk, projectType, uri));
- }
- );
+ });
}
export function installTemplates(dotNetSdk: DotnetInfo, packageInfo?: IPackageInfo) {
diff --git a/src/VSCodeExtension/src/extension.ts b/src/VSCodeExtension/src/extension.ts
index f887d417b1..7debb216c8 100644
--- a/src/VSCodeExtension/src/extension.ts
+++ b/src/VSCodeExtension/src/extension.ts
@@ -63,7 +63,7 @@ export async function activate(context: vscode.ExtensionContext) {
context,
"quantum.newProject",
() => {
- requireDotNetSdk(dotNetSdkVersion).then(createNewProject);
+ createNewProject(context);
}
);
diff --git a/src/VSCodeExtension/src/yeoman-generator.ts b/src/VSCodeExtension/src/yeoman-generator.ts
new file mode 100644
index 0000000000..189610a73c
--- /dev/null
+++ b/src/VSCodeExtension/src/yeoman-generator.ts
@@ -0,0 +1,123 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import * as vscode from 'vscode';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import yo = require("yeoman-generator");
+import yosay = require("yosay");
+
+export class QSharpGenerator extends yo {
+
+ constructor(args : any, opts : any) {
+ super(args, opts);
+ console.log(
+ yosay("Welcome to the Q# generator!")
+ );
+
+ this.sourceRoot(path.join(this.options.extensionPath, "templates"));
+ }
+
+ prompting() {
+ let done = this.async();
+
+ // This dictionary maps the public description of the project type to the name
+ // of the folder with the corresponding template files.
+ const projectTypes: {[key: string]: string} = {
+ "Standalone console application": "application",
+ "Quantum library": "library",
+ "Unit testing project": "unittest"
+ };
+
+ vscode.window.showQuickPick(
+ Object.keys(projectTypes)
+ ).then(
+ projectTypeSelection => {
+ if (projectTypeSelection === undefined) {
+ throw undefined;
+ }
+
+ vscode.window.showSaveDialog({
+ saveLabel: "Create Project"
+ }).then(
+ (uri) => {
+ if (uri !== undefined) {
+ if (uri.scheme !== "file") {
+ vscode.window.showErrorMessage(
+ "New projects must be saved to the filesystem."
+ );
+ throw new Error("URI scheme was not file.");
+ }
+ else {
+ return uri;
+ }
+ } else {
+ throw undefined;
+ }
+ }
+ )
+ .then(uri => {
+ this.options.projectType = projectTypes[projectTypeSelection];
+ this.options.outputUri = uri;
+ done();
+ });
+ }
+ );
+ }
+
+ writing() {
+ console.log(
+ yosay("Creating Q# project.")
+ );
+
+ let sourceDir = path.join(this.templatePath(), this.options.projectType);
+ let targetDir = this.options.outputUri.fsPath;
+ fs.mkdir(targetDir);
+
+ // Namespace is the directory name itself.
+ let dirs = targetDir.split(path.sep);
+
+ // In case there is a trailing separator.
+ let namespaceName = dirs.pop() || dirs.pop();
+
+ fs.readdir(sourceDir, (err, files) => {
+ if (err){
+ throw err;
+ }
+ files.forEach( (filename) => {
+ let destinationName = filename;
+ let fileExtension = filename.split(".").pop();
+
+ if (fileExtension && fileExtension.toLowerCase() === "csproj") {
+ destinationName = namespaceName + ".csproj";
+ }
+
+ this.fs.copyTpl(
+ path.join(sourceDir, filename),
+ path.join(targetDir, destinationName),
+ { name: namespaceName }
+ );
+ });
+ });
+
+ const openItem = "Open new project...";
+ vscode.window.showInformationMessage(
+ `Successfully created new project at ${targetDir}.`,
+ openItem
+ ).then(
+ (item) => {
+ if (item === openItem) {
+ vscode.commands.executeCommand(
+ "vscode.openFolder",
+ this.options.outputUri
+ ).then(
+ (value) => { },
+ (value) => {
+ vscode.window.showErrorMessage("Could not open new project");
+ }
+ );
+ }
+ }
+ );
+ }
+}
diff --git a/src/VSCodeExtension/templates/application/.gitignore b/src/VSCodeExtension/templates/application/.gitignore
new file mode 100644
index 0000000000..2b04328876
--- /dev/null
+++ b/src/VSCodeExtension/templates/application/.gitignore
@@ -0,0 +1,2 @@
+# Generated or copied files
+*.csproj
diff --git a/src/VSCodeExtension/templates/application/Application.csproj.v.template b/src/VSCodeExtension/templates/application/Application.csproj.v.template
new file mode 100644
index 0000000000..2061e46bde
--- /dev/null
+++ b/src/VSCodeExtension/templates/application/Application.csproj.v.template
@@ -0,0 +1,8 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
diff --git a/src/VSCodeExtension/templates/application/Program.qs b/src/VSCodeExtension/templates/application/Program.qs
new file mode 100644
index 0000000000..969e2dcce0
--- /dev/null
+++ b/src/VSCodeExtension/templates/application/Program.qs
@@ -0,0 +1,10 @@
+namespace <%= name %> {
+ open Microsoft.Quantum.Canon;
+ open Microsoft.Quantum.Intrinsic;
+
+
+ @EntryPoint()
+ operation SayHello() : Unit {
+ Message("Hello quantum world!");
+ }
+}
diff --git a/src/VSCodeExtension/templates/library/.gitignore b/src/VSCodeExtension/templates/library/.gitignore
new file mode 100644
index 0000000000..2b04328876
--- /dev/null
+++ b/src/VSCodeExtension/templates/library/.gitignore
@@ -0,0 +1,2 @@
+# Generated or copied files
+*.csproj
diff --git a/src/VSCodeExtension/templates/library/Library.csproj.v.template b/src/VSCodeExtension/templates/library/Library.csproj.v.template
new file mode 100644
index 0000000000..b5159e47ee
--- /dev/null
+++ b/src/VSCodeExtension/templates/library/Library.csproj.v.template
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.1
+
+
+
diff --git a/src/VSCodeExtension/templates/library/Library.qs b/src/VSCodeExtension/templates/library/Library.qs
new file mode 100644
index 0000000000..54a38c7587
--- /dev/null
+++ b/src/VSCodeExtension/templates/library/Library.qs
@@ -0,0 +1,9 @@
+namespace <%= name %> {
+ open Microsoft.Quantum.Canon;
+ open Microsoft.Quantum.Intrinsic;
+
+
+ operation SayHello() : Unit {
+ Message("Hello quantum world!");
+ }
+}
diff --git a/src/VSCodeExtension/templates/unittest/.gitignore b/src/VSCodeExtension/templates/unittest/.gitignore
new file mode 100644
index 0000000000..2b04328876
--- /dev/null
+++ b/src/VSCodeExtension/templates/unittest/.gitignore
@@ -0,0 +1,2 @@
+# Generated or copied files
+*.csproj
diff --git a/src/VSCodeExtension/templates/unittest/Test.csproj.v.template b/src/VSCodeExtension/templates/unittest/Test.csproj.v.template
new file mode 100644
index 0000000000..40bccfea26
--- /dev/null
+++ b/src/VSCodeExtension/templates/unittest/Test.csproj.v.template
@@ -0,0 +1,16 @@
+
+
+
+ netcoreapp3.1
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/VSCodeExtension/templates/unittest/Tests.qs b/src/VSCodeExtension/templates/unittest/Tests.qs
new file mode 100644
index 0000000000..901bb6b366
--- /dev/null
+++ b/src/VSCodeExtension/templates/unittest/Tests.qs
@@ -0,0 +1,16 @@
+namespace <%= name %> {
+ open Microsoft.Quantum.Canon;
+ open Microsoft.Quantum.Diagnostics;
+ open Microsoft.Quantum.Intrinsic;
+
+
+ @Test("QuantumSimulator")
+ operation AllocateQubit() : Unit {
+
+ using (q = Qubit()) {
+ Assert([PauliZ], [q], Zero, "Newly allocated qubit must be in |0> state.");
+ }
+
+ Message("Test passed.");
+ }
+}