Utop: idea to use Shift-Enter to evaluate input

Continuing the discussion from Sorry, not getting it with mli:

There’d be no need to change the grammar. We could map Shift-RET to literally the character sequence \n;; and get utop to display the double semicolons in a grayed-out colour to de-emphasize it. Eg

Screenshot 2024-07-04 at 13.58.00

1 Like

Excellent! I think I was (mis?)reading the proposal as “let’s change the grammar” as opposed to "let’s change the way people interact with the ocaml top-level. The latter is completely anodyne.

And a little story: I maintain Camlp5. Its “revised syntax” always used “;” (a single “;”) as the toplevel phrase terminator. So (haha!) when I use the Camlp5 revised syntax in the toplevel, I -always- rely on Shift-RET to terminate-and-send-across top-level phrases.

1 Like

FWIW, I’ve been using the following as part of my ~/.config/utop/init/ml:

#require "lambda-term";;
print_endline "init.ml: binding alt-enter to accept-phrase";;
LTerm_read_line.bind
  [ { control = false; meta = true; shift = false; code = Enter } ]
  [ UTop.end_and_accept_current_phrase ];;
1 Like

Tried it just now but not working. After each binding I tried the corresponding keystore but it didn’t evaluate the entered phrase. I had to enter ;; and then press Enter as usual.

# #require "lambda-term";;
# LTerm_read_line.bind [ { control = false; meta = false; shift = true; code = Enter } ]
[ UTop.end_and_accept_current_phrase ];;
- : unit = ()
# 1;;
- : int = 1
# LTerm_read_line.bind [ { control = false; meta = true; shift = false; code = Enter } ]
[ UTop.end_and_accept_current_phrase ];;
- : unit = ()
# 1
;;
- : int = 1
# LTerm_read_line.bind [ { control = true; meta = false; shift = false; code = Enter } ]
[ UTop.end_and_accept_current_phrase ];;
- : unit = ()
# 1
;;
- : int = 1
-

I have something similar in my config and it works.
Removing it and entering the binding directly in utop, as you did, doesn’t. :person_shrugging:
I’m no utop expert, but it seems we need the bindings early enough in the initialisation process.

just changing how the toplevel handles input should be enough… utop supports this but via that hook.
I don’t think you need to change the grammar, that seems way drastic and too coarse for a toplevel use-case.

plus, modifiers alone don’t have a stable bit representation anyway. if going by legacy tty+ascii repr, where for example ctrl unsets the highest two of the 7 bits, ctrl-ret & shift-ret have the same repr as ret. if so, you have no luck going this route when asking the toplevel (and/or the lexer) to handle the keys like this. you probably want alt-ret. alt-ret sends two characters as input I believe: esc+ret. so this may work for toplevel, but for grammar it’s a bit strange to ask for it: esc is (for example) handled in vim itself (and most graphical editors) before it is (or without being) reflected as input. some editors may not display esc? then someone could force-input an esc after every toplevel declaration (again for example in vim via ctrl-v) and because the way ocaml handles ;;, it may cause surprising behavior:

let x = 42^[
x + 1 (* works fine and someone may not know why if ^[ isn't visible *)

having control characters in grammar is probably not the best idea as you can see.

then again, it may not be a good idea in toplevel either. it depends on how the toplevel is accessed and whether or not it handles keys as raw input from tty like that. I think some terminals like kitty use a wholly different input protocol… I don’t know if you can guarantee these things will work consistently, really. we haven’t even considered how windows does it etc.

I just tried again with utop -init ~/.config/ocaml/init.ml to skip loading my custom init. Then from the session,

#require "lambda-term";;
LTerm_read_line.bind
  [ { control = false; meta = true; shift = false; code = Enter } ]
  [ UTop.end_and_accept_current_phrase ];;

works for me. Note this is for alt-enter, not shift-enter! Actually, the analog for shift-enter does not work for me. This must have been the reason at the time for using alt-enter. Probably some terminal input parsing weirdness from the '70s coming back to haunt us.

I tried and it works well in my setup, thanks!

While I’m here, is there a way to do the contrary: add a new line when there is a trailing “;;”?

When editing a toplevel item from the history, I often accidentally send it to the REPL when willing to add a new line… I’m forced to remove the trailing “;;” before adding a new line.

there’s a PR/issue as far as I know that addresses this, not by modifying ;;s in the middle of input, but by continuing to parse things that come after an initial ;;. If I find the PR I’ll link it here.

I managed to get this working on MacOS (Sonoma 14.4.1) with iTerm2 (3.5.5) with the following ~/.config/utop/init.ml

#require "lambda-term";;
LTerm_read_line.bind
  [ { control = false; meta = false; shift = false; code = Enter } ]
  [ UTop.end_and_accept_current_phrase ];;
LTerm_read_line.bind
  [ { control = true; meta = false; shift = false; code = Enter } ]
  [ LTerm_read_line.Edit (LTerm_edit.Zed Zed_edit.Newline)];;

This removes the need for ;; to terminate the input by mapping unmodified Enter to the end_and_accept_curent_phrase command. However, since I don’t want to lose the ability to have multiline input, I also remapped Ctrl-Enter to insert a Newline.

The lambda-term documentation was quite helpful in figuring this out: lambda-term 3.1.0 · OCaml Package

Annoyingly, iTerm2 doesn’t naively pass the modifier keys correctly to utop, so getting this working requires also remapping in iTerm2 settings the right or left option keys to Esc+ (via Settings → Profiles → Keys → General).

I suspect further workarounds to get actual Cmd-Enter or other modifier combinations working on MacOS could be possible via a combination of:

https://stackoverflow.com/questions/5388562/cant-map-s-cr-in-vim/12117076#12117076 (in a code block since apparently new users aren’t allowed to post more than two links?)

Note that most GUI applications (I’m familiar with) do it the other way around: When Enter submits immediately, Shift+Enter forces a newline; and when Enter inserts a newline, Ctrl+Enter submits instead.

1 Like