diff --git a/src/lib/eunix.ml b/src/lib/eunix.ml index 30d7345..3240aab 100644 --- a/src/lib/eunix.ml +++ b/src/lib/eunix.ml @@ -13,7 +13,9 @@ let put_env ~key ~value = let get_user_and_host () = Eio_unix.run_in_systhread ~label:"get_user_and_host" @@ fun () -> - let name = Unix.getlogin () in + let name = + try Unix.getlogin () with Unix.Unix_error (Unix.ENOENT, _, _) -> "root" + in let host = Unix.gethostname () in Fmt.str "%s@%s" name host diff --git a/src/lib/interactive.ml b/src/lib/interactive.ml index 9a0a5f9..2dee885 100644 --- a/src/lib/interactive.ml +++ b/src/lib/interactive.ml @@ -98,8 +98,18 @@ module Make (S : Types.State) (E : Types.Exec) (H : Types.History) = struct |> Option.map Ast.word_components_to_string); let p = prompt ctx in Fmt.pr "%s\r%!" p; + let hint command = + if String.length command < 2 then None + else + match H.history ~command !h |> H.commands with + | [] -> None + | x :: _ -> ( + match Astring.String.cut ~sep:command x with + | Some ("", rest) -> Some (rest, `Fg (`Hi `Magenta)) + | _ -> None) + in match - Bruit.bruit + Bruit.bruit ~hint ~history:(fun command -> H.history ~command !h |> H.commands) ~complete "% " with diff --git a/test/debootstrap/Dockerfile b/test/debootstrap/Dockerfile new file mode 100644 index 0000000..69b251f --- /dev/null +++ b/test/debootstrap/Dockerfile @@ -0,0 +1,13 @@ +FROM ocaml/opam:debian-13-ocaml-5.3@sha256:3f0e61f92c78a39cd5161d75975809d44fc12fa7e6ae0fbced236aa1a2ae45e9 AS builder + +WORKDIR /home/opam/src + +COPY --chown=opam merry.opam . +RUN opam pin . -yn +RUN opam install . --deps-only --with-test +COPY --chown=opam . . +RUN opam exec -- dune build --profile=release + +FROM debian:13 +COPY --from=builder /home/opam/src/_build/default/src/bin/main.exe /bin/msh +ENTRYPOINT [ "msh" ] diff --git a/vendor/bruit/src/bruit.ml b/vendor/bruit/src/bruit.ml index 83e44f9..afdebb1 100644 --- a/vendor/bruit/src/bruit.ml +++ b/vendor/bruit/src/bruit.ml @@ -1,7 +1,9 @@ (* See the end of the file for the original license of Linenoise. *) - +let () = Fmt.set_style_renderer Format.str_formatter `Ansi_tty let max_line = 2048 +type hint = string -> (string * Fmt.style) option + type key = | Enter | Ctrl_a @@ -59,13 +61,15 @@ module State = struct in_completion : bool; completion_idx : int; complete : completion option; + hint : hint; } let buf t = Bytes.sub t.buf 0 t.len let make ?(in_completion = false) ?(completion_idx = 0) ?complete - ?(old_pos = 0) ?(pos = 0) ?(len = 0) ?(history = []) ?(ifd = Unix.stdin) - ?(ofd = Unix.stdout) ~prompt buf = + ?(old_pos = 0) ?(pos = 0) ?(len = 0) ?(history = []) + ?(hint = fun _ -> None) ?(ifd = Unix.stdin) ?(ofd = Unix.stdout) ~prompt + buf = { in_completion; ifd; @@ -86,6 +90,7 @@ module State = struct complete; completion_idx; read_buf = Bytes.make 1 '\000' (* For reading a character *); + hint; } let override ?in_completion ?completion_idx ?complete ?ifd ?ofd ?buf ?buf_len @@ -116,6 +121,7 @@ module State = struct completion_idx = Option.value ~default:t.completion_idx completion_idx; history = Option.value ~default:t.history history; saved_buf = Option.value ~default:t.saved_buf saved_buf; + hint = t.hint; } end @@ -195,6 +201,20 @@ let utf8_prev_char_len s off = type refresh_flag = Rewrite +let refresh_with_hints ~pwidth ~ab (state : State.t) = + let buf_width = utf8_display_width state.buf state.len in + if pwidth + buf_width < state.cols then begin + match state.hint (State.buf state |> Bytes.to_string) with + | None -> () + | Some (hint, style) -> + let () = + Format.fprintf Format.str_formatter "%a" + Fmt.(styled style string) + hint + in + Buffer.add_string ab (Format.flush_str_formatter ()) + end + let refresh_single_line ?(flags = []) ?prompt (state : State.t) = let prompt = match prompt with None -> state.prompt | Some p -> p in let pwidth = utf8_display_width prompt state.plen in @@ -228,6 +248,8 @@ let refresh_single_line ?(flags = []) ?prompt (state : State.t) = Buffer.add_bytes ab (Bytes.sub state.buf 0 state.len) end; + refresh_with_hints ~pwidth:state.len ~ab state; + (* Erase to the right *) Buffer.add_string ab "\x1b[0K"; @@ -270,7 +292,7 @@ let edit_insert (state : State.t) c = < state.cols then begin write_uchar state.ofd c; - state + refresh_line state end else refresh_line state end @@ -298,10 +320,8 @@ let edit_backspace (state : State.t) = in refresh_line state -let get_bytes state = Bytes.sub state.State.buf 0 state.len - let complete_line (state : State.t) c cn = - match cn (String.of_bytes (get_bytes state)) with + match cn (String.of_bytes (State.buf state)) with | [] -> (State.override ~in_completion:false state, `Char c) | xs -> let state, c = @@ -345,12 +365,23 @@ let move_left (state : State.t) = in refresh_line s +let complete_with_hint (state : State.t) = + let buf = State.buf state |> Bytes.to_string in + match state.hint buf with + | None -> state + | Some (h, _) -> + let new_buf = buf ^ h in + let end_buf = String.length new_buf in + Bytes.blit_string new_buf 0 state.buf 0 end_buf; + State.override ~pos:end_buf ~len:end_buf state + let move_right (state : State.t) = let s = - if state.pos > 0 then + if state.pos < state.len then State.override ~pos:(state.pos + utf8_next_char_len state.buf state.pos) state + else if state.pos = state.len then complete_with_hint state else state in refresh_line s @@ -526,8 +557,8 @@ let edit_feed ~history state = type result = String of string option | Ctrl_c -let blocking_edit ?complete ~history ~stdin ~stdout buf ~prompt = - let state = State.make ?complete ~prompt buf in +let blocking_edit ?complete ~history ~hint ~stdin ~stdout buf ~prompt = + let state = State.make ?complete ~hint ~prompt buf in let res = edit_start ~stdin ~stdout state @@ fun state -> let rec loop = function @@ -541,13 +572,13 @@ let blocking_edit ?complete ~history ~stdin ~stdout buf ~prompt = type history = string -> string list -let bruit ?complete ?(history = fun _ -> []) prompt = +let bruit ?complete ?(history = fun _ -> []) ?(hint = fun _ -> None) prompt = let prompt = Bytes.of_string prompt in let buf = Bytes.make max_line '\000' in if not (Unix.isatty Unix.stdin) then failwith "Stdin is not a tty" else - blocking_edit ?complete ~history ~stdin:Unix.stdin ~stdout:Unix.stdout buf - ~prompt + blocking_edit ?complete ~history ~hint ~stdin:Unix.stdin ~stdout:Unix.stdout + buf ~prompt (* * Copyright (c) 2010-2023, Salvatore Sanfilippo diff --git a/vendor/bruit/src/bruit.mli b/vendor/bruit/src/bruit.mli index b178b75..cc6534d 100644 --- a/vendor/bruit/src/bruit.mli +++ b/vendor/bruit/src/bruit.mli @@ -8,10 +8,18 @@ type history = string -> string list (** The history callback that provides the user with the current line and expects a list of history items to scroll through using the arrow keys. *) +type hint = string -> (string * Fmt.style) option +(** The hint callback takes the current input and a user can return, optionally, + extra information to fill in on the current line. *) + type result = String of string option | Ctrl_c val bruit : - ?complete:(string -> string list) -> ?history:history -> string -> result + ?complete:(string -> string list) -> + ?history:history -> + ?hint:hint -> + string -> + result (** [bruit ?complete prompt] reads from [stdin] and returns the read string if any, and on [ctrl+c] returns {! Ctrl_c}.