Skip to content
Merged
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
40 changes: 39 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,45 @@
},
"taskDefinitions": [
{
"type": "R"
"type": "R",
"required": ["code"],
"properties": {
"code": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"x <- 'Hello, World!'",
"print(x)"
],
"description": "R code to be executed"
},
"options": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"--no-echo",
"--no-restore"
],
"description": "Command line options used to invoke R. (see R --help)"
},
"cwd": {
"type": "string",
"default": "${workspaceRoot}",
"description": "The current working directory of the executed program or shell. If omitted, the current workspace root will be used."
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"default": {},
"description": "The environment of the executed program or shell. If omitted, the current process environment will be used."
}
}
}
],
"problemMatchers": [
Expand Down
221 changes: 130 additions & 91 deletions src/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,107 +1,146 @@
'use strict';

import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { getRpath } from './util';

export class RTaskProvider implements vscode.TaskProvider {
public readonly type = 'R';

// `vscode.ShellQuoting.Strong` will treat the "value" as pure string
// and quote them based on the shell used this can ensure it works for
// different shells, e.g., zsh, PowerShell or cmd
private readonly tasks = [
const TYPE = 'R';

new vscode.Task(
{ type: this.type },
vscode.TaskScope.Workspace,
'Build',
'R',
new vscode.ShellExecution(
'Rscript',
[
'-e',
{
value: 'devtools::build()',
quoting: vscode.ShellQuoting.Strong
}
]
)
),
interface RTaskDefinition extends vscode.TaskDefinition {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More for my own knowledge sorry but what is vscode.TaskDefinition. I can't seem to find any details about what this interface actually is in the api documentation :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just my personal opinion/experience: Last time I tried (~1 year ago), the task api was quite confusing to work with and not really flexible. I tried implementing the typical build/test tasks with the possibility to supply optional arguments to e.g. devtools::install(), but this was not supported by the task API. Instead, the extension had to explicitly register all possible combinations of arguments, which is not feasible. Apparently, the idea is to scan a list of tasks specified elsewhere, e.g. package.json, and parse them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TaskDefinition is basically the schema of tasks user could specify in tasks.json for that particular task type, in our case, R.

Previously, our resolveTask does not really resolve the task.

type: string,
code: string[],
options?: string[],
cwd?: string,
env?: { [key: string]: string }
}

new vscode.Task(
{ type: this.type },
vscode.TaskScope.Workspace,
'Check',
'R',
new vscode.ShellExecution(
'Rscript',
[
'-e',
{
value: 'devtools::check()',
quoting: vscode.ShellQuoting.Strong
}
]
)
),
interface RTaskInfo {
definition: RTaskDefinition,
problemMatchers?: string | string[],
name?: string,
group?: vscode.TaskGroup
}

new vscode.Task(
{ type: this.type },
vscode.TaskScope.Workspace,
'Document',
'R',
new vscode.ShellExecution(
'Rscript',
[
'-e',
{
value: 'devtools::document()',
quoting: vscode.ShellQuoting.Strong
}
]
)
),
function makeRArgs(options: string[], code: string[]) {
const codeArgs: string[] = [];
for (const line of code) {
codeArgs.push('-e');
codeArgs.push(line);
}
const args = options.concat(codeArgs);
return args;
}

const defaultOptions: string[] = ['--no-echo', '--no-restore'];
const rtasks: RTaskInfo[] = [
{
definition: {
type: TYPE,
code: ['devtools::test()']
},
name: 'Test',
group: vscode.TaskGroup.Test,
problemMatchers: '$testthat'
},

{
definition: {
type: TYPE,
code: ['devtools::build()']
},
name: 'Build',
group: vscode.TaskGroup.Build,
problemMatchers: []
},

{
definition: {
type: TYPE,
code: ['devtools::check()']
},
name: 'Check',
group: vscode.TaskGroup.Test,
problemMatchers: []
},

{
definition: {
type: TYPE,
code: ['devtools::document()']
},
name: 'Document',
group: vscode.TaskGroup.Build,
problemMatchers: []
},

{
definition: {
type: TYPE,
code: ['devtools::install()']
},
name: 'Install',
group: vscode.TaskGroup.Build,
problemMatchers: []
}
];

new vscode.Task(
{ type: this.type },
vscode.TaskScope.Workspace,
'Install',
'R',
new vscode.ShellExecution(
'Rscript',
[
'-e',
{
value: 'devtools::install()',
quoting: vscode.ShellQuoting.Strong
}
]
)
function asRTask(rPath: string, folder: vscode.WorkspaceFolder | vscode.TaskScope, info: RTaskInfo): vscode.Task {
const args = makeRArgs(info.definition.options ?? defaultOptions, info.definition.code);
const rtask: vscode.Task = new vscode.Task(
info.definition,
folder,
info.name,
info.definition.type,
new vscode.ProcessExecution(
rPath,
args,
{
cwd: info.definition.cwd,
env: info.definition.env
}
),
info.problemMatchers
);

rtask.group = info.group;
return rtask;
}

new vscode.Task(
{ type: this.type },
vscode.TaskScope.Workspace,
'Test',
'R',
new vscode.ShellExecution(
'Rscript',
[
'-e',
{
value: 'devtools::test()',
quoting: vscode.ShellQuoting.Strong
}
]
),
'$testthat'
)
];
export class RTaskProvider implements vscode.TaskProvider {

public type = TYPE;

public provideTasks(): vscode.Task[] {
return this.tasks;
public async provideTasks(): Promise<vscode.Task[]> {
const folders = vscode.workspace.workspaceFolders;

if (!folders) {
return [];
}

const tasks: vscode.Task[] = [];
const rPath = await getRpath(false);

for (const folder of folders) {
const isRPackage = fs.existsSync(path.join(folder.uri.fsPath, 'DESCRIPTION'));
if (isRPackage) {
for (const rtask of rtasks) {
const task = asRTask(rPath, folder, rtask);
tasks.push(task);
}
}
}
return tasks;
}

public resolveTask(task: vscode.Task): vscode.Task {
return task;
public async resolveTask(task: vscode.Task): Promise<vscode.Task> {
const taskInfo: RTaskInfo = {
definition: <RTaskDefinition>task.definition,
group: task.group,
name: task.name
};
const rPath = await getRpath(false);
return asRTask(rPath, vscode.TaskScope.Workspace, taskInfo);
}
}