emacs-lsp / lsp-mode

Emacs client/library for the Language Server Protocol
https://emacs-lsp.github.io/lsp-mode
GNU General Public License v3.0
4.77k stars 883 forks source link

Add TCP LSP server support #921

Closed gcv closed 5 years ago

gcv commented 5 years ago

A common use case for me is to edit files locally (in Emacs), but run the resulting code on a different system (might be a Docker container, a virtual machine, or a server). This occurs because it's often much better to set up a system's runtime environment on a dedicated machine. On the other hand, editing files remotely is usually a much worse experience than locally (tramp is quite slow, and a remote editor instance can be impractical). The project directory is usually shared out to the runtime system as a virtual directory or network share, so local code changes propagate to the runtime system almost immediately.

It would be great if lsp-mode running on the local Emacs instance and editing local files could connect to a LSP server instance running on the remote server and provide its services to local files. lsp-mode would only need a host and port in order to connect to a server. A project could be configured to point to a host-port pair, and lsp-mode would automatically associate source files from that project to an LSP server running at that host-port.

I see a couple of roadblocks to implementing this given how lsp-mode currently works (in my understanding).

First, lsp-mode does not seem to have a clear concept of per-project LSP server instances. It primarily figures out which server to connect to by source file major mode. This does not map well to project isolation using tools like virtualenv, rbenv, rvm, nvm, and many other utilities used to isolate dependencies. I almost invariably want to install the LSP server specifically for the project I'm working on, but the nature of lsp-register-client prefers a global registry of LSP servers. But I can't easily predict the exact path of the LSP server executable, and keeping project-specific configuration in the global Emacs configuration is not a clean way to go. When dealing with remote LSP instances. If I'm working on two different projects, with their environments set up on hosts A and B, then I need to connect to LSP servers running on A and B independently.

Second, source files need to be mapped from their remote copies where the LSP server knows about them to their local copies. This requires a project root path translation when paths are exchanged across LSP.

Further, there's a complication in following cross-references to code which lives outside the project, in libraries. The library source will not necessarily be available locally, but it will be available remotely, on the same host as the LSP server process. Following a reference to a library definition needs to have Emacs open a remote source file when that file is not found locally. Tramp is a good solution here, though a good UX would need to be added to detect misconfiguration (mainly missing ssh configuration or ssh server on the remote host).

yyoncho commented 5 years ago

A common use case for me is to edit files locally (in Emacs), but run the resulting code on a different system (might be a Docker container, a virtual machine, or a server). This occurs because it's often much better to set up a system's runtime environment on a dedicated machine. On the other hand, editing files remotely is usually a much worse experience than locally (tramp is quite slow, and a remote editor instance can be impractical). The project directory is usually shared out to the runtime system as a virtual directory or network share, so local code changes propagate to the runtime system almost immediately.

FTR in order to make that work you will the server should have the access to the same copy of the files once you do save the buffer(we send didSave notification.) A delay will break the server because it will read the old copy of the source.

It would be great if lsp-mode running on the local Emacs instance and editing local files could connect to a LSP server instance running on the remote server and provide its services to local files. lsp-mode would only need a host and port in order to connect to a server. A project could be configured to point to a host-port pair, and lsp-mode would automatically associate source files from that project to an LSP server running at that host-port.

This is easy to do - we currently have lsp-tcp-connection which will start the server and then connect to it. We need a copy of this function which asks for host/port instead of starting a server.

First, lsp-mode does not seem to have a clear concept of per-project LSP server instances. It primarily figures out which server to connect to by source file major mode. This does not map well to project isolation using tools like virtualenv, rbenv, rvm, nvm, and many other utilities used to isolate dependencies. I almost invariably want to install the LSP server specifically for the project I'm working on, but the nature of lsp-register-client prefers a global registry of LSP servers. But I can't easily predict the exact path of the LSP server executable, and keeping project-specific configuration in the global Emacs configuration is not a clean way to go. When dealing with remote LSP instances. If I'm working on two different projects, with their environments set up on hosts A and B, then I need to connect to LSP servers running on A and B independently.

This is not true - in lsp-mode you could enable the server in a lot with by any way (check :activation-fn). Also, you could calculate the proper server command based on the directory (or whatever) by passing a function to lsp-stdio-connection instead of hardcoded string. And finally, there is lsp-enabled-clients which could be dir local in your project root which could be used to enforce the proper client registration per project.

Second, source files need to be mapped from their remote copies where the LSP server knows about them to their local copies. This requires a project root path translation when paths are exchanged across LSP.

Further, there's a complication in following cross-references to code which lives outside the project, in libraries. The library source will not necessarily be available locally, but it will be available remotely, on the same host as the LSP server process. Following a reference to a library definition needs to have Emacs open a remote source file when that file is not found locally. Tramp is a good solution here, though a good UX would need to be added to detect misconfiguration (mainly missing ssh configuration or ssh server on the remote host).

We could do that, but IMO the configuration of this setup will be bit hard for the users. I would propose alternative solution which I think will work better and it will be easier to set up.

  1. Start the remote server as we currently do when using tramp. (implementing the connect to tcp is single liner but IMO not necessary)
  2. Mount the remote FS locally using sshfs
  3. Extend lsp-client to have a function :translate-path . It could be used to prepend the local path (e. g. if you have mounted the remote fs in /home/you/remote-server.)

Let me know what do you think.

gcv commented 5 years ago

Just some preliminary thoughts on this:

yyoncho commented 5 years ago

Thank you for the feedback.

  • It would be helpful to have examples of how to configure the server with at least some of the available flexibility. Right now, it's pretty hard to understand the proper use of the lsp--client struct, especially of the new-connection field. (In fact, the use of -- in its name implies that it's not meant as an end-user interface, but then make-lsp-client is aliased to make-lsp--client.)

make-lsp-client is the only operation that is public, the rest, as you mentioned, are internal functions. I agree that we are weak on the documentation side, we primarily rely on the examples in lsp-XXX.el and in lsp-clients.el.

  • It would also be great to have examples of how to use dir local lsp-enabled-clients — I have had problems getting eval to work properly in .dir-locals.el, and a little guidance would go a long way.

We have instructions in FAQ section, please take a look and comment.

What if we try to resolve the didSave timing problem with an artificial (but configurable) delay?

This is solvable even right now - you may remove the existing on save handle and add your handler which will call save after delay.

  • "Extend lsp-client to have a function :translate-path": This sounds great.

If you think more about that this is actually the missing piece that is needed to implement your initial proposition and also the sshfs setup. Then we could have wiki or doc entry with a recipe for each setup.

gcv commented 5 years ago

We have [dir local] instructions in FAQ section, please take a look and comment.

The FAQ just says: "You may create dir-local for each of the projects and specify list of lsp-enabled-clients. This will narrow the list of the clients that are going to be tested for the project."

I would love to see an actual example that can be copy-pasted into a .dir-locals.el file which handles, e.g., Python virtualenv selection.

yyoncho commented 5 years ago

@gcv I am not python programmer and I am not familiar with how virtualenv should be handled(and python dev process in general). If you give me the details of what do you expect to happen(e. g. I open project FOO, in venv BAR then PYLS is started like that) I could create an example.

gcv commented 5 years ago

A virtualenv is as a directory which contains a separate Python installation, including binaries and supporting libraries, i.e., it contains bin, lib, and include directories. The easiest thing — and one which I think will translate nicely to isolation systems for other languages — is to make an example where the dir local setting just takes an explicit absolute path to the LSP server executable.

yyoncho commented 5 years ago

IIUC you want different pyls instance per project? To achieve that add .dir-locals with the following content in your project root:

;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")

((python-mode . ((lsp-pyls-server-command . ("/path/to/pyls")))))

Let me know if it helps to add it in the FAQ.

gcv commented 5 years ago

Definitely a helpful clarification, but how does generalize to other languages? There doesn't seem to be a lsp-solargraph-server-command, for example. Maybe the right thing is prepend something to exec-path?

yyoncho commented 5 years ago

@gcv

There doesn't seem to be a lsp-solargraph-server-command, for example. Maybe the right thing is prepend something to exec-path?

The easiest solution right now is to do a PR and add such variable. Alternatively, you could also do lsp-register-client within your config which has higher priority and has configurable server command.

About the generalization - the ideal solution is to make lsp-register-client a macro which automatically generates defcustoms(e. g. command, priority, etc). But given the limited manpower that we have, we often have to settle with a workaround.

delbonis commented 5 years ago

Copied from #942...

At work I have to develop on a macOS machine, but parts of our project can only be built (easily) on a Linux machine (C dependencies in a Rust project), so we have a Docker image for portably using build environment instead of setting up a cross-compilation environment.

So the idea I had is to give lsp-mode some awareness of docker so you could specify a Docker image containing the language servers needed to properly build the project via a .dir-locals.el in the root of the workspace, and instead of invoking (for example) rls directly, invoke it with something along the lines of docker run --interactive --rm $workspace:/mnt/ws/$(basename $workspace) $image rls. There'd have to be some work to adjust any absolute paths that the language server might return as they'd be relative to the docker container but those should be pretty straightforward to deal with.

This is not the same as running it on a remote server, as it's still running locally and talks via stdin/stdout. However it does have some related problems in that you'd have to adjust project paths between what the client knows is real and what the server thinks is real.

yyoncho commented 5 years ago

@delbonis yes, you are right but I think that once we have the ability to patch the uris we might cover both topologies.

Just for the record, what you are suggesting will work even now using lsp-mode tramp support.

I'm not sure exactly how much work this would be. Looking through the code I could see it being done in a very hackish way with only some smaller modifications, but I'm sure a better solution exists.

There is a great chance to make that work only by patching lsp--client to contain a to/from uri conversion functions + fixing the corresponding static functions lsp--uri-to-path and lsp--path-to-uri to check whether there is a single workspace which has a :translate-path function and then use it.

yyoncho commented 5 years ago

Just want to add one more thing - once we have that feature we will be able to create a docker image with all the language servers and the users will be able to run them using what you described in #942. It will be good if we implement support in dap-mode as well to provide a portable all in one dev env.

delbonis commented 5 years ago

I'd like to avoid the (albeit relatively small) extra complexity/overhead of using tramp though, given that source files really do live on my local computer, they're just being "viewed" in an unconventional way (docker).

yyoncho commented 5 years ago

@delbonis WIP PR at:

https://github.com/emacs-lsp/lsp-mode/pull/955/

I have extended lsp-mode to support mapping remote path <-> local path via extending lsp--client. In addition to that, I have created a sample configuration for docker(in the PR description). It is not really clear to me which are the parts of the docker stuff that should be parameterized(I am not very familiar with what are the normal docker workflows) and whether the way I am generating the docker command is sufficient to cover them.

I would expect the code sample in the PR to evolve and end up in lsp-mode.el or in a wiki(in case it is not generic enough). Would you take a look and comment?

yyoncho commented 5 years ago

@gcv 697d319 adds the ability to override the path. Let me know if you need something more to achieve the original request.

@delbonis I created https://github.com/emacs-lsp/lsp-docker which will host which is now part of #955 description.

gcv commented 5 years ago

@yyoncho: Thanks for continuing to look into this.

I'm confused about a few points here. First, with regard to Docker support: where is the implementation of lsp-docker-register-client from the #955 PR supposed to go? Into https://github.com/emacs-lsp/lsp-docker and with the expectation that it'll be installed as a separate Emacs package?

Second, with regard to my original request: Docker support was just one special case of connecting to a remote LSP server. Other use cases exist, including (non-container) virtual machines and physical remote servers. Furthermore, even with a Docker container, the invocation you have in #955 is a pretty limited use case of Docker. It only starts up the container just to run the language server. This is certainly possible, but when Docker is used as a development environment, it can also be a lightweight stand-in for a VM — in other words, the container might run several things along with the language server process, and this would make sense because these things all share dependencies.

This was why I requested a way for lsp-mode to connect to a remote TCP server. It would take care of the general case.

Going back to our discussion earlier on this ticket: the critical feature lsp-mode needed for it is path mapping. If I understand things correctly, this is now supported using lsp--client-uri->path-fn? And a potential TCP client implementation can use it?

By the way, I reread your message from earlier, in which you were concerned about setup complexity for a TCP client. I don't think it would be hard to set up a TCP LSP environment:

  1. The remote machine would have to have the LSP server installed and set up correctly for the project for which it is being used. This has to happen no matter where the LSP server runs, and is the case for all lsp-mode users today.
  2. The LSP server implementation has to support TCP — I suppose some do and some don't, and lsp-mode does not have to worry about those which do not.
  3. The lsp-mode client has to be set up appropriately, pointing to the relevant server and port. This is fiddly because of lsp-mode's global client registry, but the same problem exists with the Docker sample code in #955, where each project needs a separate entry in the registry. (It would be great to move away from the client registry and to make an easy way to register project-local clients using a mechanism which is easier to hook into .dir-locals.el, and I have some ideas for how to do it, but that's a disruptive change and probably out-of-scope for this ticket.)

cc @delbonis

yyoncho commented 5 years ago

I'm confused about a few points here. First, with regard to Docker support: where is the implementation of lsp-docker-register-client from the #955 PR supposed to go? Into https://github.com/emacs-lsp/lsp-docker and with the expectation that it'll be installed as a separate Emacs package?

It won't be a package intially but it will be elisp file which you could use with the corresponding image(Dockerfile will be in the repo). That file will have the configurations for each server that is present on that docker image. Later we may turn it into a package. It will shape itself once we put some work into it.

This was why I requested a way for lsp-mode to connect to a remote TCP server. It would take care of the general case.

It is not clear to me how this would work (e. g. will lsp-mode ask for host/port, or the ports will be preconfigured. etc) - In anycase, this will possible by a minor change in lsp-tcp-connection

Here it is a sample code(I haven't tested it but it is fairly trivial to change lsp-tcp-connection to ask for port/host instead of generating port + running the server).

(defun my/lsp-query-tcp-connection ()
  (list
   :connect (lambda (filter sentinel name)
              (let* ((host "localhost")
                     (port (string-to-number (read-string "Enter port: ")))
                     (tcp-proc (lsp--open-network-stream host port (concat name "::tcp"))))
                (set-process-query-on-exit-flag tcp-proc nil)
                (set-process-filter tcp-proc filter)
                (cons tcp-proc nil)))
   :test? (lambda () t)))

Going back to our discussion earlier on this ticket: the critical feature lsp-mode needed for it is path mapping. If I understand things correctly, this is now supported using lsp--client-uri->path-fn? And a potential TCP client implementation can use it?

Yes.

(It would be great to move away from the client registry and to make an easy way to register project-local clients using a mechanism which is easier to hook into .dir-locals.el, and I have some ideas for how to do it, but that's a disruptive change and probably out-of-scope for this ticket.)

We do support that by lsp-enabled-clients and lsp-disabled-clients and you could use dir locals to set them.

Please let me know if there are more missing pieces.

gcv commented 5 years ago

Yes, that looks right. Having a preconfigured host+port seems like the right approach, with a query fallback for both. Also, this client type needs to support the path-mappings mechanism.

fpoms commented 3 years ago

@gcv Did you end up with a usable solution that allows you to connect to a remote language server (e.g. running inside docker or over the network)? I have this exact use case (very common for any sort of large-scale systems web development with lots of dependencies!) and was hoping to get something like this working.

gcv commented 3 years ago

@fpoms: Take a look at https://github.com/emacs-lsp/lsp-docker/ — maybe that’ll help? I don’t know how well it works since have not used it myself. I gave up on lsp-mode shortly after this experiment in the summer of 2019.

fpoms commented 3 years ago

@gcv Did you find an alternative solution, or just gave up on LSP-like features in emacs entirely?

It seems like lsp-docker still has the issue that it isn't designed for connecting to an existing docker instance which is already setup (only supports starting its own container).

yyoncho commented 3 years ago

It seems like lsp-docker still has the issue that it isn't designed for connecting to an existing docker instance which is already setup (only supports starting its own container).

This is not true, you can pass whatever image you want to lsp-docker-init-clients

gcv commented 3 years ago

@fpoms: LSP-like features in Emacs are valuable, but I find the overall LSP experience unpleasant.

First and most important, LSP servers in various languages vary in quality, and I've run into several with terrible performance. More were disappointing at the time than not, though I'm sure the situation is improving.

Second, I don't care for the concept of installing a separate language server, because it needs to be managed. If it's installed automatically, I get nervous: where did it install itself? Does it auto-update (and likely auto-break)? If I have multiple environments and versions of a particular language environment installed, which one runs the server? What happens with multiple projects requiring multiple runtime environments? If it's not installed automatically, am I expected to manage it as part of the developer-specific package bundle of the project, wrangle paths, and so forth to make sure it runs correctly? This is all stressful and irritating, and not worth it when dumb-jump handles most of my use cases.

Third, and perhaps most importantly, I'm lucky to currently mainly work in languages which provide better IDE-like features than LSP. Common Lisp with SLIME, Clojure with CIDER, Elisp with Emacs itself, and Julia with my own Julia Snail (https://github.com/gcv/julia-snail) all have autocomplete, xref, and REPL integration (which LSP does not provide). I have also used Tide with TypeScript in the past, and it works well — it actually uses LSP under the hood, but manages it cleanly on its own, with little or nothing required from a configuration perspective.

Another point: when I tried to get Docker and LSP working two years ago, a major part of the reason was keeping environments separated. I have since found that Nix is a better choice for doing this at a developer-machine level than Docker. Much faster and no need to mess with VMs, virtual filesystems, or containers for that matter. There's a significant learning curve and the process is poorly documented, but it works well when set up. Nix does not replace Docker for all use cases, and if you have pre-configured container images you want to use, then of course it's a different story.

lukolszewski commented 2 years ago

I've read this thread few times and I'm still not sure, is there a way to connect lsp-mode to a language server over TCP now? The issue is closed with a comment about docker, but the utility for being able to connect via TCP is a lot wider than just docker. (for example being able to run different python language servers in different conda environments).

yyoncho commented 2 years ago

@lukolszewski there is lsp-tcp-connection and lsp-tcp-server to connect as a client or server.

goldfita commented 1 year ago

@lukolszewski I've made some progress on this. Using the sample above as a starting point, override make-lsp--client. I had to change sit-for to sleep-for. For some reason sit-for didn't seem to be working.

(defun tg/make-lsp--client (orig-fun &rest args)
  (when (eq 'jdtls (plist-get args :server-id))
    (plist-put args :new-connection
               (list
                :connect (lambda (filter sentinel name environment-fn _workspace)
                           (setq lsp--tcp-server-wait-seconds 50)
                           (let* (tcp-client-connection
                                  (tcp-server (make-network-process :name (format "*tcp-server-%s*" name)
                                                                    :buffer (format "*tcp-server-%s*" name)
                                                                    :family 'ipv4
                                                                    :host myhost
                                                                    :service myport
                                                                    :sentinel (lambda (proc _string)
                                                                                (lsp-log "Language server %s is connected." name)
                                                                                (setf tcp-client-connection proc))
                                                                    :server 't)))
                             (let ((retries 0))
                               (while (and (not tcp-client-connection) (< retries (* 2 lsp--tcp-server-wait-seconds)))
                                 (lsp--info "Waiting for connection for %s, retries: %s" name retries)
                                 (sleep-for 0.500)
                                 (cl-incf retries)))

                             (unless tcp-client-connection
                               (condition-case nil (delete-process tcp-server) (error))
                               (error "Failed to create connection to %s on port %s" name myport))
                             (lsp--info "Successfully connected to %s" name)

                             (set-process-query-on-exit-flag tcp-client-connection nil)
                             (set-process-query-on-exit-flag tcp-server nil)

                             (set-process-filter tcp-client-connection filter)
                             (set-process-sentinel tcp-client-connection sentinel)
                             (cons tcp-client-connection nil)))
                :test?  (lambda () t))))
  (apply orig-fun args))
(advice-add 'make-lsp--client :around #'tg/make-lsp--client)

But I get the following error. I did verify that my custom lsp-tcp-server-command is running, and the port is open on Windows. In fact, it seems to be connecting. If you don't open the port, it will block on tcp-proc.

Debugger entered--Lisp error: (void-function json-rpc-pid)
  json-rpc-pid(nil)
  #f(compiled-function (process) #<bytecode 0x6a4bbe81d0d3c76>)(nil)
  apply(#f(compiled-function (process) #<bytecode 0x6a4bbe81d0d3c76>) nil nil)
  lsp-process-id(nil)
  lsp--workspace-print(#s(lsp--workspace :ewoc nil :server-capabilities nil :registered-server-capabilities nil :root "C:/prj" :client #s(lsp--client :language-id nil :add-on? nil :new-connection (:connect (closure ((args :new-connection #3 :major-modes (java-mode jdee-mode) :server-id jdtls :multi-root t :notification-handlers #<hash-table equal 5/65 0x8d423222b9> :request-handlers #<hash-table equal 1/65 0x8d423224e9> :action-handlers #<hash-table equal 11/65 0x8d42322509> :uri-handlers #<hash-table equal 1/65 0x8d42322b75> :initialization-options #f(compiled-function () #<bytecode 0x1d1e7d1467d0a2a>) :library-folders-fn #f(compiled-function (workspace) #<bytecode -0x4605635fd1ab654>) :before-file-open-fn #f(compiled-function (workspace) #<bytecode 0x8ff5426b21b75a9>) :initialized-fn #f(compiled-function (workspace) #<bytecode 0x1e80fad16541e0e5>) :completion-in-comments\? t :download-server-fn lsp-java--ensure-server) (orig-fun . #f(compiled-function (&rest rest) "Constructor for objects of type `lsp--client'." #<bytecode 0x133c1a24696a0f86>)) t) (filter sentinel name environment-fn _workspace) (setq lsp--tcp-server-wait-seconds 50) (let* (tcp-client-connection (tcp-server ...)) (let (...) (while ... ... ... ...)) (if tcp-client-connection nil (condition-case nil ... ...) (error "Failed to create connection to %s on port %s" name 50000)) (lsp--info "Successfully connected to %s" name) (set-process-query-on-exit-flag tcp-client-connection nil) (set-process-query-on-exit-flag tcp-server nil) (set-process-filter tcp-client-connection filter) (set-process-sentinel tcp-client-connection sentinel) (cons tcp-client-connection nil))) :test\? (closure ((args :new-connection #3 :major-modes (java-mode jdee-mode) :server-id jdtls :multi-root t :notification-handlers #<hash-table equal 5/65 0x8d423222b9> :request-handlers #<hash-table equal 1/65 0x8d423224e9> :action-handlers #<hash-table equal 11/65 0x8d42322509> :uri-handlers #<hash-table equal 1/65 0x8d42322b75> :initialization-options #f(compiled-function () #<bytecode 0x1d1e7d1467d0a2a>) :library-folders-fn #f(compiled-function (workspace) #<bytecode -0x4605635fd1ab654>) :before-file-open-fn #f(compiled-function (workspace) #<bytecode 0x8ff5426b21b75a9>) :initialized-fn #f(compiled-function (workspace) #<bytecode 0x1e80fad16541e0e5>) :completion-in-comments\? t :download-server-fn lsp-java--ensure-server) (orig-fun . #f(compiled-function (&rest rest) "Constructor for objects of type `lsp--client'." #<bytecode 0x133c1a24696a0f86>)) t) nil t)) :ignore-regexps nil :ignore-messages nil :notification-handlers #<hash-table equal 5/65 0x8d423222b9> :request-handlers #<hash-table equal 1/65 0x8d423224e9> :response-handlers #<hash-table eql 0/65 0x8d42325a9d> :prefix-function nil :uri-handlers #<hash-table equal 1/65 0x8d42322b75> :action-handlers #<hash-table equal 11/65 0x8d42322509> :major-modes (java-mode jdee-mode) :activation-fn nil :priority 0 :server-id jdtls :multi-root t :initialization-options #f(compiled-function () #<bytecode 0x1d1e7d1467d0a2a>) :semantic-tokens-faces-overrides nil :custom-capabilities nil :library-folders-fn #f(compiled-function (workspace) #<bytecode -0x4605635fd1ab654>) :before-file-open-fn #f(compiled-function (workspace) #<bytecode 0x8ff5426b21b75a9>) :initialized-fn #f(compiled-function (workspace) #<bytecode 0x1e80fad16541e0e5>) :remote? nil :completion-in-comments? t :path->uri-fn nil :uri->path-fn nil :environment-fn nil :after-open-fn nil :async-request-handlers #<hash-table equal 0/65 0x8d42325ccd> :download-server-fn lsp-java--ensure-server :download-in-progress? nil :buffers nil :synchronize-sections nil) :host-root nil :proc #<process *tcp-server-jdtls* <192.168.131.5:58230>> :cmd-proc nil :buffers (#<buffer DXService.java>) :semantic-tokens-faces nil :semantic-tokens-modifier-faces nil :extra-client-capabilities nil :status starting :metadata #<hash-table equal 0/65 0x8d422d27a1> :watches #<hash-table equal 0/65 0x8d422d27c1> :workspace-folders nil :last-id 0 :status-string nil :shutdown-action nil :diagnostics #<hash-table equal 0/65 0x8d422d27e1> :work-done-tokens #<hash-table equal 0/65 0x8d41e3543d>))
...

You also need to run the language server. I don't know if this is actually correct since I haven't gotten things quite working on the Emacs side.

export JAVA_HOME=/home/user/jdk-17.0.2
export PATH=$JAVA_HOME/bin:$PATH
export CLIENT_PORT=my-port
export CLIENT_HOST=my-host

java \
        -Declipse.application=org.eclipse.jdt.ls.core.id1 \
        -Dosgi.bundles.defaultStartLevel=4 \
        -Declipse.product=org.eclipse.jdt.ls.core.product \
        -Dlog.level=ALL \
        -noverify \
        -Xmx1G \
        --add-modules=ALL-SYSTEM \
        --add-opens java.base/java.util=ALL-UNNAMED \
        --add-opens java.base/java.lang=ALL-UNNAMED \
        -jar ./eclipse.jdt.ls/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar  \
        -configuration ./eclipse.jdt.ls/config_linux \
        -data /home/user/data
goldfita commented 11 months ago

@yyoncho I was able to get this working reliably with the language server running on Windows and Emacs running in WSL. But I have to go in and manually edit lsp--client-new-connection after load. I could duplicate the registrations, but some of them are quite long, and I really only want to change the connection code. What do you think about adding an option for the connection?

yyoncho commented 11 months ago

I could duplicate the registrations, but some of them are quite long, and I really only want to change the connection code.

What about adding advice over lsp-register-client copying the client and changing the connection function? (you may check how we do it for tramp clients: https://github.com/yyoncho/lsp-mode/blob/byte-compilation-tests/lsp-mode.el#L8436 )

goldfita commented 11 months ago

OK, thanks. Register is public at least, so it's better than what I have currently.