···11+module History = struct
22+ type t = string
33+44+ let t = Repr.string
55+ let merge = Irmin.Merge.default (Repr.option t)
66+end
77+88+module Pass = Cshell.Make (History) (Cshell_passthrough)
99+module Shelter = Cshell.Make (Shelter.History) (Shelter)
1010+1111+let home = Unix.getenv "HOME"
1212+1313+let state_dir fs type' =
1414+ let path = Eio.Path.(fs / home / ".cache/cshell" / type') in
1515+ Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 path;
1616+ path
1717+1818+let () =
1919+ Eio_posix.run @@ fun env ->
2020+ Fmt_tty.setup_std_outputs ();
2121+ match Sys.argv.(1) with
2222+ | "shelter" ->
2323+ let dir = state_dir env#fs "shelter" in
2424+ Shelter.main env#clock env#process_mgr dir
2525+ | _ | (exception Invalid_argument _) ->
2626+ let dir = state_dir env#fs "passthrough" in
2727+ Pass.main env#clock env#process_mgr dir
+33
src/lib/cshell.ml
···11+module History = History
22+module Engine = Engine
33+44+module Make (H : History.S) (Engine : Engine.S with type entry = H.t) = struct
55+ module Store = Irmin_fs_unix.KV.Make (H)
66+77+ let run clock proc store =
88+ let store = History.Store ((module Store), store) in
99+ Engine.init store;
1010+ let rec loop store exit_code =
1111+ let prompt = Engine.prompt exit_code store in
1212+ match LNoise.linenoise prompt with
1313+ | None -> ()
1414+ | Some input -> (
1515+ let action = Engine.action_of_command input in
1616+ match Engine.run clock proc store action with
1717+ | Error (Eio.Process.Child_error exit_code) ->
1818+ Fmt.epr "%a\n%!" Eio.Process.pp_status exit_code;
1919+ loop store exit_code
2020+ | Error (Eio.Process.Executable_not_found m) ->
2121+ Fmt.epr "cshell: excutable not found %s\n%!" m;
2222+ loop store (`Exited 127)
2323+ | Ok store -> loop store (`Exited 0))
2424+ in
2525+ loop store (`Exited 0)
2626+2727+ let main clock proc directory =
2828+ Irmin_fs.run directory @@ fun () ->
2929+ let conf = Irmin_fs.config (Eio.Path.native_exn directory) in
3030+ let repo = Store.Repo.v conf in
3131+ let store = Store.main repo in
3232+ run clock proc store
3333+end
···11+module type S = sig
22+ type action
33+ (** An action to run *)
44+55+ val action : action Repr.t
66+ val action_of_command : string -> action
77+88+ type entry
99+1010+ val init : entry History.t -> unit
1111+ (** [init store] will be called before entering the shell loop. You may wish
1212+ to setup history completions etc. with LNoise. *)
1313+1414+ val run :
1515+ _ Eio.Time.clock ->
1616+ Eio_unix.Process.mgr_ty Eio_unix.Process.mgr ->
1717+ entry History.t ->
1818+ action ->
1919+ (entry History.t, Eio.Process.error) result
2020+ (** [run history action] runs the action in [history]. Return a new [history]
2121+ that can be persisted *)
2222+2323+ val prompt : Eio.Process.exit_status -> entry History.t -> string
2424+ (** [prompt previous_exit_code history] generates a prompt from the current
2525+ [history] *)
2626+end
+17
src/lib/history.ml
···11+module type S = sig
22+ type t
33+ (** A single history entry *)
44+55+ include Irmin.Contents.S with type t := t
66+end
77+88+type 'entry t =
99+ | Store :
1010+ ((module Irmin.S
1111+ with type t = 'a
1212+ and type Schema.Branch.t = string
1313+ and type Schema.Contents.t = 'entry
1414+ and type Schema.Path.t = string list
1515+ and type Schema.Path.step = string)
1616+ * 'a)
1717+ -> 'entry t
+52
src/lib/passthrough/cshell_passthrough.ml
···11+open Eio
22+33+type action = Exec of string [@@deriving repr]
44+55+let action = action_t
66+let action_of_command cmd = Exec cmd
77+88+type entry = string [@@derviving repr]
99+1010+let () = Fmt.set_style_renderer Format.str_formatter `Ansi_tty
1111+1212+let prompt _ _ =
1313+ Fmt.(styled (`Fg `Red) string) Format.str_formatter "cshell> ";
1414+ Format.flush_str_formatter ()
1515+1616+let history_key = [ "history" ]
1717+let key () = history_key @ [ string_of_float @@ Unix.gettimeofday () ]
1818+1919+let init (Cshell.History.Store ((module S), store) : entry Cshell.History.t) =
2020+ match S.list store history_key with
2121+ | [] -> ()
2222+ | xs ->
2323+ let rec loop acc = function
2424+ | `Contents (v, _meta) :: next -> loop (v :: acc) next
2525+ | _ :: next -> loop acc next
2626+ | [] -> List.rev acc
2727+ in
2828+ let entries =
2929+ loop [] (List.map (fun (_, tree) -> S.Tree.to_concrete tree) xs)
3030+ in
3131+ List.iter (fun v -> LNoise.history_add v |> ignore) entries
3232+3333+let run clock proc
3434+ ((Cshell.History.Store ((module S), store) : entry Cshell.History.t) as
3535+ full_store) (Exec command) =
3636+ let info () =
3737+ S.Info.v ~message:"cshell" (Eio.Time.now clock |> Int64.of_float)
3838+ in
3939+ let cmd =
4040+ String.split_on_char ' ' command
4141+ |> List.filter (fun v -> not (String.equal "" v))
4242+ in
4343+ Switch.run @@ fun sw ->
4444+ try
4545+ let proc = Eio.Process.spawn ~sw proc cmd in
4646+ let res = Eio.Process.await proc in
4747+ if res = `Exited 0 then (
4848+ S.set_exn ~info store (key ()) command;
4949+ let _ : (unit, string) result = LNoise.history_add command in
5050+ Ok full_store)
5151+ else Error (Eio.Process.Child_error res)
5252+ with Eio.Exn.Io (Eio.Process.E e, _) -> Error e
+1
src/lib/passthrough/cshell_passthrough.mli
···11+include Cshell.Engine.S with type entry = string
···11+open Eio
22+33+module Build = struct
44+ type cid = Cid.t
55+66+ let cid_of_string s =
77+ match Cid.of_string s with
88+ | Ok v -> v
99+ | Error (`Msg m) -> failwith m
1010+ | Error (`Unsupported _) -> failwith "unsupported cid"
1111+1212+ let cid_t = Repr.map Repr.string cid_of_string Cid.to_string
1313+1414+ type t = Image of string | Build of cid [@@deriving repr]
1515+end
1616+1717+type mode = R | RW [@@deriving repr]
1818+1919+module History = struct
2020+ type t = { mode : mode; build : Build.t; args : string list }
2121+ [@@deriving repr]
2222+2323+ let merge = Irmin.Merge.(default (Repr.option t))
2424+end
2525+2626+type entry = History.t
2727+2828+type action =
2929+ | Set_mode of mode
3030+ | Set_session of string
3131+ | Exec of string list
3232+ | Info
3333+ | Unknown of string list
3434+[@@deriving repr]
3535+3636+let split_and_remove_empty s =
3737+ String.split_on_char ' ' s |> List.filter (fun v -> not (String.equal "" v))
3838+3939+let action = action_t
4040+4141+let shelter_action = function
4242+ | "set" :: "mode" :: [ "r" ] -> Set_mode R
4343+ | "set" :: "mode" :: [ "rw" ] -> Set_mode R
4444+ | "session" :: [ m ] -> Set_session m
4545+ | [ "info" ] -> Info
4646+ | other -> Unknown other
4747+4848+let action_of_command cmd =
4949+ match split_and_remove_empty cmd with
5050+ | "@" :: rest -> shelter_action rest
5151+ | args -> Exec args
5252+5353+let () = Fmt.set_style_renderer Format.str_formatter `Ansi_tty
5454+let history_key = [ "history" ]
5555+let key clock = history_key @ [ string_of_float @@ Eio.Time.now clock ]
5656+5757+let list (Cshell.History.Store ((module S), store) : entry Cshell.History.t) =
5858+ match S.list store history_key with
5959+ | [] -> []
6060+ | xs ->
6161+ let rec loop acc = function
6262+ | (s, `Contents (v, _meta)) :: next -> loop ((s, v) :: acc) next
6363+ | _ :: next -> loop acc next
6464+ | [] -> List.rev acc
6565+ in
6666+ loop [] (List.map (fun (v, tree) -> (v, S.Tree.to_concrete tree)) xs)
6767+ |> List.stable_sort (fun (s1, _) (s2, _) -> String.compare s1 s2)
6868+ |> List.rev
6969+7070+let with_latest ~default s f =
7171+ match list s with [] -> default () | hd :: _ -> f hd
7272+7373+let text c = Fmt.(styled (`Fg c) string)
7474+7575+let sessions (Cshell.History.Store ((module S), store) : entry Cshell.History.t)
7676+ =
7777+ S.Branch.list (S.repo store)
7878+7979+let commit ~message clock
8080+ (Cshell.History.Store ((module S), store) : entry Cshell.History.t) v =
8181+ let info () = S.Info.v ~message (Eio.Time.now clock |> Int64.of_float) in
8282+ S.set_exn ~info store (key clock) v
8383+8484+let which_branch
8585+ ((Cshell.History.Store ((module S), session) : entry Cshell.History.t) as s)
8686+ =
8787+ let branches = sessions s in
8888+ let repo = S.repo session in
8989+ let heads = List.map (fun b -> (S.Branch.find repo b, b)) branches in
9090+ let head = S.Head.find session in
9191+ List.assoc_opt head heads
9292+9393+let prompt status
9494+ ((Cshell.History.Store ((module S), _session) : entry Cshell.History.t) as
9595+ store) =
9696+ let sesh = Option.value ~default:"main" (which_branch store) in
9797+ let prompt () =
9898+ Fmt.(styled (`Fg `Yellow) string) Format.str_formatter "shelter> ";
9999+ Format.flush_str_formatter ()
100100+ in
101101+ let pp_sesh fmt sesh = Fmt.pf fmt "[%a]" (text `Green) sesh in
102102+ let pp_status fmt = function
103103+ | `Exited 0 -> Fmt.nop fmt ()
104104+ | `Exited n -> Fmt.pf fmt "%a " (text `Red) (string_of_int n)
105105+ | _ -> Fmt.nop fmt ()
106106+ in
107107+ let prompt_entry (_, (e : entry)) =
108108+ Fmt.pf Format.str_formatter "%a%a%a : { mode: %a }> " pp_status status
109109+ (text `Yellow) "shelter" pp_sesh sesh (text `Red)
110110+ (if e.mode = R then "r" else "rw");
111111+ Format.flush_str_formatter ()
112112+ in
113113+ with_latest store ~default:prompt prompt_entry
114114+115115+let init s =
116116+ List.iter
117117+ (fun (_, { History.args; _ }) ->
118118+ LNoise.history_add (String.concat " " args) |> ignore)
119119+ (list s)
120120+121121+let run clock proc
122122+ ((Cshell.History.Store ((module S), store) : entry Cshell.History.t) as s) =
123123+ function
124124+ | Set_mode mode ->
125125+ with_latest ~default:(fun _ -> Ok s) s @@ fun (_, entry) ->
126126+ commit ~message:"mode change" clock s { entry with mode };
127127+ Ok s
128128+ | Set_session m ->
129129+ with_latest ~default:(fun _ -> Ok s) s @@ fun (_, entry) ->
130130+ let new_store = S.of_branch (S.repo store) m in
131131+ let new_full_store = Cshell.History.Store ((module S), new_store) in
132132+ commit ~message:"new session" clock new_full_store entry;
133133+ Ok new_full_store
134134+ | Unknown args ->
135135+ Fmt.epr "%a: %s\n%!" (text `Red) "Unknown Shelter Action"
136136+ (String.concat " " args);
137137+ Ok s
138138+ | Info ->
139139+ let sessions = sessions s in
140140+ let sesh = Option.value ~default:"main" (which_branch s) in
141141+ let history = S.history store in
142142+ let pp_commit fmt (hash, msg) =
143143+ Fmt.pf fmt "[%a]: %s" (text `Yellow) hash msg
144144+ in
145145+ let commits =
146146+ S.History.fold_vertex
147147+ (fun commit acc ->
148148+ let info = S.Commit.info commit |> S.Info.message in
149149+ let hash = S.Commit.hash commit |> Repr.to_string S.Hash.t in
150150+ ((String.sub hash 0 7), info) :: acc)
151151+ history []
152152+ in
153153+ Fmt.pr "Sessions: %a\nCurrent: %a\nCommits:@. %a\n%!"
154154+ Fmt.(list ~sep:(Fmt.any ", ") string)
155155+ sessions (text `Green) sesh
156156+ Fmt.(vbox ~indent:2 @@ list pp_commit)
157157+ commits;
158158+ Ok s
159159+ | Exec [] -> Ok s
160160+ | Exec command -> (
161161+ Switch.run @@ fun sw ->
162162+ try
163163+ let proc = Eio.Process.spawn ~sw proc [ "bash"; "-c"; String.concat " " command ] in
164164+ let res = Eio.Process.await proc in
165165+ if res = `Exited 0 then (
166166+ let entry =
167167+ History.{ mode = RW; build = Image "TODO"; args = command }
168168+ in
169169+ commit ~message:("exec " ^ (String.concat " " command)) clock s entry;
170170+ let _ : (unit, string) result =
171171+ LNoise.history_add (String.concat " " command)
172172+ in
173173+ Ok s)
174174+ else Error (Eio.Process.Child_error res)
175175+ with Eio.Exn.Io (Eio.Process.E e, _) -> Error e)
+14
src/lib/shelter/shelter.mli
···11+module Build : sig
22+ type t = Image of string | Build of Cid.t [@@deriving repr]
33+end
44+55+type mode = R | RW
66+77+module History : sig
88+ type t = { mode : mode; build : Build.t; args : string list }
99+ [@@deriving repr]
1010+1111+ include Irmin.Contents.S with type t := t
1212+end
1313+1414+include Cshell.Engine.S with type entry = History.t
···11+# 1.5.1
22+33+- fix a deadlock from 1.5
44+55+# 1.5
66+77+- release runtime lock when calling `lnoise`
88+- fix potential memleaks and use of deprecate parts of
99+ the OCaml C API
1010+- remove dependency on `result`
+19
vendor/ocaml-linenoise/Makefile
···11+22+all: build test
33+44+build:
55+ @dune build @install
66+77+test:
88+ @dune runtest --no-buffer --force
99+1010+example:
1111+ @dune exec examples/show_off.exe
1212+1313+clean:
1414+ @dune clean
1515+1616+doc:
1717+ @dune build @doc
1818+1919+.PHONY: all build test example clean doc
+70
vendor/ocaml-linenoise/README.md
···11+Linenoise in OCaml
22+--------------------
33+44+[](https://github.com/ocaml-community/ocaml-linenoise/actions/workflows/main.yml)
55+66+# Benefits
77+1. BSD licensed.
88+2. No system dependencies, no need for `readline` on your machine.
99+3. Related to 2, these bindings are self-contained, the source for
1010+ `linenoise` is in this repo and compiled all together with the
1111+ `OCaml`.
1212+4. Written in OCaml + C.
1313+5. Pretty cool hints feature, see the gif.
1414+6. Additional features compared to linenoise, such as history search
1515+1616+# Installation
1717+1818+It is easy with `opam`
1919+2020+```shell
2121+$ opam install linenoise
2222+```
2323+2424+See the pretty
2525+documentation [here](https://ocaml-community.github.io/ocaml-linenoise/)
2626+2727+# Example code
2828+This example is also included in the repo under examples:
2929+3030+<p align="center" style='min-width:100%'>
3131+ <img style='min-width:100%' src='example.gif'/>
3232+</p>
3333+3434+3535+```ocaml
3636+let rec user_input prompt cb =
3737+ match LNoise.linenoise prompt with
3838+ | None -> ()
3939+ | Some v ->
4040+ cb v;
4141+ user_input prompt cb
4242+4343+let () =
4444+ (* LNoise.set_multiline true; *)
4545+ LNoise.set_hints_callback (fun line ->
4646+ if line <> "git remote add " then None
4747+ else Some (" <this is the remote name> <this is the remote URL>",
4848+ LNoise.Yellow,
4949+ true)
5050+ );
5151+ LNoise.history_load ~filename:"history.txt" |> ignore;
5252+ LNoise.history_set ~max_length:100 |> ignore;
5353+ LNoise.set_completion_callback begin fun line_so_far ln_completions ->
5454+ if line_so_far <> "" && line_so_far.[0] = 'h' then
5555+ ["Hey"; "Howard"; "Hughes";"Hocus"]
5656+ |> List.iter (LNoise.add_completion ln_completions);
5757+ end;
5858+ ["These are OCaml bindings to linenoise";
5959+ "get tab completion with <TAB>, type h then hit <TAB>";
6060+ "type quit to exit gracefully";
6161+ "By Edgar Aroutiounian\n"]
6262+ |> List.iter print_endline;
6363+ (fun from_user ->
6464+ if from_user = "quit" then exit 0;
6565+ LNoise.history_add from_user |> ignore;
6666+ LNoise.history_save ~filename:"history.txt" |> ignore;
6767+ Printf.sprintf "Got: %s" from_user |> print_endline
6868+ )
6969+ |> user_input "test_program> "
7070+```
···11+22+type completions
33+44+external add_completion : completions -> string -> unit = "ml_add_completion"
55+66+external linenoise : string -> string option = "ml_linenoise"
77+88+external history_add_ : string -> int = "ml_history_add"
99+external history_set_ : max_length:int -> int = "ml_history_set_maxlen"
1010+external history_save_ : filename:string -> int = "ml_history_save"
1111+external history_load_ : filename:string -> int = "ml_history_load"
1212+1313+external catch_break : bool -> unit = "ml_catch_break"
1414+1515+external setup_bridges : unit -> unit = "ml_setup_bridges"
1616+1717+type hint_color = Red | Green | Yellow | Blue | Magenta | Cyan | White
1818+1919+let completion_cb = ref (fun _ _ -> ())
2020+let hints_cb = ref (fun _ -> None)
2121+2222+let set_completion_callback (f:string->completions->unit) : unit =
2323+ completion_cb := f;
2424+ Callback.register "lnoise_completion_cb" f
2525+2626+let set_hints_callback (f:string -> (string*hint_color*bool) option) : unit =
2727+ hints_cb := f;
2828+ Callback.register "lnoise_hints_cb" f
2929+3030+(* initialization: register [Sys.Break] and enable catch-break *)
3131+let () =
3232+ setup_bridges();
3333+ set_completion_callback !completion_cb;
3434+ set_hints_callback !hints_cb;
3535+ Callback.register_exception "sys_break" Sys.Break;
3636+ catch_break true
3737+3838+let history_add h =
3939+ if history_add_ h = 0 then Error "Couldn't add to history"
4040+ else Ok ()
4141+4242+let history_set ~max_length =
4343+ if history_set_ ~max_length = 0
4444+ then Error "Couldn't set the max length of history"
4545+ else Ok ()
4646+4747+let history_save ~filename =
4848+ if history_save_ ~filename = 0 then Ok ()
4949+ else Error "Couldn't save"
5050+5151+let history_load ~filename =
5252+ if history_load_ ~filename = 0 then Ok ()
5353+ else Error "Couldn't load the file"
5454+5555+external clear_screen : unit -> unit = "ml_clearscreen"
5656+external set_multiline : bool -> unit = "ml_set_multiline"
5757+external print_keycodes : unit -> unit = "ml_printkeycodes"
+65
vendor/ocaml-linenoise/src/lNoise.mli
···11+(** OCaml bindings to linenoise, functions that can fail use result
22+ type *)
33+44+(** Abstract type of completions, given to your completion callback *)
55+type completions
66+77+(** This function is used by the callback function registered by the
88+ user in order to add completion options given the input string
99+ when the user typed <TAB>. *)
1010+val add_completion : completions -> string -> unit
1111+1212+(** Register the callback function that is called for upon
1313+ tab-completion, aka when <TAB> is hit in the terminal *)
1414+val set_completion_callback : (string -> completions -> unit) -> unit
1515+1616+(** The high level function that is the main API of the linenoise
1717+ library. This function checks if the terminal has basic
1818+ capabilities, just checking for a blacklist of stupid terminals,
1919+ and later either calls the line editing function or uses dummy
2020+ fgets() so that you will be able to type something even in the
2121+ most desperate of the conditions. *)
2222+val linenoise : string -> string option
2323+2424+(** Add a string to the history *)
2525+val history_add : string -> (unit, string) result
2626+2727+(** Set the maximum length for the history. This function can be
2828+ called even if there is already some history, the function will
2929+ make sure to retain just the latest 'len' elements if the new
3030+ history length value is smaller than the amount of items already
3131+ inside the history. *)
3232+val history_set : max_length:int -> (unit, string) result
3333+3434+(** Save the history in the specified file *)
3535+val history_save : filename:string -> (unit, string) result
3636+3737+(** Load the history from the specified file. *)
3838+val history_load : filename:string -> (unit, string) result
3939+4040+(** Clear the screen; used to handle CTRL+L *)
4141+val clear_screen : unit -> unit
4242+4343+(** If [true], [ctrl-c] during a call to {!linenoise}
4444+ will raise [Sys.Break] instead of returning an empty string.
4545+ @since 1.1 *)
4646+val catch_break : bool -> unit
4747+4848+(** Set if to use or not use the multi line mode. *)
4949+val set_multiline : bool -> unit
5050+5151+(** This special mode is used by linenoise in order to print scan
5252+ codes on screen for debugging / development purposes. *)
5353+val print_keycodes : unit -> unit
5454+5555+(** What color you want the hints to be. *)
5656+type hint_color = Red | Green | Yellow | Blue | Magenta | Cyan | White
5757+5858+(** Set a hints callback, callback gets a string, aka the line input,
5959+ and you get a chance to give a hint to the user. Example, imagine
6060+ if user types git remote add, then you can give a hint of <this is
6161+ where you add a remote name> <this is where you add the remote's
6262+ URL>, see animated gif in source repo for clear example. Returned
6363+ tuple represents the hint message, color, and whether it ought to
6464+ be bold. *)
6565+val set_hints_callback : (string -> (string * hint_color * bool) option) -> unit
+1482
vendor/ocaml-linenoise/src/linenoise_src.c
···11+/* linenoise.c -- guerrilla line editing library against the idea that a
22+ * line editing lib needs to be 20,000 lines of C code.
33+ *
44+ * You can find the latest source code at:
55+ *
66+ * http://github.com/antirez/linenoise
77+ *
88+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
99+ * the 2010 UNIX computers around.
1010+ *
1111+ * ------------------------------------------------------------------------
1212+ *
1313+ * Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
1414+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
1515+ *
1616+ * All rights reserved.
1717+ *
1818+ * Redistribution and use in source and binary forms, with or without
1919+ * modification, are permitted provided that the following conditions are
2020+ * met:
2121+ *
2222+ * * Redistributions of source code must retain the above copyright
2323+ * notice, this list of conditions and the following disclaimer.
2424+ *
2525+ * * Redistributions in binary form must reproduce the above copyright
2626+ * notice, this list of conditions and the following disclaimer in the
2727+ * documentation and/or other materials provided with the distribution.
2828+ *
2929+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
3030+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
3131+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
3232+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3333+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3434+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3535+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3636+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3737+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3838+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3939+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4040+ *
4141+ * ------------------------------------------------------------------------
4242+ *
4343+ * References:
4444+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
4545+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
4646+ *
4747+ * Todo list:
4848+ * - Filter bogus Ctrl+<char> combinations.
4949+ * - Win32 support
5050+ *
5151+ * Bloat:
5252+ * - History search like Ctrl+r in readline?
5353+ *
5454+ * List of escape sequences used by this program, we do everything just
5555+ * with three sequences. In order to be so cheap we may have some
5656+ * flickering effect with some slow terminal, but the lesser sequences
5757+ * the more compatible.
5858+ *
5959+ * EL (Erase Line)
6060+ * Sequence: ESC [ n K
6161+ * Effect: if n is 0 or missing, clear from cursor to end of line
6262+ * Effect: if n is 1, clear from beginning of line to cursor
6363+ * Effect: if n is 2, clear entire line
6464+ *
6565+ * CUF (CUrsor Forward)
6666+ * Sequence: ESC [ n C
6767+ * Effect: moves cursor forward n chars
6868+ *
6969+ * CUB (CUrsor Backward)
7070+ * Sequence: ESC [ n D
7171+ * Effect: moves cursor backward n chars
7272+ *
7373+ * The following is used to get the terminal width if getting
7474+ * the width with the TIOCGWINSZ ioctl fails
7575+ *
7676+ * DSR (Device Status Report)
7777+ * Sequence: ESC [ 6 n
7878+ * Effect: reports the current cusor position as ESC [ n ; m R
7979+ * where n is the row and m is the column
8080+ *
8181+ * When multi line mode is enabled, we also use an additional escape
8282+ * sequence. However multi line editing is disabled by default.
8383+ *
8484+ * CUU (Cursor Up)
8585+ * Sequence: ESC [ n A
8686+ * Effect: moves cursor up of n chars.
8787+ *
8888+ * CUD (Cursor Down)
8989+ * Sequence: ESC [ n B
9090+ * Effect: moves cursor down of n chars.
9191+ *
9292+ * When linenoiseClearScreen() is called, two additional escape sequences
9393+ * are used in order to clear the screen and position the cursor at home
9494+ * position.
9595+ *
9696+ * CUP (Cursor position)
9797+ * Sequence: ESC [ H
9898+ * Effect: moves the cursor to upper left corner
9999+ *
100100+ * ED (Erase display)
101101+ * Sequence: ESC [ 2 J
102102+ * Effect: clear the whole screen
103103+ *
104104+ */
105105+106106+#include <termios.h>
107107+#include <unistd.h>
108108+#include <stdlib.h>
109109+#include <stdio.h>
110110+#include <errno.h>
111111+#include <string.h>
112112+#include <stdlib.h>
113113+#include <ctype.h>
114114+#include <sys/stat.h>
115115+#include <sys/types.h>
116116+#include <sys/ioctl.h>
117117+#include <unistd.h>
118118+#include "linenoise_src.h"
119119+120120+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
121121+#define LINENOISE_MAX_LINE 4096
122122+#define UNUSED(x) (void)(x)
123123+static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
124124+static linenoiseCompletionCallback *completionCallback = NULL;
125125+static linenoiseHintsCallback *hintsCallback = NULL;
126126+static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
127127+static char *linenoiseNoTTY(void);
128128+static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags);
129129+static void refreshLineWithFlags(struct linenoiseState *l, int flags);
130130+131131+static struct termios orig_termios; /* In order to restore at exit.*/
132132+static int maskmode = 0; /* Show "***" instead of input. For passwords. */
133133+static int rawmode = 0; /* For atexit() function to check if restore is needed*/
134134+static int mlmode = 0; /* Multi line mode. Default is single line. */
135135+static int atexit_registered = 0; /* Register atexit just 1 time. */
136136+static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
137137+static int history_len = 0;
138138+static char **history = NULL;
139139+140140+int linenoiseWasInterrupted = 0;
141141+142142+enum KEY_ACTION{
143143+ KEY_NULL = 0, /* NULL */
144144+ CTRL_A = 1, /* Ctrl+a */
145145+ CTRL_B = 2, /* Ctrl-b */
146146+ CTRL_C = 3, /* Ctrl-c */
147147+ CTRL_D = 4, /* Ctrl-d */
148148+ CTRL_E = 5, /* Ctrl-e */
149149+ CTRL_F = 6, /* Ctrl-f */
150150+ CTRL_H = 8, /* Ctrl-h */
151151+ TAB = 9, /* Tab */
152152+ CTRL_K = 11, /* Ctrl+k */
153153+ CTRL_L = 12, /* Ctrl+l */
154154+ ENTER = 13, /* Enter */
155155+ CTRL_N = 14, /* Ctrl-n */
156156+ CTRL_P = 16, /* Ctrl-p */
157157+ CTRL_T = 20, /* Ctrl-t */
158158+ CTRL_U = 21, /* Ctrl+u */
159159+ CTRL_W = 23, /* Ctrl+w */
160160+ ESC = 27, /* Escape */
161161+ BACKSPACE = 127 /* Backspace */
162162+};
163163+164164+static void linenoiseAtExit(void);
165165+int linenoiseHistoryAdd(const char *line);
166166+#define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen
167167+#define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen.
168168+#define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both.
169169+static void refreshLine(struct linenoiseState *l);
170170+171171+/* Debugging macro. */
172172+#if 0
173173+FILE *lndebug_fp = NULL;
174174+#define lndebug(...) \
175175+ do { \
176176+ if (lndebug_fp == NULL) { \
177177+ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
178178+ fprintf(lndebug_fp, \
179179+ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
180180+ (int)l->len,(int)l->pos,(int)l->oldcolpos,plen,rows,rpos, \
181181+ (int)l->oldrows,old_rows); \
182182+ } \
183183+ fprintf(lndebug_fp, ", " __VA_ARGS__); \
184184+ fflush(lndebug_fp); \
185185+ } while (0)
186186+#else
187187+#define lndebug(fmt, ...)
188188+#endif
189189+190190+/* ========================== Encoding functions ============================= */
191191+192192+/* Get byte length and column length of the previous character */
193193+static size_t defaultPrevCharLen(const char *buf, size_t buf_len, size_t pos, size_t *col_len) {
194194+ UNUSED(buf); UNUSED(buf_len); UNUSED(pos);
195195+ if (col_len != NULL) *col_len = 1;
196196+ return 1;
197197+}
198198+199199+/* Get byte length and column length of the next character */
200200+static size_t defaultNextCharLen(const char *buf, size_t buf_len, size_t pos, size_t *col_len) {
201201+ UNUSED(buf); UNUSED(buf_len); UNUSED(pos);
202202+ if (col_len != NULL) *col_len = 1;
203203+ return 1;
204204+}
205205+206206+/* Read bytes of the next character */
207207+static size_t defaultReadCode(int fd, char *buf, size_t buf_len, int* c) {
208208+ if (buf_len < 1) return -1;
209209+ int nread = read(fd,&buf[0],1);
210210+ if (nread == 1) *c = buf[0];
211211+ return nread;
212212+}
213213+214214+/* Set default encoding functions */
215215+static linenoisePrevCharLen *prevCharLen = defaultPrevCharLen;
216216+static linenoiseNextCharLen *nextCharLen = defaultNextCharLen;
217217+static linenoiseReadCode *readCode = defaultReadCode;
218218+219219+/* Set used defined encoding functions */
220220+void linenoiseSetEncodingFunctions(
221221+ linenoisePrevCharLen *prevCharLenFunc,
222222+ linenoiseNextCharLen *nextCharLenFunc,
223223+ linenoiseReadCode *readCodeFunc) {
224224+ prevCharLen = prevCharLenFunc;
225225+ nextCharLen = nextCharLenFunc;
226226+ readCode = readCodeFunc;
227227+}
228228+229229+/* Get column length from begining of buffer to current byte position */
230230+static size_t columnPos(const char *buf, size_t buf_len, size_t pos) {
231231+ size_t ret = 0;
232232+ size_t off = 0;
233233+ while (off < pos) {
234234+ size_t col_len;
235235+ size_t len = nextCharLen(buf,buf_len,off,&col_len);
236236+ off += len;
237237+ ret += col_len;
238238+ }
239239+ return ret;
240240+}
241241+242242+/* Get column length from begining of buffer to current byte position for multiline mode*/
243243+static size_t columnPosForMultiLine(const char *buf, size_t buf_len, size_t pos, size_t cols, size_t ini_pos) {
244244+ size_t ret = 0;
245245+ size_t colwid = ini_pos;
246246+247247+ size_t off = 0;
248248+ while (off < buf_len) {
249249+ size_t col_len;
250250+ size_t len = nextCharLen(buf,buf_len,off,&col_len);
251251+252252+ int dif = (int)(colwid + col_len) - (int)cols;
253253+ if (dif > 0) {
254254+ ret += dif;
255255+ colwid = col_len;
256256+ } else if (dif == 0) {
257257+ colwid = 0;
258258+ } else {
259259+ colwid += col_len;
260260+ }
261261+262262+ if (off >= pos) break;
263263+ off += len;
264264+ ret += col_len;
265265+ }
266266+267267+ return ret;
268268+}
269269+270270+/* ======================= Low level terminal handling ====================== */
271271+272272+/* Enable "mask mode". When it is enabled, instead of the input that
273273+ * the user is typing, the terminal will just display a corresponding
274274+ * number of asterisks, like "****". This is useful for passwords and other
275275+ * secrets that should not be displayed. */
276276+void linenoiseMaskModeEnable(void) {
277277+ maskmode = 1;
278278+}
279279+280280+/* Disable mask mode. */
281281+void linenoiseMaskModeDisable(void) {
282282+ maskmode = 0;
283283+}
284284+285285+/* Set if to use or not the multi line mode. */
286286+void linenoiseSetMultiLine(int ml) {
287287+ mlmode = ml;
288288+}
289289+290290+/* Return true if the terminal name is in the list of terminals we know are
291291+ * not able to understand basic escape sequences. */
292292+static int isUnsupportedTerm(void) {
293293+ char *term = getenv("TERM");
294294+ int j;
295295+296296+ if (term == NULL) return 0;
297297+ for (j = 0; unsupported_term[j]; j++)
298298+ if (!strcasecmp(term,unsupported_term[j])) return 1;
299299+ return 0;
300300+}
301301+302302+/* Raw mode: 1960 magic shit. */
303303+static int enableRawMode(int fd) {
304304+ struct termios raw;
305305+306306+ if (!isatty(STDIN_FILENO)) goto fatal;
307307+ if (!atexit_registered) {
308308+ atexit(linenoiseAtExit);
309309+ atexit_registered = 1;
310310+ }
311311+ if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
312312+313313+ raw = orig_termios; /* modify the original mode */
314314+ /* input modes: no break, no CR to NL, no parity check, no strip char,
315315+ * no start/stop output control. */
316316+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
317317+ /* output modes - disable post processing */
318318+ raw.c_oflag &= ~(OPOST);
319319+ /* control modes - set 8 bit chars */
320320+ raw.c_cflag |= (CS8);
321321+ /* local modes - choing off, canonical off, no extended functions,
322322+ * no signal chars (^Z,^C) */
323323+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
324324+ /* control chars - set return condition: min number of bytes and timer.
325325+ * We want read to return every single byte, without timeout. */
326326+ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
327327+328328+ /* put terminal in raw mode after flushing */
329329+ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
330330+ rawmode = 1;
331331+ return 0;
332332+333333+fatal:
334334+ errno = ENOTTY;
335335+ return -1;
336336+}
337337+338338+static void disableRawMode(int fd) {
339339+ /* Don't even check the return value as it's too late. */
340340+ if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
341341+ rawmode = 0;
342342+}
343343+344344+/* Use the ESC [6n escape sequence to query the horizontal cursor position
345345+ * and return it. On error -1 is returned, on success the position of the
346346+ * cursor. */
347347+static int getCursorPosition(int ifd, int ofd) {
348348+ char buf[32];
349349+ int cols, rows;
350350+ unsigned int i = 0;
351351+352352+ /* Report cursor location */
353353+ if (write(ofd, "\x1b[6n", 4) != 4) return -1;
354354+355355+ /* Read the response: ESC [ rows ; cols R */
356356+ while (i < sizeof(buf)-1) {
357357+ if (read(ifd,buf+i,1) != 1) break;
358358+ if (buf[i] == 'R') break;
359359+ i++;
360360+ }
361361+ buf[i] = '\0';
362362+363363+ /* Parse it. */
364364+ if (buf[0] != ESC || buf[1] != '[') return -1;
365365+ if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
366366+ return cols;
367367+}
368368+369369+/* Try to get the number of columns in the current terminal, or assume 80
370370+ * if it fails. */
371371+static int getColumns(int ifd, int ofd) {
372372+ struct winsize ws;
373373+374374+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
375375+ /* ioctl() failed. Try to query the terminal itself. */
376376+ int start, cols;
377377+378378+ /* Get the initial position so we can restore it later. */
379379+ start = getCursorPosition(ifd,ofd);
380380+ if (start == -1) goto failed;
381381+382382+ /* Go to right margin and get position. */
383383+ if (write(ofd,"\x1b[999C",6) != 6) goto failed;
384384+ cols = getCursorPosition(ifd,ofd);
385385+ if (cols == -1) goto failed;
386386+387387+ /* Restore position. */
388388+ if (cols > start) {
389389+ char seq[32];
390390+ snprintf(seq,32,"\x1b[%dD",cols-start);
391391+ if (write(ofd,seq,strlen(seq)) == -1) {
392392+ /* Can't recover... */
393393+ }
394394+ }
395395+ return cols;
396396+ } else {
397397+ return ws.ws_col;
398398+ }
399399+400400+failed:
401401+ return 80;
402402+}
403403+404404+/* Clear the screen. Used to handle ctrl+l */
405405+void linenoiseClearScreen(void) {
406406+ if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
407407+ /* nothing to do, just to avoid warning. */
408408+ }
409409+}
410410+411411+/* Beep, used for completion when there is nothing to complete or when all
412412+ * the choices were already shown. */
413413+static void linenoiseBeep(void) {
414414+ fprintf(stderr, "\x7");
415415+ fflush(stderr);
416416+}
417417+418418+/* ============================== Completion ================================ */
419419+420420+/* Free a list of completion option populated by linenoiseAddCompletion(). */
421421+static void freeCompletions(linenoiseCompletions *lc) {
422422+ size_t i;
423423+ for (i = 0; i < lc->len; i++)
424424+ free(lc->cvec[i]);
425425+ if (lc->cvec != NULL)
426426+ free(lc->cvec);
427427+}
428428+429429+/* Called by completeLine() and linenoiseShow() to render the current
430430+ * edited line with the proposed completion. If the current completion table
431431+ * is already available, it is passed as second argument, otherwise the
432432+ * function will use the callback to obtain it.
433433+ *
434434+ * Flags are the same as refreshLine*(), that is REFRESH_* macros. */
435435+static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) {
436436+ /* Obtain the table of completions if the caller didn't provide one. */
437437+ linenoiseCompletions ctable = { 0, NULL };
438438+ if (lc == NULL) {
439439+ completionCallback(ls->buf,&ctable);
440440+ lc = &ctable;
441441+ }
442442+443443+ /* Show the edited line with completion if possible, or just refresh. */
444444+ if (ls->completion_idx < lc->len) {
445445+ struct linenoiseState saved = *ls;
446446+ ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]);
447447+ ls->buf = lc->cvec[ls->completion_idx];
448448+ refreshLineWithFlags(ls,flags);
449449+ ls->len = saved.len;
450450+ ls->pos = saved.pos;
451451+ ls->buf = saved.buf;
452452+ } else {
453453+ refreshLineWithFlags(ls,flags);
454454+ }
455455+456456+ /* Free the completions table if needed. */
457457+ if (lc != &ctable) freeCompletions(&ctable);
458458+}
459459+460460+/* This is an helper function for linenoiseEdit*() and is called when the
461461+ * user types the <tab> key in order to complete the string currently in the
462462+ * input.
463463+ *
464464+ * The state of the editing is encapsulated into the pointed linenoiseState
465465+ * structure as described in the structure definition.
466466+ *
467467+ * If the function returns non-zero, the caller should handle the
468468+ * returned value as a byte read from the standard input, and process
469469+ * it as usually: this basically means that the function may return a byte
470470+ * read from the termianl but not processed. Otherwise, if zero is returned,
471471+ * the input was consumed by the completeLine() function to navigate the
472472+ * possible completions, and the caller should read for the next characters
473473+ * from stdin. */
474474+static int completeLine(struct linenoiseState *ls, int keypressed) {
475475+ linenoiseCompletions lc = { 0, NULL };
476476+ int nwritten;
477477+ char c = keypressed;
478478+479479+ completionCallback(ls->buf,&lc);
480480+ if (lc.len == 0) {
481481+ linenoiseBeep();
482482+ ls->in_completion = 0;
483483+ } else {
484484+ switch(c) {
485485+ case 9: /* tab */
486486+ if (ls->in_completion == 0) {
487487+ ls->in_completion = 1;
488488+ ls->completion_idx = 0;
489489+ } else {
490490+ ls->completion_idx = (ls->completion_idx+1) % (lc.len+1);
491491+ if (ls->completion_idx == lc.len) linenoiseBeep();
492492+ }
493493+ c = 0;
494494+ break;
495495+ case 27: /* escape */
496496+ /* Re-show original buffer */
497497+ if (ls->completion_idx < lc.len) refreshLine(ls);
498498+ ls->in_completion = 0;
499499+ c = 0;
500500+ break;
501501+ default:
502502+ /* Update buffer and return */
503503+ if (ls->completion_idx < lc.len) {
504504+ nwritten = snprintf(ls->buf,ls->buflen,"%s",
505505+ lc.cvec[ls->completion_idx]);
506506+ ls->len = ls->pos = nwritten;
507507+ }
508508+ ls->in_completion = 0;
509509+ break;
510510+ }
511511+512512+ /* Show completion or original buffer */
513513+ if (ls->in_completion && ls->completion_idx < lc.len) {
514514+ refreshLineWithCompletion(ls,&lc,REFRESH_ALL);
515515+ } else {
516516+ refreshLine(ls);
517517+ }
518518+ }
519519+520520+ freeCompletions(&lc);
521521+ return c; /* Return last read character */
522522+}
523523+524524+/* Register a callback function to be called for tab-completion. */
525525+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
526526+ completionCallback = fn;
527527+}
528528+529529+/* Register a hits function to be called to show hits to the user at the
530530+ * right of the prompt. */
531531+void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
532532+ hintsCallback = fn;
533533+}
534534+535535+/* Register a function to free the hints returned by the hints callback
536536+ * registered with linenoiseSetHintsCallback(). */
537537+void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
538538+ freeHintsCallback = fn;
539539+}
540540+541541+/* This function is used by the callback function registered by the user
542542+ * in order to add completion options given the input string when the
543543+ * user typed <tab>. See the example.c source code for a very easy to
544544+ * understand example. */
545545+void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
546546+ size_t len = strlen(str);
547547+ char *copy, **cvec;
548548+549549+ copy = malloc(len+1);
550550+ if (copy == NULL) return;
551551+ memcpy(copy,str,len+1);
552552+ cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
553553+ if (cvec == NULL) {
554554+ free(copy);
555555+ return;
556556+ }
557557+ lc->cvec = cvec;
558558+ lc->cvec[lc->len++] = copy;
559559+}
560560+561561+/* =========================== Line editing ================================= */
562562+563563+/* We define a very simple "append buffer" structure, that is an heap
564564+ * allocated string where we can append to. This is useful in order to
565565+ * write all the escape sequences in a buffer and flush them to the standard
566566+ * output in a single call, to avoid flickering effects. */
567567+struct abuf {
568568+ char *b;
569569+ int len;
570570+};
571571+572572+static void abInit(struct abuf *ab) {
573573+ ab->b = NULL;
574574+ ab->len = 0;
575575+}
576576+577577+static void abAppend(struct abuf *ab, const char *s, int len) {
578578+ char *new = realloc(ab->b,ab->len+len);
579579+580580+ if (new == NULL) return;
581581+ memcpy(new+ab->len,s,len);
582582+ ab->b = new;
583583+ ab->len += len;
584584+}
585585+586586+static void abFree(struct abuf *ab) {
587587+ free(ab->b);
588588+}
589589+590590+/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
591591+ * to the right of the prompt. */
592592+void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pcollen) {
593593+ char seq[64];
594594+ size_t collen = pcollen+columnPos(l->buf,l->len,l->len);
595595+ if (hintsCallback && collen < l->cols) {
596596+ int color = -1, bold = 0;
597597+ char *hint = hintsCallback(l->buf,&color,&bold);
598598+ if (hint) {
599599+ int hintlen = strlen(hint);
600600+ int hintmaxlen = l->cols-collen;
601601+ if (hintlen > hintmaxlen) hintlen = hintmaxlen;
602602+ if (bold == 1 && color == -1) color = 37;
603603+ if (color != -1 || bold != 0)
604604+ snprintf(seq,64,"\033[%d;%d;49m",bold,color);
605605+ else
606606+ seq[0] = '\0';
607607+ abAppend(ab,seq,strlen(seq));
608608+ abAppend(ab,hint,hintlen);
609609+ if (color != -1 || bold != 0)
610610+ abAppend(ab,"\033[0m",4);
611611+ /* Call the function to free the hint returned. */
612612+ if (freeHintsCallback) freeHintsCallback(hint);
613613+ }
614614+ }
615615+}
616616+617617+/* Check if text is an ANSI escape sequence
618618+ */
619619+static int isAnsiEscape(const char *buf, size_t buf_len, size_t* len) {
620620+ if (buf_len > 2 && !memcmp("\033[", buf, 2)) {
621621+ size_t off = 2;
622622+ while (off < buf_len) {
623623+ switch (buf[off++]) {
624624+ case 'A': case 'B': case 'C': case 'D': case 'E':
625625+ case 'F': case 'G': case 'H': case 'J': case 'K':
626626+ case 'S': case 'T': case 'f': case 'm':
627627+ *len = off;
628628+ return 1;
629629+ }
630630+ }
631631+ }
632632+ return 0;
633633+}
634634+635635+/* Get column length of prompt text
636636+ */
637637+static size_t promptTextColumnLen(const char *prompt, size_t plen) {
638638+ char buf[LINENOISE_MAX_LINE];
639639+ size_t buf_len = 0;
640640+ size_t off = 0;
641641+ while (off < plen) {
642642+ size_t len;
643643+ if (isAnsiEscape(prompt + off, plen - off, &len)) {
644644+ off += len;
645645+ continue;
646646+ }
647647+ buf[buf_len++] = prompt[off++];
648648+ }
649649+ return columnPos(buf,buf_len,buf_len);
650650+}
651651+652652+/* Single line low level line refresh.
653653+ *
654654+ * Rewrite the currently edited line accordingly to the buffer content,
655655+ * cursor position, and number of columns of the terminal.
656656+ *
657657+ * Flags is REFRESH_* macros. The function can just remove the old
658658+ * prompt, just write it, or both. */
659659+static void refreshSingleLine(struct linenoiseState *l, int flags) {
660660+ char seq[64];
661661+ size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt));
662662+ int fd = l->ofd;
663663+ char *buf = l->buf;
664664+ size_t len = l->len;
665665+ size_t pos = l->pos;
666666+ struct abuf ab;
667667+668668+ while((pcollen+columnPos(buf,len,pos)) >= l->cols) {
669669+ int chlen = nextCharLen(buf,len,0,NULL);
670670+ buf += chlen;
671671+ len -= chlen;
672672+ pos -= chlen;
673673+ }
674674+ while (pcollen+columnPos(buf,len,len) > l->cols) {
675675+ len -= prevCharLen(buf,len,len,NULL);
676676+ }
677677+678678+ abInit(&ab);
679679+ /* Cursor to left edge */
680680+ snprintf(seq,sizeof(seq),"\r");
681681+ abAppend(&ab,seq,strlen(seq));
682682+683683+ if (flags & REFRESH_WRITE) {
684684+ /* Write the prompt and the current buffer content */
685685+ abAppend(&ab,l->prompt,strlen(l->prompt));
686686+ if (maskmode == 1) {
687687+ while (len--) abAppend(&ab,"*",1);
688688+ } else {
689689+ abAppend(&ab,buf,len);
690690+ }
691691+ /* Show hits if any. */
692692+ refreshShowHints(&ab,l,pcollen);
693693+ }
694694+695695+ /* Erase to right */
696696+ snprintf(seq,sizeof(seq),"\x1b[0K");
697697+ abAppend(&ab,seq,strlen(seq));
698698+699699+ if (flags & REFRESH_WRITE) {
700700+ /* Move cursor to original position. */
701701+ snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(columnPos(buf,len,pos)+pcollen));
702702+ abAppend(&ab,seq,strlen(seq));
703703+ }
704704+705705+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
706706+ abFree(&ab);
707707+}
708708+709709+/* Multi line low level line refresh.
710710+ *
711711+ * Rewrite the currently edited line accordingly to the buffer content,
712712+ * cursor position, and number of columns of the terminal.
713713+ *
714714+ * Flags is REFRESH_* macros. The function can just remove the old
715715+ * prompt, just write it, or both. */
716716+static void refreshMultiLine(struct linenoiseState *l, int flags) {
717717+ char seq[64];
718718+ size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt));
719719+ int colpos = columnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcollen);
720720+ int colpos2; /* cursor column position. */
721721+ int rows = (pcollen+colpos+l->cols-1)/l->cols; /* rows used by current buf. */
722722+ int rpos = (pcollen+l->oldcolpos+l->cols)/l->cols; /* cursor relative row. */
723723+ int rpos2; /* rpos after refresh. */
724724+ int col; /* colum position, zero-based. */
725725+ int old_rows = l->oldrows;
726726+ int fd = l->ofd, j;
727727+ struct abuf ab;
728728+729729+ l->oldrows = rows;
730730+731731+ /* First step: clear all the lines used before. To do so start by
732732+ * going to the last row. */
733733+ abInit(&ab);
734734+735735+ if (flags & REFRESH_CLEAN) {
736736+ if (old_rows-rpos > 0) {
737737+ lndebug("go down %d", old_rows-rpos);
738738+ snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
739739+ abAppend(&ab,seq,strlen(seq));
740740+ }
741741+742742+ /* Now for every row clear it, go up. */
743743+ for (j = 0; j < old_rows-1; j++) {
744744+ lndebug("clear+up");
745745+ snprintf(seq,64,"\r\x1b[0K\x1b[1A");
746746+ abAppend(&ab,seq,strlen(seq));
747747+ }
748748+ }
749749+750750+ if (flags & REFRESH_ALL) {
751751+ /* Clean the top line. */
752752+ lndebug("clear");
753753+ snprintf(seq,64,"\r\x1b[0K");
754754+ abAppend(&ab,seq,strlen(seq));
755755+ }
756756+757757+ /* Get column length to cursor position */
758758+ colpos2 = columnPosForMultiLine(l->buf,l->len,l->pos,l->cols,pcollen);
759759+760760+ if (flags & REFRESH_WRITE) {
761761+ /* Write the prompt and the current buffer content */
762762+ abAppend(&ab,l->prompt,strlen(l->prompt));
763763+ if (maskmode == 1) {
764764+ unsigned int i;
765765+ for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
766766+ } else {
767767+ abAppend(&ab,l->buf,l->len);
768768+ }
769769+770770+ /* Show hits if any. */
771771+ refreshShowHints(&ab,l,pcollen);
772772+773773+ /* If we are at the very end of the screen with our prompt, we need to
774774+ * emit a newline and move the prompt to the first column. */
775775+ if (l->pos &&
776776+ l->pos == l->len &&
777777+ (colpos2+pcollen) % l->cols == 0)
778778+ {
779779+ lndebug("<newline>");
780780+ abAppend(&ab,"\n",1);
781781+ snprintf(seq,64,"\r");
782782+ abAppend(&ab,seq,strlen(seq));
783783+ rows++;
784784+ if (rows > (int)l->oldrows) l->oldrows = rows;
785785+ }
786786+787787+ /* Move cursor to right position. */
788788+ rpos2 = (pcollen+colpos2+l->cols)/l->cols; /* Current cursor relative row */
789789+ lndebug("rpos2 %d", rpos2);
790790+791791+ /* Go up till we reach the expected positon. */
792792+ if (rows-rpos2 > 0) {
793793+ lndebug("go-up %d", rows-rpos2);
794794+ snprintf(seq,64,"\x1b[%dA", rows-rpos2);
795795+ abAppend(&ab,seq,strlen(seq));
796796+ }
797797+798798+ /* Set column. */
799799+ col = (pcollen+colpos2) % l->cols;
800800+ lndebug("set col %d", 1+col);
801801+ if (col)
802802+ snprintf(seq,64,"\r\x1b[%dC", col);
803803+ else
804804+ snprintf(seq,64,"\r");
805805+ abAppend(&ab,seq,strlen(seq));
806806+ }
807807+808808+ lndebug("\n");
809809+ l->oldcolpos = colpos2;
810810+811811+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
812812+ abFree(&ab);
813813+}
814814+815815+/* Calls the two low level functions refreshSingleLine() or
816816+ * refreshMultiLine() according to the selected mode. */
817817+static void refreshLineWithFlags(struct linenoiseState *l, int flags) {
818818+ if (mlmode)
819819+ refreshMultiLine(l,flags);
820820+ else
821821+ refreshSingleLine(l,flags);
822822+}
823823+824824+/* Utility function to avoid specifying REFRESH_ALL all the times. */
825825+static void refreshLine(struct linenoiseState *l) {
826826+ refreshLineWithFlags(l,REFRESH_ALL);
827827+}
828828+829829+/* Hide the current line, when using the multiplexing API. */
830830+void linenoiseHide(struct linenoiseState *l) {
831831+ if (mlmode)
832832+ refreshMultiLine(l,REFRESH_CLEAN);
833833+ else
834834+ refreshSingleLine(l,REFRESH_CLEAN);
835835+}
836836+837837+/* Show the current line, when using the multiplexing API. */
838838+void linenoiseShow(struct linenoiseState *l) {
839839+ if (l->in_completion) {
840840+ refreshLineWithCompletion(l,NULL,REFRESH_WRITE);
841841+ } else {
842842+ refreshLineWithFlags(l,REFRESH_WRITE);
843843+ }
844844+}
845845+846846+/* Insert the character 'c' at cursor current position.
847847+ *
848848+ * On error writing to the terminal -1 is returned, otherwise 0. */
849849+int linenoiseEditInsert(struct linenoiseState *l, const char *cbuf, int clen) {
850850+ if (l->len+clen <= l->buflen) {
851851+ if (l->len == l->pos) {
852852+ memcpy(&l->buf[l->pos],cbuf,clen);
853853+ l->pos+=clen;
854854+ l->len+=clen;;
855855+ l->buf[l->len] = '\0';
856856+ if ((!mlmode && promptTextColumnLen(l->prompt,l->plen)+columnPos(l->buf,l->len,l->len) < l->cols && !hintsCallback)) {
857857+ /* Avoid a full update of the line in the
858858+ * trivial case. */
859859+ if (maskmode == 1) {
860860+ static const char d = '*';
861861+ if (write(l->ofd,&d,1) == -1) return -1;
862862+ } else {
863863+ if (write(l->ofd,cbuf,clen) == -1) return -1;
864864+ }
865865+ } else {
866866+ refreshLine(l);
867867+ }
868868+ } else {
869869+ memmove(l->buf+l->pos+clen,l->buf+l->pos,l->len-l->pos);
870870+ memcpy(&l->buf[l->pos],cbuf,clen);
871871+ l->pos+=clen;
872872+ l->len+=clen;
873873+ l->buf[l->len] = '\0';
874874+ refreshLine(l);
875875+ }
876876+ }
877877+ return 0;
878878+}
879879+880880+/* Move cursor on the left. */
881881+void linenoiseEditMoveLeft(struct linenoiseState *l) {
882882+ if (l->pos > 0) {
883883+ l->pos -= prevCharLen(l->buf,l->len,l->pos,NULL);
884884+ refreshLine(l);
885885+ }
886886+}
887887+888888+/* Move cursor on the right. */
889889+void linenoiseEditMoveRight(struct linenoiseState *l) {
890890+ if (l->pos != l->len) {
891891+ l->pos += nextCharLen(l->buf,l->len,l->pos,NULL);
892892+ refreshLine(l);
893893+ }
894894+}
895895+896896+/* Move cursor to the start of the line. */
897897+void linenoiseEditMoveHome(struct linenoiseState *l) {
898898+ if (l->pos != 0) {
899899+ l->pos = 0;
900900+ refreshLine(l);
901901+ }
902902+}
903903+904904+/* Move cursor to the end of the line. */
905905+void linenoiseEditMoveEnd(struct linenoiseState *l) {
906906+ if (l->pos != l->len) {
907907+ l->pos = l->len;
908908+ refreshLine(l);
909909+ }
910910+}
911911+912912+/* Substitute the currently edited line with the next or previous history
913913+ * entry as specified by 'dir'. */
914914+#define LINENOISE_HISTORY_NEXT 0
915915+#define LINENOISE_HISTORY_PREV 1
916916+void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
917917+ if (history_len > 1) {
918918+ /* Update the current history entry before to
919919+ * overwrite it with the next one. */
920920+ free(history[history_len - 1 - l->history_index]);
921921+ history[history_len - 1 - l->history_index] = strdup(l->buf);
922922+ /* Show the new entry */
923923+ l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
924924+ if (l->history_index < 0) {
925925+ l->history_index = 0;
926926+ return;
927927+ } else if (l->history_index >= history_len) {
928928+ l->history_index = history_len-1;
929929+ return;
930930+ }
931931+ strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
932932+ l->buf[l->buflen-1] = '\0';
933933+ l->len = l->pos = strlen(l->buf);
934934+ refreshLine(l);
935935+ }
936936+}
937937+938938+/* Delete the character at the right of the cursor without altering the cursor
939939+ * position. Basically this is what happens with the "Delete" keyboard key. */
940940+void linenoiseEditDelete(struct linenoiseState *l) {
941941+ if (l->len > 0 && l->pos < l->len) {
942942+ int chlen = nextCharLen(l->buf,l->len,l->pos,NULL);
943943+ memmove(l->buf+l->pos,l->buf+l->pos+chlen,l->len-l->pos-chlen);
944944+ l->len-=chlen;
945945+ l->buf[l->len] = '\0';
946946+ refreshLine(l);
947947+ }
948948+}
949949+950950+/* Backspace implementation. */
951951+void linenoiseEditBackspace(struct linenoiseState *l) {
952952+ if (l->pos > 0 && l->len > 0) {
953953+ int chlen = prevCharLen(l->buf,l->len,l->pos,NULL);
954954+ memmove(l->buf+l->pos-chlen,l->buf+l->pos,l->len-l->pos);
955955+ l->pos-=chlen;
956956+ l->len-=chlen;
957957+ l->buf[l->len] = '\0';
958958+ refreshLine(l);
959959+ }
960960+}
961961+962962+/* Delete the previosu word, maintaining the cursor at the start of the
963963+ * current word. */
964964+void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
965965+ size_t old_pos = l->pos;
966966+ size_t diff;
967967+968968+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
969969+ l->pos--;
970970+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
971971+ l->pos--;
972972+ diff = old_pos - l->pos;
973973+ memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
974974+ l->len -= diff;
975975+ refreshLine(l);
976976+}
977977+978978+/* This function is part of the multiplexed API of Linenoise, that is used
979979+ * in order to implement the blocking variant of the API but can also be
980980+ * called by the user directly in an event driven program. It will:
981981+ *
982982+ * 1. Initialize the linenoise state passed by the user.
983983+ * 2. Put the terminal in RAW mode.
984984+ * 3. Show the prompt.
985985+ * 4. Return control to the user, that will have to call linenoiseEditFeed()
986986+ * each time there is some data arriving in the standard input.
987987+ *
988988+ * The user can also call linenoiseEditHide() and linenoiseEditShow() if it
989989+ * is required to show some input arriving asyncronously, without mixing
990990+ * it with the currently edited line.
991991+ *
992992+ * When linenoiseEditFeed() returns non-NULL, the user finished with the
993993+ * line editing session (pressed enter CTRL-D/C): in this case the caller
994994+ * needs to call linenoiseEditStop() to put back the terminal in normal
995995+ * mode. This will not destroy the buffer, as long as the linenoiseState
996996+ * is still valid in the context of the caller.
997997+ *
998998+ * The function returns 0 on success, or -1 if writing to standard output
999999+ * fails. If stdin_fd or stdout_fd are set to -1, the default is to use
10001000+ * STDIN_FILENO and STDOUT_FILENO.
10011001+ */
10021002+int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) {
10031003+ /* Populate the linenoise state that we pass to functions implementing
10041004+ * specific editing functionalities. */
10051005+ l->in_completion = 0;
10061006+ l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO;
10071007+ l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO;
10081008+ l->buf = buf;
10091009+ l->buflen = buflen;
10101010+ l->prompt = prompt;
10111011+ l->plen = strlen(prompt);
10121012+ l->oldcolpos = l->pos = 0;
10131013+ l->len = 0;
10141014+10151015+ /* Enter raw mode. */
10161016+ if (enableRawMode(l->ifd) == -1) return -1;
10171017+10181018+ l->cols = getColumns(stdin_fd, stdout_fd);
10191019+ l->oldrows = 0;
10201020+ l->history_index = 0;
10211021+10221022+ /* Buffer starts empty. */
10231023+ l->buf[0] = '\0';
10241024+ l->buflen--; /* Make sure there is always space for the nulterm */
10251025+10261026+ /* If stdin is not a tty, stop here with the initialization. We
10271027+ * will actually just read a line from standard input in blocking
10281028+ * mode later, in linenoiseEditFeed(). */
10291029+ if (!isatty(l->ifd)) return 0;
10301030+10311031+ /* The latest history entry is always our current buffer, that
10321032+ * initially is just an empty string. */
10331033+ linenoiseHistoryAdd("");
10341034+10351035+ if (write(l->ofd,prompt,l->plen) == -1) return -1;
10361036+ return 0;
10371037+}
10381038+10391039+char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information.";
10401040+10411041+/* This function is part of the multiplexed API of linenoise, see the top
10421042+ * comment on linenoiseEditStart() for more information. Call this function
10431043+ * each time there is some data to read from the standard input file
10441044+ * descriptor. In the case of blocking operations, this function can just be
10451045+ * called in a loop, and block.
10461046+ *
10471047+ * The function returns linenoiseEditMore to signal that line editing is still
10481048+ * in progress, that is, the user didn't yet pressed enter / CTRL-D. Otherwise
10491049+ * the function returns the pointer to the heap-allocated buffer with the
10501050+ * edited line, that the user should free with linenoiseFree().
10511051+ *
10521052+ * On special conditions, NULL is returned and errno is populated:
10531053+ *
10541054+ * EAGAIN if the user pressed Ctrl-C
10551055+ * ENOENT if the user pressed Ctrl-D
10561056+ *
10571057+ * Some other errno: I/O error.
10581058+ */
10591059+char *linenoiseEditFeed(struct linenoiseState *l) {
10601060+ /* Not a TTY, pass control to line reading without character
10611061+ * count limits. */
10621062+ if (!isatty(l->ifd)) return linenoiseNoTTY();
10631063+10641064+ int c;
10651065+ int nread;
10661066+ char cbuf[32]; // large enough for any encoding?
10671067+ char seq[3];
10681068+10691069+ nread = readCode(l->ifd,cbuf,sizeof(cbuf),&c);
10701070+ if (nread <= 0) return NULL;
10711071+10721072+ /* Only autocomplete when the callback is set. It returns < 0 when
10731073+ * there was an error reading from fd. Otherwise it will return the
10741074+ * character that should be handled next. */
10751075+ if ((l->in_completion || c == 9) && completionCallback != NULL) {
10761076+ c = completeLine(l,c);
10771077+ /* Return on errors */
10781078+ if (c < 0) return NULL;
10791079+ /* Read next character when 0 */
10801080+ if (c == 0) return linenoiseEditMore;
10811081+ }
10821082+10831083+ switch(c) {
10841084+ case ENTER: /* enter */
10851085+ history_len--;
10861086+ free(history[history_len]);
10871087+ if (mlmode) linenoiseEditMoveEnd(l);
10881088+ if (hintsCallback) {
10891089+ /* Force a refresh without hints to leave the previous
10901090+ * line as the user typed it after a newline. */
10911091+ linenoiseHintsCallback *hc = hintsCallback;
10921092+ hintsCallback = NULL;
10931093+ refreshLine(l);
10941094+ hintsCallback = hc;
10951095+ }
10961096+ return strdup(l->buf);
10971097+ case CTRL_C: /* ctrl-c */
10981098+ errno = EAGAIN;
10991099+ linenoiseWasInterrupted = 1;
11001100+ return NULL;
11011101+ case BACKSPACE: /* backspace */
11021102+ case 8: /* ctrl-h */
11031103+ linenoiseEditBackspace(l);
11041104+ break;
11051105+ case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
11061106+ line is empty, act as end-of-file. */
11071107+ if (l->len > 0) {
11081108+ linenoiseEditDelete(l);
11091109+ } else {
11101110+ history_len--;
11111111+ free(history[history_len]);
11121112+ errno = ENOENT;
11131113+ return NULL;
11141114+ }
11151115+ break;
11161116+ case CTRL_T: /* ctrl-t, swaps current character with previous. */
11171117+ if (l->pos > 0 && l->pos < l->len) {
11181118+ int aux = l->buf[l->pos-1];
11191119+ l->buf[l->pos-1] = l->buf[l->pos];
11201120+ l->buf[l->pos] = aux;
11211121+ if (l->pos != l->len-1) l->pos++;
11221122+ refreshLine(l);
11231123+ }
11241124+ break;
11251125+ case CTRL_B: /* ctrl-b */
11261126+ linenoiseEditMoveLeft(l);
11271127+ break;
11281128+ case CTRL_F: /* ctrl-f */
11291129+ linenoiseEditMoveRight(l);
11301130+ break;
11311131+ case CTRL_P: /* ctrl-p */
11321132+ linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV);
11331133+ break;
11341134+ case CTRL_N: /* ctrl-n */
11351135+ linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT);
11361136+ break;
11371137+ case ESC: /* escape sequence */
11381138+ /* Read the next two bytes representing the escape sequence.
11391139+ * Use two calls to handle slow terminals returning the two
11401140+ * chars at different times. */
11411141+ if (read(l->ifd,seq,1) == -1) break;
11421142+ if (read(l->ifd,seq+1,1) == -1) break;
11431143+11441144+ /* ESC [ sequences. */
11451145+ if (seq[0] == '[') {
11461146+ if (seq[1] >= '0' && seq[1] <= '9') {
11471147+ /* Extended escape, read additional byte. */
11481148+ if (read(l->ifd,seq+2,1) == -1) break;
11491149+ if (seq[2] == '~') {
11501150+ switch(seq[1]) {
11511151+ case '3': /* Delete key. */
11521152+ linenoiseEditDelete(l);
11531153+ break;
11541154+ }
11551155+ }
11561156+ } else {
11571157+ switch(seq[1]) {
11581158+ case 'A': /* Up */
11591159+ linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV);
11601160+ break;
11611161+ case 'B': /* Down */
11621162+ linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT);
11631163+ break;
11641164+ case 'C': /* Right */
11651165+ linenoiseEditMoveRight(l);
11661166+ break;
11671167+ case 'D': /* Left */
11681168+ linenoiseEditMoveLeft(l);
11691169+ break;
11701170+ case 'H': /* Home */
11711171+ linenoiseEditMoveHome(l);
11721172+ break;
11731173+ case 'F': /* End*/
11741174+ linenoiseEditMoveEnd(l);
11751175+ break;
11761176+ }
11771177+ }
11781178+ }
11791179+11801180+ /* ESC O sequences. */
11811181+ else if (seq[0] == 'O') {
11821182+ switch(seq[1]) {
11831183+ case 'H': /* Home */
11841184+ linenoiseEditMoveHome(l);
11851185+ break;
11861186+ case 'F': /* End*/
11871187+ linenoiseEditMoveEnd(l);
11881188+ break;
11891189+ }
11901190+ }
11911191+ break;
11921192+ default:
11931193+ if (linenoiseEditInsert(l,cbuf,nread)) return NULL;
11941194+ break;
11951195+ case CTRL_U: /* Ctrl+u, delete the whole line. */
11961196+ l->buf[0] = '\0';
11971197+ l->pos = l->len = 0;
11981198+ refreshLine(l);
11991199+ break;
12001200+ case CTRL_K: /* Ctrl+k, delete from current to end of line. */
12011201+ l->buf[l->pos] = '\0';
12021202+ l->len = l->pos;
12031203+ refreshLine(l);
12041204+ break;
12051205+ case CTRL_A: /* Ctrl+a, go to the start of the line */
12061206+ linenoiseEditMoveHome(l);
12071207+ break;
12081208+ case CTRL_E: /* ctrl+e, go to the end of the line */
12091209+ linenoiseEditMoveEnd(l);
12101210+ break;
12111211+ case CTRL_L: /* ctrl+l, clear screen */
12121212+ linenoiseClearScreen();
12131213+ refreshLine(l);
12141214+ break;
12151215+ case CTRL_W: /* ctrl+w, delete previous word */
12161216+ linenoiseEditDeletePrevWord(l);
12171217+ break;
12181218+ }
12191219+ return linenoiseEditMore;
12201220+}
12211221+12221222+/* This is part of the multiplexed linenoise API. See linenoiseEditStart()
12231223+ * for more information. This function is called when linenoiseEditFeed()
12241224+ * returns something different than NULL. At this point the user input
12251225+ * is in the buffer, and we can restore the terminal in normal mode. */
12261226+void linenoiseEditStop(struct linenoiseState *l) {
12271227+ if (!isatty(l->ifd)) return;
12281228+ disableRawMode(l->ifd);
12291229+ printf("\n");
12301230+}
12311231+12321232+/* This just implements a blocking loop for the multiplexed API.
12331233+ * In many applications that are not event-drivern, we can just call
12341234+ * the blocking linenoise API, wait for the user to complete the editing
12351235+ * and return the buffer. */
12361236+static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
12371237+{
12381238+ struct linenoiseState l;
12391239+12401240+ /* Editing without a buffer is invalid. */
12411241+ if (buflen == 0) {
12421242+ errno = EINVAL;
12431243+ return NULL;
12441244+ }
12451245+12461246+ linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt);
12471247+ char *res;
12481248+ while((res = linenoiseEditFeed(&l)) == linenoiseEditMore);
12491249+ linenoiseEditStop(&l);
12501250+ return res;
12511251+}
12521252+12531253+/* This special mode is used by linenoise in order to print scan codes
12541254+ * on screen for debugging / development purposes. It is implemented
12551255+ * by the linenoise_example program using the --keycodes option. */
12561256+void linenoisePrintKeyCodes(void) {
12571257+ char quit[4];
12581258+12591259+ printf("Linenoise key codes debugging mode.\n"
12601260+ "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
12611261+ if (enableRawMode(STDIN_FILENO) == -1) return;
12621262+ memset(quit,' ',4);
12631263+ while(1) {
12641264+ char c;
12651265+ int nread;
12661266+12671267+ nread = read(STDIN_FILENO,&c,1);
12681268+ if (nread <= 0) continue;
12691269+ memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
12701270+ quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
12711271+ if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
12721272+12731273+ printf("'%c' %02x (%d) (type quit to exit)\n",
12741274+ isprint((int)c) ? c : '?', (int)c, (int)c);
12751275+ printf("\r"); /* Go left edge manually, we are in raw mode. */
12761276+ fflush(stdout);
12771277+ }
12781278+ disableRawMode(STDIN_FILENO);
12791279+}
12801280+12811281+/* This function is called when linenoise() is called with the standard
12821282+ * input file descriptor not attached to a TTY. So for example when the
12831283+ * program using linenoise is called in pipe or with a file redirected
12841284+ * to its standard input. In this case, we want to be able to return the
12851285+ * line regardless of its length (by default we are limited to 4k). */
12861286+static char *linenoiseNoTTY(void) {
12871287+ char *line = NULL;
12881288+ size_t len = 0, maxlen = 0;
12891289+12901290+ while(1) {
12911291+ if (len == maxlen) {
12921292+ if (maxlen == 0) maxlen = 16;
12931293+ maxlen *= 2;
12941294+ char *oldval = line;
12951295+ line = realloc(line,maxlen);
12961296+ if (line == NULL) {
12971297+ if (oldval) free(oldval);
12981298+ return NULL;
12991299+ }
13001300+ }
13011301+ int c = fgetc(stdin);
13021302+ if (c == EOF || c == '\n') {
13031303+ if (c == EOF && len == 0) {
13041304+ free(line);
13051305+ return NULL;
13061306+ } else {
13071307+ line[len] = '\0';
13081308+ return line;
13091309+ }
13101310+ } else {
13111311+ line[len] = c;
13121312+ len++;
13131313+ }
13141314+ }
13151315+}
13161316+13171317+/* The high level function that is the main API of the linenoise library.
13181318+ * This function checks if the terminal has basic capabilities, just checking
13191319+ * for a blacklist of stupid terminals, and later either calls the line
13201320+ * editing function or uses dummy fgets() so that you will be able to type
13211321+ * something even in the most desperate of the conditions. */
13221322+char *linenoise(const char *prompt) {
13231323+ char buf[LINENOISE_MAX_LINE];
13241324+13251325+ if (!isatty(STDIN_FILENO)) {
13261326+ /* Not a tty: read from file / pipe. In this mode we don't want any
13271327+ * limit to the line size, so we call a function to handle that. */
13281328+ return linenoiseNoTTY();
13291329+ } else if (isUnsupportedTerm()) {
13301330+ size_t len;
13311331+13321332+ printf("%s",prompt);
13331333+ fflush(stdout);
13341334+ if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
13351335+ len = strlen(buf);
13361336+ while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
13371337+ len--;
13381338+ buf[len] = '\0';
13391339+ }
13401340+ return strdup(buf);
13411341+ } else {
13421342+ char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt);
13431343+ return retval;
13441344+ }
13451345+}
13461346+13471347+/* This is just a wrapper the user may want to call in order to make sure
13481348+ * the linenoise returned buffer is freed with the same allocator it was
13491349+ * created with. Useful when the main program is using an alternative
13501350+ * allocator. */
13511351+void linenoiseFree(void *ptr) {
13521352+ if (ptr == linenoiseEditMore) return; // Protect from API misuse.
13531353+ free(ptr);
13541354+}
13551355+13561356+/* ================================ History ================================= */
13571357+13581358+/* Free the history, but does not reset it. Only used when we have to
13591359+ * exit() to avoid memory leaks are reported by valgrind & co. */
13601360+static void freeHistory(void) {
13611361+ if (history) {
13621362+ int j;
13631363+13641364+ for (j = 0; j < history_len; j++)
13651365+ free(history[j]);
13661366+ free(history);
13671367+ }
13681368+}
13691369+13701370+/* At exit we'll try to fix the terminal to the initial conditions. */
13711371+static void linenoiseAtExit(void) {
13721372+ disableRawMode(STDIN_FILENO);
13731373+ freeHistory();
13741374+}
13751375+13761376+/* This is the API call to add a new entry in the linenoise history.
13771377+ * It uses a fixed array of char pointers that are shifted (memmoved)
13781378+ * when the history max length is reached in order to remove the older
13791379+ * entry and make room for the new one, so it is not exactly suitable for huge
13801380+ * histories, but will work well for a few hundred of entries.
13811381+ *
13821382+ * Using a circular buffer is smarter, but a bit more complex to handle. */
13831383+int linenoiseHistoryAdd(const char *line) {
13841384+ char *linecopy;
13851385+13861386+ if (history_max_len == 0) return 0;
13871387+13881388+ /* Initialization on first call. */
13891389+ if (history == NULL) {
13901390+ history = malloc(sizeof(char*)*history_max_len);
13911391+ if (history == NULL) return 0;
13921392+ memset(history,0,(sizeof(char*)*history_max_len));
13931393+ }
13941394+13951395+ /* Don't add duplicated lines. */
13961396+ if (history_len && !strcmp(history[history_len-1], line)) return 0;
13971397+13981398+ /* Add an heap allocated copy of the line in the history.
13991399+ * If we reached the max length, remove the older line. */
14001400+ linecopy = strdup(line);
14011401+ if (!linecopy) return 0;
14021402+ if (history_len == history_max_len) {
14031403+ free(history[0]);
14041404+ memmove(history,history+1,sizeof(char*)*(history_max_len-1));
14051405+ history_len--;
14061406+ }
14071407+ history[history_len] = linecopy;
14081408+ history_len++;
14091409+ return 1;
14101410+}
14111411+14121412+/* Set the maximum length for the history. This function can be called even
14131413+ * if there is already some history, the function will make sure to retain
14141414+ * just the latest 'len' elements if the new history length value is smaller
14151415+ * than the amount of items already inside the history. */
14161416+int linenoiseHistorySetMaxLen(int len) {
14171417+ char **new;
14181418+14191419+ if (len < 1) return 0;
14201420+ if (history) {
14211421+ int tocopy = history_len;
14221422+14231423+ new = malloc(sizeof(char*)*len);
14241424+ if (new == NULL) return 0;
14251425+14261426+ /* If we can't copy everything, free the elements we'll not use. */
14271427+ if (len < tocopy) {
14281428+ int j;
14291429+14301430+ for (j = 0; j < tocopy-len; j++) free(history[j]);
14311431+ tocopy = len;
14321432+ }
14331433+ memset(new,0,sizeof(char*)*len);
14341434+ memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
14351435+ free(history);
14361436+ history = new;
14371437+ }
14381438+ history_max_len = len;
14391439+ if (history_len > history_max_len)
14401440+ history_len = history_max_len;
14411441+ return 1;
14421442+}
14431443+14441444+/* Save the history in the specified file. On success 0 is returned
14451445+ * otherwise -1 is returned. */
14461446+int linenoiseHistorySave(const char *filename) {
14471447+ mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
14481448+ FILE *fp;
14491449+ int j;
14501450+14511451+ fp = fopen(filename,"w");
14521452+ umask(old_umask);
14531453+ if (fp == NULL) return -1;
14541454+ chmod(filename,S_IRUSR|S_IWUSR);
14551455+ for (j = 0; j < history_len; j++)
14561456+ fprintf(fp,"%s\n",history[j]);
14571457+ fclose(fp);
14581458+ return 0;
14591459+}
14601460+14611461+/* Load the history from the specified file. If the file does not exist
14621462+ * zero is returned and no operation is performed.
14631463+ *
14641464+ * If the file exists and the operation succeeded 0 is returned, otherwise
14651465+ * on error -1 is returned. */
14661466+int linenoiseHistoryLoad(const char *filename) {
14671467+ FILE *fp = fopen(filename,"r");
14681468+ char buf[LINENOISE_MAX_LINE];
14691469+14701470+ if (fp == NULL) return -1;
14711471+14721472+ while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
14731473+ char *p;
14741474+14751475+ p = strchr(buf,'\r');
14761476+ if (!p) p = strchr(buf,'\n');
14771477+ if (p) *p = '\0';
14781478+ linenoiseHistoryAdd(buf);
14791479+ }
14801480+ fclose(fp);
14811481+ return 0;
14821482+}
+123
vendor/ocaml-linenoise/src/linenoise_src.h
···11+/* linenoise.h -- VERSION 1.0
22+ *
33+ * Guerrilla line editing library against the idea that a line editing lib
44+ * needs to be 20,000 lines of C code.
55+ *
66+ * See linenoise.c for more information.
77+ *
88+ * ------------------------------------------------------------------------
99+ *
1010+ * Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
1111+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
1212+ *
1313+ * All rights reserved.
1414+ *
1515+ * Redistribution and use in source and binary forms, with or without
1616+ * modification, are permitted provided that the following conditions are
1717+ * met:
1818+ *
1919+ * * Redistributions of source code must retain the above copyright
2020+ * notice, this list of conditions and the following disclaimer.
2121+ *
2222+ * * Redistributions in binary form must reproduce the above copyright
2323+ * notice, this list of conditions and the following disclaimer in the
2424+ * documentation and/or other materials provided with the distribution.
2525+ *
2626+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2727+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2828+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2929+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3030+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3131+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3232+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3333+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3434+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3535+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3636+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3737+ */
3838+3939+#ifndef __LINENOISE_H
4040+#define __LINENOISE_H
4141+4242+#ifdef __cplusplus
4343+extern "C" {
4444+#endif
4545+4646+#include <stddef.h> /* For size_t. */
4747+4848+extern int linenoiseWasInterrupted; /* boolean signalling if last call was ctrl-c */
4949+extern char *linenoiseEditMore;
5050+5151+/* The linenoiseState structure represents the state during line editing.
5252+ * We pass this state to functions implementing specific editing
5353+ * functionalities. */
5454+struct linenoiseState {
5555+ int in_completion; /* The user pressed TAB and we are now in completion
5656+ * mode, so input is handled by completeLine(). */
5757+ size_t completion_idx; /* Index of next completion to propose. */
5858+ int ifd; /* Terminal stdin file descriptor. */
5959+ int ofd; /* Terminal stdout file descriptor. */
6060+ char *buf; /* Edited line buffer. */
6161+ size_t buflen; /* Edited line buffer size. */
6262+ const char *prompt; /* Prompt to display. */
6363+ size_t plen; /* Prompt length. */
6464+ size_t pos; /* Current cursor position. */
6565+ size_t oldcolpos; /* Previous refresh cursor column position. */
6666+ size_t len; /* Current edited line length. */
6767+ size_t cols; /* Number of columns in terminal. */
6868+ size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */
6969+ int history_index; /* The history index we are currently editing. */
7070+};
7171+7272+typedef struct linenoiseCompletions {
7373+ size_t len;
7474+ char **cvec;
7575+} linenoiseCompletions;
7676+7777+/* Non blocking API. */
7878+int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt);
7979+char *linenoiseEditFeed(struct linenoiseState *l);
8080+void linenoiseEditStop(struct linenoiseState *l);
8181+void linenoiseHide(struct linenoiseState *l);
8282+void linenoiseShow(struct linenoiseState *l);
8383+8484+/* Blocking API. */
8585+char *linenoise(const char *prompt);
8686+void linenoiseFree(void *ptr);
8787+8888+/* Completion API. */
8989+typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
9090+typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
9191+typedef void(linenoiseFreeHintsCallback)(void *);
9292+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
9393+void linenoiseSetHintsCallback(linenoiseHintsCallback *);
9494+void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
9595+void linenoiseAddCompletion(linenoiseCompletions *, const char *);
9696+9797+/* History API. */
9898+int linenoiseHistoryAdd(const char *line);
9999+int linenoiseHistorySetMaxLen(int len);
100100+int linenoiseHistorySave(const char *filename);
101101+int linenoiseHistoryLoad(const char *filename);
102102+103103+/* Other utilities. */
104104+void linenoiseClearScreen(void);
105105+void linenoiseSetMultiLine(int ml);
106106+void linenoisePrintKeyCodes(void);
107107+void linenoiseMaskModeEnable(void);
108108+void linenoiseMaskModeDisable(void);
109109+110110+typedef size_t (linenoisePrevCharLen)(const char *buf, size_t buf_len, size_t pos, size_t *col_len);
111111+typedef size_t (linenoiseNextCharLen)(const char *buf, size_t buf_len, size_t pos, size_t *col_len);
112112+typedef size_t (linenoiseReadCode)(int fd, char *buf, size_t buf_len, int* c);
113113+114114+void linenoiseSetEncodingFunctions(
115115+ linenoisePrevCharLen *prevCharLenFunc,
116116+ linenoiseNextCharLen *nextCharLenFunc,
117117+ linenoiseReadCode *readCodeFunc);
118118+119119+#ifdef __cplusplus
120120+}
121121+#endif
122122+123123+#endif /* __LINENOISE_H */
···11+/* encodings/utf8.h -- VERSION 1.0
22+ *
33+ * Guerrilla line editing library against the idea that a line editing lib
44+ * needs to be 20,000 lines of C code.
55+ *
66+ * See linenoise.c for more information.
77+ *
88+ * ------------------------------------------------------------------------
99+ *
1010+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
1111+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
1212+ *
1313+ * All rights reserved.
1414+ *
1515+ * Redistribution and use in source and binary forms, with or without
1616+ * modification, are permitted provided that the following conditions are
1717+ * met:
1818+ *
1919+ * * Redistributions of source code must retain the above copyright
2020+ * notice, this list of conditions and the following disclaimer.
2121+ *
2222+ * * Redistributions in binary form must reproduce the above copyright
2323+ * notice, this list of conditions and the following disclaimer in the
2424+ * documentation and/or other materials provided with the distribution.
2525+ *
2626+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2727+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2828+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2929+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3030+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3131+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3232+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3333+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3434+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3535+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3636+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3737+ */
3838+3939+#ifndef __LINENOISE_ENCODINGS_UTF8_H
4040+#define __LINENOISE_ENCODINGS_UTF8_H
4141+4242+#ifdef __cplusplus
4343+extern "C" {
4444+#endif
4545+4646+size_t linenoiseUtf8PrevCharLen(const char* buf, size_t buf_len, size_t pos, size_t *col_len);
4747+size_t linenoiseUtf8NextCharLen(const char* buf, size_t buf_len, size_t pos, size_t *col_len);
4848+size_t linenoiseUtf8ReadCode(int fd, char* buf, size_t buf_len, int* cp);
4949+5050+#ifdef __cplusplus
5151+}
5252+#endif
5353+5454+#endif /* __LINENOISE_ENCODINGS_UTF8_H */
5555+