diff --git a/.ocamlformat b/.ocamlformat index 1f3888a..187e7df 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -1 +1 @@ -version=0.28.1 +version=0.29.0 diff --git a/bruit.opam b/bruit.opam index a43bbcb..d2de2ed 100644 --- a/bruit.opam +++ b/bruit.opam @@ -11,6 +11,8 @@ doc: "https://url/to/documentation" bug-reports: "https://github.com/username/reponame/issues" depends: [ "dune" {>= "3.20"} + "fmt" + "astring" "ocaml" "odoc" {with-doc} ] diff --git a/dune-project b/dune-project index 6508e9e..480ad60 100644 --- a/dune-project +++ b/dune-project @@ -19,7 +19,10 @@ (name bruit) (synopsis "A short synopsis") (description "A longer description") - (depends ocaml) + (depends + fmt + astring + ocaml) (tags ("add topics" "to describe" your project))) diff --git a/example/dune b/example/dune index 941e1ae..a44a9a3 100644 --- a/example/dune +++ b/example/dune @@ -1,3 +1,3 @@ (executable (name main) - (libraries bruit fmt.tty)) + (libraries bruit terminal fmt.tty)) diff --git a/example/main.ml b/example/main.ml index 63fec56..a8ad0c3 100644 --- a/example/main.ml +++ b/example/main.ml @@ -16,7 +16,11 @@ let () = if sys_break then "[\x1b[31m130\x1b[0m] \x1b[33m>>\x1b[0m " else "\x1b[33m>>\x1b[0m " in - match Bruit.bruit ~history ~complete prompt with + let ctx = + Bruit.make_ctx ~get_columns:Terminal.Size.get_columns + ~guess_printed_width:Terminal.guess_printed_width + in + match Bruit.bruit ~history ~complete ctx prompt with | String (Some s) -> Fmt.pr "%s\n%!" s; h := s :: !h; diff --git a/src/bruit.ml b/src/bruit.ml index 0ecf283..c2d2fbf 100644 --- a/src/bruit.ml +++ b/src/bruit.ml @@ -37,12 +37,22 @@ let key_of_char c = | 127 -> Backspace | _ -> Unknown (Uchar.of_char c) +type ctx = { + get_columns : unit -> int option; + guess_printed_width : string -> int; +} + +let make_ctx = + fun ~get_columns -> + fun ~guess_printed_width -> { get_columns; guess_printed_width } + module State = struct type completion = string -> string list type t = { ifd : Unix.file_descr; ofd : Unix.file_descr; + ctx : ctx; buf : bytes; buf_len : int; prompt : bytes; @@ -69,9 +79,10 @@ module State = struct let make ?(in_completion = false) ?(completion_idx = 0) ?complete ?(old_pos = 0) ?(pos = 0) ?(len = 0) ?(history = []) ?(hint = fun _ -> None) ?(ifd = Unix.stdin) ?(ofd = Unix.stdout) ~prompt - buf = + ctx buf = { in_completion; + ctx; ifd; ofd; buf; @@ -122,12 +133,10 @@ module State = struct history = Option.value ~default:t.history history; saved_buf = Option.value ~default:t.saved_buf saved_buf; hint = t.hint; + ctx = t.ctx; } end -let get_columns () = - match Terminal.Size.get_columns () with Some n -> n | None -> 80 - let with_raw_mode (state : State.t) fn = let saved_tio = Unix.tcgetattr state.ifd in let tio : Unix.terminal_io = @@ -174,15 +183,15 @@ let read_char state = let edit_start ~stdin:_ ~stdout:_ state fn = with_raw_mode state @@ fun () -> - let cols = get_columns () in + let cols = state.ctx.get_columns () |> Option.value ~default:80 in (* Bytes.set state.buf 0 '\000'; *) let state = State.override ~cols ~buf_len:(state.buf_len - 1) state in write_bytes state.ofd state.prompt; fn state -let utf8_display_width b len = +let utf8_display_width state b len = let s = Bytes.to_string b in - Terminal.guess_printed_width (String.sub s 0 len) + state.State.ctx.guess_printed_width (String.sub s 0 len) let utf8_next_char_len s off = Bytes.get_utf_8_uchar s off @@ -202,9 +211,9 @@ 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 + let buf_width = utf8_display_width state 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 () = @@ -213,13 +222,13 @@ let refresh_with_hints ~pwidth ~ab (state : State.t) = hint in Buffer.add_string ab (Format.flush_str_formatter ()) - end + 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 - let poscol = ref @@ utf8_display_width state.buf state.pos in - let lencol = ref @@ utf8_display_width state.buf state.len in + let pwidth = utf8_display_width state prompt state.plen in + let poscol = ref @@ utf8_display_width state state.buf state.pos in + let lencol = ref @@ utf8_display_width state state.buf state.len in let rec loop (state : State.t) = if pwidth + !poscol >= state.cols then begin @@ -287,8 +296,8 @@ let edit_insert (state : State.t) c = State.override ~pos:(state.pos + clen) ~len:(state.len + clen) state in if - utf8_display_width state.prompt state.plen - + utf8_display_width state.buf state.len + utf8_display_width state state.prompt state.plen + + utf8_display_width state state.buf state.len < state.cols then begin write_uchar state.ofd c; @@ -615,14 +624,14 @@ let edit_feed ~history state = | Some 'C' -> Editing (move_right_next_word state) | _ -> Editing state) | _ -> Editing state - else begin - match c1 with + else + begin match c1 with | 'A' -> Editing (edit_history `Prev history state) | 'B' -> Editing (edit_history `Next history state) | 'C' -> Editing (move_right state) | 'D' -> Editing (move_left state) | _ -> Editing state - end + end | _ -> Editing state) | Ctrl_a | Ctrl_g | Ctrl_e | Ctrl_p -> Editing state | Unknown c when Uchar.(equal (of_int 8) c) (* ctrl+backspace *) -> @@ -633,8 +642,8 @@ let edit_feed ~history state = type result = String of string option | Ctrl_c -let blocking_edit ?complete ~history ~hint ~stdin ~stdout buf ~prompt = - let state = State.make ?complete ~hint ~prompt buf in +let blocking_edit ?complete ~history ~hint ~stdin ~stdout ctx buf ~prompt = + let state = State.make ?complete ~hint ~prompt ctx buf in let res = edit_start ~stdin ~stdout state @@ fun state -> let rec loop = function @@ -648,13 +657,13 @@ let blocking_edit ?complete ~history ~hint ~stdin ~stdout buf ~prompt = type history = string -> string list -let bruit ?complete ?(history = fun _ -> []) ?(hint = fun _ -> None) prompt = +let bruit ?complete ?(history = fun _ -> []) ?(hint = fun _ -> None) ctx 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 ~hint ~stdin:Unix.stdin ~stdout:Unix.stdout - buf ~prompt + (* if not (Unix.isatty Unix.stdin) then failwith "Stdin is not a tty" *) + blocking_edit ?complete ~history ~hint ~stdin:Unix.stdin ~stdout:Unix.stdout + ctx buf ~prompt (* * Copyright (c) 2010-2023, Salvatore Sanfilippo diff --git a/src/bruit.mli b/src/bruit.mli index cc6534d..86d8e15 100644 --- a/src/bruit.mli +++ b/src/bruit.mli @@ -14,10 +14,17 @@ type hint = string -> (string * Fmt.style) option type result = String of string option | Ctrl_c +type ctx +(** The context is an abstraction that provides OS-specific backend details. *) + +val make_ctx : + get_columns:(unit -> int option) -> guess_printed_width:(string -> int) -> ctx + val bruit : ?complete:(string -> string list) -> ?history:history -> ?hint:hint -> + ctx -> string -> result (** [bruit ?complete prompt] reads from [stdin] and returns the read string if diff --git a/src/dune b/src/dune index c846d59..2f1c9da 100644 --- a/src/dune +++ b/src/dune @@ -1,4 +1,4 @@ (library (public_name bruit) - (libraries terminal unix fmt astring) + (libraries fmt unix astring) (name bruit))