diff --git a/extensions/positron-r/package.json b/extensions/positron-r/package.json index 6159286eb42..82eb3c1d68a 100644 --- a/extensions/positron-r/package.json +++ b/extensions/positron-r/package.json @@ -3,7 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "version": "0.0.2", - "publisher": "vscode", + "publisher": "positron", "engines": { "vscode": "^1.65.0" }, @@ -281,6 +281,12 @@ }, "default": [], "description": "%r.configuration.extraArguments.description%" + }, + "positron.r.taskHyperlinks": { + "scope": "window", + "type": "boolean", + "default": false, + "description": "%r.configuration.taskHyperlinks.description%" } } } diff --git a/extensions/positron-r/package.nls.json b/extensions/positron-r/package.nls.json index 3f5ee6eb8ab..6a12749ba20 100644 --- a/extensions/positron-r/package.nls.json +++ b/extensions/positron-r/package.nls.json @@ -58,5 +58,6 @@ "r.configuration.pipe.magrittr.token": "%>%", "r.configuration.pipe.native.description": "Native pipe available in R >= 4.1", "r.configuration.pipe.magrittr.description": "Pipe operator from the magrittr package, re-exported by many other packages", - "r.configuration.diagnostics.enable.description": "Enable R diagnostics globally" + "r.configuration.diagnostics.enable.description": "Enable R diagnostics globally", + "r.configuration.taskHyperlinks.description": "Turn on experimental support for hyperlinks in package development tasks" } diff --git a/extensions/positron-r/src/extension.ts b/extensions/positron-r/src/extension.ts index 7cb8aec93af..26963c07caf 100644 --- a/extensions/positron-r/src/extension.ts +++ b/extensions/positron-r/src/extension.ts @@ -12,6 +12,7 @@ import { providePackageTasks } from './tasks'; import { setContexts } from './contexts'; import { setupTestExplorer, refreshTestExplorer } from './testing/testing'; import { RRuntimeManager } from './runtime-manager'; +import { registerUriHandler } from './uri-handler'; export const LOGGER = vscode.window.createOutputChannel('Positron R Extension', { log: true }); @@ -36,6 +37,9 @@ export function activate(context: vscode.ExtensionContext) { // Provide tasks. providePackageTasks(context); + // Prepare to handle cli-produced hyperlinks that target the positron-r extension. + registerUriHandler(); + // Setup testthat test explorer. setupTestExplorer(context); vscode.workspace.onDidChangeConfiguration(async event => { diff --git a/extensions/positron-r/src/session.ts b/extensions/positron-r/src/session.ts index b6fa660496f..7fb50748838 100644 --- a/extensions/positron-r/src/session.ts +++ b/extensions/positron-r/src/session.ts @@ -27,7 +27,7 @@ interface RPackageInstallation { compatible: boolean; } -interface EnvVar { +export interface EnvVar { [key: string]: string; } diff --git a/extensions/positron-r/src/tasks.ts b/extensions/positron-r/src/tasks.ts index 8a49ae825ab..9f62b844479 100644 --- a/extensions/positron-r/src/tasks.ts +++ b/extensions/positron-r/src/tasks.ts @@ -8,6 +8,7 @@ import * as os from 'os'; import { RSessionManager } from './session-manager'; import { getPandocPath } from './pandoc'; import { getEnvVars } from './session'; +import { prepCliEnvVars } from './uri-handler'; export class RPackageTaskProvider implements vscode.TaskProvider { @@ -39,7 +40,7 @@ export async function getRPackageTasks(editorFilePath?: string): Promise('taskHyperlinks'); + + return taskHyperlinksEnabled === true; +} + +// Example of a URI we expect to handle: +// positron://positron.positron-r/cli?command=x-r-run:testthat::snapshot_review('snap') +// +// How the example URI breaks down: +// { +// "scheme": "positron", +// "authority": "positron.positron-r", +// "path": "/cli", +// "query": "command=x-r-run:testthat::snapshot_review('zzz')", +// "fragment": "", +// "fsPath": "/cli" +// } +function handleUri(uri: vscode.Uri): void { + if (uri.path !== '/cli') { + return; + } + + // Turns this query string + // "command=x-r-run:testthat::snapshot_review('zzz')" + // into this object + // { "command": "x-r-run:testthat::snapshot_review('zzz')" } + const query = new URLSearchParams(uri.query); + const command = query.get('command'); + if (!command) { + return; + } + + const commandRegex = /^(x-r-(help|run|vignette)):(.+)$/; + if (!commandRegex.test(command)) { + return; + } + + const session = RSessionManager.instance.getConsoleSession(); + if (!session) { + return; + } + + session.openResource(command); + vscode.commands.executeCommand('workbench.panel.positronConsole.focus'); +} + +export async function prepCliEnvVars(session?: RSession): Promise { + session = session || RSessionManager.instance.getConsoleSession(); + if (!session) { + return {}; + } + + const taskHyperlinks = taskHyperlinksEnabled(); + const cliPkg = await session.packageVersion('cli', '3.6.3.9001'); + const cliSupportsHyperlinks = cliPkg?.compatible ?? false; + + if (!taskHyperlinks || !cliSupportsHyperlinks) { + // eslint-disable-next-line @typescript-eslint/naming-convention + return { R_CLI_HYPERLINKS: 'FALSE' }; + } + + return { + /* eslint-disable @typescript-eslint/naming-convention */ + R_CLI_HYPERLINKS: 'TRUE', + // TODO: I'd like to request POSIX compliant hyperlinks in the future, but currently + // cli's tests implicitly assume the default and there are more important changes to + // propose in cli, such as tweaks to file hyperlinks. Leave this alone for now. + // R_CLI_HYPERLINK_MODE: "posix", + R_CLI_HYPERLINK_RUN: 'TRUE', + R_CLI_HYPERLINK_RUN_URL_FORMAT: 'positron://positron.positron-r/cli?command=x-r-run:{code}', + R_CLI_HYPERLINK_HELP: 'TRUE', + R_CLI_HYPERLINK_HELP_URL_FORMAT: 'positron://positron.positron-r/cli?command=x-r-help:{topic}', + R_CLI_HYPERLINK_VIGNETTE: 'TRUE', + R_CLI_HYPERLINK_VIGNETTE_URL_FORMAT: 'positron://positron.positron-r/cli?command=x-r-vignette:{vignette}' + /* eslint-enable @typescript-eslint/naming-convention */ + }; +} diff --git a/extensions/positron-reticulate/package.json b/extensions/positron-reticulate/package.json index 4f22abe4d53..5567a353038 100644 --- a/extensions/positron-reticulate/package.json +++ b/extensions/positron-reticulate/package.json @@ -36,7 +36,7 @@ }, "extensionDependencies": [ "ms-python.python", - "vscode.positron-r", + "positron.positron-r", "vscode.jupyter-adapter" ], "devDependencies": { diff --git a/src/vs/workbench/services/positronNewProject/common/positronNewProjectService.ts b/src/vs/workbench/services/positronNewProject/common/positronNewProjectService.ts index c61d1fa49c0..20d13d2ebf8 100644 --- a/src/vs/workbench/services/positronNewProject/common/positronNewProjectService.ts +++ b/src/vs/workbench/services/positronNewProject/common/positronNewProjectService.ts @@ -283,7 +283,7 @@ export class PositronNewProjectService extends Disposable implements IPositronNe /** * Runs R Project specific tasks. - * Relies on extension vscode.positron-r + * Relies on extension positron.positron-r */ private async _runRTasks() { // no-op for now, since we haven't defined any pre-runtime startup R tasks @@ -305,7 +305,7 @@ export class PositronNewProjectService extends Disposable implements IPositronNe /** * Runs R Project specific post-initialization tasks. - * Relies on extension vscode.positron-r + * Relies on extension positron.positron-r */ private async _runRPostInitTasks() { // Create the R environment @@ -518,7 +518,7 @@ export class PositronNewProjectService extends Disposable implements IPositronNe /** * Creates the R environment. - * Relies on extension vscode.positron-r + * Relies on extension positron.positron-r */ private async _createREnvironment() { if (this._newProjectConfig?.useRenv) {