From 030d78345fa79af07f8ebd89a9d244576fac992b Mon Sep 17 00:00:00 2001 From: veetaha Date: Sat, 23 May 2020 04:58:22 +0300 Subject: Fix invoking cargo without consulting CARGO or standard installation paths --- editors/code/src/cargo.ts | 4 ++-- editors/code/src/tasks.ts | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts index a55b2f860..46cd3d777 100644 --- a/editors/code/src/cargo.ts +++ b/editors/code/src/cargo.ts @@ -126,8 +126,8 @@ export class Cargo { } } -// Mirrors `ra_env::get_path_for_executable` implementation -function getCargoPathOrFail(): string { +// Mirrors `ra_toolchain::cargo()` implementation +export function getCargoPathOrFail(): string { const envVar = process.env.CARGO; const executableName = "cargo"; diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index 1366c76d6..c22d69362 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { getCargoPathOrFail } from "./cargo"; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // our configuration should be compatible with it so use the same key. @@ -24,6 +25,8 @@ class CargoTaskProvider implements vscode.TaskProvider { // set of tasks that always exist. These tasks cannot be removed in // tasks.json - only tweaked. + const cargoPath = getCargoPathOrFail(); + return [ { command: 'build', group: vscode.TaskGroup.Build }, { command: 'check', group: vscode.TaskGroup.Build }, @@ -46,7 +49,7 @@ class CargoTaskProvider implements vscode.TaskProvider { `cargo ${command}`, 'rust', // What to do when this command is executed. - new vscode.ShellExecution('cargo', [command]), + new vscode.ShellExecution(cargoPath, [command]), // Problem matchers. ['$rustc'], ); @@ -80,4 +83,4 @@ class CargoTaskProvider implements vscode.TaskProvider { export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { const provider = new CargoTaskProvider(target); return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); -} \ No newline at end of file +} -- cgit v1.2.3 From d605ec9c321392d9c7ee4b440c560e1e405d92e6 Mon Sep 17 00:00:00 2001 From: veetaha Date: Sun, 31 May 2020 05:13:08 +0300 Subject: Change Runnable.bin -> Runnable.kind As per matklad, we now pass the responsibility for finding the binary to the frontend. Also, added caching for finding the binary path to reduce the amount of filesystem interactions. --- editors/code/src/cargo.ts | 151 ---------------------- editors/code/src/debug.ts | 2 +- editors/code/src/lsp_ext.ts | 5 +- editors/code/src/run.ts | 3 +- editors/code/src/tasks.ts | 4 +- editors/code/src/toolchain.ts | 174 ++++++++++++++++++++++++++ editors/code/src/util.ts | 18 +++ editors/code/tests/unit/launch_config.test.ts | 14 +-- 8 files changed, 208 insertions(+), 163 deletions(-) delete mode 100644 editors/code/src/cargo.ts create mode 100644 editors/code/src/toolchain.ts (limited to 'editors/code') diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts deleted file mode 100644 index 46cd3d777..000000000 --- a/editors/code/src/cargo.ts +++ /dev/null @@ -1,151 +0,0 @@ -import * as cp from 'child_process'; -import * as os from 'os'; -import * as path from 'path'; -import * as readline from 'readline'; -import { OutputChannel } from 'vscode'; -import { isValidExecutable } from './util'; - -interface CompilationArtifact { - fileName: string; - name: string; - kind: string; - isTest: boolean; -} - -export interface ArtifactSpec { - cargoArgs: string[]; - filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[]; -} - -export function artifactSpec(args: readonly string[]): ArtifactSpec { - const cargoArgs = [...args, "--message-format=json"]; - - // arguments for a runnable from the quick pick should be updated. - // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens - switch (cargoArgs[0]) { - case "run": cargoArgs[0] = "build"; break; - case "test": { - if (!cargoArgs.includes("--no-run")) { - cargoArgs.push("--no-run"); - } - break; - } - } - - const result: ArtifactSpec = { cargoArgs: cargoArgs }; - if (cargoArgs[0] === "test") { - // for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests - // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"} - result.filter = (artifacts) => artifacts.filter(it => it.isTest); - } - - return result; -} - -export class Cargo { - constructor(readonly rootFolder: string, readonly output: OutputChannel) { } - - private async getArtifacts(spec: ArtifactSpec): Promise { - const artifacts: CompilationArtifact[] = []; - - try { - await this.runCargo(spec.cargoArgs, - message => { - if (message.reason === 'compiler-artifact' && message.executable) { - const isBinary = message.target.crate_types.includes('bin'); - const isBuildScript = message.target.kind.includes('custom-build'); - if ((isBinary && !isBuildScript) || message.profile.test) { - artifacts.push({ - fileName: message.executable, - name: message.target.name, - kind: message.target.kind[0], - isTest: message.profile.test - }); - } - } else if (message.reason === 'compiler-message') { - this.output.append(message.message.rendered); - } - }, - stderr => this.output.append(stderr), - ); - } catch (err) { - this.output.show(true); - throw new Error(`Cargo invocation has failed: ${err}`); - } - - return spec.filter?.(artifacts) ?? artifacts; - } - - async executableFromArgs(args: readonly string[]): Promise { - const artifacts = await this.getArtifacts(artifactSpec(args)); - - if (artifacts.length === 0) { - throw new Error('No compilation artifacts'); - } else if (artifacts.length > 1) { - throw new Error('Multiple compilation artifacts are not supported.'); - } - - return artifacts[0].fileName; - } - - private runCargo( - cargoArgs: string[], - onStdoutJson: (obj: any) => void, - onStderrString: (data: string) => void - ): Promise { - return new Promise((resolve, reject) => { - let cargoPath; - try { - cargoPath = getCargoPathOrFail(); - } catch (err) { - return reject(err); - } - - const cargo = cp.spawn(cargoPath, cargoArgs, { - stdio: ['ignore', 'pipe', 'pipe'], - cwd: this.rootFolder - }); - - cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`))); - - cargo.stderr.on('data', chunk => onStderrString(chunk.toString())); - - const rl = readline.createInterface({ input: cargo.stdout }); - rl.on('line', line => { - const message = JSON.parse(line); - onStdoutJson(message); - }); - - cargo.on('exit', (exitCode, _) => { - if (exitCode === 0) - resolve(exitCode); - else - reject(new Error(`exit code: ${exitCode}.`)); - }); - }); - } -} - -// Mirrors `ra_toolchain::cargo()` implementation -export function getCargoPathOrFail(): string { - const envVar = process.env.CARGO; - const executableName = "cargo"; - - if (envVar) { - if (isValidExecutable(envVar)) return envVar; - - throw new Error(`\`${envVar}\` environment variable points to something that's not a valid executable`); - } - - if (isValidExecutable(executableName)) return executableName; - - const standardLocation = path.join(os.homedir(), '.cargo', 'bin', executableName); - - if (isValidExecutable(standardLocation)) return standardLocation; - - throw new Error( - `Failed to find \`${executableName}\` executable. ` + - `Make sure \`${executableName}\` is in \`$PATH\`, ` + - `or set \`${envVar}\` to point to a valid executable.` - ); -} diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 027504ecd..bdec5b735 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -3,7 +3,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as ra from './lsp_ext'; -import { Cargo } from './cargo'; +import { Cargo } from './toolchain'; import { Ctx } from "./ctx"; const debugOutput = vscode.window.createOutputChannel("Debug"); diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 4da12eb30..3e0b60699 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -45,10 +45,13 @@ export interface RunnablesParams { textDocument: lc.TextDocumentIdentifier; position: lc.Position | null; } + +export type RunnableKind = "cargo" | "rustc" | "rustup"; + export interface Runnable { range: lc.Range; label: string; - bin: string; + kind: RunnableKind; args: string[]; extraArgs: string[]; env: { [key: string]: string }; diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 2a7a429cf..401cb76af 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as ra from './lsp_ext'; +import * as toolchain from "./toolchain"; import { Ctx, Cmd } from './ctx'; import { startDebugSession, getDebugConfiguration } from './debug'; @@ -175,7 +176,7 @@ export function createTask(spec: ra.Runnable): vscode.Task { const definition: CargoTaskDefinition = { type: 'cargo', label: spec.label, - command: spec.bin, + command: toolchain.getPathForExecutable(spec.kind), args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, env: spec.env, }; diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index c22d69362..9748824df 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { getCargoPathOrFail } from "./cargo"; +import * as toolchain from "./toolchain"; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // our configuration should be compatible with it so use the same key. @@ -25,7 +25,7 @@ class CargoTaskProvider implements vscode.TaskProvider { // set of tasks that always exist. These tasks cannot be removed in // tasks.json - only tweaked. - const cargoPath = getCargoPathOrFail(); + const cargoPath = toolchain.cargoPath(); return [ { command: 'build', group: vscode.TaskGroup.Build }, diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts new file mode 100644 index 000000000..80a7915e9 --- /dev/null +++ b/editors/code/src/toolchain.ts @@ -0,0 +1,174 @@ +import * as cp from 'child_process'; +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as readline from 'readline'; +import { OutputChannel } from 'vscode'; +import { log, memoize } from './util'; + +interface CompilationArtifact { + fileName: string; + name: string; + kind: string; + isTest: boolean; +} + +export interface ArtifactSpec { + cargoArgs: string[]; + filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[]; +} + +export class Cargo { + constructor(readonly rootFolder: string, readonly output: OutputChannel) { } + + // Made public for testing purposes + static artifactSpec(args: readonly string[]): ArtifactSpec { + const cargoArgs = [...args, "--message-format=json"]; + + // arguments for a runnable from the quick pick should be updated. + // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens + switch (cargoArgs[0]) { + case "run": cargoArgs[0] = "build"; break; + case "test": { + if (!cargoArgs.includes("--no-run")) { + cargoArgs.push("--no-run"); + } + break; + } + } + + const result: ArtifactSpec = { cargoArgs: cargoArgs }; + if (cargoArgs[0] === "test") { + // for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests + // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"} + result.filter = (artifacts) => artifacts.filter(it => it.isTest); + } + + return result; + } + + private async getArtifacts(spec: ArtifactSpec): Promise { + const artifacts: CompilationArtifact[] = []; + + try { + await this.runCargo(spec.cargoArgs, + message => { + if (message.reason === 'compiler-artifact' && message.executable) { + const isBinary = message.target.crate_types.includes('bin'); + const isBuildScript = message.target.kind.includes('custom-build'); + if ((isBinary && !isBuildScript) || message.profile.test) { + artifacts.push({ + fileName: message.executable, + name: message.target.name, + kind: message.target.kind[0], + isTest: message.profile.test + }); + } + } else if (message.reason === 'compiler-message') { + this.output.append(message.message.rendered); + } + }, + stderr => this.output.append(stderr), + ); + } catch (err) { + this.output.show(true); + throw new Error(`Cargo invocation has failed: ${err}`); + } + + return spec.filter?.(artifacts) ?? artifacts; + } + + async executableFromArgs(args: readonly string[]): Promise { + const artifacts = await this.getArtifacts(Cargo.artifactSpec(args)); + + if (artifacts.length === 0) { + throw new Error('No compilation artifacts'); + } else if (artifacts.length > 1) { + throw new Error('Multiple compilation artifacts are not supported.'); + } + + return artifacts[0].fileName; + } + + private runCargo( + cargoArgs: string[], + onStdoutJson: (obj: any) => void, + onStderrString: (data: string) => void + ): Promise { + return new Promise((resolve, reject) => { + const cargo = cp.spawn(cargoPath(), cargoArgs, { + stdio: ['ignore', 'pipe', 'pipe'], + cwd: this.rootFolder + }); + + cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`))); + + cargo.stderr.on('data', chunk => onStderrString(chunk.toString())); + + const rl = readline.createInterface({ input: cargo.stdout }); + rl.on('line', line => { + const message = JSON.parse(line); + onStdoutJson(message); + }); + + cargo.on('exit', (exitCode, _) => { + if (exitCode === 0) + resolve(exitCode); + else + reject(new Error(`exit code: ${exitCode}.`)); + }); + }); + } +} + +/** Mirrors `ra_toolchain::cargo()` implementation */ +export function cargoPath(): string { + return getPathForExecutable("cargo"); +} + +/** Mirrors `ra_toolchain::get_path_for_executable()` implementation */ +export const getPathForExecutable = memoize( + // We apply caching to decrease file-system interactions + (executableName: "cargo" | "rustc" | "rustup"): string => { + { + const envVar = process.env[executableName.toUpperCase()]; + if (envVar) return envVar; + } + + if (lookupInPath(executableName)) return executableName; + + try { + // hmm, `os.homedir()` seems to be infallible + // it is not mentioned in docs and cannot be infered by the type signature... + const standardPath = path.join(os.homedir(), ".cargo", "bin", executableName); + + if (isFile(standardPath)) return standardPath; + } catch (err) { + log.error("Failed to read the fs info", err); + } + return executableName; + } +); + +function lookupInPath(exec: string): boolean { + const paths = process.env.PATH ?? "";; + + const candidates = paths.split(path.delimiter).flatMap(dirInPath => { + const candidate = path.join(dirInPath, exec); + return os.type() === "Windows_NT" + ? [candidate, `${candidate}.exe`] + : [candidate]; + }); + + return candidates.some(isFile); +} + +function isFile(suspectPath: string): boolean { + // It is not mentionned in docs, but `statSync()` throws an error when + // the path doesn't exist + try { + return fs.statSync(suspectPath).isFile(); + } catch { + return false; + } +} diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 352ef9162..fe3fb71cd 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -99,3 +99,21 @@ export function isValidExecutable(path: string): boolean { export function setContextValue(key: string, value: any): Thenable { return vscode.commands.executeCommand('setContext', key, value); } + +/** + * Returns a higher-order function that caches the results of invoking the + * underlying function. + */ +export function memoize(func: (this: TThis, arg: Param) => Ret) { + const cache = new Map(); + + return function(this: TThis, arg: Param) { + const cached = cache.get(arg); + if (cached) return cached; + + const result = func.call(this, arg); + cache.set(arg, result); + + return result; + }; +} diff --git a/editors/code/tests/unit/launch_config.test.ts b/editors/code/tests/unit/launch_config.test.ts index d5cf1b74e..68794d53e 100644 --- a/editors/code/tests/unit/launch_config.test.ts +++ b/editors/code/tests/unit/launch_config.test.ts @@ -1,25 +1,25 @@ import * as assert from 'assert'; -import * as cargo from '../../src/cargo'; +import { Cargo } from '../../src/toolchain'; suite('Launch configuration', () => { suite('Lens', () => { test('A binary', async () => { - const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]); + const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]); assert.deepEqual(args.filter, undefined); }); test('One of Multiple Binaries', async () => { - const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]); + const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]); assert.deepEqual(args.filter, undefined); }); test('A test', async () => { - const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]); + const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]); assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]); assert.notDeepEqual(args.filter, undefined); @@ -28,7 +28,7 @@ suite('Launch configuration', () => { suite('QuickPick', () => { test('A binary', async () => { - const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]); + const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]); assert.deepEqual(args.filter, undefined); @@ -36,14 +36,14 @@ suite('Launch configuration', () => { test('One of Multiple Binaries', async () => { - const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]); + const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]); assert.deepEqual(args.filter, undefined); }); test('A test', async () => { - const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]); + const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]); assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]); assert.notDeepEqual(args.filter, undefined); -- cgit v1.2.3 From 0ced18eee00c53e2c060f674918255844edf30a6 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 2 Jun 2020 14:33:47 +0200 Subject: Move run commands to commands.ts --- editors/code/src/commands.ts | 74 ++++++++++++++++++++++----- editors/code/src/debug.ts | 119 ++++++++++++++++++++++++++----------------- editors/code/src/run.ts | 74 ++------------------------- 3 files changed, 134 insertions(+), 133 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 86302db37..534d2a984 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -8,6 +8,7 @@ import { spawnSync } from 'child_process'; import { RunnableQuickPick, selectRunnable, createTask } from './run'; import { AstInspector } from './ast_inspector'; import { isRustDocument, sleep, isRustEditor } from './util'; +import { startDebugSession, makeDebugConfig } from './debug'; export * from './ast_inspector'; export * from './run'; @@ -197,20 +198,6 @@ export function toggleInlayHints(ctx: Ctx): Cmd { }; } -export function run(ctx: Ctx): Cmd { - let prevRunnable: RunnableQuickPick | undefined; - - return async () => { - const item = await selectRunnable(ctx, prevRunnable); - if (!item) return; - - item.detail = 'rerun'; - prevRunnable = item; - const task = createTask(item.runnable); - return await vscode.tasks.executeTask(task); - }; -} - // Opens the virtual file that will show the syntax tree // // The contents of the file come from the `TextDocumentContentProvider` @@ -368,3 +355,62 @@ export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd { await applySnippetWorkspaceEdit(edit); }; } + +export function run(ctx: Ctx): Cmd { + let prevRunnable: RunnableQuickPick | undefined; + + return async () => { + const item = await selectRunnable(ctx, prevRunnable); + if (!item) return; + + item.detail = 'rerun'; + prevRunnable = item; + const task = createTask(item.runnable); + return await vscode.tasks.executeTask(task); + }; +} + +export function runSingle(ctx: Ctx): Cmd { + return async (runnable: ra.Runnable) => { + const editor = ctx.activeRustEditor; + if (!editor) return; + + const task = createTask(runnable); + task.group = vscode.TaskGroup.Build; + task.presentationOptions = { + reveal: vscode.TaskRevealKind.Always, + panel: vscode.TaskPanelKind.Dedicated, + clear: true, + }; + + return vscode.tasks.executeTask(task); + }; +} + +export function debug(ctx: Ctx): Cmd { + let prevDebuggee: RunnableQuickPick | undefined; + + return async () => { + const item = await selectRunnable(ctx, prevDebuggee, true); + if (!item) return; + + item.detail = 'restart'; + prevDebuggee = item; + return await startDebugSession(ctx, item.runnable); + }; +} + +export function debugSingle(ctx: Ctx): Cmd { + return async (config: ra.Runnable) => { + await startDebugSession(ctx, config); + }; +} + +export function newDebugConfig(ctx: Ctx): Cmd { + return async () => { + const item = await selectRunnable(ctx, undefined, true, false); + if (!item) return; + + await makeDebugConfig(ctx, item.runnable); + }; +} diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index bdec5b735..1e421d407 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -9,40 +9,53 @@ import { Ctx } from "./ctx"; const debugOutput = vscode.window.createOutputChannel("Debug"); type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record) => vscode.DebugConfiguration; -function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { - return { - type: "lldb", - request: "launch", - name: config.label, - program: executable, - args: config.extraArgs, - cwd: config.cwd, - sourceMap: sourceFileMap, - sourceLanguages: ["rust"] - }; -} +export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise { + const scope = ctx.activeRustEditor?.document.uri; + if (!scope) return; -function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { - return { - type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", - request: "launch", - name: config.label, - program: executable, - args: config.extraArgs, - cwd: config.cwd, - sourceFileMap: sourceFileMap, - }; + const debugConfig = await getDebugConfiguration(ctx, runnable); + if (!debugConfig) return; + + const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); + const configurations = wsLaunchSection.get("configurations") || []; + + const index = configurations.findIndex(c => c.name === debugConfig.name); + if (index !== -1) { + const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); + if (answer === "Cancel") return; + + configurations[index] = debugConfig; + } else { + configurations.push(debugConfig); + } + + await wsLaunchSection.update("configurations", configurations); } -async function getDebugExecutable(config: ra.Runnable): Promise { - const cargo = new Cargo(config.cwd || '.', debugOutput); - const executable = await cargo.executableFromArgs(config.args); +export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise { + let debugConfig: vscode.DebugConfiguration | undefined = undefined; + let message = ""; - // if we are here, there were no compilation errors. - return executable; + const wsLaunchSection = vscode.workspace.getConfiguration("launch"); + const configurations = wsLaunchSection.get("configurations") || []; + + const index = configurations.findIndex(c => c.name === runnable.label); + if (-1 !== index) { + debugConfig = configurations[index]; + message = " (from launch.json)"; + debugOutput.clear(); + } else { + debugConfig = await getDebugConfiguration(ctx, runnable); + } + + if (!debugConfig) return false; + + debugOutput.appendLine(`Launching debug configuration${message}:`); + debugOutput.appendLine(JSON.stringify(debugConfig, null, 2)); + return vscode.debug.startDebugging(undefined, debugConfig); } -export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Promise { +async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise { const editor = ctx.activeRustEditor; if (!editor) return; @@ -78,8 +91,8 @@ export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Prom return path.normalize(p).replace(wsFolder, '${workspaceRoot}'); } - const executable = await getDebugExecutable(config); - const debugConfig = knownEngines[debugEngine.id](config, simplifyPath(executable), debugOptions.sourceFileMap); + const executable = await getDebugExecutable(runnable); + const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap); if (debugConfig.type in debugOptions.engineSettings) { const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; for (var key in settingsMap) { @@ -100,25 +113,35 @@ export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Prom return debugConfig; } -export async function startDebugSession(ctx: Ctx, config: ra.Runnable): Promise { - let debugConfig: vscode.DebugConfiguration | undefined = undefined; - let message = ""; - - const wsLaunchSection = vscode.workspace.getConfiguration("launch"); - const configurations = wsLaunchSection.get("configurations") || []; +async function getDebugExecutable(runnable: ra.Runnable): Promise { + const cargo = new Cargo(runnable.cwd || '.', debugOutput); + const executable = await cargo.executableFromArgs(runnable.args); - const index = configurations.findIndex(c => c.name === config.label); - if (-1 !== index) { - debugConfig = configurations[index]; - message = " (from launch.json)"; - debugOutput.clear(); - } else { - debugConfig = await getDebugConfiguration(ctx, config); - } + // if we are here, there were no compilation errors. + return executable; +} - if (!debugConfig) return false; +function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { + return { + type: "lldb", + request: "launch", + name: runnable.label, + program: executable, + args: runnable.extraArgs, + cwd: runnable.cwd, + sourceMap: sourceFileMap, + sourceLanguages: ["rust"] + }; +} - debugOutput.appendLine(`Launching debug configuration${message}:`); - debugOutput.appendLine(JSON.stringify(debugConfig, null, 2)); - return vscode.debug.startDebugging(undefined, debugConfig); +function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { + return { + type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", + request: "launch", + name: runnable.label, + program: executable, + args: runnable.extraArgs, + cwd: runnable.cwd, + sourceFileMap: sourceFileMap, + }; } diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 113354bab..5fc4f8e41 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -3,8 +3,8 @@ import * as lc from 'vscode-languageclient'; import * as ra from './lsp_ext'; import * as toolchain from "./toolchain"; -import { Ctx, Cmd } from './ctx'; -import { startDebugSession, getDebugConfiguration } from './debug'; +import { Ctx } from './ctx'; +import { makeDebugConfig } from './debug'; const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; @@ -65,7 +65,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, quickPick.onDidHide(() => close()), quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), quickPick.onDidTriggerButton((_button) => { - (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))(); + (async () => await makeDebugConfig(ctx, quickPick.activeItems[0].runnable))(); close(); }), quickPick.onDidChangeActive((active) => { @@ -84,74 +84,6 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, }); } -export function runSingle(ctx: Ctx): Cmd { - return async (runnable: ra.Runnable) => { - const editor = ctx.activeRustEditor; - if (!editor) return; - - const task = createTask(runnable); - task.group = vscode.TaskGroup.Build; - task.presentationOptions = { - reveal: vscode.TaskRevealKind.Always, - panel: vscode.TaskPanelKind.Dedicated, - clear: true, - }; - - return vscode.tasks.executeTask(task); - }; -} - -export function debug(ctx: Ctx): Cmd { - let prevDebuggee: RunnableQuickPick | undefined; - - return async () => { - const item = await selectRunnable(ctx, prevDebuggee, true); - if (!item) return; - - item.detail = 'restart'; - prevDebuggee = item; - return await startDebugSession(ctx, item.runnable); - }; -} - -export function debugSingle(ctx: Ctx): Cmd { - return async (config: ra.Runnable) => { - await startDebugSession(ctx, config); - }; -} - -async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise { - const scope = ctx.activeRustEditor?.document.uri; - if (!scope) return; - - const debugConfig = await getDebugConfiguration(ctx, item.runnable); - if (!debugConfig) return; - - const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); - const configurations = wsLaunchSection.get("configurations") || []; - - const index = configurations.findIndex(c => c.name === debugConfig.name); - if (index !== -1) { - const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); - if (answer === "Cancel") return; - - configurations[index] = debugConfig; - } else { - configurations.push(debugConfig); - } - - await wsLaunchSection.update("configurations", configurations); -} - -export function newDebugConfig(ctx: Ctx): Cmd { - return async () => { - const item = await selectRunnable(ctx, undefined, true, false); - if (!item) return; - - await makeDebugConfig(ctx, item); - }; -} - export class RunnableQuickPick implements vscode.QuickPickItem { public label: string; public description?: string | undefined; -- cgit v1.2.3 From 03039821195c9d9c4bbc1e4cbddb6378c43a6c52 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 2 Jun 2020 17:22:23 +0200 Subject: New runnables API --- editors/code/src/debug.ts | 12 ++++++------ editors/code/src/lsp_ext.ts | 15 +++++++-------- editors/code/src/run.ts | 21 +++++++++++++++------ 3 files changed, 28 insertions(+), 20 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 1e421d407..a0c9b3ab2 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -114,8 +114,8 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise { - const cargo = new Cargo(runnable.cwd || '.', debugOutput); - const executable = await cargo.executableFromArgs(runnable.args); + const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput); + const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); // if we are here, there were no compilation errors. return executable; @@ -127,8 +127,8 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFil request: "launch", name: runnable.label, program: executable, - args: runnable.extraArgs, - cwd: runnable.cwd, + args: runnable.args.executableArgs, + cwd: runnable.args.workspaceRoot, sourceMap: sourceFileMap, sourceLanguages: ["rust"] }; @@ -140,8 +140,8 @@ function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFi request: "launch", name: runnable.label, program: executable, - args: runnable.extraArgs, - cwd: runnable.cwd, + args: runnable.args.executableArgs, + cwd: runnable.args.workspaceRoot, sourceFileMap: sourceFileMap, }; } diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 3e0b60699..73d573678 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -46,16 +46,15 @@ export interface RunnablesParams { position: lc.Position | null; } -export type RunnableKind = "cargo" | "rustc" | "rustup"; - export interface Runnable { - range: lc.Range; label: string; - kind: RunnableKind; - args: string[]; - extraArgs: string[]; - env: { [key: string]: string }; - cwd: string | null; + location?: lc.LocationLink; + kind: "cargo"; + args: { + workspaceRoot?: string; + cargoArgs: string[]; + executableArgs: string[]; + }; } export const runnables = new lc.RequestType("rust-analyzer/runnables"); diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 5fc4f8e41..5c790741f 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -103,18 +103,27 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { env?: { [key: string]: string }; } -export function createTask(spec: ra.Runnable): vscode.Task { +export function createTask(runnable: ra.Runnable): vscode.Task { const TASK_SOURCE = 'Rust'; + + let command; + switch (runnable.kind) { + case "cargo": command = toolchain.getPathForExecutable("cargo"); + } + const args = runnable.args.cargoArgs; + if (runnable.args.executableArgs.length > 0) { + args.push('--', ...runnable.args.executableArgs); + } const definition: CargoTaskDefinition = { type: 'cargo', - label: spec.label, - command: toolchain.getPathForExecutable(spec.kind), - args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, - env: Object.assign({}, process.env, spec.env), + label: runnable.label, + command, + args, + env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), }; const execOption: vscode.ShellExecutionOptions = { - cwd: spec.cwd || '.', + cwd: runnable.args.workspaceRoot || '.', env: definition.env, }; const exec = new vscode.ShellExecution( -- cgit v1.2.3 From a83ab820a4633bac718ee0fd11f06d1b3142be6b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 2 Jun 2020 17:34:18 +0200 Subject: Spec better runnables --- editors/code/src/lsp_ext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 73d573678..c51acfccb 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -56,7 +56,7 @@ export interface Runnable { executableArgs: string[]; }; } -export const runnables = new lc.RequestType("rust-analyzer/runnables"); +export const runnables = new lc.RequestType("experimental/runnables"); export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint; -- cgit v1.2.3 From 7a66d9989713475a10eb20b8c772287b435fecd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Tue, 2 Jun 2020 12:24:33 +0300 Subject: Disable rust-analyzer.{cargo,checkOnSave}.allFeatures by default --- editors/code/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index 75dbafc05..d8f4287fd 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -238,7 +238,7 @@ }, "rust-analyzer.cargo.allFeatures": { "type": "boolean", - "default": true, + "default": false, "description": "Activate all available features" }, "rust-analyzer.cargo.features": { @@ -319,7 +319,7 @@ }, "rust-analyzer.checkOnSave.allFeatures": { "type": "boolean", - "default": true, + "default": false, "markdownDescription": "Check with all features (will be passed as `--all-features`)" }, "rust-analyzer.inlayHints.enable": { -- cgit v1.2.3 From 57cd936c5262c3b43626618be42d7a72f71c3539 Mon Sep 17 00:00:00 2001 From: Mikhail Rakhmanov Date: Tue, 2 Jun 2020 22:21:48 +0200 Subject: Preliminary implementation of lazy CodeAssits --- editors/code/src/client.ts | 76 +++++++++++++++++++++++--------------------- editors/code/src/commands.ts | 19 +++++++++-- editors/code/src/lsp_ext.ts | 7 ++++ editors/code/src/main.ts | 1 + 4 files changed, 65 insertions(+), 38 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index d64f9a3f9..a25091f79 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -1,8 +1,11 @@ import * as lc from 'vscode-languageclient'; import * as vscode from 'vscode'; +import * as ra from '../src/lsp_ext'; +import * as Is from 'vscode-languageclient/lib/utils/is'; import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; +import { assert } from './util'; export function createClient(serverPath: string, cwd: string): lc.LanguageClient { // '.' Is the fallback if no folder is open @@ -32,6 +35,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient if (res === undefined) throw new Error('busy'); return res; }, + // Using custom handling of CodeActions where each code action is resloved lazily + // That's why we are not waiting for any command or edits async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { const params: lc.CodeActionParams = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), @@ -43,32 +48,38 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient const result: (vscode.CodeAction | vscode.Command)[] = []; const groups = new Map(); for (const item of values) { + // In our case we expect to get code edits only from diagnostics if (lc.CodeAction.is(item)) { + assert(!item.command, "We don't expect to receive commands in CodeActions"); const action = client.protocol2CodeConverter.asCodeAction(item); - const group = actionGroup(item); - if (isSnippetEdit(item) || group) { - action.command = { - command: "rust-analyzer.applySnippetWorkspaceEdit", - title: "", - arguments: [action.edit], - }; - action.edit = undefined; - } - - if (group) { - let entry = groups.get(group); - if (!entry) { - entry = { index: result.length, items: [] }; - groups.set(group, entry); - result.push(action); - } - entry.items.push(action); - } else { + result.push(action); + continue; + } + assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here"); + const action = new vscode.CodeAction(item.title); + const group = (item as any).group; + const id = (item as any).id; + const resolveParams: ra.ResolveCodeActionParams = { + id: id, + // TODO: delete after discussions if needed + label: item.title, + codeActionParams: params + }; + action.command = { + command: "rust-analyzer.resolveCodeAction", + title: item.title, + arguments: [resolveParams], + }; + if (group) { + let entry = groups.get(group); + if (!entry) { + entry = { index: result.length, items: [] }; + groups.set(group, entry); result.push(action); } + entry.items.push(action); } else { - const command = client.protocol2CodeConverter.asCommand(item); - result.push(command); + result.push(action); } } for (const [group, { index, items }] of groups) { @@ -80,7 +91,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient command: "rust-analyzer.applyActionGroup", title: "", arguments: [items.map((item) => { - return { label: item.title, edit: item.command!!.arguments!![0] }; + return { label: item.title, arguments: item.command!!.arguments!![0] }; })], }; result[index] = action; @@ -119,24 +130,17 @@ class ExperimentalFeatures implements lc.StaticFeature { const caps: any = capabilities.experimental ?? {}; caps.snippetTextEdit = true; caps.codeActionGroup = true; + caps.resolveCodeAction = true; capabilities.experimental = caps; } initialize(_capabilities: lc.ServerCapabilities, _documentSelector: lc.DocumentSelector | undefined): void { } } -function isSnippetEdit(action: lc.CodeAction): boolean { - const documentChanges = action.edit?.documentChanges ?? []; - for (const edit of documentChanges) { - if (lc.TextDocumentEdit.is(edit)) { - if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) { - return true; - } - } - } - return false; -} - -function actionGroup(action: lc.CodeAction): string | undefined { - return (action as any).group; +function isCodeActionWithoutEditsAndCommands(value: any): boolean { + const candidate: lc.CodeAction = value; + return candidate && Is.string(candidate.title) && + (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) && + (candidate.kind === void 0 || Is.string(candidate.kind)) && + (candidate.edit === void 0 && candidate.command === void 0); } diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 534d2a984..3e9c3aa0e 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -343,10 +343,25 @@ export function showReferences(ctx: Ctx): Cmd { } export function applyActionGroup(_ctx: Ctx): Cmd { - return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => { + return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => { const selectedAction = await vscode.window.showQuickPick(actions); if (!selectedAction) return; - await applySnippetWorkspaceEdit(selectedAction.edit); + vscode.commands.executeCommand( + 'rust-analyzer.resolveCodeAction', + selectedAction.arguments, + ); + }; +} + +export function resolveCodeAction(ctx: Ctx): Cmd { + const client = ctx.client; + return async (params: ra.ResolveCodeActionParams) => { + const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); + if (!item) { + return; + } + const edit = client.protocol2CodeConverter.asWorkspaceEdit(item); + await applySnippetWorkspaceEdit(edit); }; } diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 3e0b60699..f881bae47 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -33,6 +33,13 @@ export const matchingBrace = new lc.RequestType("experimental/parentModule"); +export interface ResolveCodeActionParams { + id: string; + label: string; + codeActionParams: lc.CodeActionParams; +} +export const resolveCodeAction = new lc.RequestType('experimental/resolveCodeAction'); + export interface JoinLinesParams { textDocument: lc.TextDocumentIdentifier; ranges: lc.Range[]; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index b7337621c..a92c676fa 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -98,6 +98,7 @@ export async function activate(context: vscode.ExtensionContext) { ctx.registerCommand('debugSingle', commands.debugSingle); ctx.registerCommand('showReferences', commands.showReferences); ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); + ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); ctx.registerCommand('applyActionGroup', commands.applyActionGroup); ctx.pushCleanup(activateTaskProvider(workspaceFolder)); -- cgit v1.2.3 From fa019c8f562326a720d2ef9165626c4c5703f67b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 14:48:38 +0200 Subject: Document rust-project.json --- editors/code/package.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index d8f4287fd..30ab7ba4a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -475,6 +475,25 @@ "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true + }, + "rust-analyzer.linkedProjects": { + "markdownDescription": [ + "Disable project auto-discovery in favor of explicitly specified set of projects.", + "Elements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format" + ], + "type": "array", + "items": { + "type": [ + "string", + "object" + ] + }, + "default": null + }, + "rust-analyzer.withSysroot": { + "markdownDescription": "Internal config for debugging, disables loading of sysroot crates", + "type": "boolean", + "default": true } } }, -- cgit v1.2.3 From bacd0428fa0fd744eb0aac6d5d7abd18c6c707b7 Mon Sep 17 00:00:00 2001 From: Mikhail Rakhmanov Date: Wed, 3 Jun 2020 18:39:01 +0200 Subject: Fix review comments --- editors/code/src/client.ts | 2 -- editors/code/src/lsp_ext.ts | 1 - 2 files changed, 3 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index a25091f79..40ad1e3cd 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -61,8 +61,6 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient const id = (item as any).id; const resolveParams: ra.ResolveCodeActionParams = { id: id, - // TODO: delete after discussions if needed - label: item.title, codeActionParams: params }; action.command = { diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 35d73ce31..9793b926c 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -35,7 +35,6 @@ export const parentModule = new lc.RequestType('experimental/resolveCodeAction'); -- cgit v1.2.3 From 7d0dd17b09240385333805637ea17992a8089cf2 Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 3 Jun 2020 14:15:54 +0300 Subject: Add hover actions as LSP extension --- editors/code/package.json | 16 +++++++++++++--- editors/code/src/client.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ editors/code/src/config.ts | 13 +++++++++---- editors/code/src/lsp_ext.ts | 12 ++++++++++++ 4 files changed, 79 insertions(+), 7 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index 30ab7ba4a..b9c57db3b 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -462,17 +462,27 @@ "default": true }, "rust-analyzer.lens.run": { - "markdownDescription": "Whether to show Run lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Run` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true }, "rust-analyzer.lens.debug": { - "markdownDescription": "Whether to show Debug lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Debug` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true }, "rust-analyzer.lens.implementations": { - "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Implementations` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.enable": { + "description": "Whether to show HoverActions in Rust files.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.implementations": { + "markdownDescription": "Whether to show `Implementations` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", "type": "boolean", "default": true }, diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 40ad1e3cd..9df670283 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -7,6 +7,29 @@ import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.pr import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; import { assert } from './util'; +function toTrusted(obj: vscode.MarkedString): vscode.MarkedString { + const md = obj; + if (md && md.value.includes("```rust")) { + md.isTrusted = true; + return md; + } + return obj; +} + +function renderCommand(cmd: CommandLink) { + return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; +} + +function renderHoverActions(actions: CommandLinkGroup[]): vscode.MarkdownString { + const text = actions.map(group => + (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ') + ).join('___'); + + const result = new vscode.MarkdownString(text); + result.isTrusted = true; + return result; +} + export function createClient(serverPath: string, cwd: string): lc.LanguageClient { // '.' Is the fallback if no folder is open // TODO?: Workspace folders support Uri's (eg: file://test.txt). @@ -35,6 +58,27 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient if (res === undefined) throw new Error('busy'); return res; }, + async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) { + return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then( + (result) => { + const hover = client.protocol2CodeConverter.asHover(result); + if (hover) { + // Workaround to support command links (trusted vscode.MarkdownString) in hovers + // https://github.com/microsoft/vscode/issues/33577 + hover.contents = hover.contents.map(toTrusted); + + const actions = (result).actions; + if (actions) { + hover.contents.push(renderHoverActions(actions)); + } + } + return hover; + }, + (error) => { + client.logFailedRequest(lc.HoverRequest.type, error); + return Promise.resolve(null); + }); + }, // Using custom handling of CodeActions where each code action is resloved lazily // That's why we are not waiting for any command or edits async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { @@ -129,6 +173,7 @@ class ExperimentalFeatures implements lc.StaticFeature { caps.snippetTextEdit = true; caps.codeActionGroup = true; caps.resolveCodeAction = true; + caps.hoverActions = true; capabilities.experimental = caps; } initialize(_capabilities: lc.ServerCapabilities, _documentSelector: lc.DocumentSelector | undefined): void { diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index e8abf8284..d8f0037d4 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -16,10 +16,8 @@ export class Config { "files", "highlighting", "updates.channel", - "lens.enable", - "lens.run", - "lens.debug", - "lens.implementations", + "lens", // works as lens.* + "hoverActions", // works as hoverActions.* ] .map(opt => `${this.rootSection}.${opt}`); @@ -132,4 +130,11 @@ export class Config { implementations: this.get("lens.implementations"), }; } + + get hoverActions() { + return { + enable: this.get("hoverActions.enable"), + implementations: this.get("hoverActions.implementations"), + }; + } } diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 9793b926c..e16ea799c 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -90,3 +90,15 @@ export interface SsrParams { parseOnly: boolean; } export const ssr = new lc.RequestType('experimental/ssr'); + +export interface CommandLink extends lc.Command { + /** + * A tooltip for the command, when represented in the UI. + */ + tooltip?: string; +} + +export interface CommandLinkGroup { + title?: string; + commands: CommandLink[]; +} -- cgit v1.2.3 From da7ec4b3398ffaf672a755bf57066e17ac42303a Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 3 Jun 2020 14:34:11 +0300 Subject: Add hover actions LSP extension documentation. --- editors/code/src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 9df670283..f2094b5ce 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -66,7 +66,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient // Workaround to support command links (trusted vscode.MarkdownString) in hovers // https://github.com/microsoft/vscode/issues/33577 hover.contents = hover.contents.map(toTrusted); - + const actions = (result).actions; if (actions) { hover.contents.push(renderHoverActions(actions)); -- cgit v1.2.3 From 78c9223b7bd4bffe64b7ec69e5fee08604dc0057 Mon Sep 17 00:00:00 2001 From: vsrs Date: Fri, 5 Jun 2020 15:25:01 +0300 Subject: Remove hover contents marking as trusted. Hover contents might be extracted from raw doc comments and need some validation. --- editors/code/src/client.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index f2094b5ce..65ad573d8 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -7,20 +7,11 @@ import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.pr import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; import { assert } from './util'; -function toTrusted(obj: vscode.MarkedString): vscode.MarkedString { - const md = obj; - if (md && md.value.includes("```rust")) { - md.isTrusted = true; - return md; - } - return obj; -} - -function renderCommand(cmd: CommandLink) { +function renderCommand(cmd: ra.CommandLink) { return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; } -function renderHoverActions(actions: CommandLinkGroup[]): vscode.MarkdownString { +function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { const text = actions.map(group => (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ') ).join('___'); @@ -63,10 +54,6 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient (result) => { const hover = client.protocol2CodeConverter.asHover(result); if (hover) { - // Workaround to support command links (trusted vscode.MarkdownString) in hovers - // https://github.com/microsoft/vscode/issues/33577 - hover.contents = hover.contents.map(toTrusted); - const actions = (result).actions; if (actions) { hover.contents.push(renderHoverActions(actions)); -- cgit v1.2.3 From b91fa7494ece3ca26e5797c4b23469d82b8e1322 Mon Sep 17 00:00:00 2001 From: vsrs Date: Sat, 6 Jun 2020 15:12:17 +0300 Subject: Fix Run lens. --- editors/code/src/run.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 5c790741f..bb060cfe1 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -110,7 +110,7 @@ export function createTask(runnable: ra.Runnable): vscode.Task { switch (runnable.kind) { case "cargo": command = toolchain.getPathForExecutable("cargo"); } - const args = runnable.args.cargoArgs; + const args = [...runnable.args.cargoArgs]; // should be a copy! if (runnable.args.executableArgs.length > 0) { args.push('--', ...runnable.args.executableArgs); } -- cgit v1.2.3 From 3434f1dd2c47fff3df159b9d62115c2df3fd6401 Mon Sep 17 00:00:00 2001 From: vsrs Date: Sat, 6 Jun 2020 14:30:29 +0300 Subject: Add Run|Debug hover actions --- editors/code/package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index b9c57db3b..7fdb5c27d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -486,6 +486,16 @@ "type": "boolean", "default": true }, + "rust-analyzer.hoverActions.run": { + "markdownDescription": "Whether to show `Run` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.debug": { + "markdownDescription": "Whether to show `Debug` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", + "type": "boolean", + "default": true + }, "rust-analyzer.linkedProjects": { "markdownDescription": [ "Disable project auto-discovery in favor of explicitly specified set of projects.", -- cgit v1.2.3 From 3162b9ed8f70701a1534acc47f8d0ea0ba8e3b2d Mon Sep 17 00:00:00 2001 From: Vincent Isambart Date: Sun, 7 Jun 2020 12:07:00 +0900 Subject: Fix VSCode settings --- editors/code/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index b9c57db3b..859ab4477 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -487,10 +487,7 @@ "default": true }, "rust-analyzer.linkedProjects": { - "markdownDescription": [ - "Disable project auto-discovery in favor of explicitly specified set of projects.", - "Elements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format" - ], + "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format", "type": "array", "items": { "type": [ -- cgit v1.2.3 From 47ef544fa57ca1833b466e491315e54a88780b4d Mon Sep 17 00:00:00 2001 From: Clemens Wasser Date: Wed, 10 Jun 2020 08:51:11 +0200 Subject: Added the rust-analyzer.checkOnSave.features option. --- editors/code/package.json | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index 779d7e1b8..6389499e9 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -322,6 +322,14 @@ "default": false, "markdownDescription": "Check with all features (will be passed as `--all-features`)" }, + "rust-analyzer.checkOnSave.features": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "List of features to activate. Set to `rust-analyzer.cargo.features` if empty." + }, "rust-analyzer.inlayHints.enable": { "type": "boolean", "default": true, -- cgit v1.2.3 From 33b905883819038ad67476fe14b7b48212a73f93 Mon Sep 17 00:00:00 2001 From: Clemens Wasser Date: Wed, 10 Jun 2020 09:27:25 +0200 Subject: Most of the checkOnSafe options now default to the cargo equivalent. --- editors/code/package.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index 6389499e9..647c83685 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -318,9 +318,12 @@ "markdownDescription": "Check all targets and tests (will be passed as `--all-targets`)" }, "rust-analyzer.checkOnSave.allFeatures": { - "type": "boolean", - "default": false, - "markdownDescription": "Check with all features (will be passed as `--all-features`)" + "type": [ + "null", + "boolean" + ], + "default": null, + "markdownDescription": "Check with all features (will be passed as `--all-features`). Defaults to `rust-analyzer.cargo.allFeatures`." }, "rust-analyzer.checkOnSave.features": { "type": "array", @@ -328,7 +331,7 @@ "type": "string" }, "default": [], - "description": "List of features to activate. Set to `rust-analyzer.cargo.features` if empty." + "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." }, "rust-analyzer.inlayHints.enable": { "type": "boolean", -- cgit v1.2.3 From fe21fc2d259cbe2a32bfee3432f2c51ade079083 Mon Sep 17 00:00:00 2001 From: Clemens Wasser Date: Wed, 10 Jun 2020 09:37:26 +0200 Subject: checkOnSafe.features and checkOnSafe.allFeatures now work identically. --- editors/code/package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index 647c83685..e2027970d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -326,11 +326,14 @@ "markdownDescription": "Check with all features (will be passed as `--all-features`). Defaults to `rust-analyzer.cargo.allFeatures`." }, "rust-analyzer.checkOnSave.features": { - "type": "array", + "type": [ + "null", + "array" + ], "items": { "type": "string" }, - "default": [], + "default": null, "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." }, "rust-analyzer.inlayHints.enable": { -- cgit v1.2.3