Auto merge of #12277 - listochkin:show-implementations-display-error, r=listochkin

"Show implementations" link display error fix

While VSCode [uses it's own implementation for URIs](https://github.com/microsoft/vscode-uri)
which notably doesn't have any limits of URI size, the renderer itself
relies on Web platform engine, that limits the length of the URLs and
bails out when the attribute length of an `href` inside `a` tag is too
long.

Command URIs have a form of `command:command-name?arguments`, where
`arguments` is a percent-encoded array of data we want to pass along to
the command function. For "Show References" this is a list of all file
URIs with locations of every reference, and it can get quite long.

This PR introduces another intermediary `linkToCommand` command. When
we render a command link, a reference to a command with all its arguments
is stored in a map, and instead a `linkToCommand` link is rendered
with the key to that map.

For now the map is cleaned up periodically (I've set it to every
10 minutes). In general case we'll probably need to introduce TTLs or
flags to denote ephemeral links (like these in hover popups) and
persistent links and clean those separately. But for now simply keeping
the last few links in the map should be good enough. Likewise, we could
add code to remove a target command from the map after the link is
clicked, but assuming most links in hover sheets won't be clicked anyway
this code won't change the overall memory use much.

Closes #9926
This commit is contained in:
bors 2022-05-18 13:13:09 +00:00
commit 76030ebdea
3 changed files with 48 additions and 3 deletions

View File

@ -7,14 +7,46 @@ import { WorkspaceEdit } from "vscode";
import { Workspace } from "./ctx"; import { Workspace } from "./ctx";
import { updateConfig } from "./config"; import { updateConfig } from "./config";
import { substituteVariablesInEnv } from "./config"; import { substituteVariablesInEnv } from "./config";
import { randomUUID } from "crypto";
export interface Env { export interface Env {
[name: string]: string; [name: string]: string;
} }
function renderCommand(cmd: ra.CommandLink) { // Command URIs have a form of command:command-name?arguments, where
return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent( // arguments is a percent-encoded array of data we want to pass along to
JSON.stringify(cmd.arguments) // the command function. For "Show References" this is a list of all file
// URIs with locations of every reference, and it can get quite long.
//
// To work around it we use an intermediary linkToCommand command. When
// we render a command link, a reference to a command with all its arguments
// is stored in a map, and instead a linkToCommand link is rendered
// with the key to that map.
export const LINKED_COMMANDS = new Map<string, ra.CommandLink>();
// For now the map is cleaned up periodically (I've set it to every
// 10 minutes). In general case we'll probably need to introduce TTLs or
// flags to denote ephemeral links (like these in hover popups) and
// persistent links and clean those separately. But for now simply keeping
// the last few links in the map should be good enough. Likewise, we could
// add code to remove a target command from the map after the link is
// clicked, but assuming most links in hover sheets won't be clicked anyway
// this code won't change the overall memory use much.
setInterval(function cleanupOlderCommandLinks() {
// keys are returned in insertion order, we'll keep a few
// of recent keys available, and clean the rest
const keys = [...LINKED_COMMANDS.keys()];
const keysToRemove = keys.slice(0, keys.length - 10);
for (const key of keysToRemove) {
LINKED_COMMANDS.delete(key);
}
}, 10 * 60 * 1000);
function renderCommand(cmd: ra.CommandLink): string {
const commandId = randomUUID();
LINKED_COMMANDS.set(commandId, cmd);
return `[${cmd.title}](command:rust-analyzer.linkToCommand?${encodeURIComponent(
JSON.stringify([commandId])
)} '${cmd.tooltip}')`; )} '${cmd.tooltip}')`;
} }

View File

@ -11,6 +11,7 @@ import { AstInspector } from "./ast_inspector";
import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from "./util"; import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from "./util";
import { startDebugSession, makeDebugConfig } from "./debug"; import { startDebugSession, makeDebugConfig } from "./debug";
import { LanguageClient } from "vscode-languageclient/node"; import { LanguageClient } from "vscode-languageclient/node";
import { LINKED_COMMANDS } from "./client";
export * from "./ast_inspector"; export * from "./ast_inspector";
export * from "./run"; export * from "./run";
@ -928,3 +929,13 @@ export function newDebugConfig(ctx: Ctx): Cmd {
await makeDebugConfig(ctx, item.runnable); await makeDebugConfig(ctx, item.runnable);
}; };
} }
export function linkToCommand(ctx: Ctx): Cmd {
return async (commandId: string) => {
const link = LINKED_COMMANDS.get(commandId);
if (ctx.client && link) {
const { command, arguments: args = [] } = link;
await vscode.commands.executeCommand(command, ...args);
}
};
}

View File

@ -160,6 +160,8 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
ctx.registerCommand("resolveCodeAction", commands.resolveCodeAction); ctx.registerCommand("resolveCodeAction", commands.resolveCodeAction);
ctx.registerCommand("applyActionGroup", commands.applyActionGroup); ctx.registerCommand("applyActionGroup", commands.applyActionGroup);
ctx.registerCommand("gotoLocation", commands.gotoLocation); ctx.registerCommand("gotoLocation", commands.gotoLocation);
ctx.registerCommand("linkToCommand", commands.linkToCommand);
} }
export async function deactivate() { export async function deactivate() {