Skip to content
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

Proposal/Enhancement: Allow C#/F# to use "local" tools for LSP servers #3906

Open
Gastove opened this issue Jan 11, 2023 · 2 comments · May be fixed by #4299
Open

Proposal/Enhancement: Allow C#/F# to use "local" tools for LSP servers #3906

Gastove opened this issue Jan 11, 2023 · 2 comments · May be fixed by #4299

Comments

@Gastove
Copy link
Contributor

Gastove commented Jan 11, 2023

C# and F# both lean heavily on the dotnet CLI for all kinds of tasks -- including installing and managing language server binaries. This is referred to as dotnet "tools". lsp-mode currently makes use of both the dotnet CLI and the "tools" functionality for installing C# and F# language servers.

An important detail here is: a dotnet tool can be either local or global. Local tools are described in a "tool manifest" that is version controlled within a repo, allowing all contributors to share tool versions. Importantly: local tools can be installed one of two ways:

  1. If the tool has never been installed into a given repo before, dotnet tool install will install it and add it to the repo's tool manifest so it can be version controlled.
  2. If the tool is already in the repo's tool manifest, it must be "restored" to be used. This is functionally equivalent to installing it, except that a different command must be issued -- dotnet tool restore.

Both lsp-fsharp and lsp-csharp currently hard-code command line flags to force using only global tool installations. This works just fine in many cases, so this isn't a bug. However! Since dotnet can install language servers on a per-project basis, it would be an excellent -- and not especially complex -- lsp-mode enhancement to support running local tools if present.

To my eye, this could happen loosely following one of two patterns:

  1. A new defcustom for lsp-<language>-use-local-tool.
  2. A new defcustom for lsp-<language>-prefer-local-tool.

I'm very happy to implement either approach; I personally prefer approach 1.

lsp-<language>-use-local-tool / lsp-<language>-use-global-tool

In this implementation, a new boolean defcustom, safe as a local variable, would simply add or remove the global flag from dotnet tool invocations. If the correct language server isn't installed locally, lsp-mode would prompt for a server install. I like this approach because it's very explicit, and the semantics are fairly clear from a "global default / dir-locals overrides" perspective. To maintain the current behavior without disruption, I would default to adding lsp-<language>-use-local-tool with default value false/nil; the semantics of adding a dir-local for use-local-tool 't seem very tidy to me. But, we'd get the same behavior (with inverted t/f semantics) from use-global-tool.

The downside here is that it introduces some complexity around tool installation. If a tool manifest already exists, it should be restored, then lsp-mode can check to see if the needed language server is available, and it can be installed/upgraded locally. If no tool manifest was present, then one must be created before installation can happen. This could all be totally do-able in code or a local tool install could revert to the user, prompting them to do their own restore/installation.

lsp-<language>-prefer-local-tool

In this implementation, a new boolean defcustom would be added with default value false/nil. If set to true, the code that builds server run commands would check first for a locally installed tool, then fall back to a global tool if present, then prompt the user to do a tool install. This is a more "auto-magic" approach. To me, this approach opens up the possibility of some surprising behavior. For example: after cloning a dotnet project locally, a dotnet tool must be "restored" before it can be used. One could cheerfully wind up using a global version of a tool, encountering strange errors if versions miss-match expectations, without realizing that the wrong server is running.

I see this option as overcoming the server installation complexity of use-local-tool by making it sensible for lsp-mode to only install servers globally. This is, strictly speaking, open to debate -- just my opening stance.

I'm happy to help

There may, of course, be other great options! These are the two I can see straight out the gate. I'm likely to get cracking with Option 1, lsp-<language>-use-local-tool, to get myself unblocked and to see how I like it. I'll put up a PR when I have one, unless y'all feel very strongly about not doing this at all, or doing it yourselves, or not doing it either of the ways I've mentioned above. Just let me know.

Thanks for the awesome project!

@yyoncho
Copy link
Member

yyoncho commented Jan 11, 2023

cc @razzmatazz

@Gastove
Copy link
Contributor Author

Gastove commented Jan 11, 2023

Ha. I've also just discovered that dotnet has a way to prefer local installations but fall back to global ones; this appears to already be in place for F#, but not C#. This still gets a little messy with global-versus-local installation and install detection, but still, it helps.

Gastove added a commit to Gastove/lsp-mode that referenced this issue Jan 13, 2024
This commit implements an approach proposed in emacs-lsp#3906. Specifically, it provides
new control variables to make it possible to use C# and F# language servers
_either_ as global _or_ local dotnet tools. So far, I've done this by providing
boolean variables to control command construction, but I'm not attached to this
approach.

Closes emacs-lsp#3906.
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 a pull request may close this issue.

2 participants