mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 05:08:52 +00:00
Merge #11445
11445: Upstream inlay hints r=lnicola a=lnicola Closes https://github.com/rust-analyzer/rust-analyzer/issues/2797 Closes https://github.com/rust-analyzer/rust-analyzer/issues/3394 (since now resolve the hints for the range given only, not for the whole document. We don't actually resolve anything due to [hard requirement](https://github.com/rust-analyzer/rust-analyzer/pull/11445#issuecomment-1035227434) on label being immutable. Any further heavy actions could go to the `resolve` method that's now available via the official Code API for hints) Based on `@SomeoneToIgnore's` branch, with a couple of updates: - I squashed, more or less successfully, the commits on that branch - downloading the `.d.ts` no longer works, but you can get it manually from https://raw.githubusercontent.com/microsoft/vscode/release/1.64/src/vscode-dts/vscode.proposed.inlayHints.d.ts - you might need to pass `--enable-proposed-api matklad.rust-analyzer` - if I'm reading the definition right, `InlayHintKind` needs to be serialized as a number, not string - this doesn't work anyway -- the client-side gets the hints, but they don't display Co-authored-by: Kirill Bulatov <mail4score@gmail.com> Co-authored-by: Laurențiu Nicola <lnicola@dend.ro>
This commit is contained in:
commit
49646b71d4
13 changed files with 552 additions and 640 deletions
|
@ -5,7 +5,7 @@ use itertools::Itertools;
|
|||
use stdx::to_lower_snake_case;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, HasArgList, HasName, UnaryOp},
|
||||
match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T,
|
||||
match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, TextRange, T,
|
||||
};
|
||||
|
||||
use crate::FileId;
|
||||
|
@ -58,6 +58,7 @@ pub struct InlayHint {
|
|||
pub(crate) fn inlay_hints(
|
||||
db: &RootDatabase,
|
||||
file_id: FileId,
|
||||
range_limit: Option<FileRange>,
|
||||
config: &InlayHintsConfig,
|
||||
) -> Vec<InlayHint> {
|
||||
let _p = profile::span("inlay_hints");
|
||||
|
@ -65,25 +66,50 @@ pub(crate) fn inlay_hints(
|
|||
let file = sema.parse(file_id);
|
||||
let file = file.syntax();
|
||||
|
||||
let mut res = Vec::new();
|
||||
let mut hints = Vec::new();
|
||||
|
||||
for node in file.descendants() {
|
||||
if let Some(expr) = ast::Expr::cast(node.clone()) {
|
||||
get_chaining_hints(&mut res, &sema, config, &expr);
|
||||
match expr {
|
||||
ast::Expr::CallExpr(it) => {
|
||||
get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it));
|
||||
if let Some(range_limit) = range_limit {
|
||||
let range_limit = range_limit.range;
|
||||
match file.covering_element(range_limit) {
|
||||
NodeOrToken::Token(_) => return hints,
|
||||
NodeOrToken::Node(n) => {
|
||||
for node in n
|
||||
.descendants()
|
||||
.filter(|descendant| range_limit.contains_range(descendant.text_range()))
|
||||
{
|
||||
get_hints(&mut hints, &sema, config, node);
|
||||
}
|
||||
ast::Expr::MethodCallExpr(it) => {
|
||||
get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Some(it) = ast::IdentPat::cast(node.clone()) {
|
||||
get_bind_pat_hints(&mut res, &sema, config, &it);
|
||||
}
|
||||
} else {
|
||||
for node in file.descendants() {
|
||||
get_hints(&mut hints, &sema, config, node);
|
||||
}
|
||||
}
|
||||
res
|
||||
|
||||
hints
|
||||
}
|
||||
|
||||
fn get_hints(
|
||||
hints: &mut Vec<InlayHint>,
|
||||
sema: &Semantics<RootDatabase>,
|
||||
config: &InlayHintsConfig,
|
||||
node: SyntaxNode,
|
||||
) {
|
||||
if let Some(expr) = ast::Expr::cast(node.clone()) {
|
||||
get_chaining_hints(hints, sema, config, &expr);
|
||||
match expr {
|
||||
ast::Expr::CallExpr(it) => {
|
||||
get_param_name_hints(hints, sema, config, ast::Expr::from(it));
|
||||
}
|
||||
ast::Expr::MethodCallExpr(it) => {
|
||||
get_param_name_hints(hints, sema, config, ast::Expr::from(it));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if let Some(it) = ast::IdentPat::cast(node) {
|
||||
get_bind_pat_hints(hints, sema, config, &it);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_chaining_hints(
|
||||
|
@ -541,6 +567,8 @@ fn get_callable(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use ide_db::base_db::FileRange;
|
||||
use syntax::{TextRange, TextSize};
|
||||
use test_utils::extract_annotations;
|
||||
|
||||
use crate::{fixture, inlay_hints::InlayHintsConfig};
|
||||
|
@ -604,7 +632,7 @@ mod tests {
|
|||
fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
|
||||
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
|
||||
let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap();
|
||||
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
|
||||
let actual =
|
||||
inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
|
||||
assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
|
||||
|
@ -613,7 +641,7 @@ mod tests {
|
|||
#[track_caller]
|
||||
fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
|
||||
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||
let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap();
|
||||
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
|
||||
expect.assert_debug_eq(&inlay_hints)
|
||||
}
|
||||
|
||||
|
@ -1045,6 +1073,55 @@ fn main() {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_hint_range_limit() {
|
||||
let fixture = r#"
|
||||
//- minicore: fn, sized
|
||||
fn foo() -> impl Fn() { loop {} }
|
||||
fn foo1() -> impl Fn(f64) { loop {} }
|
||||
fn foo2() -> impl Fn(f64, f64) { loop {} }
|
||||
fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
|
||||
fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
|
||||
fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
|
||||
fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
|
||||
fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
|
||||
|
||||
fn main() {
|
||||
let foo = foo();
|
||||
let foo = foo1();
|
||||
let foo = foo2();
|
||||
let foo = foo3();
|
||||
// ^^^ impl Fn(f64, f64) -> u32
|
||||
let foo = foo4();
|
||||
// ^^^ &dyn Fn(f64, f64) -> u32
|
||||
let foo = foo5();
|
||||
let foo = foo6();
|
||||
let foo = foo7();
|
||||
}
|
||||
"#;
|
||||
let (analysis, file_id) = fixture::file(fixture);
|
||||
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
|
||||
let inlay_hints = analysis
|
||||
.inlay_hints(
|
||||
&InlayHintsConfig {
|
||||
parameter_hints: false,
|
||||
type_hints: true,
|
||||
chaining_hints: false,
|
||||
hide_named_constructor_hints: false,
|
||||
max_length: None,
|
||||
},
|
||||
file_id,
|
||||
Some(FileRange {
|
||||
file_id,
|
||||
range: TextRange::new(TextSize::from(500), TextSize::from(600)),
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let actual =
|
||||
inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
|
||||
assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_hints_ptr_rpit_fn_parentheses() {
|
||||
check_types(
|
||||
|
|
|
@ -358,8 +358,9 @@ impl Analysis {
|
|||
&self,
|
||||
config: &InlayHintsConfig,
|
||||
file_id: FileId,
|
||||
range: Option<FileRange>,
|
||||
) -> Cancellable<Vec<InlayHint>> {
|
||||
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config))
|
||||
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
|
||||
}
|
||||
|
||||
/// Returns the set of folding ranges.
|
||||
|
|
|
@ -112,6 +112,7 @@ impl StaticIndex<'_> {
|
|||
max_length: Some(25),
|
||||
},
|
||||
file_id,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
// hovers
|
||||
|
|
|
@ -115,6 +115,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
|||
experimental: Some(json!({
|
||||
"externalDocs": true,
|
||||
"hoverRange": true,
|
||||
"inlayHints": true,
|
||||
"joinLines": true,
|
||||
"matchingBrace": true,
|
||||
"moveItem": true,
|
||||
|
|
|
@ -1318,11 +1318,22 @@ pub(crate) fn handle_inlay_hints(
|
|||
params: InlayHintsParams,
|
||||
) -> Result<Vec<InlayHint>> {
|
||||
let _p = profile::span("handle_inlay_hints");
|
||||
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let document_uri = ¶ms.text_document.uri;
|
||||
let file_id = from_proto::file_id(&snap, document_uri)?;
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let range = params
|
||||
.range
|
||||
.map(|range| {
|
||||
from_proto::file_range(
|
||||
&snap,
|
||||
TextDocumentIdentifier::new(document_uri.to_owned()),
|
||||
range,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(snap
|
||||
.analysis
|
||||
.inlay_hints(&snap.config.inlay_hints(), file_id)?
|
||||
.inlay_hints(&snap.config.inlay_hints(), file_id, range)?
|
||||
.into_iter()
|
||||
.map(|it| to_proto::inlay_hint(&line_index, it))
|
||||
.collect())
|
||||
|
|
|
@ -233,27 +233,34 @@ pub enum InlayHints {}
|
|||
impl Request for InlayHints {
|
||||
type Params = InlayHintsParams;
|
||||
type Result = Vec<InlayHint>;
|
||||
const METHOD: &'static str = "rust-analyzer/inlayHints";
|
||||
const METHOD: &'static str = "experimental/inlayHints";
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintsParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
pub range: Option<lsp_types::Range>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum InlayKind {
|
||||
TypeHint,
|
||||
ParameterHint,
|
||||
ChainingHint,
|
||||
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct InlayHintKind(u8);
|
||||
|
||||
impl InlayHintKind {
|
||||
pub const TYPE: InlayHintKind = InlayHintKind(1);
|
||||
pub const PARAMETER: InlayHintKind = InlayHintKind(2);
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHint {
|
||||
pub range: Range,
|
||||
pub kind: InlayKind,
|
||||
pub label: String,
|
||||
pub position: Position,
|
||||
pub kind: Option<InlayHintKind>,
|
||||
pub tooltip: Option<String>,
|
||||
pub padding_left: Option<bool>,
|
||||
pub padding_right: Option<bool>,
|
||||
}
|
||||
|
||||
pub enum Ssr {}
|
||||
|
|
|
@ -416,12 +416,18 @@ pub(crate) fn signature_help(
|
|||
pub(crate) fn inlay_hint(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ext::InlayHint {
|
||||
lsp_ext::InlayHint {
|
||||
label: inlay_hint.label.to_string(),
|
||||
range: range(line_index, inlay_hint.range),
|
||||
kind: match inlay_hint.kind {
|
||||
InlayKind::ParameterHint => lsp_ext::InlayKind::ParameterHint,
|
||||
InlayKind::TypeHint => lsp_ext::InlayKind::TypeHint,
|
||||
InlayKind::ChainingHint => lsp_ext::InlayKind::ChainingHint,
|
||||
position: match inlay_hint.kind {
|
||||
InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()),
|
||||
_ => position(line_index, inlay_hint.range.end()),
|
||||
},
|
||||
kind: match inlay_hint.kind {
|
||||
InlayKind::ParameterHint => Some(lsp_ext::InlayHintKind::PARAMETER),
|
||||
InlayKind::TypeHint => Some(lsp_ext::InlayHintKind::TYPE),
|
||||
InlayKind::ChainingHint => None,
|
||||
},
|
||||
tooltip: None,
|
||||
padding_left: Some(true),
|
||||
padding_right: Some(true),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!---
|
||||
lsp_ext.rs hash: 5b53b92c9f9d6650
|
||||
lsp_ext.rs hash: e32fdde032ff6ebc
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
|
@ -562,11 +562,11 @@ Expands macro call at a given position.
|
|||
|
||||
## Inlay Hints
|
||||
|
||||
**Method:** `rust-analyzer/inlayHints`
|
||||
**Method:** `experimental/inlayHints`
|
||||
|
||||
This request is sent from client to server to render "inlay hints" -- virtual text inserted into editor to show things like inferred types.
|
||||
Generally, the client should re-query inlay hints after every modification.
|
||||
Note that we plan to move this request to `experimental/inlayHints`, as it is not really Rust-specific, but the current API is not necessary the right one.
|
||||
Until it gets upstreamed, this follows the VS Code API.
|
||||
Upstream issues: https://github.com/microsoft/language-server-protocol/issues/956 , https://github.com/rust-analyzer/rust-analyzer/issues/2797
|
||||
|
||||
**Request:**
|
||||
|
@ -581,9 +581,12 @@ interface InlayHintsParams {
|
|||
|
||||
```typescript
|
||||
interface InlayHint {
|
||||
kind: "TypeHint" | "ParameterHint" | "ChainingHint",
|
||||
range: Range,
|
||||
label: string,
|
||||
position: Position;
|
||||
label: string | InlayHintLabelPart[];
|
||||
tooltip?: string | MarkdownString | undefined;
|
||||
kind?: InlayHintKind;
|
||||
paddingLeft?: boolean;
|
||||
paddingRight?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
2
editors/code/.gitignore
vendored
2
editors/code/.gitignore
vendored
|
@ -3,3 +3,5 @@ node_modules
|
|||
server
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
bundle
|
||||
vscode.proposed.d.ts
|
||||
|
|
621
editors/code/package-lock.json
generated
621
editors/code/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -21,9 +21,9 @@
|
|||
"Programming Languages"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.63.0"
|
||||
"vscode": "^1.65.0"
|
||||
},
|
||||
"enableProposedApi": true,
|
||||
"enabledApiProposals": [],
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run build-base -- --minify",
|
||||
"package": "vsce package -o rust-analyzer.vsix",
|
||||
|
@ -36,18 +36,18 @@
|
|||
"test": "node ./out/tests/runTests.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "8.0.0-next.8",
|
||||
"vscode-languageclient": "8.0.0-next.12",
|
||||
"d3": "^7.3.0",
|
||||
"d3-graphviz": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "~14.17.5",
|
||||
"@types/vscode": "~1.63.0",
|
||||
"@types/vscode": "~1.65.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"@vscode/test-electron": "^2.1.1",
|
||||
"esbuild": "^0.14.12",
|
||||
"eslint": "^8.7.0",
|
||||
"eslint": "^8.10.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript-formatter": "^7.2.2",
|
||||
|
@ -298,11 +298,6 @@
|
|||
"default": true,
|
||||
"description": "Whether to show inlay hints."
|
||||
},
|
||||
"rust-analyzer.inlayHints.smallerHints": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether inlay hints font size should be smaller than editor's font size."
|
||||
},
|
||||
"rust-analyzer.server.path": {
|
||||
"type": [
|
||||
"null",
|
||||
|
@ -1083,78 +1078,6 @@
|
|||
}
|
||||
],
|
||||
"colors": [
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.foreground",
|
||||
"description": "Foreground color of inlay hints (is overriden by more specific rust_analyzer.inlayHints.foreground.* configurations)",
|
||||
"defaults": {
|
||||
"dark": "#A0A0A0F0",
|
||||
"light": "#747474",
|
||||
"highContrast": "#BEBEBE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.background",
|
||||
"description": "Background color of inlay hints (is overriden by more specific rust_analyzer.inlayHints.background.* configurations)",
|
||||
"defaults": {
|
||||
"dark": "#11223300",
|
||||
"light": "#11223300",
|
||||
"highContrast": "#11223300"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.foreground.typeHints",
|
||||
"description": "Foreground color of inlay type hints for variables (overrides rust_analyzer.inlayHints.foreground)",
|
||||
"defaults": {
|
||||
"dark": "rust_analyzer.inlayHints.foreground",
|
||||
"light": "rust_analyzer.inlayHints.foreground",
|
||||
"highContrast": "rust_analyzer.inlayHints.foreground"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.foreground.chainingHints",
|
||||
"description": "Foreground color of inlay type hints for method chains (overrides rust_analyzer.inlayHints.foreground)",
|
||||
"defaults": {
|
||||
"dark": "rust_analyzer.inlayHints.foreground",
|
||||
"light": "rust_analyzer.inlayHints.foreground",
|
||||
"highContrast": "rust_analyzer.inlayHints.foreground"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.foreground.parameterHints",
|
||||
"description": "Foreground color of function parameter name inlay hints at the call site (overrides rust_analyzer.inlayHints.foreground)",
|
||||
"defaults": {
|
||||
"dark": "rust_analyzer.inlayHints.foreground",
|
||||
"light": "rust_analyzer.inlayHints.foreground",
|
||||
"highContrast": "rust_analyzer.inlayHints.foreground"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.background.typeHints",
|
||||
"description": "Background color of inlay type hints for variables (overrides rust_analyzer.inlayHints.background)",
|
||||
"defaults": {
|
||||
"dark": "rust_analyzer.inlayHints.background",
|
||||
"light": "rust_analyzer.inlayHints.background",
|
||||
"highContrast": "rust_analyzer.inlayHints.background"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.background.chainingHints",
|
||||
"description": "Background color of inlay type hints for method chains (overrides rust_analyzer.inlayHints.background)",
|
||||
"defaults": {
|
||||
"dark": "rust_analyzer.inlayHints.background",
|
||||
"light": "rust_analyzer.inlayHints.background",
|
||||
"highContrast": "rust_analyzer.inlayHints.background"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.inlayHints.background.parameterHints",
|
||||
"description": "Background color of function parameter name inlay hints at the call site (overrides rust_analyzer.inlayHints.background)",
|
||||
"defaults": {
|
||||
"dark": "rust_analyzer.inlayHints.background",
|
||||
"light": "rust_analyzer.inlayHints.background",
|
||||
"highContrast": "rust_analyzer.inlayHints.background"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rust_analyzer.syntaxTreeBorder",
|
||||
"description": "Color of the border displayed in the Rust source code for the selected syntax node (see \"Show Syntax Tree\" command)",
|
||||
|
|
|
@ -1,267 +1,54 @@
|
|||
import * as lc from "vscode-languageclient";
|
||||
import * as vscode from 'vscode';
|
||||
import * as ra from './lsp_ext';
|
||||
|
||||
import { Ctx, Disposable } from './ctx';
|
||||
import { sendRequestWithRetry, isRustDocument, RustDocument, RustEditor, sleep } from './util';
|
||||
|
||||
interface InlayHintStyle {
|
||||
decorationType: vscode.TextEditorDecorationType;
|
||||
toDecoration(hint: ra.InlayHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions;
|
||||
};
|
||||
|
||||
interface InlayHintsStyles {
|
||||
typeHints: InlayHintStyle;
|
||||
paramHints: InlayHintStyle;
|
||||
chainingHints: InlayHintStyle;
|
||||
}
|
||||
|
||||
import { sendRequestWithRetry, isRustDocument } from './util';
|
||||
|
||||
export function activateInlayHints(ctx: Ctx) {
|
||||
const maybeUpdater = {
|
||||
updater: null as null | HintsUpdater,
|
||||
hintsProvider: null as Disposable | null,
|
||||
updateHintsEventEmitter: new vscode.EventEmitter<void>(),
|
||||
|
||||
async onConfigChange() {
|
||||
this.dispose();
|
||||
|
||||
const anyEnabled = ctx.config.inlayHints.typeHints
|
||||
|| ctx.config.inlayHints.parameterHints
|
||||
|| ctx.config.inlayHints.chainingHints;
|
||||
const enabled = ctx.config.inlayHints.enable && anyEnabled;
|
||||
if (!enabled) return;
|
||||
|
||||
if (!enabled) return this.dispose();
|
||||
|
||||
await sleep(100);
|
||||
if (this.updater) {
|
||||
this.updater.updateInlayHintsStyles();
|
||||
this.updater.syncCacheAndRenderHints();
|
||||
} else {
|
||||
this.updater = new HintsUpdater(ctx);
|
||||
}
|
||||
const event = this.updateHintsEventEmitter.event;
|
||||
this.hintsProvider = vscode.languages.registerInlayHintsProvider({ scheme: 'file', language: 'rust' }, new class implements vscode.InlayHintsProvider {
|
||||
onDidChangeInlayHints = event;
|
||||
async provideInlayHints(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken): Promise<vscode.InlayHint[]> {
|
||||
const request = { textDocument: { uri: document.uri.toString() }, range: { start: range.start, end: range.end } };
|
||||
const hints = await sendRequestWithRetry(ctx.client, ra.inlayHints, request, token).catch(_ => null);
|
||||
if (hints == null) {
|
||||
return [];
|
||||
} else {
|
||||
return hints;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) {
|
||||
if (contentChanges.length === 0 || !isRustDocument(document)) return;
|
||||
this.updateHintsEventEmitter.fire();
|
||||
},
|
||||
|
||||
dispose() {
|
||||
this.updater?.dispose();
|
||||
this.updater = null;
|
||||
}
|
||||
this.hintsProvider?.dispose();
|
||||
this.hintsProvider = null;
|
||||
this.updateHintsEventEmitter.dispose();
|
||||
},
|
||||
};
|
||||
|
||||
ctx.pushCleanup(maybeUpdater);
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions
|
||||
);
|
||||
vscode.workspace.onDidChangeConfiguration(maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions);
|
||||
vscode.workspace.onDidChangeTextDocument(maybeUpdater.onDidChangeTextDocument, maybeUpdater, ctx.subscriptions);
|
||||
|
||||
maybeUpdater.onConfigChange().catch(console.error);
|
||||
}
|
||||
|
||||
function createHintStyle(hintKind: "type" | "parameter" | "chaining", smallerHints: boolean): InlayHintStyle {
|
||||
// U+200C is a zero-width non-joiner to prevent the editor from forming a ligature
|
||||
// between code and type hints
|
||||
const [pos, render] = ({
|
||||
type: ["after", (label: string) => `\u{200c}: ${label}`],
|
||||
parameter: ["before", (label: string) => `${label}: `],
|
||||
chaining: ["after", (label: string) => `\u{200c}: ${label}`],
|
||||
} as const)[hintKind];
|
||||
|
||||
const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`);
|
||||
const bg = new vscode.ThemeColor(`rust_analyzer.inlayHints.background.${hintKind}Hints`);
|
||||
return {
|
||||
decorationType: vscode.window.createTextEditorDecorationType({
|
||||
[pos]: {
|
||||
color: fg,
|
||||
backgroundColor: bg,
|
||||
fontStyle: "normal",
|
||||
fontWeight: "normal",
|
||||
textDecoration: smallerHints ? ";font-size:smaller" : "none",
|
||||
},
|
||||
}),
|
||||
toDecoration(hint: ra.InlayHint, conv: lc.Protocol2CodeConverter): vscode.DecorationOptions {
|
||||
return {
|
||||
range: conv.asRange(hint.range),
|
||||
renderOptions: { [pos]: { contentText: render(hint.label) } }
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const smallHintsStyles = {
|
||||
typeHints: createHintStyle("type", true),
|
||||
paramHints: createHintStyle("parameter", true),
|
||||
chainingHints: createHintStyle("chaining", true),
|
||||
};
|
||||
|
||||
const biggerHintsStyles = {
|
||||
typeHints: createHintStyle("type", false),
|
||||
paramHints: createHintStyle("parameter", false),
|
||||
chainingHints: createHintStyle("chaining", false),
|
||||
};
|
||||
|
||||
class HintsUpdater implements Disposable {
|
||||
private sourceFiles = new Map<string, RustSourceFile>(); // map Uri -> RustSourceFile
|
||||
private readonly disposables: Disposable[] = [];
|
||||
private pendingDisposeDecorations: undefined | InlayHintsStyles = undefined;
|
||||
private inlayHintsStyles!: InlayHintsStyles;
|
||||
|
||||
constructor(private readonly ctx: Ctx) {
|
||||
vscode.window.onDidChangeVisibleTextEditors(
|
||||
this.onDidChangeVisibleTextEditors,
|
||||
this,
|
||||
this.disposables
|
||||
);
|
||||
|
||||
vscode.workspace.onDidChangeTextDocument(
|
||||
this.onDidChangeTextDocument,
|
||||
this,
|
||||
this.disposables
|
||||
);
|
||||
|
||||
// Set up initial cache shape
|
||||
ctx.visibleRustEditors.forEach(editor => this.sourceFiles.set(
|
||||
editor.document.uri.toString(),
|
||||
{
|
||||
document: editor.document,
|
||||
inlaysRequest: null,
|
||||
cachedDecorations: null
|
||||
}
|
||||
));
|
||||
|
||||
this.updateInlayHintsStyles();
|
||||
this.syncCacheAndRenderHints();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.sourceFiles.forEach(file => file.inlaysRequest?.cancel());
|
||||
this.ctx.visibleRustEditors.forEach(editor => this.renderDecorations(editor, { param: [], type: [], chaining: [] }));
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
|
||||
onDidChangeTextDocument({ contentChanges, document }: vscode.TextDocumentChangeEvent) {
|
||||
if (contentChanges.length === 0 || !isRustDocument(document)) return;
|
||||
this.syncCacheAndRenderHints();
|
||||
}
|
||||
|
||||
updateInlayHintsStyles() {
|
||||
const inlayHintsStyles = this.ctx.config.inlayHints.smallerHints ? smallHintsStyles : biggerHintsStyles;
|
||||
|
||||
if (inlayHintsStyles !== this.inlayHintsStyles) {
|
||||
this.pendingDisposeDecorations = this.inlayHintsStyles;
|
||||
this.inlayHintsStyles = inlayHintsStyles;
|
||||
}
|
||||
}
|
||||
|
||||
syncCacheAndRenderHints() {
|
||||
this.sourceFiles.forEach((file, uri) => this.fetchHints(file).then(hints => {
|
||||
if (!hints) return;
|
||||
|
||||
file.cachedDecorations = this.hintsToDecorations(hints);
|
||||
|
||||
for (const editor of this.ctx.visibleRustEditors) {
|
||||
if (editor.document.uri.toString() === uri) {
|
||||
this.renderDecorations(editor, file.cachedDecorations);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
onDidChangeVisibleTextEditors() {
|
||||
const newSourceFiles = new Map<string, RustSourceFile>();
|
||||
|
||||
// Rerendering all, even up-to-date editors for simplicity
|
||||
this.ctx.visibleRustEditors.forEach(async editor => {
|
||||
const uri = editor.document.uri.toString();
|
||||
const file = this.sourceFiles.get(uri) ?? {
|
||||
document: editor.document,
|
||||
inlaysRequest: null,
|
||||
cachedDecorations: null
|
||||
};
|
||||
newSourceFiles.set(uri, file);
|
||||
|
||||
// No text documents changed, so we may try to use the cache
|
||||
if (!file.cachedDecorations) {
|
||||
const hints = await this.fetchHints(file);
|
||||
if (!hints) return;
|
||||
|
||||
file.cachedDecorations = this.hintsToDecorations(hints);
|
||||
}
|
||||
|
||||
this.renderDecorations(editor, file.cachedDecorations);
|
||||
});
|
||||
|
||||
// Cancel requests for no longer visible (disposed) source files
|
||||
this.sourceFiles.forEach((file, uri) => {
|
||||
if (!newSourceFiles.has(uri)) file.inlaysRequest?.cancel();
|
||||
});
|
||||
|
||||
this.sourceFiles = newSourceFiles;
|
||||
}
|
||||
|
||||
private renderDecorations(editor: RustEditor, decorations: InlaysDecorations) {
|
||||
const { typeHints, paramHints, chainingHints } = this.inlayHintsStyles;
|
||||
if (this.pendingDisposeDecorations !== undefined) {
|
||||
const { typeHints, paramHints, chainingHints } = this.pendingDisposeDecorations;
|
||||
editor.setDecorations(typeHints.decorationType, []);
|
||||
editor.setDecorations(paramHints.decorationType, []);
|
||||
editor.setDecorations(chainingHints.decorationType, []);
|
||||
}
|
||||
editor.setDecorations(typeHints.decorationType, decorations.type);
|
||||
editor.setDecorations(paramHints.decorationType, decorations.param);
|
||||
editor.setDecorations(chainingHints.decorationType, decorations.chaining);
|
||||
}
|
||||
|
||||
private hintsToDecorations(hints: ra.InlayHint[]): InlaysDecorations {
|
||||
const { typeHints, paramHints, chainingHints } = this.inlayHintsStyles;
|
||||
const decorations: InlaysDecorations = { type: [], param: [], chaining: [] };
|
||||
const conv = this.ctx.client.protocol2CodeConverter;
|
||||
|
||||
for (const hint of hints) {
|
||||
switch (hint.kind) {
|
||||
case ra.InlayHint.Kind.TypeHint: {
|
||||
decorations.type.push(typeHints.toDecoration(hint, conv));
|
||||
continue;
|
||||
}
|
||||
case ra.InlayHint.Kind.ParamHint: {
|
||||
decorations.param.push(paramHints.toDecoration(hint, conv));
|
||||
continue;
|
||||
}
|
||||
case ra.InlayHint.Kind.ChainingHint: {
|
||||
decorations.chaining.push(chainingHints.toDecoration(hint, conv));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return decorations;
|
||||
}
|
||||
|
||||
private async fetchHints(file: RustSourceFile): Promise<null | ra.InlayHint[]> {
|
||||
file.inlaysRequest?.cancel();
|
||||
|
||||
const tokenSource = new vscode.CancellationTokenSource();
|
||||
file.inlaysRequest = tokenSource;
|
||||
|
||||
const request = { textDocument: { uri: file.document.uri.toString() } };
|
||||
|
||||
return sendRequestWithRetry(this.ctx.client, ra.inlayHints, request, tokenSource.token)
|
||||
.catch(_ => null)
|
||||
.finally(() => {
|
||||
if (file.inlaysRequest === tokenSource) {
|
||||
file.inlaysRequest = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface InlaysDecorations {
|
||||
type: vscode.DecorationOptions[];
|
||||
param: vscode.DecorationOptions[];
|
||||
chaining: vscode.DecorationOptions[];
|
||||
}
|
||||
|
||||
interface RustSourceFile {
|
||||
/**
|
||||
* Source of the token to cancel in-flight inlay hints request if any.
|
||||
*/
|
||||
inlaysRequest: null | vscode.CancellationTokenSource;
|
||||
/**
|
||||
* Last applied decorations.
|
||||
*/
|
||||
cachedDecorations: null | InlaysDecorations;
|
||||
|
||||
document: RustDocument;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* This file mirrors `crates/rust-analyzer/src/lsp_ext.rs` declarations.
|
||||
*/
|
||||
|
||||
import { InlayHint } from "vscode";
|
||||
import * as lc from "vscode-languageclient";
|
||||
|
||||
export interface AnalyzerStatusParams {
|
||||
|
@ -99,26 +100,11 @@ export interface TestInfo {
|
|||
|
||||
export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>("rust-analyzer/relatedTests");
|
||||
|
||||
export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint;
|
||||
|
||||
export namespace InlayHint {
|
||||
export const enum Kind {
|
||||
TypeHint = "TypeHint",
|
||||
ParamHint = "ParameterHint",
|
||||
ChainingHint = "ChainingHint",
|
||||
}
|
||||
interface Common {
|
||||
range: lc.Range;
|
||||
label: string;
|
||||
}
|
||||
export type TypeHint = Common & { kind: Kind.TypeHint };
|
||||
export type ParamHint = Common & { kind: Kind.ParamHint };
|
||||
export type ChainingHint = Common & { kind: Kind.ChainingHint };
|
||||
}
|
||||
export interface InlayHintsParams {
|
||||
textDocument: lc.TextDocumentIdentifier;
|
||||
range: lc.Range;
|
||||
}
|
||||
export const inlayHints = new lc.RequestType<InlayHintsParams, InlayHint[], void>("rust-analyzer/inlayHints");
|
||||
export const inlayHints = new lc.RequestType<InlayHintsParams, InlayHint[], void>("experimental/inlayHints");
|
||||
|
||||
export interface SsrParams {
|
||||
query: string;
|
||||
|
|
Loading…
Reference in a new issue