PerlNavigator
PerlNavigator copied to clipboard
Instructions for use in Emacs Eglot
The upcoming release of Emacs 29 will have eglot as a built-in LSP client. It would be nice to have instructions on how to use the Navigator, and do some basic testing to ensure the key features work.
Getting the Perl Navigator added as a default language server option would be great too: https://github.com/joaotavora/eglot/blob/e501275e06952889056268dabe08ccd0dbaf23e5/eglot.el#L235
I use PerlNavigator with eglot occasionally with this invocation in my Emacs configuration (I'm using cperl-mode, for which eglot has no default language server configured):
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
`(cperl-mode . ("node" "/home/haj/devel/perl/PerlNavigator/server/out/server.js" "--stdio"))))
I have installed PerlNavigator from source (as described here), and added the relevant path as documented in the eglot manual. The path to the navigator is installation-specific, therefore this might be useful for documentation, but it is not suitable for inclusion in eglot. As for basic testing: Is there a list of tests which should be done? Per manual testing I verified that finding a function's definition and symbol completion for functions work. I could not run a test for the perlimports integration: This seems to be disabled per default, and eglot does not allow server-specific configuration items.
For comparison: LSP-mode, another LSP client for Emacs, does allow server-specific configuration, but the perlimports variables have not yet been added to its PerlNavigator support. LSP-mode works around installation specific paths by offering to download and install the server from github (the release file, not the sources) into a known directory of its own.
Thanks @HaraldJoerg! Other than building from source, npm -g perlnavigator-server
also installs the Navigator, but does not include an executable. If I were to include an executable as part of this, npm would automatically add it to the path and perhaps that could be suitable for inclusion in eglot? There are also binaries, although it may take some work to get them listed in the various package managers and automatically updated with each github release.
Sounds like many of the features work already, which is great. There's not a specific list anywhere, but the primary features are syntax checking, perlcritic, perltidy, perlimports, go-to-definition, auto-completion, hover, and outline view.
In terms of configuration, that's unfortunate that eglot does not allow for configuration. Although I could allow configuration via a file somewhere (maybe ~/.perlnavigatorrc
or similar). The perlcritic integration is similar in that it checks a variety of places looking for the config; first the lsp specified profile, then the PERLCRITIC
environment variable, then ~/.perlcriticrc
and then the Navigator's default critic profile.
Thanks again @HaraldJoerg ! Using your config as a starting point, I was able to get up and running with eglot in emacs. I was able to get a some specific configuration variables set, but I couldn't figure out how to set list variables (e.g. include paths). Pretty good start though. I also fixed a couple minor issues along the way: one startup warning, and an issue with symbol boundaries in goto definition. Essentially, if the cursor is at the beginning of a symbol (e.g. on the sigil of a variable), the navigator did not recognize it as being over the variable.
Leaving this open for now, as I think adding a bin
file in the npm install and then including the default config in emacs core would be great.
Some more details about Eglot: Unlike lsp-mode, most of its configuration is not available through the "customize" interface of Emacs. This makes it somewhat clumsy and ... dangerous. So, the example config with setq-default
overwrites any configuration which a user might have for other programming languages. Eglot recommends that the variable is set as a directory-local variable, but probably this is out of scope for the PerlNavigator documentation. Fiddling with individual elements within an Emacs lisp variable can be tricky.
Regarding the installation of a callable program somewhere on the path: This would, of course, be a prerequisite for getting it into the Emacs sources. Also, the Emacs folks care about compatibility. So, instead of overwriting the current configuration for perl-mode
it might be better to add PerlNavigator as an alternative to eglot-server-programs
. As of recently (not in Emacs 29), Perl::LanguageServer
has also been added for cperl-mode
.
In case of alternatives, if more than one of them happens to be installed: eglot uses the name of the executable to prompt the user for a selection, which emphasizes the need for a dedicated executable (or script): In my setup (as described above), the alternatives are offered as "perl" (for Perl::LanguageServer) and "node" (for PerlNavigator) - slightly irritating, or even misleading, since the availability of "perl" or "node" does not always bring the corresponding language servers.
To get it included into Emacs, I suggest to open an Emacs bug when the starter program "on the path" is available. I can help with that and also suggest a patch. In any case, we should collect feedback from João Távora, who is the main developer and maintainer of Eglot, and from the contributors which did the recent changes to advertise Perl::LanguageServer
.
I can manage to get the language server running and it does show some warnings and expected and so on.
However, it is unusable because I get this error message every second or so:
[eglot] (warning) Server tried to register unsupported capability `workspace/didChangeConfiguration'
Is there some way to get the server to stop doing this thing that Eglot doesn't like, and if so does someone have some Eglot config to do it?
Update: tried again today and have the same problem, but realized the warning above is probably unrelated.
I did
(setq eglot-autoreconnect nil)
to prevent Eglot from trying to restart the server over and over and flood the log, and now the log looks like this:
[internal] Thu Jul 25 16:57:58 2024:
(:message "Running language server: podman run --rm --interactive --volume=/tmp/test/:/tmp/test/:z --workdir=/tmp/test/ localhost/perl-navigator:latest node /opt/PerlNavigator/server/out/server.js --stdio")
[client-request] (id:1) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
(:processId 5568 :rootPath "/tmp/test/" :rootUri "file:///tmp/test" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
())
:capabilities
(:workspace
(:applyEdit t :executeCommand
(:dynamicRegistration :json-false)
:workspaceEdit
(:documentChanges t)
:didChangeWatchedFiles
(:dynamicRegistration t)
:symbol
(:dynamicRegistration :json-false)
:configuration t :workspaceFolders t)
:textDocument
(:synchronization
(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
:completion
(:dynamicRegistration :json-false :completionItem
(:snippetSupport :json-false :deprecatedSupport t :resolveSupport
(:properties
["documentation" "details" "additionalTextEdits"])
:tagSupport
(:valueSet
[1]))
:contextSupport t)
:hover
(:dynamicRegistration :json-false :contentFormat
["plaintext"])
:signatureHelp
(:dynamicRegistration :json-false :signatureInformation
(:parameterInformation
(:labelOffsetSupport t)
:activeParameterSupport t))
:references
(:dynamicRegistration :json-false)
:definition
(:dynamicRegistration :json-false :linkSupport t)
:declaration
(:dynamicRegistration :json-false :linkSupport t)
:implementation
(:dynamicRegistration :json-false :linkSupport t)
:typeDefinition
(:dynamicRegistration :json-false :linkSupport t)
:documentSymbol
(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
(:valueSet
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
:documentHighlight
(:dynamicRegistration :json-false)
:codeAction
(:dynamicRegistration :json-false :codeActionLiteralSupport
(:codeActionKind
(:valueSet
["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
:isPreferredSupport t)
:formatting
(:dynamicRegistration :json-false)
:rangeFormatting
(:dynamicRegistration :json-false)
:rename
(:dynamicRegistration :json-false)
:inlayHint
(:dynamicRegistration :json-false)
:publishDiagnostics
(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
(:valueSet
[1 2])))
:window
(:workDoneProgress t)
:general
(:positionEncodings
["utf-32" "utf-8" "utf-16"])
:experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
:workspaceFolders
[(:uri "file:///tmp/test" :name "/tmp/test/")]))
[server-reply] (id:1) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 1 :result
(:capabilities
(:textDocumentSync 2 :completionProvider
(:resolveProvider t :triggerCharacters
["$" "@" "%" "-" ">" ":"])
:definitionProvider t :documentSymbolProvider t :workspaceSymbolProvider t :hoverProvider t :documentFormattingProvider t :documentRangeFormattingProvider t :signatureHelpProvider
(:triggerCharacters
["(" "," ")"])
:workspace
(:workspaceFolders
(:supported t)))))
[client-notification] Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
[client-notification] Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
(:textDocument
(:uri "file:///tmp/test/test.pl" :version 0 :languageId "cperl" :text "\n")))
[client-notification] Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
(:settings
(:perlnavigator
(:perlPath "/opt/perl5/bin/perl"))))
[server-request] (id:0) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 0 :method "client/registerCapability" :params
(:registrations
[(:id "3b1e62f7-26ea-466a-a612-661c16cb739c" :method "workspace/didChangeConfiguration" :registerOptions nil)]))
[client-reply] (id:0) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 0 :result nil)
[server-request] (id:1) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 1 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///tmp/test/test.pl" :section "perlnavigator")]))
[client-reply] (id:1) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 1 :result
[(:perlPath "/opt/perl5/bin/perl")])
[server-request] (id:2) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 2 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///tmp/test/test.pl" :section "perlnavigator")]))
[client-reply] (id:2) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 2 :result
[(:perlPath "/opt/perl5/bin/perl")])
[server-request] (id:3) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 3 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///tmp/test/test.pl" :section "perlnavigator")]))
[client-reply] (id:3) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 3 :result
[(:perlPath "/opt/perl5/bin/perl")])
[server-request] (id:4) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 4 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///tmp/test/test.pl" :section "perlnavigator")]))
[client-reply] (id:4) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 4 :result
[(:perlPath "/opt/perl5/bin/perl")])
[stderr] Found settings
[stderr] Found settings
[server-request] (id:5) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 5 :method "workspace/workspaceFolders")
[client-reply] (id:5) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 5 :result
[(:uri "file:///tmp/test" :name "/tmp/test/")])
[server-request] (id:6) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 6 :method "workspace/workspaceFolders")
[client-reply] (id:6) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 6 :result
[(:uri "file:///tmp/test" :name "/tmp/test/")])
[server-request] (id:7) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 7 :method "workspace/workspaceFolders")
[client-reply] (id:7) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 7 :result
[(:uri "file:///tmp/test" :name "/tmp/test/")])
[server-request] (id:8) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 8 :method "workspace/workspaceFolders")
[client-reply] (id:8) Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :id 8 :result
[(:uri "file:///tmp/test" :name "/tmp/test/")])
[stderr] Now starting perlcritic with: /opt/PerlNavigator/server/src/perl/criticWrapper.pl --file /tmp/test/test.pl
[stderr] Starting perl compilation check with the equivalent of: /opt/perl5/bin/perl -c -Mwarnings -M-warnings=redefine -I /tmp/test/lib -I /opt/PerlNavigator/server/src/perl -MInquisitor /tmp/test/test.pl
[stderr] Starting to look for perl modules with -I /tmp/test/lib /opt/PerlNavigator/server/src/perl/lib_bs22/ModHunter.pl
[stderr] Starting to look for perl modules with -I /tmp/test/lib /opt/PerlNavigator/server/src/perl/lib_bs22/ModHunter.pl
[stderr] Now starting perlcritic with: /opt/PerlNavigator/server/src/perl/criticWrapper.pl --file /tmp/test/test.pl
[stderr] Starting perl compilation check with the equivalent of: /opt/perl5/bin/perl -c -Mwarnings -M-warnings=redefine -I /tmp/test/lib -I /opt/PerlNavigator/server/src/perl -MInquisitor /tmp/test/test.pl
[stderr] Compilation Time: 0.042 seconds
[server-notification] Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
(:uri "file:///tmp/test/test.pl" :diagnostics
[]))
[stderr] Compilation Time: 0.06 seconds
[server-notification] Thu Jul 25 16:57:58 2024:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
(:uri "file:///tmp/test/test.pl" :diagnostics
[]))
[stderr] Success running mod hunter
[stderr] Success running mod hunter
[stderr] Critic output: Perlcritic on /tmp/test/test.pl and using profile /opt/PerlNavigator/server/src/perl/defaultCriticProfile
[stderr] Perl Critic violations:
[stderr]
[stderr] Perl Critic Time: 0.515 seconds
[stderr] Critic output: Perlcritic on /tmp/test/test.pl and using profile /opt/PerlNavigator/server/src/perl/defaultCriticProfile
[stderr] Perl Critic violations:
[stderr]
[stderr] Perl Critic Time: 0.516 seconds
[server-notification] Thu Jul 25 16:57:59 2024:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
(:uri "file:///tmp/test/test.pl" :diagnostics
[]))
[server-notification] Thu Jul 25 16:57:59 2024:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
(:uri "file:///tmp/test/test.pl" :diagnostics
[]))
[stderr] Found settings
[server-request] (id:9) Thu Jul 25 16:57:59 2024:
(:jsonrpc "2.0" :id 9 :method "workspace/workspaceFolders")
[client-reply] (id:9) Thu Jul 25 16:57:59 2024:
(:jsonrpc "2.0" :id 9 :result
[(:uri "file:///tmp/test" :name "/tmp/test/")])
[stderr] Now starting perlcritic with: /opt/PerlNavigator/server/src/perl/criticWrapper.pl --file /tmp/test/test.pl
[stderr] Starting perl compilation check with the equivalent of: /opt/perl5/bin/perl -c -Mwarnings -M-warnings=redefine -I /tmp/test/lib -I /opt/PerlNavigator/server/src/perl -MInquisitor /tmp/test/test.pl
[stderr] Compilation Time: 0.035 seconds
[server-notification] Thu Jul 25 16:57:59 2024:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
(:uri "file:///tmp/test/test.pl" :diagnostics
[]))
[stderr] Critic output: Perlcritic on /tmp/test/test.pl and using profile /opt/PerlNavigator/server/src/perl/defaultCriticProfile
[stderr] Perl Critic violations:
[stderr]
[stderr] Perl Critic Time: 0.345 seconds
[server-notification] Thu Jul 25 16:58:00 2024:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
(:uri "file:///tmp/test/test.pl" :diagnostics
[]))
[internal] Thu Jul 25 16:58:01 2024:
(:message "Connection state changed" :change "exited abnormally with code 1\n")
----------b---y---e---b---y---e----------
[stderr]
[stderr]
[stderr] nil
[stderr] nil
[stderr] Process EGLOT (test/(cperl-mode perl-mode)) stderr finished
Any pointers on how we might find out why the server exists would be nice.