Skip to content

Server-side Semantic Tokens #3159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 25, 2020
Merged

Server-side Semantic Tokens #3159

merged 2 commits into from
Feb 25, 2020

Conversation

kjeremy
Copy link
Contributor

@kjeremy kjeremy commented Feb 15, 2020

Takes the output of syntax_highlighting and converts it to the proposed semantic tokens API (so far only textDocument/semanticTokens. There's a lot of cool stuff we could do with this and the "Inspect Editor Tokens and Scopes" vscode command (pic below) is a cool way to see what has tokens and what doesn't.

Incredibly hacky and could panic due to unwraps, panic! etc. To use: run with code-insiders --enable-proposed-api matklad.rust-analyzer. If you try to run this without --enable-proposed-api it will crash.

image

@matklad I'm mostly looking for design feedback.

@@ -57,6 +59,49 @@ pub fn server_capabilities() -> ServerCapabilities {
execute_command_provider: None,
workspace: None,
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensOptions(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking the token and modifier lists should be exposed in a semantic_tokens.rs file in ra_ide so they can be reused in conv.rs. Maybe as arrays or strings that then get converted to SemanticTokenType and SemanticTokenModifer via into() when we fill in the server caps?

data: Vec<SemanticToken>,
}

impl SemanticTokensBuilder {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A straight forward port of the typescript version but without the support for edits.

@@ -0,0 +1,1737 @@
/*---------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale behind copying it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed instructions for proposed extensions. It will be removed before merging.

type Output = (SemanticTokenType, Vec<SemanticTokenModifier>);

fn conv_with(self, _world: &WorldSnapshot) -> (SemanticTokenType, Vec<SemanticTokenModifier>) {
match self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also put a function-local use SemanticTokenType as Stt; here to decrease verbosity, but I am not sure that everyone likes this style...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I am not a fan of local use * or renames.

To make this less verbose, I'd ranter

let tag = match {
    // many trivial branches;
}

let mods = match {
    // copule of non-trivial branches
    _ => vec![]
}

It also makes sense to refactor our internal repr to use tag + mods apporach, but that should be done separately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or

let simple_token_type = match self {
    tags::KEYWORD => Stt::KEYWORD,
    tags::SOMETHING => return (Stt::SOMETHING, vec![MODIFIER])
}
return (simple_token_type, vec![])

Comment on lines 356 to 375
SemanticTokenType::COMMENT,
SemanticTokenType::KEYWORD,
SemanticTokenType::STRING,
SemanticTokenType::NUMBER,
SemanticTokenType::REGEXP,
SemanticTokenType::OPERATOR,
SemanticTokenType::NAMESPACE,
SemanticTokenType::TYPE,
SemanticTokenType::STRUCT,
SemanticTokenType::CLASS,
SemanticTokenType::INTERFACE,
SemanticTokenType::ENUM,
SemanticTokenType::TYPE_PARAMETER,
SemanticTokenType::FUNCTION,
SemanticTokenType::MEMBER,
SemanticTokenType::PROPERTY,
SemanticTokenType::MACRO,
SemanticTokenType::VARIABLE,
SemanticTokenType::PARAMETER,
SemanticTokenType::LABEL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kjeremy kjeremy force-pushed the semantic-tokens branch 3 times, most recently from a176cec to f49e942 Compare February 19, 2020 14:33
@matklad
Copy link
Member

matklad commented Feb 22, 2020

Haven't looked too closely into this, but looks 👍 on the first glance!

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 22, 2020 via email

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 25, 2020

@matklad I've refactored a few things and I think the rust side is in good shape. However it's not useful from vscode yet. In order to get it to work you have to:

  1. Use "enabledProposedApi": "true" in package.json
  2. Run code or code-insiders with --enable-proposed-api matklad.rust-analyzer
  3. On vscode stable you have to set editor.semanticHighlighting.enable

So I think I would like to back out the typescript changes and leave those as an exercise to anyone who would like to mess with tokens.

@kjeremy kjeremy changed the title WIP Early semantic tokens support Server-side Semantic Tokens Feb 25, 2020
@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 25, 2020

I've removed the client side changes.

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 25, 2020

This only supports whole file token support. Once accepted I can tackle edits

@matklad
Copy link
Member

matklad commented Feb 25, 2020

bors r+

Once accepted I can tackle edits

Edits are not important. They add quite a bit of complexity for questionable performance benefits. What is important to support are ranges for narrowing down the highlighted bit.

@bors
Copy link
Contributor

bors bot commented Feb 25, 2020

@bors bors bot merged commit 1fe48a0 into rust-lang:master Feb 25, 2020
@matklad
Copy link
Member

matklad commented Feb 25, 2020

We also should remove our custom client-side hacks. It's ok if this regresses functionality for non-insiders builds.

@Veetaha
Copy link
Contributor

Veetaha commented Feb 25, 2020

Let's not rush with removing working hacks, I personally don't use code insiders...

@matklad
Copy link
Member

matklad commented Feb 25, 2020

Aggressively not accumulating technical debt is an explicit design guideline for rust-analyzer. We should actively migrate from hacks to insiders functionality. It's OK to regress the experience of users who don't use insiders.

@bjorn3
Copy link
Member

bjorn3 commented Feb 25, 2020

I disagree with both requiring the insiders version, which 99% of the people don't use, and having to manually pass an argument to vscode every time you want to start it. In this particular case the old implementation is kind of broken anyway, so it is not a very big problem, but in the general case I would prefer that users of the latest stable vscode release can keep using features.

@matklad
Copy link
Member

matklad commented Feb 25, 2020

In this particular case the old implementation is kind of broken anyway

Yeah, this is exactly my reasoning here: we replacing something completely ad hoc and broken with something on path to stabilization.

We should migrate from hacks, but we shouldn't change something that already works just because a new shiny thing just came out.

impl Conv for &'static str {
type Output = (SemanticTokenType, Vec<SemanticTokenModifier>);

fn conv(self) -> (SemanticTokenType, Vec<SemanticTokenModifier>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do a more direct conversion here, avoiding the Vec allocation, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think smallvec could be more appropriate here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is using &'static str type in the first place. It doesn't make sense to optimize impl details while the API itself is wrong. I think we should move to a model closer to tag+modifier of LSP.

@matklad
Copy link
Member

matklad commented Feb 25, 2020

While we are at it, this is why I think it's important to migrate to new APIs:

  • this immediately decrees overall maintenance burden. All the time when the new API is available but we use the old one is accumulated debt interest.
  • nightly APIs are in general higher quality than the hacky ones. Ones we migrated extend selection in a similar way, we immediately got the undo functionality working
  • probably most important one: we can't not report bugs with the new API unless we fully embrace it. And it's important to report bugs (especially design bugs) while the API is nightly.
  • these "I'll do it later" things tend to acquire mass and momentum with time. Getting rid of hacks now is much less effort than deciding to do so some time in the future.

@kjeremy kjeremy deleted the semantic-tokens branch February 25, 2020 12:17
@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 25, 2020

@matklad I noticed last night that edits aren't used in the extensions so will focus on ranges. Thanks.

@matklad
Copy link
Member

matklad commented Feb 26, 2020

@kjeremy should I do anything else to test this besides code-insiders --enable-proposed-api matklad.rust-analyzer .? Doesn't work for me so far, probably I am doing something stupid like using an old version of extension or what not, but I can't find the problem immediately

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 26, 2020

@matklad

You have to also set "enableProposedApi": "true" in your package.json and in client.ts either:

  1. call res.registerProposedFeatures() instead of registering CallHierarchyFeature
  2. OR import and register SemanticTokensFeature

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 26, 2020

Note that range requests and possibly edits are not implemented yet in vscode: microsoft/vscode#86415 (comment)

@matklad
Copy link
Member

matklad commented Feb 26, 2020

Oh, right... @kjeremy do you have client-side implemenation around by any chance? If you have, could you send a PR?

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 26, 2020

You mean the client.ts changes I mentioned above? I can't do that because it will kill the client on stable and insiders without that command line switch... maybe we can catch and ignore the error somehow.

@matklad
Copy link
Member

matklad commented Feb 26, 2020

It's still useful to have at least a PR

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 26, 2020

Done! #3321

@matklad
Copy link
Member

matklad commented Feb 26, 2020

Also, I think there was a problem that requests are canceld with ContentModifed, but are not retried by the client.. Am i remmebering this correctly? Have we reported an upstream issue?

@kjeremy
Copy link
Contributor Author

kjeremy commented Feb 26, 2020

I have not reported that issue. I THINK I saw it yesterday but then ran into the panics in #3310 which may be the actual problem.

@runbmp runbmp mentioned this pull request Mar 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants