vshaxe/vshaxe

Standalone usage of haxe-languageserver (Sublime Text, Neovim...)

wighawag opened this issue ยท 32 comments

You might be already aware of it and this might be out of scope but there is a language server protocol (LSP) plugin for sublime : https://github.com/tomv564/LSP

Sorry if it is the wrong repo to post about sublime but I wondered how easy would it be to reuse what you did here and hook it up with sublime.

For example according to sublime LSP plugin doc here : https://lsp.readthedocs.io/en/latest/ you need to specify the path to the LSP executable. Is it what https://github.com/vshaxe/haxe-languageserver produce ?

Yes, haxe-languageserver compiles to the node.js (currently) stdio LSP server, so it should be usable already, but it might require some specific configuration (haxe executable path, etc). I don't have much time and motivation to support sublime plugin, but I'll be happy to help with haxe-ls integration to whoever would work on it.

I came here looking for roughly the same info for neovim, which also integrates LSP in a low-touch way.
All we need really is documentation on haxe-languageserver specifically, as an external tool.
This would include:

  • how to install it (haxelib? npm?)
  • what CLI options/transports it supports, if any
  • what settings it supports from didChangeConfiguration, if any

I see, so now when there's clearly some demand for this. We have to do some organizational work wrt making haxe-ls a standalone tool as it was originally designed :)

kLabz commented

I use haxe-languageserver with neovim but it doesn't play too well with it (I may have messed up somewhere). I would love some documentation :)

My main issue was that I ended up with a lot of haxe-languageserver zombie processes.. But I had to go back to vscode recently (c# + haxe project) and I have the same issue with vscode so it's probably not related to neovim at all.

Hm, no idea what that would cause that issue, but that's quite cool to hear nevertheless! Could you share what you needed to do to set it up?

kLabz commented

Actually it's not bad at all, I commented out most of my hacks overtime and now the needed setup seems sane. I think this is what saved me in the end :)

init.vim

" Language Server Protocol (LSP) support for vim and neovim.
Plug 'autozimu/LanguageClient-neovim', {'do': ':UpdateRemotePlugins'}

" Language server protocol config
let g:LanguageClient_serverCommands = {
	\ 'haxe': ['node', '/git/haxe-languageserver/bin/server.js'],
	\ }
let g:LanguageClient_autoStart = 1
let g:LanguageClient_loadSettings = 1

" Language Server Protocol
nnoremap <silent> K :call LanguageClient_textDocument_hover()<CR>
nnoremap <silent> gd :call LanguageClient_textDocument_definition()<CR>
nnoremap <silent> <F2> :call LanguageClient_textDocument_rename()<CR> 

.vim/settings.json (no context switching between build and test.hxml here)

{
    "initializationOptions": {
        "displayServerConfig": {
            "env": {},
            "path": "/usr/bin/haxe",
            "arguments": []
        },
        "displayArguments": [
            "build.hxml"
        ]
    },
    "haxe": {
        "enableDiagnostics": false,
        "diagnosticsPathFilter": "",
        "enableCodeLens": false,
        "displayPort": null,
        "buildCompletionCache": false,
        "codeGeneration": {},
        "format": {}
    }
}

I added a build.hxml in haxe-languageserver:

-lib haxe-hxparser
-lib hxnodejs

-cp src
-cp protocol/src
-cp formatter/src

-D hxnodejs-no-version-warning
-D JSTACK_FORMAT=vscode

-js bin/server.js
-dce full
-debug
-main haxeLanguageServer.Main

And I added a nullcheck around options in Context.onInitialize(). The other checks/hacks have been removed and it still works, I'm not even sure this one is still necessary:

if (options != null) {
    displayServerConfig = options.displayServerConfig;
    displayArguments = options.displayArguments;
}

Thanks for sharing! Setting things up took a bit of trial-and-error, since I'm not familiar with Vim or NeoVim at all. I had to make some adjustments to your init.vim:

  • The Plug command needs to be surrounded by call plug#begin('plugged') and call plug#end()
  • I needed to associate .hx files with haxe (presumably you already have some Haxe plugin installed that does this?)
    au BufNewFile,BufRead *.hx setlocal ft=haxe

I also made a small patch for haxe-languageserver: vshaxe/haxe-language-server@95dfaa35fb20b466c70fb9387e77a82e0e5557fa- seems like LanguageClient-neovim doesn't like the use of workspaceFolders, which was a recent change. :)

Btw, you shouldn't need that build.hxml, you can build the language server as usual with vshaxe-build. We should probably make sure not to build it to ../bin/server.js so it works more smoothly when you've checked out the language server standalone.

That all being said, I still didn't get completion features to work so far, just getting messages like 'Unhandled method textDocument/definition' in the LanguageClient.log file so far. Have you had any luck getting any log output from the server (what you would normally see in the Haxe output channel in VSCode)? It seems it's supposed to go into LanguageServer.log, but that's just empty for me. I've tried a few different options like LanguageClient_loggingLevel and LanguageClient_windowLogMessageLevel. I guess I can work around it by writing logs to a file...

Also, it looks like Plug 'autozimu/LanguageClient-neovim', {'do': ':UpdateRemotePlugins'} installs the "legacy" version of LanguageClient-neovim - I wonder if that even has the settings.json support you linked to?

kLabz commented

Sorry the vim config was minimal and a lot of needed things were stripped off. I'll try to find some time to make a minimal working vim config with all other needed plugins/configs.

The Unhandled method textDocument/definition issue was what I had until I got the lsp to use my settings.json, btw. I don't remember where/how I got the logs, but I remember that I managed to get them after a while.

kLabz commented

Err, the haxe 4 dependency is really painful. Will do that later, if I find some time. Shouldn't need much more anyway, the legacy version works fine for me with these:

Plug 'roxma/nvim-completion-manager'
Plug 'jdonaldson/vaxe'

Will try the new versions of both LanguageClient-neovim and vshaxe-languageserver later to see if I see some improvements :) (and if they work together)

(as for the build.hxml, I made it because installing and using an undocumented build tool for something that could be handled by a hxml file wasn't something I wanted to do)

kLabz commented

I can't make it work with current master branch of vshaxe-languageserver, but it works with vshaxe/haxe-language-server@e119a7e

I'll try to find when things got wrong for LanguageClient-neovim support =/

kLabz commented

Got it to work (still on legacy LanguageClient-neovim) by reverting two commits:

I'll see what I can do with the latest LanguageClient-neovim version.

kLabz commented

Have you had any luck getting any log output from the server (what you would normally see in the Haxe output channel in VSCode)?

Yep.
LanguageClient-neovim logs will be in LanguageClient.log, and server stderr (so use console.error()) will be in LanguageServer.log.

I don't get what's wrong between LanguageClient-neovim next and current haxe-languageserver..

I see these in logs during initialization:

{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
        "capabilities": {
            "textDocument": {
                "completion": {
                    "completionItem": {
                        "commitCharactersSupport": null,
                        "documentationFormat": null,
                        "snippetSupport": false
                    },
                    "dynamicRegistration": null
                }
            }
        },
        "initializationOptions": {
            "displayArguments": [
                "build.hxml"
            ],
            "displayServerConfig": {
                "arguments": [],
                "env": {},
                "path": "/usr/bin/haxe"
            }
        },
        "processId": 9321,
        "rootPath": "/git/haxe-languageserver",
        "rootUri": "file:///git/haxe-languageserver",
        "trace": "off"
    },
    "id": 9
}
{
    "jsonrpc": "2.0",
    "id": 9,
    "result": {
        "capabilities": {
            "textDocumentSync": 2,
            "completionProvider": {
                "triggerCharacters": [
                    ".",
                    "@",
                    ":"
                ]
            },
            "signatureHelpProvider": {
                "triggerCharacters": [
                    "(",
                    ","
                ]
            },
            "definitionProvider": true,
            "hoverProvider": true,
            "referencesProvider": true,
            "documentSymbolProvider": true,
            "workspaceSymbolProvider": true,
            "codeActionProvider": true,
            "documentFormattingProvider": true,
            "codeLensProvider": {
                "resolveProvider": true
            },
            "renameProvider": true
        }
    }
}

And then when triggering autocompletion:

{
    "jsonrpc": "2.0",
    "method": "textDocument/completion",
    "params": {
        "position": {
            "character": 6,
            "line": 4
        },
        "textDocument": {
            "uri": "file:///git/haxe-languageserver/src/haxeLanguageServer/ApplyFixesCommand.hx"
        }
    },
    "id": 21
}
{
    "jsonrpc": "2.0",
    "id": 21,
    "error": {
        "code": -32601,
        "message": "Unhandled method textDocument/completion"
    }
}

and server stderr (so use console.error()) will be in LanguageServer.log

Oh, interesting. So I guess there's no support for thet LogMessage method that it uses by default for the output channel in VSCode?

Thanks for figuring out the problematic commits, I'll check if downgrading makes it work for me and if I can fix it so it works for both Neovim and VSCode. :)

kLabz commented

Ok, got "next" LanguageClient-neovim to work with haxe-languageserver, by doing two things:

  • revert vshaxe/haxe-language-server@0a0ef50 (this one is an issue with both legacy and next LanguageClient-neovim)
  • manually send onDidChangeConfiguration, seems like an issue to solve on the LanguageClient-neovim side

I used a custom function to send onDidChangeConfiguration:

function! DidChangeConfig()
	let config = json_decode(system("cat .vim/settings.json"))
	call LanguageClient#Notify('workspace/didChangeConfiguration', { 'settings': config })
endfunction

I'll see what can be done to send this automatically.

As for LogMessage, I have no idea =/

Regarding the "next" branch of LanguageClient-neovim - I guess you need to have Rust installed and build it from source? Or are there pre-built binaries anywhere?

kLabz commented

I added Plug 'autozimu/LanguageClient-neovim', {'branch': 'next', 'do': 'bash install.sh'} instead of the old one in my init.vim.

I also did :UpdateRemotePlugins after the :PlugInstall, not sure if it was useful.

Oh, I guess for Windows it needs to be install.ps1 instead: https://github.com/autozimu/LanguageClient-neovim/blob/next/install.ps1

I made a few changes to check what features the language client actually supports (before we would just use new features without checking, which caused some of the issues you mentioned). I also added some default settings, so haxe-languageserver is now fine with getting nothing / null as its settings.

Anyway, I got completion / hover / document symbols working!

Still some issues with rename and goto defintion it appears.

kLabz commented

Nice! Thanks for your efforts on making haxe-languageserver work on non-vscode editors ๐Ÿ‘

Still having some troubles though, as I want to be able to switch hxml files.

onDidChangeDisplayArguments and onDidChangeDisplayServerConfig don't seem to be available outside vscode, so onDidChangeConfiguration seemed to me to be a candidate to do that by handling initializationOptions if provided. Of course, if you have a better way to do that I'd be happy to change my nvim config to handle it.

The patch I am using:

diff --git a/src/haxeLanguageServer/Context.hx b/src/haxeLanguageServer/Context.hx
index d42fa89..cf2b31f 100644
--- a/src/haxeLanguageServer/Context.hx
+++ b/src/haxeLanguageServer/Context.hx
@@ -166,6 +166,19 @@ class Context {
 
         var newConfigJson = Json.stringify(newHaxeConfig);
         var configUnchanged = Json.stringify(unmodifiedConfig) == newConfigJson;
+
+        var initOptions = newConfig.settings.initializationOptions;
+        if (initOptions != null) {
+            if (initOptions.displayServerConfig != null) {
+                displayServerConfig = initOptions.displayServerConfig;
+                configUnchanged = false;
+            }
+            if (initOptions.displayArguments != null) {
+                displayArguments = initOptions.displayArguments;
+                configUnchanged = false;
+            }
+        }
+
         if (!firstInit && configUnchanged) {
             return;
         }

The relevant nvim config:

init.vim

" [...]
let g:LanguageClient_serverCommands = {
	\ 'haxe': ['node', '/git/haxe-languageserver/bin/server.js']
	\ }
let g:LanguageClient_autoStart = 1
let g:LanguageClient_loadSettings = 0
" let g:LanguageClient_loggingLevel = 'DEBUG'

" Auto config Haxe LSP
augroup LanguageClient_config
    autocmd!
    autocmd User LanguageClientStarted call k#haxelsp#SetConfig("build.hxml")
augroup END
" [...]

~/.config/nvim/autoload/k/haxelsp.vim

function! k#haxelsp#SetConfig(hxml)
	if &ft=="haxe"
		let haxeConfig = json_decode(system("cat ~/.config/nvim/haxelsp/haxe-settings.json"))
		let serverConfig = json_decode(system("cat ~/.config/nvim/haxelsp/server-config.json"))

		call LanguageClient#Notify('workspace/didChangeConfiguration', {'settings': {
			\ 'haxe': haxeConfig,
			\ 'initializationOptions': {
				\ 'displayServerConfig': serverConfig,
				\ 'displayArguments': [a:hxml]
			\ }
		\ }})
	endif
endfunction

~/.config/nvim/haxelsp/haxe-settings.json

{
	"enableDiagnostics": true,
	"diagnosticsPathFilter": "",
	"enableCodeLens": true,
	"displayPort": "auto",
	"buildCompletionCache": true,
	"codeGeneration": {},
	"format": {}
}

~/.config/nvim/haxelsp/server-config.json

{
	"env": {},
	"path": "/usr/bin/haxe",
	"arguments": []
}

Still some issues with rename and goto defintion it appears.

Works for me with this config :)

onDidChangeDisplayArguments and onDidChangeDisplayServerConfig don't seem to be available outside vscode

They're custom methods not defined in LSP, yes, but it should still be possible to call them I think? Your call to LanguageClient#Notify() there just seems to take an arbitrary string after all. The methods are defined here:

https://github.com/vshaxe/haxe-languageserver/blob/master/src/haxeLanguageServer/VshaxeMethods.hx

("vshaxe/didChangeDisplayArguments" and "vshaxe/didChangeDisplayServerConfig")

Still some issues with rename and goto defintion it appears.

Works for me with this config :)

Interesting, maybe some issue with LanguageClient-neovim legacy then. I should try the next branch.

kLabz commented

Indeed I can call them, I just had an issue with an early restartServer, so I modified the functions:

    function onDidChangeDisplayArguments(params:{arguments:Array<String>}) {
        displayArguments = params.arguments;
        if (config != null) restartServer("display arguments changed");
    }

    function onDidChangeDisplayServerConfig(config:DisplayServerConfig) {
        displayServerConfig = config;
        if (config != null) restartServer("display server configuration changed");
    }

Is this reasonable? I removed the previous patch and call these instead now.

Ah, yeah, I see the issue. Can't hurt to check for that. :)

I think I'm also going to rename the vshaxe methods to haxe, since the former doesn't make so much sense in a standalone context.

I'm not sure what the final UX should look like.. I guess some aspects of vshaxe's HXML-file auto-discovery could be moved into the server, so onDidChangeDisplayArguments() calls can be optional (at least in the case of there only being a single build.hxml in the root directory). But that requires to move HXML parsing etc too.. would need some thought.

@jdonaldson has expressed interest in a vaxe fork using haxe-languageserver, presumably that plugin would want to interface with onDidChangeDisplayArguments in some way too.

kLabz commented

Looks good to me, thanks!

Indeed an integration within vaxe would be perfect. I am using some vimscript I created today, but nothing publishable and I have no experience in developing nvim plugins.

kLabz commented

Works for sublime text too:

// Settings in here override those in "LSP/LSP.sublime-settings",
{
	"clients": {
		"haxe": {
			"command": ["node", "/git/haxe-languageserver/bin/server.js"],
			"scopes": ["source.haxe"],
			"syntaxes": ["Packages/Haxe/Haxe.sublime-syntax"],
			"languageId": "haxe",
			"initializationOptions": {
				"displayServerConfig": {
					"env": {},
					"path": "/usr/bin/haxe",
					"arguments": []
				},
				"displayArguments": ["build.hxml"],
			},
			"settings": {
				"haxe": {
					"enableDiagnostics": true,
					"diagnosticsPathFilter": "",
					"enableCodeLens": true,
					"displayPort": "auto",
					"buildCompletionCache": true,
					"codeGeneration": {},
					"format": {}
				}
			}
		}
	}
}

That's great to hear. :) I don't think that "haxe" settings section should be needed anymore?

kLabz commented

Yep, works fine without the settings block too (just confirmed it), but I added it to show how to set these settings.

I just spent some time messing around with the plugins. I can get the context server to start, but none of the config is sent to it successfully there are some other issues.

      let haxeConfig = json_decode(system("cat ~/haxe-settings.json"))
      let serverConfig = json_decode(system("cat ~/server-config.json"))

      echomsg string(haxeConfig)
      echomsg string(serverConfig)

      call LanguageClient#Notify('workspace/didChangeConfiguration', {
            \'settings': {
              \'haxe': haxeConfig,
              \'initializationOptions': {
                \'displayServerConfig': serverConfig,
                \'displayArguments': [a:hxml]
              \ }
            \}
            \})

Sanity checking the changeConfiguration object shows that everything is where it should be according to the notes here.

Some more initial notes...

  • It looks like the haxe language server is "pinged" immediately when g:LanguageClient_serverCommands are set. This is done before any of the config parameters are set, and the config options are an empty object.
  • There's a null check for options inside of the haxe server's start method. This null check will populate the config with standard options if the option object is null. However the options are not null (empty object), so the language server treats them as present, and fails subsequent configuration lookups.

I'll put up a pull request over here with some more discussion : vshaxe/haxe-language-server#35

In the end where is the right thing to do?

Here's the most up-to-date setup with Neovim: #328 (comment)

thanks

Closing this in favor of the more up-to-date coc.nvim "guide" in #328. Eventually we should probably document this properly in a wiki page.