Prolog LSP now supports colours (Discussion)

Looking forward. I think this is the most promising way to get proper IDEs for Prolog. Note that PceEmacs uses the colouring library as follows:

  • On any keystroke it parses the clause around the caret and colors it based on the currently known cross-referencing data. It uses prolog_colourise_term/4 for that.
  • On a 2 sec pause in typing it re-runs cross-referencing for the file and runs prolog_colourise_stream/4 to re-colour the entire file, provided the file is below some size limit (I think 100K).

That works pretty well. Step 1 uses a regex to find the most likely start of the current clause. This is error prone (but usable). Next, Prolog opens the edit buffer as a stream, reads and analyses the clause and updates the highlighting. Step 2 simply opens the edit buffer and runs the entire analysis.

1 Like

The protocol is supposed to allow clients to request a colour update in a limited range, as well as “delta” changes, which I think would let me make use of prolog_colourise_term/4, but right now my editor doesn’t seem to actually send those requests. I already have code to “walk up” the syntax tree to find the start of the term that a given point is in, so once the editors support that, it should hopefully be easy to do.

1 Like

Does it also allow managing a mirror based on edit operations on the Prolog side? For SWISH, the CodeMirror instance maintains a list of edit operations. If we need new color info the client sends the entire buffer if this is small. If it is big, it sends the changes. The server checks for the mirror it has, applies the changes and computes the highlight. If the server has no mirror it replies with an HTTP error code and the client falls back to sending the entire buffer.

I guess Prolog can also maintain the current highlight and only sends a delta. Might be a bit of a challenge :slight_smile:

As soon as Emacs can do what PceEmacs can I’ll quite likely switch!

Theoretically! The server already tracks changes (to make auto-complete work, since the file isn’t saved while typing) and the protocol specifies handling deltas (i.e. remove these tokens, add these tokens). Like the ranges, just need the editor to support sending those requests so I can test (I think I might just need to update lsp-mode).

:grin: I will do my best to make that possible!

1 Like

When the editor can do its part, this SWISH file may give some inspiration on managing a mirror:

1 Like

Version 2.2.0 was released last night, which now can both operate just on ranges of a file (if the LSP client implementation supports it) and also will highlight based on the current state of the buffer, not just after saving.

Using the range updates for Emacs’ lsp-mode will require a patch until my PR to that project is accepted though (their client is currently based on a slightly older version of the (as-yet unreleased) spec).

1 Like

Can’t wait :slight_smile: Gave it a little try yesterday. Below is the relevant ~/.emacs. After a package install of lsp-mode, it sort of works now. On startup is says in the miniwindow

LSP :: Error from the Language server: Unimplemented (Internal Error)

Does that ring a bell?

The package install seems to have installed lsp-mode-20200716.2052


(require 'package)
(add-to-list 'package-archives '("melpa" . "") t)

(require 'lsp-mode)
   (lsp-stdio-connection (list "swipl"
                               "-g" "use_module(library(lsp_server))."
                               "-g" "lsp_server:main"
                               "-t" "halt"
                               "--" "stdio"))
   :major-modes '(prolog-mode)
   :priority 1
   :multi-root t
   :server-id 'prolog-ls))
(add-to-list 'lsp-language-id-configuration '(prolog-mode . "prolog-mode"))

(add-hook 'prolog-mode-hook #'lsp)

Ah right, that’s the other issue my aforementioned PR to lsp-mode fixes…for now, I’ve released 2.2.1 of the lsp_server pack, which should work with the current release of lsp-mode.

Oh, and to have the highlighting, I believe you have to set lsp-enable-semantic-highlighting to t.

1 Like

Works! It still gives this error though:

LSP :: Error from the Language server: Unimplemented (Internal Error)

It doesn’t seem to understand what are calls and what are just terms though. PceEmacs doesn’t provide an edit on arbitrary terms, but does do the right things on files (open the file), modules (open the file that defines the module) and terms that are actual calls, also in case call/N causes the actually called predicate to have a higher arity.

PceEmacs also tells you the meaning of a highlighted fragment in the miniwindow, SWISH using hovering).

Is this stuff in reach using LSP?

Don’t get me wrong, this is no critique. I’m beginning to believe this can bring us a really good editing experience on a wide range of editors in a foreseeable future. Most people just want one editor/ide for all their work :slight_smile:

1 Like

At this point, the server doesn’t handle that, it just looks up the term under the cursor & tries to look it up. I can add some special handling for meta calls.

The big limitation with the current limitation though is that the server is purely using static analysis (mainly prolog_xref). Obviously it could get much more information by loading the code, but I’ve been avoiding that for fear of the side-effects of loading code, not to mention that it frequently won’t be syntactically valid while editing.

Oh, it should be showing docs (on hover, if lsp-ui-doc-mode is on) but it doesn’t seem to be working now, I’ll investigate.

Ah, so it seems like lsp-mode now uses eldoc to show the docs & hides it if it’s too long – try setting lsp-eldoc-render-all to t?

Actually, I’ve just released 2.2.2 of lsp_server, which should show the mode documentation for predicates in the echo area and full docs when doing lsp-ui-doc-show or lsp-describe-thing-at-point.

1 Like

The library(prolog_xref) tells you more than enough AFAIK. Surely it is also what is driving PceEmacs, although for example find definition first queries the xref db and if not found it inspects the loaded program. Ideally it would (also) be possible to use a loaded Prolog program as LSP server. I guess that should be possible, possibly with some external helper program. For example we could make Prolog setup an AF_UNIX domain socket implementing the LSP protocol.

As for the term. If you enable highlighting you get a fragment for each body term that is a goal that tells you it is a goal as well as the predicate it calls. It you can pass on ranges you can also send the current clause to the server with the target location and ask which predicate is called at that point (if any).

Cool, I can work on that. The semantic token highlighting API from the server is a little limited, but I’ll try to get it to highlight goals vs terms.

Going to references/definitions should be mostly working; I’m using xref to look up the information, although getting the correct arity of the goal under the cursor gets somewhat confused by meta-calls.

That was going to be my original approach, mimicking Clojure’s CIDER system. Perhaps using engines or something to isolate the loaded code from the LSP itself…

Is there a way I can setup neovim/vim to use these features?

I’ve only tested neovim with the LanguageClient plugin, which seems to show the full documentation when manually invoking the function it provides.

However, the “mode in echo area, full docs when manually invoked” works in Emacs by taking advantage of a heuristic that package uses, where it will take just the first line of the documentation to show in the echo area when it’s longer than one line. Maybe a good feature request for LanguageClient-neovim…

1 Like

The PDT (plugin for Eclipse IDE) takes a similar route, using a thread to inform the IDE. PceEmacs is a little like that, running the entire editor it its own thread. As long as you keep all code in modules such that it doesn’t conflict with the user core this should be fine.

1 Like

Released 2.2.4 that colourizes many more things – I’m getting concerned it’s getting a little too busy, but it is pretty fun :grin:

One concern I had was “hooks” – e.g., at one point I need to set user:message_hook (for getting errors & warnings), and presumably that would be a side-effect that could affect/be affected by loading arbitrary modules.