using vim to play with racket

418 views
Skip to first unread message

gokceh...@gmail.com

unread,
Dec 17, 2017, 8:33:26 AM12/17/17
to Racket Users
Hello,

I have posted this to /r/Racket a few days ago and someone suggested I should also post here.

I have been playing with racket lately. I think DrRacket is a fantastic editor but unfortunately as a long time vim user I feel quite uncomfortable when I use something else. I had some mappings to execute code while editing scripting languages (e.g. python, ruby) which I turned into a plugin a few days ago:


It also works for racket if your vim is compiled with `+mzscheme`. There are some issues (language is always `racket/base` and I think gui and threading do not work) but in general I'm quite happy about it. I have a screencast here with a racket example here if anyone is interested:


This plugin simply defines two custom operator functions and the real work is done with mzscheme interface (`:mz` command). By default, `gx` is mapped to an execution operator which displays the output in `:messages` and `gz` is mapped to an appending operator which appends the output as comment below the expression. These operators either take a text object or work with visual selection just like builtin operators. So for example, in the demo, following examples are shown:

gxab -> evaluate current s-expression (or block in vim's terms)
gx2ab -> evaluate the current 2-level s-expression
v2abgx -> evaluate the current 2-level s-expression (with visual selection)
gxip -> evaluate current paragraph
gzip -> evaluate and append the output of current paragraph

More information can be found in the github page.

There are already many plugins which launches a repl from your editor and send text to that repl from within your editor and I think most people are already familiar with slime. This plugin is a different approach to the same problem with following differences:

- Embedded mzscheme interface is used for evaluation instead of launching an external process
- Operator functions are defined for mappings to work with existing text objects instead of custom mappings specific to an object
- Output is displayed as a vim message and history can be accessed with `:messages` or `:mes` for short
- Arbitrary expressions can be evaluated using `:mzscheme` command or `:mz` for short
- These expressions can then be accessed again from the command history (i.e. `q:`) which would act similarly to a repl history
- In theory, whole file can be evaluated using `:mzfile %` but this does not currently work when a file has a `#lang` statement

Compared to other such plugins I find this more natural and easier to work with. It is meant to work out of the box without any configuration. The only headache would be to have a vim version compiled with `+mzscheme` feature which may require manually compiling vim from source.

Feel free to leave comments, bugs or ideas.

*P.S.* Thank you all who worked on integrating `if_mzsch` to vim.

Gokcehan

HiPhish

unread,
Dec 20, 2017, 5:49:29 AM12/20/17
to Racket Users
Having to re-compile Vim with support for a language is one of its bigger
drawbacks. It's fine for a common language like Python, but the more obscure it
gets, the less likely it is that your plugin will be of any use to other
people. You should take a look at Neovim:
https://neovim.io/

Neovim is a fork of Vim which aims to bring the code up to modern standards. It
is not a rewrite, so all existing plugins should work and the Neovim developers
keep up with new Vim patches.

One of the innovations in Neovim is its remote API. In Vim you have to compile
Vim with support for a foreign language, but in Neovim that support can be
retrofitted to the editor as an external process. So for example, if you want
to write Neovim plugins in Python you install the Python client via pip. And if
you want to write plugins in Racket you install my Racket client:
https://gitlab.com/HiPhish/neovim.rkt

You could then write an "evaluator" plugin in Racket like this:

    #lang racket
    ;; Save in a file like '~/.config/nvim/rplugin/racket/eval.rkt'
    (require nvim)
    (nvim-function "RacketEval"
      (λ (args)
        (define str (vector-ref args 0))
        (call-with-input-string str (λ (in) (eval (read in))))))

This won't actually work with an argument like "(+ 2 3)", Racket complains that
the '+ is an undefined identifier, but that's a problem on the Racket side (I
don't know that much about Racket yet to be able to do eval magic), not on the
editor side. The RacketEval function is like any other Neovim function, except
that it offloads the work to an external process behind the scene, but the user
never notices that.

The Racket client is still under development, I have not yet committed fully to
the public interface and I need to take care of some edge cases when something
goes wrong. Other than that, the client fully works.


On Sunday, December 17, 2017 at 2:33:26 PM UTC+1, gokceh...@gmail.com wrote:
...

gokceh...@gmail.com

unread,
Dec 20, 2017, 9:42:41 AM12/20/17
to Racket Users
I agree manual compilation can be quite a hassle. I have tried to avoid such configurations for many years myself. Then I decided I could just do it once for all supported languages in vim and be done with it. For racket, if you have it installed you most likely just need something along the line:

./configure --enable-mzschemeinterp && make && sudo make install

Maybe it is because of the fact that my daily job requires me to do manual compilation frequently but I don't find this very difficult. Having the correct packages installed is the difficult part but if you're working on racket then it is likely already installed on your machine. I would say racket is only obscure to those that are not using it. If I were to develop a vim plugin using mzscheme interface but has nothing to do with racket language itself then it would really be a problem.

I'm aware of neovim project. In fact, opex works well with neovim as well for languages with providers (e.g. lua, python, ruby, shell and vim). I don't know much about remote plugins in neovim but I feel like they have a distinction between providers and remote plugins, because I can't seem to find some of the interfaces in vim (perl, tcl and mzscheme). Or they may have simply removed these in the account that nobody is using them. If a remote plugin can provide such an interface with a similar command, then I think opex can simply be configured with something like the following:

autocmd Filetype scheme let b:opex_cmd = 'RacketEval'

Neovim indeed suggests good engineering solutions to some of the problems in vim. I think the biggest problem is that the community more often talks about the possibilities rather than showing existing work. End users may simply prefer something that exists and simply works. In fact, I was already aware of neovim.rkt and I'm very glad you're here to give some feedback. It would be nice if you can give some information about the current situation in neovim and neovim.rkt.

Regarding opex, it is a tiny plugin that consists of a few hundreds lines of codes. It is an iteration of a very simple idea that uses the existing work . It is finished and I doubt I will change anything except for maybe small bug fixes. It already works well for myself and I frequently use it to prepare lecture notes or do simple literate programming. This is all that matters for me. I put the source to public and announced it in a few places so that maybe a few other people on the planet may find it useful as well.

Gokcehan

Greg Hendershott

unread,
Dec 20, 2017, 1:03:45 PM12/20/17
to HiPhish, Racket Users
> This won't actually work with an argument like "(+ 2 3)", Racket complains
> that
> the '+ is an undefined identifier, but that's a problem on the Racket side
> (I
> don't know that much about Racket yet to be able to do eval magic), not on
> the
> editor side. The RacketEval function is like any other Neovim function,

Instead of `eval` using a default, empty namespace:

(eval '(+ 1 2))

Try giving `eval` a namespace that includes `racket/base`:

(define ns (make-base-namespace))
(eval '(+ 1 2) ns)

See https://docs.racket-lang.org/guide/eval.html#%28part._namespaces%29

Greg Hendershott

unread,
Dec 20, 2017, 1:09:57 PM12/20/17
to HiPhish, Racket Users
> Try giving `eval` a namespace that includes `racket/base`:
>
> (define ns (make-base-namespace))
> (eval '(+ 1 2) ns)

p.s. If you don't supply the second, namespace argument to `eval`, it
defaults to `(current-namespace)` -- which by default is an empty
namespace.

So, you can also change the value of the `current-namespace`
parameter, around one or more calls to `eval`:

(parameterize ([current-namespace (make-base-namespace)])
(eval '(+ 1 2))
(eval '(+ 1 41)))

HiPhish

unread,
Dec 20, 2017, 7:48:12 PM12/20/17
to Racket Users
> Maybe it is because of the fact that my daily job requires me to do manual
> compilation frequently but I don't find this very difficult.
Yeah, I use a package manager and only compile manually when I absolutely have
to because I don't like dealing with dependencies. A matter of perspective I
guess.



> In fact, opex works well with neovim as well for languages with providers
> (e.g. lua, python, ruby, shell and vim). I don't know much about remote
> plugins in neovim but I feel like they have a distinction between providers
> and remote plugins, because I can't seem to find some of the interfaces in
> vim (perl, tcl and mzscheme).
Right, providers and remote plugins are two different things. The idea behind
providers is that some functionality should not be implemented in Neovim, but
instead there should be a place to hook an application into. Take for example
the `:make` command: make is not built into Vim, instead you can set the
`makeprg` variable to a binary of you choice. Providers take this idea further,
offloading for example the clipboard support to an external program.

Remote plugins on the other hand are plugins written in a foreign language.
Where it might get confusing is that sometimes there is both: you can write a
plugin in Python as a remote plugin (new style) or you can write it in the old
style. When writing it the old style there needs to be a provider to supply a
Python implementation for the `:python` command to work (since there is none
built into Neovim itself).

There is no provider for the old `:mzscheme` command yet. I am planning to get
around to it, but it has very low priority. If anyone wants to help out I'm
open to that.


> If a remote plugin can provide such an interface with a similar command, then
> I think opex can simply be configured with something like the following:
>
>    autocmd Filetype scheme let b:opex_cmd = 'RacketEval'

Right, that's the idea. Keep in mind that there is no difference between
functions defined in VimScript and those defined in remote plugins, so you
could also create a function reference like `funciton('RacketEval')`.


> I think the biggest problem is that the community more often talks about the
> possibilities rather than showing existing work. End users may simply prefer
> something that exists and simply works.
Many projects start with the idea of rewriting Vim, so you get someone who says
"I'm going to rewrite Vim in Python, and it will be much better code". Then he
starts implementing features one by one, and eventually he reaches the point
where he himself is satisfied with the result because it fits his own needs. He
never gets around implementing the rest and so you end up with a clone that
isn't compatible with anything from the Vim ecosystem. Neovim is a fork, so it
starts with full Vim compatibility and the developers can surgically remove and
replace the bits that need to be changed.


> In fact, I was already aware of neovim.rkt and I'm very glad you're here to
> give some feedback. It would be nice if you can give some information about
> the current situation in neovim and neovim.rkt.
Neovim.rkt is something I do on the side and it was my first time doing
something with asynchronicity and message passing, so progress is slow. It does
work though, but don't expect graceful error handling. I also haven't yet
committed to the public interface, but what I have now looks promising in my
opinion.

As for Neovim, that project is doing great. I have completely replaced Vim and
there are already plugins making use of its new features. Externalising
language support also makes it easier to maintain when you can just swap out
components. My personal killer feature is the terminal emulator. I thing the
fact that Vim has been copying features from Neovim instead of the other way
around should be telling enough.
Reply all
Reply to author
Forward
0 new messages