joaotavora / eglot

A client for Language Server Protocol servers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Gripes about lsp-mode/elgot comparison

yyoncho opened this issue · comments

Most of the statements in lsp-mode/eglot comparison are either out of date, wrong or misleading. For example:

  1. new versions of lsp-mode have single entry point
  2. lsp-mode startup is also asynchronous
  3. lsp-mode supports flymake
  4. lsp has tests against real servers(~ at least 7 months).
  5. lsp-mode and eglot are comparable in size having in mind that the parser of eglot is in separate package and that lsp-mode core supports more functionality(real multifolder support, support for multiple servers in single file, etc.)
  6. lsp-mode is as minimal as eglot and it also works out of the box. Even more, lsp-mode does not depend neither on projectile/project.el since it uses the same root resolving logic as the reference LSP client implementation - VSCode.
  7. lsp-mode similar to eglot does not require any configuration and it automatically detects if the extension modules are present and it automatically configure them.
  8. lsp-mode automatically handles crashing servers

In addition to these base features lsp-mode has (in separate packages):

  1. Visual debugger integration via dap-mode
  2. Better support for the complex server. For example, Java LS has 40+ extension methods and none of them is exposed by eglot.
  3. Supports multiple servers in single file
  4. Third party integration with packages like flycheck, treemacs(and much more will be added in the near future).
  5. "Fancy" UI features provided by lsp-ui
  6. Spacemacs integration.

There are a lot of small things that are not included in the list, but this is the general overview.

@yyoncho

Relax, I get it: you're lsp-mode's dev now, rewrote it, and now think the comparison is unfair. That's quite alright and congratulations! I'm sure none of those changes were motivated by Eglot's appearance at all... ;-)

Now, I don't use lsp-mode, don't visit its website and/or follow its development closely. So I can't know what's going on. I've added a preamble to the comparison framing it as an historical artifact. You see, I added those paragraphs in the first place because some people ask, sometimes indignantly, why I write new things and don't contribute to an existing project. Apparently "because I want to" and "to have fun" aren't acceptable answers. But for me, they are still totally great answers, so if you are having fun writing lsp-mode.el I can only wish you well.

I had a look at your new version: it's much shorter and seems to read better, even with all those foreign macros, so kudos again. Has it drifted away from the goal of integration into Emacs/ELPA? If so, that's unfortunate: Eglot requires nothing that is not in Emacs, which is an explicit goal.

Anyway, as you probably expected, I still think Eglot design is better. For example, I think it's much nicer to write method handlers as cl-defmethod's instead of anonymous entries in a hash. They're easier to toggle-trace, debug-on-entry, edebug, etc... I also think, reading your code, that you repeat needlessly bits of code here and there.
Also, these (-let (((&hash "field1" "field2")) object ... constructs throw away precious byte-compiler information that precludes optimizations. Eglot now has as a mini-LSP DSL with compile-time and run-time checking of LSP interfaces, which is also good for cutting down on the mistakes as the spec grows and grows.

But that's all under the hood. Here are some user-visible differences I picked up that you might want to bring into lsp.el. For example, in no particular order:

  • In Eglot, startup can be synchronous, which is good for quick to startup servers, or asynchronous for slow-to-startup servers. That's decided automatically based on the the time that the server is taking to startup.

  • In Eglot, if the user presses many keystrokes, it won't send as many didChange to the server, only one when the user stops for a while. Otherwise you'll choke the server with a lot of requests. You should definitely do this in lsp-mode.el: it's easy, just use an idle timer.

  • I tried the newest lsp-mode from MELPA in a fresh emacs -Q in a sandbox. I package-installed lsp-mode (this pulled in a million packages, which is a bit annoying for me personally, hence the sandbox, but that's OK for most people I guess). Then I navigated to a Rust file in a git project, and typed M-x lsp, expecting greatness. It said no project was found and gave me some actions about whitelisting or blacklisting. It was very confusing for a newbie like me. I ended up choosing "Import project root" (no idea what I am "importing" or where I am importing it to, but OK). Then it said "Unable to start server". Now mind you: there may be amazing functionality hidden in all those options, but I don't care about it just yet and it is significantly different from the M-x eglot user experience, where it starts working immediately.

  • I did the same for a C file and now I chose "Import project by selecting root dir interactively", which I did in another prompt. And then it said again can't handle c-mode. So then I M-x package-install RET lsp-clangd RET and still failed to start a server. I gave up, but perhaps you can write a short Emacs -Q tutorial for people like me.

  • In Eglot, if a server crashes it is automatically restarted, but if it crashed too many times in too short an interval it's not. In lsp-mode it's either interactive or auto-restart where the former is annoying and the latter is problematic if the server misbehaves a lot.

  • Regarding the JSONRPC interface, you should look into jsonrpc.el (and perhaps suggest improvements). For example, it can give you synchronous LSP requests that give up as soon as a user types a character. This is useful for fluent company integration, where the user is typing stuff and you're requesting completions eagerly, but discarding the responses that are out of date. while-no-input and sit-for are the main primitives for this, if you're interested, but I would just use jsonrpc.el.

  • Another bit that's useful in Eglot, is the deferring of requests to when the server is ready. It happens when RLS isn't ready to give hover information because you just inserted a completion. You have to wait for a "ready" signal from the server. I think I once explained this to you when you asked me (or was it someone else?) Doesn't seem like lsp-mode has that yet.

  • Some programs (especially on Windows) don't run very well on stdio interfaces, so it's useful to use TCP instead. Eglot has a special syntax in eglot-server-programs that automatically starts a process starting a server and then starts a second process conneting to that server. This is a very good approach pioneered by SLIME.

  • By the way, company integration is still something you need a separate package for, whereas Eglot just needs company. And there appears to be no snippet support (or sophisticated completion support with text edits) if you don't like company at all and just use completion-at-point. Consider killing company-lsp and just using company-capf. Suggest improvements in the Emacs bug tracker if you think completion-at-point-functions are not good enough.

  • As I wrote, I couldn't easily test the new lsp-mode. I'm curious as to the Flymake integration. Did you know you can have interactive diagnostics that can e.g. bring up associated code actions? Eglot does that :-)

So, in my view, lsp.el is now smaller but still larger than Eglot, and I still think it does less in these core areas. One notable exception seems the multi-server support, which I noted the other day that you were looking into. How's that going? For what modes is it most used? I think it's tricky to handle correctly and so will probably write a kind of a meta-server (or find a good existing one) in some other language that uncouples it fully from eglot.

Another very interesting bit is this DAP thing. Is it a standard already? Where can I find it? Again, if it's JSONRPC-based, you could look into using jsonrpc.el. And even better would be to integrate it with gud.el. How's the copyright situation in your DAP mode? Do you want to integrate into Emacs?

Lastly, I don't care much about Spacemacs, but I don't see why Eglot wouldn't work there. It's still Emacs right?

I also don't care much about servers who work outside the spec (I begrudgingly added the java/jdt bits which could be much simpler if it had some startup scripts). Also, I care even less about VS code :-) Morally, non-compliance or compliance to a particular set of clients/servers is against the idea of an open standard in the first place, and against the core ideas of Emacs, which are about software freedom.

First of all my focus is on language servers, elisp is not my strength but I
follow the development of both eglot and lsp-mode closely (and sometimes
other Vim clients and even vscode-languageserver-node) and want to see both
moving forward.

I'm sure none of those changes were motivated by Eglot's appearance at all... ;-)

There are many great points mentioned in the reply and lots of places that
lsp-mode can learn from eglot. Also as a contributor of lsp-mode, I confess I've
taken inspirations from eglot and ported them to lsp-mode for quite some time.

You see, I added those paragraphs in the first place because some people ask, sometimes indignantly, why I write new things and don't contribute to an existing project.

As a person who mostly focuses on server-side implementation I've faced the same embarrassment and done something similar. I'll learn from the arguments here.

Has it drifted away from the goal of integration into Emacs/ELPA?

The goal of integration into Emacs/ELPA has been abandoned emacs-lsp/lsp-mode#83

I think it's much nicer to write method handlers as cl-defmethod's instead of anonymous entries in a hash.
these (-let (((&hash "field1" "field2")) object ... constructs throw away precious byte-compiler information that precludes optimizations.

I second these arguments. lsp-mode can definitely learn from the generic function style used throught in eglot.

The point about byte-compiler is insightful. Do you mind sharing some more pointers?

In Eglot, if the user presses many keystrokes, it won't send as many didChange to the server, only one when the user stops for a while. Otherwise you'll choke the server with a lot of requests. You should definitely do this in lsp-mode.el: it's easy, just use an idle timer.

The impression that textDocument/didChange may harm performance is probably a result of how textDocument/publishDiagnostics is triggered.

Futhermore, in emacs-ccls/emacs-cquery, after receiving
textDocument/publishDiagnostics notification from the server, the client will request textDocument/codeLens (which can be a very large response, cquery's worse).

I ended up choosing "Import project root" (no idea what I am "importing" or where I am importing it to, but OK).

I'm very sensitive to this sort of project root topics.

(setq lsp-auto-guess-root t) makes it behave like eglot / legacy (it is what I use). I use something like the following:

(use-package lsp-mode
  :commands lsp
  :init (setq lsp-auto-guess-root t)
  :config (require 'lsp-clients))

For my own good, I am pretty happy with 1) bottom-up root indicator file
.ccls-root 2) projectile-project-root (I know the existence of #129 :)
https://github.com/MaskRay/ccls/wiki/lsp-mode#projects-with-subprojects I rarely use other language servers and don't know how others view my choice.

Returning to this menu, it and (:multi-root) is related to workspace folders, a feature standarized in LSP but no language clients has done/implemented it well.
VSCode has had this feature for a long time, predating all other clients, but how it
works is not transparent at all: it has menu items "Open Workspace" and "Open
Folder" but whether a new language server is created and whether a new workspace
folder is created if very unclear to me.
There is some other discussion in
emacs-lsp/lsp-mode#474 (comment)
I have checked various other language clients (a lot of Vim/NeoVim's) but none of them makes me satisfied.

So then I M-x package-install RET lsp-clangd RET and still failed to start a server. I gave up, but perhaps you can write a short Emacs -Q tutorial for people like me.

See here https://github.com/emacs-lsp/lsp-clangd/issues/11#issuecomment-445587207

Regarding the JSONRPC interface, you should look into jsonrpc.el

Do you think using plist for JSON Objects has performance/memory advantages over
hash (the pervasive gethash pattern in lsp-mode which will make you sick..)?

is the deferring of requests to when the server is ready.

This is another thing that is not regulated in the spec but matters: there is
some time after the server returned "initialize": it may still be loading index
files (probably in background threads) and isn't ready for serving requests.

According to cl-defmethod jsonrpc-connection-ready-p ((server eglot-rls) what), it seems to use a global window/progress? But isn't this a per-file status?
There are some discussions around issues referencing microsoft/language-server-protocol#70

Consider killing company-lsp and just using company-capf.

@tigersoldier but he is probably quite busy at this point :(

company-lsp did cause some confusion among lsp-mode/cquery/ccls users.

(I prefer to keep the status quo from the perspective of ease of maintenance burden.)

Did you know you can have interactive diagnostics that can e.g. bring up associated code actions?

M-x lsp-execute-code-action It will apply the action without a prompt if there is only one.

I migrated from lsp-ui-flycheck.el to flymake integration in lsp-mode but
missed flycheck-posframe (the child frame in the screenshot )

@tumashu @alexmurray (sorry if I shouldn't mention you here.. but you can shed some light how to make flymake look better)

Do you think using plist for JSON Objects has performance/memory advantages over
hash (the pervasive gethash pattern in lsp-mode which will make you sick..)?

I haven't measured anything so I don't know. It would depend on the application. Jsonrpc could be taught to use hash tables, it's an implementation detail...

But isn't this a per-file status?

Maybe yes. But now we say only "do certain things things when all files are ready".

@MaskRay
What about company-lsp integrating lps.el?

I reckon that lsp-mode users prefer all of its essential features/packages be under lsp.el!

company-lsp is that important!

@eubarbosa, to discuss lsp-mode.el issues, please use its repository :-)

@joaotavora

After 973cd81 I won't waste my time on detailed answer. I just wanted to you update the out of date readme but apparently this is too hard. Here are few comments:

  1. Your implementation of didChange is wrong and does not follow the spec.
  2. You said that you have implemented suppressing of calls against RLS server until build is ready using the notification that is not part of the spec and at the same time you said that you don't care about the servers that work outside of the spec!?
  3. It's funny that you state that you cannot debug/trace notification handlers in lsp-mode...
  4. You said you have created eglot instead of contributing to lsp-mode "to have fun" and at the same time you advised using and contributing to your packages or extending existing one. I guess "to have fun" is valid point only for you. And for the record dap-mode cannot be integrated with gud...
  5. -let is macro and it does not affect byte compilation ...

And much more...

@yyoncho

After 973cd81 I won't waste my time on detailed answer. I just wanted to you update the out of date readme but apparently this is too hard. Here are few comments:

Look, I don't want to write a comparison table, but you can write your own anywhere you want. I framed that comparison table as an historical artifact, so even though it's out-of-date, now it's out-of-date by definition. I've now changed the section title to Historical differences to lsp-mode (also I fixed the lsp-mode link so people can go to your repo and see what's up).

I left the comparison in the last part of the READMEbecause it contains some of the reasons that led me to start Eglot in May 2018. I briefly discussed that comparison with Vibhav even.

You seem to be a slightly angry and I don't understand why. The reason I am investing time in answering this is I think it's nice to be a little less competitive and a little more cooperative. Like @MaskRay for example. Wouldn't that be great for both projects?

Your implementation of didChange is wrong and does not follow the spec.

Then please make a bug report of it, or better yet, a pull request. It's no fun just to say "you are wrong" and not explain why.

You said that you have implemented suppressing of calls against RLS server until build is ready using the notification that is not part of the spec and at the same time you said that you don't care about the servers that work outside of the spec!?

Yes. I talked to RLS developers and it has no easy way to fix that problem and there's nothing yet in the spec (yet) that solves the problem. So until then there is a small addition to eglot.el that can be wiped away. It's quite different from supporting "40+ extension methods".

it's funny that you state that you cannot debug/trace notification handlers in lsp-mode...

Here's the difference: a newcomer to lsp.el has to eventually arrive at an indirection table in lsp--default-notification-handlers, whereas in Eglot they are all implementations of the same generic function. And the LSP method name is right beside the function. But obviously, if you know your code well, you can debug anything! I just said it was "easier" in Eglot: it was just my opinion. If you think yours is great, knock yourself out!

You said you have created eglot instead of contributing to lsp-mode "to have fun" and at the same time you advised using and contributing to your packages or extending existing one. I guess "to have fun" is valid point only for you.

It's really a very simple message: sometimes contributing to other projects is fun. And sometimes it isn't, when the implementations suck. You decide that, not me. Maximize your fun, I say, and don't be so angry ;-) If you think jsonrpc.el sucks, it's fine by me! If you find it useful, use it! Please don't not use it just because it's in Emacs core, or was written by me.

And the packages aren't "mine" anymore, they're Emacs's, or technically the FSF's.

And for the record dap-mode cannot be integrated with gud...

(You seem to take much pride in that, why?) What can it be integrated with? Would you interested in contributing it to Emacs anyway?

-let is macro and it does not affect byte compilation ...

Being a macro doesn't mean it does not affect compilation. Different macros have different expansions, and some are easier to optimize than others. But fair enough, I take it back. I'll only say it looks very odd for a Lisp macro, and there are alternatives like pcase that can destructure hash tables.

And much more...

lol :-)

@joaotavora Actually, I am not angered at all.

Now, I don't use lsp-mode, don't visit its website and/or follow its development closely. So I can't know what's going on.

I think it's nice to be a little less competitive and a little more cooperative.

Is this what you call cooperative?!(I am not angered, it is funny)

Here's the difference: a newcomer to lsp.el has to eventually arrive at an indirection table in lsp--default-notification-handlers, whereas in Eglot they are all implementations of the same generic function.

Here it is how the clients actually register a notification handler: https://github.com/emacs-lsp/lsp-java/blob/master/lsp-java.el#L899 - easier to read and track than the eglot solution since you have all of the configuration in one place. You do 5 minute research and you throw comments like that all the time, why should I care you explain?

And for dap-mode: I am fine with contributing it to Emacs but this is not possible(I might be wrong, I am not an expert).

  1. It depends on projects outside of emacs(dash).
  2. I have adapted some code from another emacs projects(cider for overlays,skewer for debug repl, etc).
  3. Since the tree widget from emacs core is not what I need(I am putting this lightly) I will migrate the tree components to use treemacs as a tree rendering

PS: You could do better with the doc change.

Is this what you call cooperative?!

Cooperative doesn't mean I have to track your movements or even use your project. I don't have time for that, sorry. But it does mean that if you ask me something, like I asked you to point out the error in didChange, I will respond.

I am not a cynic Ivan, I really do mean this.

(I am not angered, it is funny)

Good. On writing things can indeed sound different. Now can you point out the actual flaw you said you found in didChange? Did you stumble upon it in testing or was it by reading the code? Open a new issue: it would be very helpful.

You do 5 minute research and you throw comments like that all the time, why should I care you explain?

(There you go, finding things funny again in a way that some humans would characterize as being a tiny bit aggrieved.)

Look I've understood perfectly how it works in lsp-mode. Here's my last attempt at explaining what I think:

Taking your example, how can I debug the handler for workspaceEdit requests for java servers?

  • In LSP-mode, I have to navigate to that file you linked to, or inspect the server object and go rummage in its hash-table: eventually I arrive at lsp-java--apply-workspace-edit. There is no M-. solution that will bring me to that place.

  • In Eglot I do M-. on the eglot-handle-request and in the *xref* buffer look for the closest matching specialization, in this case a Java server and the workspaceEdit method.

Putting handlers in a global object is a very JS-esque solution, also useful for languages with no direct OO support, like Elisp. But in Common Lisp, you have generic functions (look up CLOS if you're interested). Emacs lisp has a decent CLOS subset in cl-generic.el. I find this easier, but you like to make your tables, go ahead. It's not terrible, it's just a bit worse in my opinion.

PS: You could do better with the doc change.

You mean the README? Sorry, but no. Especially if you are vaguely going to make me guess for the changes instead of arguing properly.

@yyoncho
spacemacs does not support c-c++ with lsp-mode well.... Well, I haven't try elgot for spacemacs. (I have to say.)

@beyondpie, this is maybe not the best place to report LSP-mode issues, I'd suggest to use its repository instead.

If you do get to try Eglot in Spacemacs (or any other kind of Emacs distribution), do give feedback (in a new issue).

Also, I am locking this issue, as it has the potential to attract off-topic comments. Mail me directly or open a new issue requesting this one to be opened if you really must comment here.