Trying to get Pascal intelligent completion in Emacs (and VS Code) using LSP.
LSP is cross-editor protocol, known from VS Code but useful in other editors alike, see https://microsoft.github.io/language-server-protocol/ .
-
Install lsp-pascal,
lsp-mode,companyEmacs packages. -
Install LSP server from https://github.com/castle-engine/pascal-language-server . It is easiest to just download it with Castle Game Engine, find the
paslsinbinsubdirectory of CGE. -
Configure it like this in your
~/.emacs:
(require 'lsp-pascal)
;; choose LSP server binary
(setq lsp-pascal-command "/home/michalis/cge/bin/pasls")
;; pass basic info to LSP server, all LSP Pascal servers above support these:
(setq lsp-pascal-fpcdir "/home/michalis/cge/tools/contrib/fpc/src")
(setq lsp-pascal-lazarusdir "/home/michalis/lazarus")
(setq lsp-pascal-pp "/home/michalis/cge/tools/contrib/fpc/bin/fpc-cge")
(setq lsp-pascal-fpctarget "linux")
(setq lsp-pascal-fpctargetcpu "x86_64")
https://github.com/arjanadriaanse/pascal-language-server started it all, but after 4 commits in 2020 it seems unmaintained.
I found 2 maintained forks of it that I got to work. (I did create 2 forks of them in turn, through I also contributed back some commits to original authors.)
Michalis fork: https://github.com/michaliskambi/pascal-language-server-genericptr
Installation:
sudo apt install libsqlite3-dev
git clone https://github.com/michaliskambi/pascal-language-server-genericptr
cd pascal-language-server-genericptr
lazbuild pasls.lpi
create $HOME/.config/pasls/castle-pasls.ini following https://github.com/michaliskambi/pascal-language-server-genericptr docs
Notes:
-
Tested in both VS Code and Emacs.
-
DONE: initially failed to work, VS Code reports (when opening any Pascal file):
[Error - 02:25:51] Server initialization failed. Message: TFPCUnitToSrcCache.GetConfigCache missing CompilerFilename Code: -32603 [Error - 02:25:51] Starting client failed Message: TFPCUnitToSrcCache.GetConfigCache missing CompilerFilename Code: -32603From Emacs lsp-pascal, failed with the same message.
-
(NICE): It exposes extra FPC options as LSP initialization options, which allows me to add CGE paths from VS Code.
This also means I could, in principle, use it with CGE without my mods to add
~/.config/pasls/castle-pasls.ini. Though~/.config/pasls/castle-pasls.inistill makes it easier by allowing me to provide just single CGE path. -
(SOLVED IN MY FORK, BUT WITH A HACK): On really invalid syntax (e.g. open LFM and try to use kambi-pascal-mode) it can send invalid JSON to LSP (
{"result":,"id":85,"jsonrpc":"2.0"}), and Emacs will spam console with errors.Warning (lsp-mode): Failed to parse the following chunk: ’’’ Content-Type: application/vscode-jsonrpc; charset=utf-8 Content-Length: 35 {"result":,"id":85,"jsonrpc":"2.0"}Content-Type: application/vscode-jsonrpc; charset=utf-8 Content-Length: 39 {"result":null,"id":86,"jsonrpc":"2.0"}Content-Type: application/vscode-jsonrpc; charset=utf-8 Content-Length: 39 {"result":null,"id":87,"jsonrpc":"2.0"} ’’’ with message (json-parse-error unexpected token near ',' <callback> 1 11 11) -
It does error message (like on missing unit) using LSP
window/showMessagemethod. To activate this you need to pass LSP initialization options with showSyntaxErrors=true (all LSP clients should enable to pass LSP initialization options somehow).This works great in VS Code.
It works in Emacs, but poorly: The message is there but it is immediately covered by "No completion found". You have to switch to Emacs
*Messages*buffer to see it. Note: seekambi-pascal-lsp.elfor:initialization-optionsexample how to activate it using Lisp. -
It is not efficient at finding LCL units, it seems. It does not read LPI / LPK, unlike Philip Zander's LSP server.
Testcase:
-
go to line 91, https://github.com/michaliskambi/pascal-language-server-genericptr/blob/castle-master/references.pas#L91 , after
StartSrcCode:=CodeToolBoss.LoadFile(Filename,false,false);assignment -
type
Startand try to complete.Ryan Joseph's LSP server fails to complete. Without any message in Emacs (but lack of clear message may be Emacs problem), though there is a clear message in VS Code. It cannot find
Laz_AVL_Tree.Lazarus IDE does code completion, so it evidently finds
Laz_AVL_Tree(since we know it would fail to do code completion when units are missing).Philip Zander's LSP server does code completion (after fix in Isopod/pascal-language-server#1 ). The conversation in #1 , and my tests confirm now, that it can find LPK/LPI nicely and from them know that
lazutilspackage is used and it containsLaz_AVL_Tree.
Notes specific to this fork:
-
Tested in both VS Code and Emacs.
-
DONE: Make it aware of CGE paths, make it do completion in CGE units like gamestatemain.pas. Done in https://github.com/castle-engine/pascal-language-server by special option in config file.
-
TODO: It doesn't take extra FPC options as LSP initialization options, though author is open to add this functionality ( #1 ).
-
As an extra feature, can read configuration from Lazarus options in home directory. This is completely optional though.
-
Nice: Error message reporting is configurable and can work everywhere. See
syntaxErrorReportingModeoption documented on https://github.com/Isopod/pascal-language-server -
Nice: It reads LPK / LPI, and is thus better at finding units, e.g. can find
Laz_AVL_Treewhen the program usesLazUtilspackage.This is extra important in the light of above (CodeTools fail if unit cannot be found).
Fork of Isopod version ( https://github.com/Isopod/pascal-language-server ).
Adds Windows compatibility (but since then it was addressed by Michalis, in CGE and Isopod versions) and more.
Fork of Isopod version ( https://github.com/Isopod/pascal-language-server ). We added various enhancements, contributing back to upstream.
We also added some CGE-specific options, and work now on tighter CGE integration in cge_vscode_extension branch.
Installation:
git clone https://github.com/castle-engine/pascal-language-server cge-pascal-language-server
cd cge-pascal-language-server/
git submodule update --init --recursive
cd server
lazbuild pasls.lpi
create $HOME/.config/pasls/castle-pasls.ini following https://github.com/castle-engine/pascal-language-server docs
-
Their capabilites are really quite even, due to them both enabling just Lazarus CodeTools as LSP server.
-
Both are actively developed with recent commmmits and multiple devs (confirmed again as of 2024-01-04).
-
My forks:
...strive to "standardize" part of them: they both support reading an INI file with a few useful options. They both allow to specify a per-process log file, that allows to debug JSON requests / responses. It's sometimes tremendously useful to compare what happens with 2 LSP servers.
We put more intensive work in https://github.com/castle-engine/pascal-language-server , adding CGE-specific features, making it aware of CGE projects. -
Both LSP server forks and Lazarus IDE too are rather "fragile" when it comes to having non-existing units on the
usesclause. The code completion fails until the CodeTools can find the unit.Testcase:
-
I opened CGE unit https://github.com/castle-engine/castle-engine/blob/master/examples/creature_behaviors/code/gamestatemenu.pas .
-
I tested my forks that add simple CGE support ( https://github.com/michaliskambi/pascal-language-server-genericptr , https://github.com/castle-engine/pascal-language-server ) -- this just results in adding a bunch of
-Fuxxx,-Fixxxto FPC options. I tested also Lazarus IDE 2.2.2. -
If I edit the
TStateMenu.Startto sayButand try to complete...begin inherited; ButtonPlay.OnClick := {$ifdef FPC}@{$endif} ClickPlay; ButtonQuit.OnClick := {$ifdef FPC}@{$endif} ClickQuit; // Hide "Quit" button on mobile/console platforms, where users don't expect such button ButtonQuit.Exists := ApplicationProperties.ShowUserInterfaceToQuit; But end;
-> this works great. It says to me that it can complete
ButtoButtonPlayorButtonQuit. -
But if I now mess up the
usesclause, adding there non-existent name...uses CastleApplicationProperties, CastleWindow, GameStatePlay, Foobar;-> now completion completely fails.
Butcannot be completed to anything. Which is a shame, it would be better IMHO if CodeTools would just ignore the missingFoobarin theusesclause.
-
-
TODO (IN PROGRESS, see cge_vscode_extension branch ): We should make LSP server find and understand
CastleEngineManifest.xmlin containing directory, and extract extra file paths from it.Now this fails: Code completion on CGE
tests/code/testcases/testcastlecomponentserialize.pasfails: it cannot findCastleTestCasewhich is intests/code/tester-fpcunit/. And LSP cannot guess by itself to search in../tester-fpcunit/for this. Only project files (we maintain bothCastleEngineManifest.xmland LPI for this) contain the necessary information to find all units.Although Philip Zander's LSP server can read LPI / LPK, and works in both VS Code and (after Isopod/pascal-language-server#1 ) in Emacs... but still completion in
tests/code/testcases/testcastlecomponentserialize.pasfails: no wonder, it doesn't know which LPI to process fortests/code/testcases/testcastlecomponentserialize.pas.Our
CastleEngineManifest.xmlcan make assumption, as our editor: just go up in dir hierarchy, finding firstCastleEngineManifest.xml.Workaround for now:
castle-pasls.iniwith[extra_options] ;; makes code completion in CGE tests working, otherwise it cannot find CastleTestCase option_1=-Fu/home/michalis/sources/castle-engine/castle-engine/tests/code/tester-fpcunit
To be complete, I should mention Embarcadero also makes their proprietary LSP server.
To use it, make sure to generate .delphilsp.json in the project folder, following
https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Code_Insight_Reference#Creating_.delphilsp.json_Files :
- Go to Tools > Options > User Interface > Editor > Language > Code Insight and turn on Generate LSP Config
- Save and close your project in the IDE.
TODO: I didn't manage to make it actually work. When trying to complete, it fails with DelphiLSP Agent1 terminated and will be respawned..
git clone https://github.com/genericptr/pasls-vscode
install vsix in VS Code
config:
- FPC sources:
- Laz sources:
- FPC exe:
- pasls exe: the one you got from above
Tested, works nicely, with both pasls forks.
Available in https://github.com/Isopod/pascal-language-server repo in client/nvim ( https://github.com/Isopod/pascal-language-server/tree/master/client/nvim ).
-
lsp-pascalfrom https://github.com/arjanadriaanse/lsp-pascal is in Melpa now. So just install it as a normal Emacs packages (e.g. choosing inM-x package-list-packages). -
lsp-modewill be installed as dependency oflsp-pascal, good -
lsp-ivy(because I like Ivy completion) -
lsp-ui -
company(completion framework thatlsp-modeneeds to work) -
yasnippet(necessary to show nicely routine parameters)
This is the basic configuration you need:
(require 'lsp-pascal)
;; choose LSP server binary
(setq lsp-pascal-command "/home/michalis/sources/pascal-language-server/server/lib/x86_64-linux/pasls")
;; pass basic info to LSP server, all LSP Pascal servers above support these:
(setq lsp-pascal-fpcdir "/home/michalis/installed/fpclazarus/current/fpcsrc/")
(setq lsp-pascal-lazarusdir "/home/michalis/installed/fpclazarus/current/lazarus")
(setq lsp-pascal-pp "/home/michalis/installed/fpclazarus/current/fpc/bin/x86_64-linux/fpc.sh")
(setq lsp-pascal-fpctarget "linux")
(setq lsp-pascal-fpctargetcpu "x86_64")
Add this to ~/.emacs
(add-to-list 'load-path (concat kambi-elisp-path "lsp/"))
(require 'kambi-pascal-lsp)
Read and customize kambi-pascal-lsp.el.
-
Completion aware of methods/properties in each namespace.
Demo:
- Open a new Pascal file.
- Declare instance of some known class from used unit, e.g.
TList. - Type
MyInstance.and thenM-x company-complete. - This should be intelligent completion, listing
TListproperties/methods now.
-
Ctrll+clicking on identifier jumps to declaration (works in VS Code and Emacs alike).
-
When you start
(, you see parameters of method/routine.TODO: How to show them all in Emacs? Currently I only see 1st parameters (in case of overloads) in Emacs, with prefix like "1/2", I still don't know how to view them all. And/or how to see all possible parameters using a tooltop, like in VS Code. So functionality works in Emacs, but presentation is poor.
-
"Go to Definition" / "Go to Declaration"
In VS Code: context menu (right click), ctrl + click, F12.
In Emacs: context menu (right click), ctrl + click, shortcuts mentioned there. lsp-find-declaration lsp-find-definition
-
Is there anything like "Code complete" (Ctrl+Shift+C in Lazarus) available in any fork?
So that e.g. writing "MyButton.OnClick := @Foo", pressing Ctrl+Shift+C would automatically create empty Foo implementation.
Note: GitHub Copilot in practice can often perform this job.
-
How to see CGE docs in company mode?
company-mode in Emacs can show docs in F1. How to configure it to show docs of CGE routine? It is useful from VS Code too?
-
How to make company-show-location work?
company docs say:
""" C-w ¶ Display a buffer with the definition of the selected candidate (company-show-location). """
but it does nothing for me.
-
In Emacs: make completion not case sensitive, e.g. Event.is should complete IsKey.