diff --git a/task-provider-sample/.gitignore b/task-provider-sample/.gitignore new file mode 100644 index 00000000..8e5962ee --- /dev/null +++ b/task-provider-sample/.gitignore @@ -0,0 +1,2 @@ +out +node_modules \ No newline at end of file diff --git a/task-provider-sample/.vscode/launch.json b/task-provider-sample/.vscode/launch.json new file mode 100644 index 00000000..d3c83441 --- /dev/null +++ b/task-provider-sample/.vscode/launch.json @@ -0,0 +1,38 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/out/src/**/*.js"], + "preLaunchTask": "npm: run compile" + }, + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/out/test/**/*.js"], + "preLaunchTask": "npm: run compile" + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Extension Host", + "protocol": "legacy", + "port": 5870, + "sourceMaps": true, + "restart": true, + "outDir": "${workspaceRoot}/out/src" + } + ] +} diff --git a/task-provider-sample/.vscode/tasks.json b/task-provider-sample/.vscode/tasks.json new file mode 100644 index 00000000..5fba26e1 --- /dev/null +++ b/task-provider-sample/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "compile", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch" + ] + } + ] +} \ No newline at end of file diff --git a/task-provider-sample/.vscodeignore b/task-provider-sample/.vscodeignore new file mode 100644 index 00000000..5ff3c193 --- /dev/null +++ b/task-provider-sample/.vscodeignore @@ -0,0 +1,9 @@ +.vscode/** +.vscode-test/** +out/test/** +test/** +src/** +**/*.map +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/task-provider-sample/README.md b/task-provider-sample/README.md new file mode 100644 index 00000000..a5c17932 --- /dev/null +++ b/task-provider-sample/README.md @@ -0,0 +1,10 @@ +# Task Provider Example + +- Detects tasks in Rakefiles + +## Running the example + +- Open this example in VS Code +- `npm install` +- `npm run compile` +- `F5` to start debugging \ No newline at end of file diff --git a/task-provider-sample/package.json b/task-provider-sample/package.json new file mode 100644 index 00000000..f3a01651 --- /dev/null +++ b/task-provider-sample/package.json @@ -0,0 +1,45 @@ +{ + "name": "task-provider-samples", + "displayName": "Task Provider Samples", + "description": "Samples for VSCode's view API", + "version": "0.0.1", + "publisher": "ms-vscode", + "engines": { + "vscode": "^1.13.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onCommand:workbench.action.tasks.runTask" + ], + "main": "./out/src/extension", + "contributes": { + "taskDefinitions": [ + { + "type": "rake", + "required": ["task"], + "properties": { + "task": { + "type": "string", + "description": "The Rake task to customize" + }, + "file": { + "type": "string", + "description": "The Rake file that provides the task. Can be omitted." + } + } + } + ] + }, + "scripts": { + "vscode:prepublish": "tsc -p ./", + "compile": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install" + }, + "devDependencies": { + "typescript": "^2.4.1", + "vscode": "^1.1.2", + "@types/node": "*" + } +} \ No newline at end of file diff --git a/task-provider-sample/src/extension.ts b/task-provider-sample/src/extension.ts new file mode 100644 index 00000000..def7fb16 --- /dev/null +++ b/task-provider-sample/src/extension.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as path from 'path'; +import * as fs from 'fs'; +import * as cp from 'child_process'; +import * as vscode from 'vscode'; + +let taskProvider: vscode.Disposable | undefined; + +export function activate(_context: vscode.ExtensionContext): void { + let workspaceRoot = vscode.workspace.rootPath; + if (!workspaceRoot) { + return; + } + let pattern = path.join(workspaceRoot, 'Rakefile'); + let rakePromise: Thenable | undefined = undefined; + let fileWatcher = vscode.workspace.createFileSystemWatcher(pattern); + fileWatcher.onDidChange(() => rakePromise = undefined); + fileWatcher.onDidCreate(() => rakePromise = undefined); + fileWatcher.onDidDelete(() => rakePromise = undefined); + taskProvider = vscode.workspace.registerTaskProvider('grunt', { + provideTasks: () => { + if (!rakePromise) { + rakePromise = getRakeTasks(); + } + return rakePromise; + }, + resolveTask(_task: vscode.Task): vscode.Task | undefined { + return undefined; + } + }); +} + +export function deactivate(): void { + if (taskProvider) { + taskProvider.dispose(); + } +} + +function exists(file: string): Promise { + return new Promise((resolve, _reject) => { + fs.exists(file, (value) => { + resolve(value); + }); + }); +} + +function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> { + return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { + cp.exec(command, options, (error, stdout, stderr) => { + if (error) { + reject({ error, stdout, stderr }); + } + resolve({ stdout, stderr }); + }); + }); +} + +let _channel: vscode.OutputChannel; +function getOutputChannel(): vscode.OutputChannel { + if (!_channel) { + _channel = vscode.window.createOutputChannel('Rake Auto Detection'); + } + return _channel; +} + +interface RakeTaskDefinition extends vscode.TaskDefinition { + task: string; + file?: string; +} + +const buildNames: string[] = ['build', 'compile', 'watch']; +function isBuildTask(name: string): boolean { + for (let buildName of buildNames) { + if (name.indexOf(buildName) !== -1) { + return true; + } + } + return false; +} + +const testNames: string[] = ['test']; +function isTestTask(name: string): boolean { + for (let testName of testNames) { + if (name.indexOf(testName) !== -1) { + return true; + } + } + return false; +} + +async function getRakeTasks(): Promise { + let workspaceRoot = vscode.workspace.rootPath; + let emptyTasks: vscode.Task[] = []; + if (!workspaceRoot) { + return emptyTasks; + } + let rakeFile = path.join(workspaceRoot, 'Rakefile'); + if (!await exists(rakeFile)) { + return emptyTasks; + } + + let commandLine = 'rake -AT -f Rakefile'; + try { + let { stdout, stderr } = await exec(commandLine, { cwd: workspaceRoot }); + if (stderr && stderr.length > 0) { + getOutputChannel().appendLine(stderr); + getOutputChannel().show(true); + } + let result: vscode.Task[] = []; + if (stdout) { + let lines = stdout.split(/\r{0,1}\n/); + for (let line of lines) { + if (line.length === 0) { + continue; + } + let regExp = /rake\s(.*)#/; + let matches = regExp.exec(line); + if (matches && matches.length === 2) { + let taskName = matches[1].trim(); + let kind: RakeTaskDefinition = { + type: 'rake', + task: taskName + }; + let task = new vscode.Task(kind, taskName, 'rake', new vscode.ShellExecution(`rake ${taskName}`)); + result.push(task); + let lowerCaseLine = line.toLowerCase(); + if (isBuildTask(lowerCaseLine)) { + task.group = vscode.TaskGroup.Build; + } else if (isTestTask(lowerCaseLine)) { + task.group = vscode.TaskGroup.Test; + } + } + } + } + return result; + } catch (err) { + let channel = getOutputChannel(); + if (err.stderr) { + channel.appendLine(err.stderr); + } + if (err.stdout) { + channel.appendLine(err.stdout); + } + channel.appendLine('Auto detecting rake tasts failed.'); + channel.show(true); + return emptyTasks; + } +} \ No newline at end of file diff --git a/task-provider-sample/tsconfig.json b/task-provider-sample/tsconfig.json new file mode 100644 index 00000000..fbda0cd6 --- /dev/null +++ b/task-provider-sample/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "." + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file