Prolog LSP Update - Code Formatting!

Version three of my Prolog LSP implementation has been released! The big new feature is automatic code formatting, a feature several people have expressed interest in.

You can install or update the pack with swipl pack install lsp_server and configure your editor using the instructions in the README.

As a bonus, after installing the pack, the formatter can be run stand-alone with swipl formatter $file_to_format.

4 Likes

Hello,
I just wanted to say thank you for developing and maintaining your Prolog LSP.
It is your pack that allows all of us vim users to write prolog code in our favorite environment :slight_smile:

1 Like

Probably not :slight_smile: Code formatting in SWI-Tinker uses the formatting facilities of CodeMirror, which was copied from SWISH. This facility also deals with highlighting, but were SWISH highligting uses the CodeMirror “mode” enriched with server-side generated semantic labels, SWI-Tinker uses an approach that is very close to PceEmacs. In this scenario the editor calls Prolog, which directly highlights fragments in the editor (using CodeMirror.markText()).

Basically there was no reusable Prolog formatter (auto-indent) for SWI-Prolog. There are several independent ones, notably the one in PceEmacs (using hacky regex look-back inspired by old GNU-Emacs), sweep (using syntax analysis in GNU-Emacs), SWISH/Tinker (using CodeMirror syntax analysis). Maybe @jamesnvc can comment on the reusability of the reformatter for LSP?

You’re welcome! That’s very nice to hear, thank you!

Sure; I wrote briefly about it here. Essentially, the formatter uses prolog_read_source_term/4 to parse the source, and flattens the content into a list of tokens, with whitespace and comments made explicit (the lsp_formatter_parser module). That reified representation is processed by the predicates in lsp_formatter, which applies a series of rules to edit the whitespace to subjectively improve indentation, alignment, and other formatter preferences. I plan on adding more rules to that module as well as some configuration, so users can pick-and-choose more how things are formatted.

I hope it is somewhat reusable in other code. As I mentioned, it can also be run independently of the LSP sever as a swipl “app”; see swipl formatter -h

1 Like

What editors do you use with the prolog lsp? I was looking into using this a few days ago but my usual editor is notepad++ which doesn’t have much lsp support outside of a broken extension.

I use Emacs and I’ve tested with vim and VSCode. Unfortunately I don’t have access to a Windows machine to look at Notepad++. In any case, it seems like that editor doesn’t have a single solid LSP implementation? A cursory search just finds several “work-in-progress” clients…

Thank you!

Theoretically it could do LLM-based completions, but with the way the LSP API works, I don’t think it could really do the “ghost text” effect. I’ve implemented LLM-based completion for a different LSP server and the experience wasn’t great, since there’s a noticeable lag waiting for completion candidates to be suggested, as the completion system with LSPs is synchronous.

A lag wouldn’t be a problem. I guess the problem is that
the GUI gets blocked, right? If your GUI has tasks and timers,
you could try the follow:

  • Debouncing Timer: To start ghost after lets say 1000 ms,
    the debouncing is to assure that the ghost appears after
    the last edit and not after the first edit.

  • Abortable Task: The ghost is then computed asynchronously
    via a task. But this task is again tied to the debouncing, means
    the task gets aborted if the end-user shows impatience.

I checked a couple of GUI tools to do that. Had rather bad
experience with Java and OpenJFX, so I am returning back to
the JavaScript and WHATWG with my ongoing research.

But maybe there are other GUI frameworks which can do it.
If tasks are also used for highlighting I belive they can be also
run asynchronously without explicitly juggling with some queues,

just await the asynchrous routine that the task would execute,
in a instrumentation loop, and ignore the aborts, since an abort
is now good news and not bad news, it only indicates that

the end user did an edit and that the task will be restartet. (*)

Edit 17.06.2025
The restart maybe explains why IntelliJ has a Power Safe mode,
but I am not 100% sure about the details. But if the end-user
behaviour is not compatible with the chosen delays, he might

tear down the tasks again and again. This could use compute
power. Some incremental tasks or tasks that block (i.e. the “lag”)
could be a solution. Incremental tasks that do only small work and

not big work, are less perceived as blocking (i.e. the lag). But a
ghost text that maybe involves calling an Artificial Intelligence API
is probably in nature not a small work task, in the sense that

it would occupy a short work time, the Artificial Intelligence API
server might be busy and cause a task slow down.

Disclaimer: I am doing some brain writing here…

Yes, that would be possible if we were writing something integrated into the editor, but the problem is the way the LSP protocol works is that the server only receives messages from the editor, which then waits for the response back. One of the trade-offs of the LSP system being decoupled from any particular editor is that smoother integrations like the one you describe aren’t easily possible without changing the protocol itself.

Right, but what I’m saying is that with the current implementation as a separate process, running independently of the editor and only communicating over the channel defined by the protocol, the ability to have fine-grained asynchronous communication is limited.