eglot icon indicating copy to clipboard operation
eglot copied to clipboard

Close #615: Add support for semantic tokens

Open AkibAzmain opened this issue 3 years ago • 37 comments

I've implemented semantic tokens (introduced in LSP 3.16) support. Closes #615.

  • [x] Support full buffer highlighting (textDocument/semanticTokens/full).

  • [x] Support full buffer highlighting using token delta (textDocument/semanticTokens/full/delta). It mostly works, but becomes inaccurate when you do the following before the changes are sent to server:

      printf (str);
    //        ^^^ fontified
    

    Delete (str);:

      printf
    

    Write (str);:

      printf (str);
    //        ^^^ not fontified
    

    Setting eglot-send-changes-idle-time to 0 fixes it, but probably makes Emacs slow.

  • [x] Support partial buffer highlighting (textDocument/semanticTokens/range). I think it works, because full buffer highlighting and partial buffer highlighting is implemented in the same function.

  • [x] Support on-the-fly highlighting.

  • [x] Support highlighting on server start.

Note: I've tested using only clangd 12.0.1, which doesn't support textDocument/semanticTokens/range.

AkibAzmain avatar Feb 21 '22 12:02 AkibAzmain

The other day I tried this PR out on a very simple example and it worked perfectly. Also, at first glance, the implementation is quite solid. However, I'd wait for someone more knowledgeable for a proper review. Nevertheless, this a legally significant contribution. @AkibAzmain, have you completed your copyright paperwork? If not, you might like to start the process, because it may take a day or two. (Or months in rare cases.)

I see that the PR is not feature complete, which is not a problem IMHO, because any support for semantic tokens is better than no support.

I think adding some simple tests to eglot-tests.el would help the potential reviewer a lot.

Thank you!

nemethf avatar Mar 09 '22 10:03 nemethf

@nemethf it is very good to hear that preliminary feedback. Agree with everything else you said.

joaotavora avatar Mar 09 '22 10:03 joaotavora

And of course thanks @AkibAzmain for this work :-)

joaotavora avatar Mar 09 '22 10:03 joaotavora

@nemethf,

I see that the PR is not feature complete, which is not a problem IMHO, because any support for semantic tokens is better than no support.

It looks like there race condition. When large edits are made or large files are opened, semantic tokens are probably requested before the server updates it's internal state and the server replies with data for the old state. Is it a bug of my LSP server (clangd), or do I need to wait for a ready notification or similar from the server?

However, I'd wait for someone more knowledgeable for a proper review. Nevertheless, this a legally significant contribution. @AkibAzmain, have you completed your copyright paperwork? If not, you might like to start the process, because it may take a day or two. (Or months in rare cases.)

What's the process?

AkibAzmain avatar Mar 10 '22 03:03 AkibAzmain

What's the process?

Please send email to [email protected] very briefly describing this contribution to Eglot and requesting from the Emacs maintainers they send you a copyright assignment form off-list.

do I need to wait for a ready notification or similar from the server?

There was one a server that had such a problem, rls: though the problem was mostly on its side, it was to eager to provide stale completions before having been informed of and parsing the virtual file.

So, motivated by that, Eglot (jsonrpc.el, actually) has mechanisms to facilitate that sync, but first I'd like to see the race condition highlighted in as jsonrpc transcript snippet, and shown two versions of that snippet. One where the race is "lost" and the desirable (fabricated) case where it's "won"

joaotavora avatar Mar 10 '22 09:03 joaotavora

@joaotavora That's no longer needed, because the server has no problem. The problem is that the server is takes much time for large files and large edits, and the request times out. :-(

Should I request again when a request times out? Or just ignore it?

AkibAzmain avatar Mar 10 '22 13:03 AkibAzmain

Should I request again when a request times out? Or just ignore it?

Not sure I know how to answer that, to be honest. What are the consequences of such a timeout? I suppose the "safe" thing to do is to somehow warn the user with an eglot--message or something to that effect.

joaotavora avatar Mar 10 '22 13:03 joaotavora

The consequence is that the colors of the buffer (I mean tokens) become outdated. Not a serious problem IMHO. So timeout is harmless. I think reporting it would just annoy the user, as it is very common when opening a file of a large project (atleast for me). We can also increase or disable the timeout, is that an option?

AkibAzmain avatar Mar 10 '22 13:03 AkibAzmain

do I need to wait for a ready notification or similar from the server?

I think the server doesn't send anything like that after didOpen. rust-analyzer has an experimental/serverStatus notification, but clients are discouraged to use the status to decide if it's worth sending a request to the server.

Probably switching to textDocument/semanticTokens/range would solve the problem, wouldn't it? However, clangd doesn't support this. Maybe we should ask the developers of clangd to tell us how they imagine a client should handle large files with respect to semantic tokens.

(If they answer the clients should implement workDoneProgress support, that would mean lot of work, unfortunately.)

nemethf avatar Mar 10 '22 16:03 nemethf

Marking the last two checkbox, as there is no problem on our side.

AkibAzmain avatar Mar 11 '22 08:03 AkibAzmain

@nemethf The request times out sometimes, and that's why the problem occurs.

AkibAzmain avatar Mar 11 '22 08:03 AkibAzmain

@nemethf The request times out sometimes, and that's why the problem occurs.

But the timeout occurs because the client asks the server to do lots of things at once. The client could ask less from the server like for example to calculate the tokens for just a small portion of the file, or it could let the server to send its answers in small pieces. Or at least that's my understanding of the specification.

nemethf avatar Mar 11 '22 10:03 nemethf

But the timeout occurs because the client asks the server to do lots of things at once.

Possibly the client asks the server for tokens before its ready, and the server replies after its ready, in the meantime the client times out.

The client could ask less from the server like for example to calculate the tokens for just a small portion of the file,

Yes, there is textDocument/semanticTokens/range method for that, but I don't think it'll work. Because IMHO the server doesn't take much time to process the range but to be ready to process it.

or it could let the server to send its answers in small pieces.

Looks like a good idea, but I can't find any partial result implementation in Eglot. Implementing it would probably need changes in many things, including eglot-lsp-server class.

AkibAzmain avatar Mar 11 '22 12:03 AkibAzmain

@AkibAzmain says:

Looks like a good idea, but I can't any partial result implementation in Eglot. Implementing it would probably need changes many things, including eglot-lsp-server class.

@nemethf says:

(If they answer the clients should implement workDoneProgress support, that would mean lot of work, unfortunately.)

I'm curious to learn more about this "loat of work" and "change many things".

If the current proposed implementation is minimally useful/clean already, I don't think these things should block it. Should we make a discussion thread about them?

joaotavora avatar Mar 11 '22 12:03 joaotavora

@joaotavora says,

@AkibAzmain says:

Looks like a good idea, but I can't any partial result implementation in Eglot. Implementing it would probably need changes many things, including eglot-lsp-server class.

@nemethf says:

(If they answer the clients should implement workDoneProgress support, that would mean lot of work, unfortunately.)

I'm curious to learn more about this "loat of work" and "change many things".

If the current proposed implementation is minimally useful/clean already, I don't think these things should block it. Should we make a discussion thread about them?

I was wrong. Implementing $/progress is trivial, and so is partial result and work done progress; they shouldn't pollute the code. But my server didn't return partial result when I tested with a dummy progress token, so implementing these things is useless for me.

AkibAzmain avatar Mar 11 '22 13:03 AkibAzmain

If the current proposed implementation is minimally useful/clean already, I don't think these things should block it.

I think the current PR is at least minimally useful. A proper review is necessary, but I agree the timeout issue shouldn't block its acceptance.

Should we make a discussion thread about them?

(I don't think the discussion forum is a good fit for feature requests.)

nemethf avatar Mar 11 '22 17:03 nemethf

I'd have a request concerning this feature. It should be possible to disable certain kinds of highlighting (possibly by setting the appropriate entry of eglot-semantic-token-faces to nil), and it should also be possible to somehow do this kind of configuration on a per-mode or per-server basis.

Motivation for this: I'm looking forward to something that can fontify lexical variables accurately, but since I want to actually be able to tell those things apart from the rest, I like to keep my syntax highlighting to a minimum.

astoff avatar Mar 24 '22 11:03 astoff

I'd have a request concerning this feature. It should be possible to disable certain kinds of highlighting (possibly by setting the appropriate entry of eglot-semantic-token-faces to nil), and it should also be possible to somehow do this kind of configuration on a per-mode or per-server basis.

I think that's all possible with this PR, you just have to write hook functions to buffer-locally set the relevant variables.

nemethf avatar Mar 24 '22 12:03 nemethf

Well, with hooks and advices you can do just about anything.

astoff avatar Mar 24 '22 12:03 astoff

@astoff,

I'd have a request concerning this feature. It should be possible to disable certain kinds of highlighting (possibly by setting the appropriate entry of eglot-semantic-token-faces to nil), and it should also be possible to somehow do this kind of configuration on a per-mode or per-server basis.

You can set eglot-semantic-token-faces and eglot-semantic-token-modifier-faces buffer-locally, that should work (though I haven't tested).

AkibAzmain avatar Mar 25 '22 07:03 AkibAzmain

What's the status on this? It would be nice to see this merged in time for the upcoming 29.1 release in a few months. Is there anything we can do to help?

leungbk avatar Sep 25 '22 08:09 leungbk

@leungbk It works well. AFAIK the only blocker is copyright assignment. I hope I can start the process within about 3 weeks.

AkibAzmain avatar Oct 01 '22 13:10 AkibAzmain

I've started using this with Ada, and Adacore ada_language_server.

It uses a timer to trigger highlight, and requests a full buffer highlight at the start; this can easily time out on a large file. Instead, I think it should let font-lock request the highlight; that way only a small region will be done in one request, which should be fast. I'll fork this to work on that.

stephe-ada-guru avatar Oct 18 '22 21:10 stephe-ada-guru

@stephe-ada-guru: Nice idea. I really don't know enough about font lock, so I took the simplest way: add font-lock-face property.

AkibAzmain avatar Oct 20 '22 13:10 AkibAzmain

Now as Eglot is in Emacs core, do I need to take any additional steps?

AkibAzmain avatar Oct 21 '22 03:10 AkibAzmain

Now as Eglot is in Emacs core, do I need to take any additional steps?

No.

But the FSF copyright assignment is still not finished, or is it?

At a certain point int the future I will have a better look at this PR and convert it from PR to patch format. But your authorship of the commits remain unaltered. At that point, we may start following up discussion on the patch on Emacs's bug tracker list, i.e. via email. But for now, no additional steps to take.

joaotavora avatar Oct 21 '22 07:10 joaotavora

@joaotavora, I've sent the copyright assignment form to [email protected]. Waiting for reply.

AkibAzmain avatar Oct 21 '22 07:10 AkibAzmain

Sorry to disturb. What is the current status of this PR?

ksqsf avatar Mar 11 '23 14:03 ksqsf

@ksqsf Waiting for me to sign the copyright assignment paper.

AkibAzmain avatar Mar 11 '23 16:03 AkibAzmain

By the way, @joaotavora, what's the state of this repository?

AkibAzmain avatar Mar 11 '23 17:03 AkibAzmain