Merge commit 'aa9bc86125' into sync-from-ra

This commit is contained in:
Laurențiu Nicola 2023-06-05 12:04:23 +03:00
parent 1570299af4
commit c48062fe2a
598 changed files with 57696 additions and 17615 deletions

View file

@ -44,7 +44,8 @@
"anser": "^2.1.1",
"d3": "^7.6.1",
"d3-graphviz": "^5.0.2",
"vscode-languageclient": "^8.0.2"
"vscode-languageclient": "^8.0.2",
"@hpcc-js/wasm": "2.5.0"
},
"devDependencies": {
"@types/node": "~16.11.7",
@ -119,6 +120,11 @@
"title": "View Mir",
"category": "rust-analyzer (debug command)"
},
{
"command": "rust-analyzer.interpretFunction",
"title": "Interpret Function",
"category": "rust-analyzer (debug command)"
},
{
"command": "rust-analyzer.viewFileText",
"title": "View File Text (as seen by the server)",
@ -199,13 +205,18 @@
"title": "Reload workspace",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.rebuildProcMacros",
"title": "Rebuild proc macros and build scripts",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.addProject",
"title": "Add current file's crate to workspace",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.reload",
"command": "rust-analyzer.restartServer",
"title": "Restart server",
"category": "rust-analyzer"
},
@ -273,6 +284,11 @@
"command": "rust-analyzer.clearFlycheck",
"title": "Clear flycheck diagnostics",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.revealDependency",
"title": "Reveal File",
"category": "rust-analyzer"
}
],
"keybindings": [
@ -444,6 +460,16 @@
"type": "string"
}
},
"rust-analyzer.showUnlinkedFileNotification": {
"markdownDescription": "Whether to show a notification for unlinked files asking the user to add the corresponding Cargo.toml to the linked projects setting.",
"default": true,
"type": "boolean"
},
"rust-analyzer.showDependenciesExplorer": {
"markdownDescription": "Whether to show the dependencies view.",
"default": true,
"type": "boolean"
},
"$generated-start": {},
"rust-analyzer.assist.emitMustUse": {
"markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.",
@ -527,6 +553,11 @@
"default": true,
"type": "boolean"
},
"rust-analyzer.cargo.cfgs": {
"markdownDescription": "List of cfg options to enable with the given values.",
"default": {},
"type": "object"
},
"rust-analyzer.cargo.extraArgs": {
"markdownDescription": "Extra arguments that are passed to every cargo invocation.",
"default": [],
@ -591,7 +622,7 @@
]
},
"rust-analyzer.cargo.unsetTest": {
"markdownDescription": "Unsets `#[cfg(test)]` for the specified crates.",
"markdownDescription": "Unsets the implicit `#[cfg(test)]` for the specified crates.",
"default": [
"core"
],
@ -870,6 +901,11 @@
"default": true,
"type": "boolean"
},
"rust-analyzer.highlightRelated.closureCaptures.enable": {
"markdownDescription": "Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.",
"default": true,
"type": "boolean"
},
"rust-analyzer.highlightRelated.exitPoints.enable": {
"markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
"default": true,
@ -926,10 +962,89 @@
"type": "boolean"
},
"rust-analyzer.hover.links.enable": {
"markdownDescription": "Use markdown syntax for links in hover.",
"markdownDescription": "Use markdown syntax for links on hover.",
"default": true,
"type": "boolean"
},
"rust-analyzer.hover.memoryLayout.alignment": {
"markdownDescription": "How to render the align information in a memory layout hover.",
"default": "hexadecimal",
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"enum": [
"both",
"decimal",
"hexadecimal"
],
"enumDescriptions": [
"Render as 12 (0xC)",
"Render as 12",
"Render as 0xC"
]
}
]
},
"rust-analyzer.hover.memoryLayout.enable": {
"markdownDescription": "Whether to show memory layout data on hover.",
"default": true,
"type": "boolean"
},
"rust-analyzer.hover.memoryLayout.niches": {
"markdownDescription": "How to render the niche information in a memory layout hover.",
"default": false,
"type": [
"null",
"boolean"
]
},
"rust-analyzer.hover.memoryLayout.offset": {
"markdownDescription": "How to render the offset information in a memory layout hover.",
"default": "hexadecimal",
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"enum": [
"both",
"decimal",
"hexadecimal"
],
"enumDescriptions": [
"Render as 12 (0xC)",
"Render as 12",
"Render as 0xC"
]
}
]
},
"rust-analyzer.hover.memoryLayout.size": {
"markdownDescription": "How to render the size information in a memory layout hover.",
"default": "both",
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"enum": [
"both",
"decimal",
"hexadecimal"
],
"enumDescriptions": [
"Render as 12 (0xC)",
"Render as 12",
"Render as 0xC"
]
}
]
},
"rust-analyzer.imports.granularity.enforce": {
"markdownDescription": "Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.",
"default": false,
@ -1003,6 +1118,11 @@
"type": "integer",
"minimum": 0
},
"rust-analyzer.inlayHints.closureCaptureHints.enable": {
"markdownDescription": "Whether to show inlay hints for closure captures.",
"default": false,
"type": "boolean"
},
"rust-analyzer.inlayHints.closureReturnTypeHints.enable": {
"markdownDescription": "Whether to show inlay type hints for return types of closures.",
"default": "never",
@ -1018,6 +1138,23 @@
"Only show type hints for return types of closures with blocks."
]
},
"rust-analyzer.inlayHints.closureStyle": {
"markdownDescription": "Closure notation in type and chaining inlay hints.",
"default": "impl_fn",
"type": "string",
"enum": [
"impl_fn",
"rust_analyzer",
"with_id",
"hide"
],
"enumDescriptions": [
"`impl_fn`: `impl FnMut(i32, u64) -> i8`",
"`rust_analyzer`: `|i32, u64| -> i8`",
"`with_id`: `{closure#14352}`, where that id is the unique number of the closure in r-a internals",
"`hide`: Shows `...` for every closure type"
]
},
"rust-analyzer.inlayHints.discriminantHints.enable": {
"markdownDescription": "Whether to show enum variant discriminant hints.",
"default": "never",
@ -1066,8 +1203,8 @@
"enumDescriptions": [
"Always show adjustment hints as prefix (`*expr`).",
"Always show adjustment hints as postfix (`expr.*`).",
"Show prefix or postfix depending on which uses less parenthesis, prefering prefix.",
"Show prefix or postfix depending on which uses less parenthesis, prefering postfix."
"Show prefix or postfix depending on which uses less parenthesis, preferring prefix.",
"Show prefix or postfix depending on which uses less parenthesis, preferring postfix."
]
},
"rust-analyzer.inlayHints.lifetimeElisionHints.enable": {
@ -1242,6 +1379,11 @@
],
"minimum": 0
},
"rust-analyzer.lru.query.capacities": {
"markdownDescription": "Sets the LRU capacity of the specified queries.",
"default": {},
"type": "object"
},
"rust-analyzer.notifications.cargoTomlNotFound": {
"markdownDescription": "Whether to show `can't find Cargo.toml` error message.",
"default": true,
@ -1272,7 +1414,7 @@
"type": "object"
},
"rust-analyzer.procMacro.server": {
"markdownDescription": "Internal config, path to proc-macro server executable (typically,\nthis is rust-analyzer itself, but we override this in tests).",
"markdownDescription": "Internal config, path to proc-macro server executable.",
"default": null,
"type": [
"null",
@ -1337,6 +1479,11 @@
"default": true,
"type": "boolean"
},
"rust-analyzer.semanticHighlighting.nonStandardTokens": {
"markdownDescription": "Whether the server is allowed to emit non-standard tokens and modifiers.",
"default": true,
"type": "boolean"
},
"rust-analyzer.semanticHighlighting.operator.enable": {
"markdownDescription": "Use semantic tokens for operators.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for operator tokens when\nthey are tagged with modifiers.",
"default": true,
@ -1348,7 +1495,7 @@
"type": "boolean"
},
"rust-analyzer.semanticHighlighting.punctuation.enable": {
"markdownDescription": "Use semantic tokens for punctuations.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when\nthey are tagged with modifiers or have a special role.",
"markdownDescription": "Use semantic tokens for punctuation.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when\nthey are tagged with modifiers or have a special role.",
"default": false,
"type": "boolean"
},
@ -1358,7 +1505,7 @@
"type": "boolean"
},
"rust-analyzer.semanticHighlighting.punctuation.specialization.enable": {
"markdownDescription": "Use specialized semantic tokens for punctuations.\n\nWhen enabled, rust-analyzer will emit special token types for punctuation tokens instead\nof the generic `punctuation` token type.",
"markdownDescription": "Use specialized semantic tokens for punctuation.\n\nWhen enabled, rust-analyzer will emit special token types for punctuation tokens instead\nof the generic `punctuation` token type.",
"default": false,
"type": "boolean"
},
@ -1461,6 +1608,18 @@
"endColumn": 6
}
]
},
{
"name": "rust-panic",
"patterns": [
{
"regexp": "^thread '.*' panicked at '(.*)', (.*):(\\d*):(\\d*)$",
"message": 1,
"file": 2,
"line": 3,
"column": 4
}
]
}
],
"languages": [
@ -1487,6 +1646,16 @@
"language": "ra_syntax_tree",
"scopeName": "source.ra_syntax_tree",
"path": "ra_syntax_tree.tmGrammar.json"
},
{
"scopeName": "rustdoc.markdown.injection",
"path": "rustdoc.markdown.injection.tmGrammar.json",
"injectTo": [
"source.rust"
],
"embeddedLanguages": {
"meta.embedded.block.markdown": "text.html.markdown"
}
}
],
"problemMatchers": [
@ -1510,6 +1679,16 @@
],
"pattern": "$rustc-json"
},
{
"name": "rust-panic",
"owner": "rust-panic",
"source": "panic",
"fileLocation": [
"autoDetect",
"${workspaceRoot}"
],
"pattern": "$rust-panic"
},
{
"name": "rustc-watch",
"owner": "rustc",
@ -1876,7 +2055,7 @@
"when": "inRustProject"
},
{
"command": "rust-analyzer.reload",
"command": "rust-analyzer.restartServer",
"when": "inRustProject"
},
{
@ -1913,6 +2092,15 @@
}
]
},
"views": {
"explorer": [
{
"id": "rustDependencies",
"name": "Rust Dependencies",
"when": "inRustProject && config.rust-analyzer.showDependenciesExplorer"
}
]
},
"jsonValidation": [
{
"fileMatch": "rust-project.json",

View file

@ -0,0 +1,36 @@
{
"scopeName": "rustdoc.markdown.injection",
"injectionSelector": "L:source.rust",
"patterns": [
{
"include": "#doc-comment-line"
},
{
"include": "#doc-comment-block"
}
],
"repository": {
"doc-comment-line": {
"name": "comment.line.documentation.rust",
"begin": "^\\s*//(/|!)",
"while": "^\\s*//(/|!)",
"contentName": "meta.embedded.block.markdown",
"patterns": [
{
"include": "text.html.markdown"
}
]
},
"doc-comment-block": {
"name": "comment.block.documentation.rust",
"begin": "/\\*(\\*|!)",
"end": "\\s*\\*/",
"contentName": "meta.embedded.block.markdown",
"patterns": [
{
"include": "text.html.markdown"
}
]
}
}
}

View file

@ -8,6 +8,7 @@ import * as diagnostics from "./diagnostics";
import { WorkspaceEdit } from "vscode";
import { Config, prepareVSCodeConfig } from "./config";
import { randomUUID } from "crypto";
import { sep as pathSeparator } from "path";
export interface Env {
[name: string]: string;
@ -69,7 +70,8 @@ export async function createClient(
outputChannel: vscode.OutputChannel,
initializationOptions: vscode.WorkspaceConfiguration,
serverOptions: lc.ServerOptions,
config: Config
config: Config,
unlinkedFiles: vscode.Uri[]
): Promise<lc.LanguageClient> {
const clientOptions: lc.LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "rust" }],
@ -119,6 +121,69 @@ export async function createClient(
const preview = config.previewRustcOutput;
const errorCode = config.useRustcErrorCode;
diagnosticList.forEach((diag, idx) => {
const value =
typeof diag.code === "string" || typeof diag.code === "number"
? diag.code
: diag.code?.value;
if (
value === "unlinked-file" &&
!unlinkedFiles.includes(uri) &&
diag.message !== "file not included in module tree"
) {
const config = vscode.workspace.getConfiguration("rust-analyzer");
if (config.get("showUnlinkedFileNotification")) {
unlinkedFiles.push(uri);
const folder = vscode.workspace.getWorkspaceFolder(uri)?.uri.fsPath;
if (folder) {
const parentBackslash = uri.fsPath.lastIndexOf(
pathSeparator + "src"
);
const parent = uri.fsPath.substring(0, parentBackslash);
if (parent.startsWith(folder)) {
const path = vscode.Uri.file(
parent + pathSeparator + "Cargo.toml"
);
void vscode.workspace.fs.stat(path).then(async () => {
const choice = await vscode.window.showInformationMessage(
`This rust file does not belong to a loaded cargo project. It looks like it might belong to the workspace at ${path.path}, do you want to add it to the linked Projects?`,
"Yes",
"No",
"Don't show this again"
);
switch (choice) {
case undefined:
break;
case "No":
break;
case "Yes":
const pathToInsert =
"." +
parent.substring(folder.length) +
pathSeparator +
"Cargo.toml";
await config.update(
"linkedProjects",
config
.get<any[]>("linkedProjects")
?.concat(pathToInsert),
false
);
break;
case "Don't show this again":
await config.update(
"showUnlinkedFileNotification",
false,
false
);
break;
}
});
}
}
}
}
// Abuse the fact that VSCode leaks the LSP diagnostics data field through the
// Diagnostic class, if they ever break this we are out of luck and have to go
// back to the worst diagnostics experience ever:)
@ -138,14 +203,6 @@ export async function createClient(
.substring(0, index)
.replace(/^ -->[^\n]+\n/m, "");
}
let value;
if (errorCode) {
if (typeof diag.code === "string" || typeof diag.code === "number") {
value = diag.code;
} else {
value = diag.code?.value;
}
}
diag.code = {
target: vscode.Uri.from({
scheme: diagnostics.URI_SCHEME,
@ -153,7 +210,8 @@ export async function createClient(
fragment: uri.toString(),
query: idx.toString(),
}),
value: value ?? "Click for full compiler diagnostic",
value:
errorCode && value ? value : "Click for full compiler diagnostic",
};
}
});
@ -308,6 +366,7 @@ export async function createClient(
// To turn on all proposed features use: client.registerProposedFeatures();
client.registerFeature(new ExperimentalFeatures());
client.registerFeature(new OverrideFeatures());
return client;
}
@ -343,6 +402,25 @@ class ExperimentalFeatures implements lc.StaticFeature {
dispose(): void {}
}
class OverrideFeatures implements lc.StaticFeature {
getState(): lc.FeatureState {
return { kind: "static" };
}
fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
// Force disable `augmentsSyntaxTokens`, VSCode's textmate grammar is somewhat incomplete
// making the experience generally worse
const caps = capabilities.textDocument?.semanticTokens;
if (caps) {
caps.augmentsSyntaxTokens = false;
}
}
initialize(
_capabilities: lc.ServerCapabilities,
_documentSelector: lc.DocumentSelector | undefined
): void {}
dispose(): void {}
}
function isCodeActionWithoutEditsAndCommands(value: any): boolean {
const candidate: lc.CodeAction = value;
return (

View file

@ -8,10 +8,18 @@ import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets";
import { spawnSync } from "child_process";
import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run";
import { AstInspector } from "./ast_inspector";
import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from "./util";
import {
isRustDocument,
isCargoTomlDocument,
sleep,
isRustEditor,
RustEditor,
RustDocument,
} from "./util";
import { startDebugSession, makeDebugConfig } from "./debug";
import { LanguageClient } from "vscode-languageclient/node";
import { LINKED_COMMANDS } from "./client";
import { DependencyId } from "./dependencies_provider";
export * from "./ast_inspector";
export * from "./run";
@ -89,7 +97,13 @@ export function shuffleCrateGraph(ctx: CtxInit): Cmd {
export function triggerParameterHints(_: CtxInit): Cmd {
return async () => {
await vscode.commands.executeCommand("editor.action.triggerParameterHints");
const parameterHintsEnabled = vscode.workspace
.getConfiguration("editor")
.get<boolean>("parameterHints.enabled");
if (parameterHintsEnabled) {
await vscode.commands.executeCommand("editor.action.triggerParameterHints");
}
};
}
@ -260,6 +274,71 @@ export function openCargoToml(ctx: CtxInit): Cmd {
};
}
export function revealDependency(ctx: CtxInit): Cmd {
return async (editor: RustEditor) => {
if (!ctx.dependencies?.isInitialized()) {
return;
}
const documentPath = editor.document.uri.fsPath;
const dep = ctx.dependencies?.getDependency(documentPath);
if (dep) {
await ctx.treeView?.reveal(dep, { select: true, expand: true });
} else {
await revealParentChain(editor.document, ctx);
}
};
}
/**
* This function calculates the parent chain of a given file until it reaches it crate root contained in ctx.dependencies.
* This is need because the TreeView is Lazy, so at first it only has the root dependencies: For example if we have the following crates:
* - core
* - alloc
* - std
*
* if I want to reveal alloc/src/str.rs, I have to:
* 1. reveal every children of alloc
* - core
* - alloc\
* &emsp;|-beches\
* &emsp;|-src\
* &emsp;|- ...
* - std
* 2. reveal every children of src:
* core
* alloc\
* &emsp;|-beches\
* &emsp;|-src\
* &emsp;&emsp;|- lib.rs\
* &emsp;&emsp;|- str.rs <------- FOUND IT!\
* &emsp;&emsp;|- ...\
* &emsp;|- ...\
* std
*/
async function revealParentChain(document: RustDocument, ctx: CtxInit) {
let documentPath = document.uri.fsPath;
const maxDepth = documentPath.split(path.sep).length - 1;
const parentChain: DependencyId[] = [{ id: documentPath.toLowerCase() }];
do {
documentPath = path.dirname(documentPath);
parentChain.push({ id: documentPath.toLowerCase() });
if (parentChain.length >= maxDepth) {
// this is an odd case that can happen when we change a crate version but we'd still have
// a open file referencing the old version
return;
}
} while (!ctx.dependencies?.contains(documentPath));
parentChain.reverse();
for (const idx in parentChain) {
await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true });
}
}
export async function execRevealDependency(e: RustEditor): Promise<void> {
await vscode.commands.executeCommand("rust-analyzer.revealDependency", e);
}
export function ssr(ctx: CtxInit): Cmd {
return async () => {
const editor = vscode.window.activeTextEditor;
@ -416,8 +495,20 @@ export function syntaxTree(ctx: CtxInit): Cmd {
function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
const viewXir = xir === "hir" ? "viewHir" : "viewMir";
const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
const uri = `rust-analyzer-${xir}://${viewXir}/${xir}.rs`;
const scheme = `rust-analyzer-${xir}`;
return viewFileUsingTextDocumentContentProvider(ctx, requestType, uri, scheme, true);
}
function viewFileUsingTextDocumentContentProvider(
ctx: CtxInit,
requestType: lc.RequestType<lc.TextDocumentPositionParams, string, void>,
uri: string,
scheme: string,
shouldUpdate: boolean
): Cmd {
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse(`rust-analyzer-${xir}://${viewXir}/${xir}.rs`);
readonly uri = vscode.Uri.parse(uri);
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor() {
vscode.workspace.onDidChangeTextDocument(
@ -433,14 +524,14 @@ function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
}
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
if (isRustDocument(event.document)) {
if (isRustDocument(event.document) && shouldUpdate) {
// We need to order this after language server updates, but there's no API for that.
// Hence, good old sleep().
void sleep(10).then(() => this.eventEmitter.fire(this.uri));
}
}
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
if (editor && isRustEditor(editor)) {
if (editor && isRustEditor(editor) && shouldUpdate) {
this.eventEmitter.fire(this.uri);
}
}
@ -467,9 +558,7 @@ function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
}
})();
ctx.pushExtCleanup(
vscode.workspace.registerTextDocumentContentProvider(`rust-analyzer-${xir}`, tdcp)
);
ctx.pushExtCleanup(vscode.workspace.registerTextDocumentContentProvider(scheme, tdcp));
return async () => {
const document = await vscode.workspace.openTextDocument(tdcp.uri);
@ -495,6 +584,20 @@ export function viewMir(ctx: CtxInit): Cmd {
return viewHirOrMir(ctx, "mir");
}
// Opens the virtual file that will show the MIR of the function containing the cursor position
//
// The contents of the file come from the `TextDocumentContentProvider`
export function interpretFunction(ctx: CtxInit): Cmd {
const uri = `rust-analyzer-interpret-function://interpretFunction/result.log`;
return viewFileUsingTextDocumentContentProvider(
ctx,
ra.interpretFunction,
uri,
`rust-analyzer-interpret-function`,
false
);
}
export function viewFileText(ctx: CtxInit): Cmd {
const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer-file-text://viewFileText/file.rs");
@ -663,21 +766,26 @@ function crateGraph(ctx: CtxInit, full: boolean): Cmd {
</head>
<body>
<script type="text/javascript" src="${uri}/d3/dist/d3.min.js"></script>
<script type="text/javascript" src="${uri}/@hpcc-js/wasm/dist/index.min.js"></script>
<script type="text/javascript" src="${uri}/@hpcc-js/wasm/dist/graphviz.umd.js"></script>
<script type="text/javascript" src="${uri}/d3-graphviz/build/d3-graphviz.min.js"></script>
<div id="graph"></div>
<script>
let dot = \`${dot}\`;
let graph = d3.select("#graph")
.graphviz()
.graphviz({ useWorker: false, useSharedWorker: false })
.fit(true)
.zoomScaleExtent([0.1, Infinity])
.renderDot(\`${dot}\`);
.renderDot(dot);
d3.select(window).on("click", (event) => {
if (event.ctrlKey) {
graph.resetZoom(d3.transition().duration(100));
}
});
d3.select(window).on("copy", (event) => {
event.clipboardData.setData("text/plain", dot);
event.preventDefault();
});
</script>
</body>
`;
@ -699,7 +807,7 @@ export function viewFullCrateGraph(ctx: CtxInit): Cmd {
// The contents of the file come from the `TextDocumentContentProvider`
export function expandMacro(ctx: CtxInit): Cmd {
function codeFormat(expanded: ra.ExpandedMacro): string {
let result = `// Recursive expansion of ${expanded.name}! macro\n`;
let result = `// Recursive expansion of ${expanded.name} macro\n`;
result += "// " + "=".repeat(result.length - 3);
result += "\n\n";
result += expanded.expansion;
@ -749,6 +857,10 @@ export function reloadWorkspace(ctx: CtxInit): Cmd {
return async () => ctx.client.sendRequest(ra.reloadWorkspace);
}
export function rebuildProcMacros(ctx: CtxInit): Cmd {
return async () => ctx.client.sendRequest(ra.rebuildProcMacros);
}
export function addProject(ctx: CtxInit): Cmd {
return async () => {
const discoverProjectCommand = ctx.config.discoverProjectCommand;
@ -757,12 +869,13 @@ export function addProject(ctx: CtxInit): Cmd {
}
const workspaces: JsonProject[] = await Promise.all(
vscode.workspace.workspaceFolders!.map(async (folder): Promise<JsonProject> => {
const rustDocuments = vscode.workspace.textDocuments.filter(isRustDocument);
return discoverWorkspace(rustDocuments, discoverProjectCommand, {
cwd: folder.uri.fsPath,
});
})
vscode.workspace.textDocuments
.filter(isRustDocument)
.map(async (file): Promise<JsonProject> => {
return discoverWorkspace([file], discoverProjectCommand, {
cwd: path.dirname(file.uri.fsPath),
});
})
);
ctx.addToDiscoveredWorkspaces(workspaces);

View file

@ -21,7 +21,6 @@ export class Config {
"serverPath",
"server",
"files",
"lens", // works as lens.*
].map((opt) => `${this.rootSection}.${opt}`);
readonly package: {
@ -70,7 +69,7 @@ export class Config {
if (!requiresReloadOpt) return;
if (this.restartServerOnConfigChange) {
await vscode.commands.executeCommand("rust-analyzer.reload");
await vscode.commands.executeCommand("rust-analyzer.restartServer");
return;
}
@ -78,7 +77,7 @@ export class Config {
const userResponse = await vscode.window.showInformationMessage(message, "Restart now");
if (userResponse) {
const command = "rust-analyzer.reload";
const command = "rust-analyzer.restartServer";
await vscode.commands.executeCommand(command);
}
}
@ -285,6 +284,10 @@ export class Config {
get useRustcErrorCode() {
return this.get<boolean>("diagnostics.useRustcErrorCode");
}
get showDependenciesExplorer() {
return this.get<boolean>("showDependenciesExplorer");
}
}
// the optional `cb?` parameter is meant to be used to add additional

View file

@ -1,11 +1,13 @@
import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node";
import * as ra from "./lsp_ext";
import * as path from "path";
import { Config, prepareVSCodeConfig } from "./config";
import { createClient } from "./client";
import {
executeDiscoverProject,
isDocumentInWorkspace,
isRustDocument,
isRustEditor,
LazyOutputChannel,
@ -13,6 +15,13 @@ import {
RustEditor,
} from "./util";
import { ServerStatusParams } from "./lsp_ext";
import {
Dependency,
DependencyFile,
RustDependenciesProvider,
DependencyId,
} from "./dependencies_provider";
import { execRevealDependency } from "./commands";
import { PersistentState } from "./persistent_state";
import { bootstrap } from "./bootstrap";
import { ExecOptions } from "child_process";
@ -82,11 +91,22 @@ export class Ctx {
private state: PersistentState;
private commandFactories: Record<string, CommandFactory>;
private commandDisposables: Disposable[];
private unlinkedFiles: vscode.Uri[];
private _dependencies: RustDependenciesProvider | undefined;
private _treeView: vscode.TreeView<Dependency | DependencyFile | DependencyId> | undefined;
get client() {
return this._client;
}
get treeView() {
return this._treeView;
}
get dependencies() {
return this._dependencies;
}
constructor(
readonly extCtx: vscode.ExtensionContext,
commandFactories: Record<string, CommandFactory>,
@ -94,12 +114,11 @@ export class Ctx {
) {
extCtx.subscriptions.push(this);
this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
this.statusBar.show();
this.workspace = workspace;
this.clientSubscriptions = [];
this.commandDisposables = [];
this.commandFactories = commandFactories;
this.unlinkedFiles = [];
this.state = new PersistentState(extCtx.globalState);
this.config = new Config(extCtx);
@ -191,12 +210,13 @@ export class Ctx {
const discoverProjectCommand = this.config.discoverProjectCommand;
if (discoverProjectCommand) {
const workspaces: JsonProject[] = await Promise.all(
vscode.workspace.workspaceFolders!.map(async (folder): Promise<JsonProject> => {
const rustDocuments = vscode.workspace.textDocuments.filter(isRustDocument);
return discoverWorkspace(rustDocuments, discoverProjectCommand, {
cwd: folder.uri.fsPath,
});
})
vscode.workspace.textDocuments
.filter(isRustDocument)
.map(async (file): Promise<JsonProject> => {
return discoverWorkspace([file], discoverProjectCommand, {
cwd: path.dirname(file.uri.fsPath),
});
})
);
this.addToDiscoveredWorkspaces(workspaces);
@ -218,7 +238,8 @@ export class Ctx {
this.outputChannel,
initializationOptions,
serverOptions,
this.config
this.config,
this.unlinkedFiles
);
this.pushClientCleanup(
this._client.onNotification(ra.serverStatus, (params) =>
@ -242,6 +263,56 @@ export class Ctx {
}
await client.start();
this.updateCommands();
if (this.config.showDependenciesExplorer) {
this.prepareTreeDependenciesView(client);
}
}
private prepareTreeDependenciesView(client: lc.LanguageClient) {
const ctxInit: CtxInit = {
...this,
client: client,
};
this._dependencies = new RustDependenciesProvider(ctxInit);
this._treeView = vscode.window.createTreeView("rustDependencies", {
treeDataProvider: this._dependencies,
showCollapseAll: true,
});
this.pushExtCleanup(this._treeView);
vscode.window.onDidChangeActiveTextEditor(async (e) => {
// we should skip documents that belong to the current workspace
if (this.shouldRevealDependency(e)) {
try {
await execRevealDependency(e);
} catch (reason) {
await vscode.window.showErrorMessage(`Dependency error: ${reason}`);
}
}
});
this.treeView?.onDidChangeVisibility(async (e) => {
if (e.visible) {
const activeEditor = vscode.window.activeTextEditor;
if (this.shouldRevealDependency(activeEditor)) {
try {
await execRevealDependency(activeEditor);
} catch (reason) {
await vscode.window.showErrorMessage(`Dependency error: ${reason}`);
}
}
}
});
}
private shouldRevealDependency(e: vscode.TextEditor | undefined): e is RustEditor {
return (
e !== undefined &&
isRustEditor(e) &&
!isDocumentInWorkspace(e.document) &&
(this.treeView?.visible || false)
);
}
async restart() {
@ -335,6 +406,7 @@ export class Ctx {
setServerStatus(status: ServerStatusParams | { health: "stopped" }) {
let icon = "";
const statusBar = this.statusBar;
statusBar.show();
statusBar.tooltip = new vscode.MarkdownString("", true);
statusBar.tooltip.isTrusted = true;
switch (status.health) {
@ -343,6 +415,7 @@ export class Ctx {
statusBar.color = undefined;
statusBar.backgroundColor = undefined;
statusBar.command = "rust-analyzer.stopServer";
this.dependencies?.refresh();
break;
case "warning":
if (status.message) {
@ -378,12 +451,17 @@ export class Ctx {
if (statusBar.tooltip.value) {
statusBar.tooltip.appendText("\n\n");
}
statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)");
statusBar.tooltip.appendMarkdown(
"\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)"
);
statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)");
statusBar.tooltip.appendMarkdown("\n\n[Restart server](command:rust-analyzer.startServer)");
statusBar.tooltip.appendMarkdown("[Stop server](command:rust-analyzer.stopServer)");
statusBar.tooltip.appendMarkdown(
"\n\n[Rebuild Proc Macros](command:rust-analyzer.rebuildProcMacros)"
);
statusBar.tooltip.appendMarkdown(
"\n\n[Restart server](command:rust-analyzer.restartServer)"
);
statusBar.tooltip.appendMarkdown("\n\n[Stop server](command:rust-analyzer.stopServer)");
if (!status.quiescent) icon = "$(sync~spin) ";
statusBar.text = `${icon}rust-analyzer`;
}
@ -400,4 +478,5 @@ export class Ctx {
export interface Disposable {
dispose(): void;
}
export type Cmd = (...args: any[]) => unknown;

View file

@ -118,8 +118,8 @@ async function getDebugConfiguration(
return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}");
}
const executable = await getDebugExecutable(runnable);
const env = prepareEnv(runnable, ctx.config.runnableEnv);
const executable = await getDebugExecutable(runnable, env);
let sourceFileMap = debugOptions.sourceFileMap;
if (sourceFileMap === "auto") {
// let's try to use the default toolchain
@ -156,8 +156,11 @@ async function getDebugConfiguration(
return debugConfig;
}
async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput);
async function getDebugExecutable(
runnable: ra.Runnable,
env: Record<string, string>
): Promise<string> {
const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env);
const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
// if we are here, there were no compilation errors.

View file

@ -0,0 +1,148 @@
import * as vscode from "vscode";
import * as fspath from "path";
import * as fs from "fs";
import { CtxInit } from "./ctx";
import * as ra from "./lsp_ext";
import { FetchDependencyListResult } from "./lsp_ext";
export class RustDependenciesProvider
implements vscode.TreeDataProvider<Dependency | DependencyFile>
{
dependenciesMap: { [id: string]: Dependency | DependencyFile };
ctx: CtxInit;
constructor(ctx: CtxInit) {
this.dependenciesMap = {};
this.ctx = ctx;
}
private _onDidChangeTreeData: vscode.EventEmitter<
Dependency | DependencyFile | undefined | null | void
> = new vscode.EventEmitter<Dependency | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<
Dependency | DependencyFile | undefined | null | void
> = this._onDidChangeTreeData.event;
getDependency(filePath: string): Dependency | DependencyFile | undefined {
return this.dependenciesMap[filePath.toLowerCase()];
}
contains(filePath: string): boolean {
return filePath.toLowerCase() in this.dependenciesMap;
}
isInitialized(): boolean {
return Object.keys(this.dependenciesMap).length !== 0;
}
refresh(): void {
this.dependenciesMap = {};
this._onDidChangeTreeData.fire();
}
getParent?(
element: Dependency | DependencyFile
): vscode.ProviderResult<Dependency | DependencyFile> {
if (element instanceof Dependency) return undefined;
return element.parent;
}
getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> {
if (element.id! in this.dependenciesMap) return this.dependenciesMap[element.id!];
return element;
}
getChildren(
element?: Dependency | DependencyFile
): vscode.ProviderResult<Dependency[] | DependencyFile[]> {
return new Promise((resolve, _reject) => {
if (!vscode.workspace.workspaceFolders) {
void vscode.window.showInformationMessage("No dependency in empty workspace");
return Promise.resolve([]);
}
if (element) {
const files = fs.readdirSync(element.dependencyPath).map((fileName) => {
const filePath = fspath.join(element.dependencyPath, fileName);
const collapsibleState = fs.lstatSync(filePath).isDirectory()
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None;
const dep = new DependencyFile(fileName, filePath, element, collapsibleState);
this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep;
return dep;
});
return resolve(files);
} else {
return resolve(this.getRootDependencies());
}
});
}
private async getRootDependencies(): Promise<Dependency[]> {
const dependenciesResult: FetchDependencyListResult = await this.ctx.client.sendRequest(
ra.fetchDependencyList,
{}
);
const crates = dependenciesResult.crates;
return crates
.map((crate) => {
const dep = this.toDep(crate.name || "unknown", crate.version || "", crate.path);
this.dependenciesMap[dep.dependencyPath.toLowerCase()] = dep;
return dep;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
}
private toDep(moduleName: string, version: string, path: string): Dependency {
return new Dependency(
moduleName,
version,
vscode.Uri.parse(path).fsPath,
vscode.TreeItemCollapsibleState.Collapsed
);
}
}
export class Dependency extends vscode.TreeItem {
constructor(
public readonly label: string,
private version: string,
readonly dependencyPath: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.resourceUri = vscode.Uri.file(dependencyPath);
this.id = this.resourceUri.fsPath.toLowerCase();
this.description = this.version;
if (this.version) {
this.tooltip = `${this.label}-${this.version}`;
} else {
this.tooltip = this.label;
}
}
}
export class DependencyFile extends vscode.TreeItem {
constructor(
readonly label: string,
readonly dependencyPath: string,
readonly parent: Dependency | DependencyFile,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(vscode.Uri.file(dependencyPath), collapsibleState);
this.id = this.resourceUri!.fsPath.toLowerCase();
const isDir = fs.lstatSync(this.resourceUri!.fsPath).isDirectory();
if (!isDir) {
this.command = {
command: "vscode.open",
title: "Open File",
arguments: [this.resourceUri],
};
}
}
}
export type DependencyId = { id: string };

View file

@ -10,12 +10,9 @@ export const hover = new lc.RequestType<
HoverParams,
(lc.Hover & { actions: CommandLinkGroup[] }) | null,
void
>("textDocument/hover");
export type HoverParams = { position: lc.Position | lc.Range } & Omit<
lc.TextDocumentPositionParams,
"position"
> &
lc.WorkDoneProgressParams;
>(lc.HoverRequest.method);
export type HoverParams = { position: lc.Position | lc.Range } & Omit<lc.HoverParams, "position">;
export type CommandLink = {
/**
* A tooltip for the command, when represented in the UI.
@ -43,6 +40,7 @@ export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, Te
"rust-analyzer/relatedTests"
);
export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
export const rebuildProcMacros = new lc.RequestType0<null, void>("rust-analyzer/rebuildProcMacros");
export const runFlycheck = new lc.NotificationType<{
textDocument: lc.TextDocumentIdentifier | null;
@ -63,12 +61,47 @@ export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string,
export const viewMir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
"rust-analyzer/viewMir"
);
export const interpretFunction = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
"rust-analyzer/interpretFunction"
);
export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>(
"rust-analyzer/viewItemTree"
);
export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier };
export interface FetchDependencyListParams {}
export interface FetchDependencyListResult {
crates: {
name: string | undefined;
version: string | undefined;
path: string;
}[];
}
export const fetchDependencyList = new lc.RequestType<
FetchDependencyListParams,
FetchDependencyListResult,
void
>("rust-analyzer/fetchDependencyList");
export interface FetchDependencyGraphParams {}
export interface FetchDependencyGraphResult {
crates: {
name: string;
version: string;
path: string;
}[];
}
export const fetchDependencyGraph = new lc.RequestType<
FetchDependencyGraphParams,
FetchDependencyGraphResult,
void
>("rust-analyzer/fetchDependencyGraph");
export type ExpandMacroParams = {
textDocument: lc.TextDocumentIdentifier;
position: lc.Position;

View file

@ -120,13 +120,11 @@ function createCommands(): Record<string, CommandFactory> {
enabled: commands.onEnter,
disabled: (_) => () => vscode.commands.executeCommand("default:type", { text: "\n" }),
},
reload: {
restartServer: {
enabled: (ctx) => async () => {
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
await ctx.restart();
},
disabled: (ctx) => async () => {
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
await ctx.start();
},
},
@ -153,6 +151,7 @@ function createCommands(): Record<string, CommandFactory> {
memoryUsage: { enabled: commands.memoryUsage },
shuffleCrateGraph: { enabled: commands.shuffleCrateGraph },
reloadWorkspace: { enabled: commands.reloadWorkspace },
rebuildProcMacros: { enabled: commands.rebuildProcMacros },
addProject: { enabled: commands.addProject },
matchingBrace: { enabled: commands.matchingBrace },
joinLines: { enabled: commands.joinLines },
@ -160,6 +159,7 @@ function createCommands(): Record<string, CommandFactory> {
syntaxTree: { enabled: commands.syntaxTree },
viewHir: { enabled: commands.viewHir },
viewMir: { enabled: commands.viewMir },
interpretFunction: { enabled: commands.interpretFunction },
viewFileText: { enabled: commands.viewFileText },
viewItemTree: { enabled: commands.viewItemTree },
viewCrateGraph: { enabled: commands.viewCrateGraph },
@ -190,5 +190,6 @@ function createCommands(): Record<string, CommandFactory> {
showReferences: { enabled: commands.showReferences },
triggerParameterHints: { enabled: commands.triggerParameterHints },
openLogs: { enabled: commands.openLogs },
revealDependency: { enabled: commands.revealDependency },
};
}

View file

@ -157,7 +157,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
cargoTask.presentationOptions.clear = true;
// Sadly, this doesn't prevent focus stealing if the terminal is currently
// hidden, and will become revealed due to task exucution.
// hidden, and will become revealed due to task execution.
cargoTask.presentationOptions.focus = false;
return cargoTask;

View file

@ -128,7 +128,7 @@ export async function buildCargoTask(
name,
TASK_SOURCE,
exec,
["$rustc"]
["$rustc", "$rust-panic"]
);
}

View file

@ -18,7 +18,11 @@ export interface ArtifactSpec {
}
export class Cargo {
constructor(readonly rootFolder: string, readonly output: vscode.OutputChannel) {}
constructor(
readonly rootFolder: string,
readonly output: vscode.OutputChannel,
readonly env: Record<string, string>
) {}
// Made public for testing purposes
static artifactSpec(args: readonly string[]): ArtifactSpec {
@ -102,6 +106,7 @@ export class Cargo {
const cargo = cp.spawn(path, cargoArgs, {
stdio: ["ignore", "pipe", "pipe"],
cwd: this.rootFolder,
env: this.env,
});
cargo.on("error", (err) => reject(new Error(`could not launch cargo: ${err}`)));

View file

@ -112,6 +112,19 @@ export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
return isRustDocument(editor.document);
}
export function isDocumentInWorkspace(document: RustDocument): boolean {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
return false;
}
for (const folder of workspaceFolders) {
if (document.uri.fsPath.startsWith(folder.uri.fsPath)) {
return true;
}
}
return false;
}
export function isValidExecutable(path: string): boolean {
log.debug("Checking availability of a binary at", path);