Goal
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/ .
Short version
-
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")
Various LSP Pascal servers
Arjan Adriaanse repo (original, unmaintained)
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.)
Ryan Joseph (genericptr) LSP server
https://github.com/genericptr/pascal-language-server
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:
-
editing https://github.com/michaliskambi/pascal-language-server-genericptr/blob/castle-master/references.pas
-
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.
-
Philip Zander (Isopod) LSP server
https://github.com/Isopod/pascal-language-server
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).
Kagamma
https://github.com/Kagamma/pascal-language-server
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.
Castle Game Engine (Michalis Kamburelis, Andrzej Kilijanski, other CGE developers)
https://github.com/castle-engine/pascal-language-server
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
Notes about both Ryan Joseph (genericptr) and Philip Zander (Isopod) versions
-
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).
https://github.com/genericptr/pascal-language-server/commits/trunk/
https://github.com/Isopod/pascal-language-server/commits/master/
-
My forks:
https://github.com/michaliskambi/pascal-language-server-genericptr
https://github.com/castle-engine/pascal-language-server
...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
Delphi LSP
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..
Other editors than Emacs (mentioning it here for completeness)
VS Code Extension
https://github.com/genericptr/pasls-vscode
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.
Neovim
Available in https://github.com/Isopod/pascal-language-server repo in client/nvim ( https://github.com/Isopod/pascal-language-server/tree/master/client/nvim ).
Installing necessary packages in Emacs
-
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)
Basic config
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")
Complicated configuration, specific to Michalis preferences
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.
What works
-
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
TODO (in general, for both VS Code and Emacs)
-
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.
TODO (specifically for Emacs)
-
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.