Hello Racketeers, I wrote a new package, an API client for the Neovim text editor: https://gitlab.com/HiPhish/neovim.rkt https://pkgs.racket-lang.org/package/nvim-client
With this it is possible to control Neovim with Racket. In particular, it is now possible to write Neovim plugins in Racket rather than VimScript. What is Neovim? =============== Neovim (or Nvim for short) is a fork of Vim. Unlike the usual "I'll rewrite Vim in my favourite language" projects which aim to imitate Vim, but end up being incompatible with most Vim plugins, Neovim intends to clean up the plumbing, but keep the porcelain the same. This means we get to keep using the existing ecosystem of extensions and other techniques, while the core of the editor gets cleaned up and new features get added. One such new feature are the language hosts. In Vim if you want to write plugins in a foreign language you have to compile Vim with support for that language. For something as common as Python you can be fairly certain that most users have it and that support in Vim is always up to date. However, the more you move away from that, the worse it gets. Vim has support for Racket, but it's still called MzScheme and who knows when it was last updated. What is the Racket host? ======================== Neovim externalises the problem. The editor no longer comes with support for any foreign languages, only VimScript and Lua are the default. Instead, it defines an API interface so that external programs can connect to the editor and exchange messages. Among other things this allows plugins to be written in any language if a host for that language exists. The maintenance burden is now on the host author and hosts can be added, removed and swapped without having to re-compile Neovim. Messages can be sent asynchronously, so it is possible for example to have semantic code highlighting without blocking: https://github.com/arakashic/chromatica.nvim/ This type of communication works both ways: Neovim can control a Racket instance, but Racket can also control a Neovim instance. One could write a GUI for Neovim in Racket, the GUI would be just a Racket program which spawns a headless Neovim instance, connects to it, and then sends user inputer over and receives the result for display. What I am personally interested in are remote plugins, plugins written for Neovim in Racket. See the readme of the repo to see what a remote plugin looks like. I would like to make use of the DrRacket code to implement IDE-like capabilities as a plugin. Status of the Racket host ========================= Right now the host is feature-complete, so if anyone wants to try it out it is ready. There are a few issues I would like to see sorted out before it can considered to be finished. - Why is the documentation not building? I can compile the scribble file normally, but running `raco setup` does not build it, and it doesn't show up on https://docs.racket-lang.org/ either. - The directory layout is weird. The reason for this is that the repo is both a Racket package, and a Neovim plugin and has to be installed as both. This works fine, but now Racket seems to think that there is also an "autoload", "doc", "plugin" and "test" collection. Is there a way to tell Racket "only the nvim directory is a collection"? - The API bindings (nvim/api.rkt) are a big sore in my eyes. Neovim defines a set of "API methods", basically the specifications for messages you can send over RPC. https://neovim.io/doc/user/api.html#api-global Right now I'm generating the function bindings using macros, but it looks weird: ;; Set the current line (set-current-line "Hello world!") (define two (eval "2")) ; This is not the Racket eval There is no exclamation mark and the API can collide with existing names. The name collision can be resolved by adding a prefix like `nvim-` or `nvim:`. Which one would be better? Would it be better to have separate getter- and setter functions, or is it better to use `set!`? ;; Setters and getters (define line (get-current-line)) (set-current-line! (string-append line "!!!")) ;; using set! (define line (current-line)) (set! (current-line) (string-append line "!!!")) At the moment I am generating the API automatically, but I think I'll have to do it manually. I can for example append a `!` if the word `set_` occurs in the name, but there are also other functions which have side effects (e.g. `nvim_del_current_line()`). And some like `nvim_call_function()` may or may not mutate state. Some API calls are limited to a part of the editor, like a tab page, a window or a buffer. What should their names be? ;; Two prefixes (define line-count (nvim-buffer-line-count buf)) ;; One prefix (define line-count (buffer-line-count buf)) ;; Keyword argument (define line-count (nvim-line-count #:buffer buf)) Only buffers have a line count, but some concepts apply to multiple aspects; for example, there are global variables, buffer variables, tab-page variables and window variables. ;; Two prefixes (define v (nvim-get-var "detail")) (define v (nvim-buffer-get-var buf "detail")) ;; One prefix (define v (nvim-get-var "detail")) (define v (buffer-get-var b "detail")) ;; Keyword argument (define v (nvim-get-var "detail" #:buffer b)) - Finally, about testing, how can I test the code without having to rely on Neovim being installed on the testing machine? I want the tests on the Racket package repository to pass. Currently (test/connection.rkt) I am using byte stream ports, but they have the problem that they are always ready for synchronisation, so I have to do ugly hacks to get them to work. Normally I use a Unix socket to connect Racket and Neovim, but any types of binary input and output ports suffice (see `start-client` in nvim/client.rkt). Should I use a temporary file created with the function `make-temporary-file` instead? -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.