ocaml-community / lambda-term

Terminal manipulation library for OCaml
Other
209 stars 43 forks source link

Add `LTerm.flush term` when `lTerm_read_line` accepts user inputs (press Enter). #106

Open arbipher opened 2 years ago

arbipher commented 2 years ago

I am working on my repl based on examples/repl.ml. A common scenario is e.g. Interpreter.eval prints something to the stdout.

module Interpreter = struct
  type state = { n : int }

  let eval state s =
    let out = "evaluated " ^ s in
    let new_state = { n = state.n + 1 } in

    Printf.printf "(side effect: \nwrite to stdout)\n";
    flush stdout;

    (new_state, out)
end

This will make the LTerm_read_line behave strange (though reasonable). It will write the user input line again and line border if show_box is set true e.g.

$ dune exec examples/repl.exe
In  [1]: 11(side effect: 
write to stdout)───────────────────────────────────────────────────────────────────────────────────────────────────┐
In  [1]: 11
Out [2]: evaluated 11

The original example prints like this when my input is 1 1 (Enter).

$ dune exec examples/repl.exe
In  [1]: 11
Out [2]: evaluated 11

My expected restult is

$ dune exec examples/repl.exe
In  [1]: 11
(side effect: 
write to stdout)
Out [2]: evaluated 11

The problem can be fixed by adding a flush in the main loop of the example

let rec loop term history state =
  Lwt.catch (fun () ->
    let rl = new read_line ~term ~history:(LTerm_history.contents history) ~state in
    rl#run >|= fun command -> Some command)
    (function
      | Sys.Break -> return None
      | exn -> Lwt.fail exn)
  >>= function
  | Some command ->
    (* ADD HERE *)
    (* flush the stdout when it got a user input *)
    LTerm.flush term >>= fun () ->

    let command_utf8= Zed_string.to_utf8 command in
    let state, out = Interpreter.eval state command_utf8 in
    LTerm.fprintls term (make_output state out)
    >>= fun () ->
    LTerm_history.add history command;
    loop term history state
  | None ->
    loop term history state
...

My point to make the issue is I think it may be better to flush the stdout inside src/lterm_read_line.ml when it accepts user input when pressing enter (or ^M). There are LTerm.flush term in the code when handling other cases.

arbipher commented 2 years ago

As a comparison, utop which is based on lambda_term doesn't have this problem, .e.g

utop # print_endline "\n42\n";;

42

- : unit = ()

To make utop err (I did it for debug my reply), printing sth after it reads user input before flush the buffer at line 827 of uTop_main.ml e.g. ((fun () -> print_endline "suffix\n"; LTerm.flush term)). Now utop also prints my input print_endline "\n42\n";; again and then do the execution.

utop # print_endline "\n42\nsuffix
─( 12:22:07 )─< command 1 >─────────────────────────────────────────────────────────────────────────────────────────────────────────────{ counter: 0 }─
utop # print_endline "\n42\n";;

42

- : unit = ()

I could also guess the reason in the redraw logic of LTerm.read_line on this input line is triggered when extra output is put here.