Move Lines API into a separate module, add dummy Horizontal_fill

This commit is contained in:
Dmitrii Kovanikov 2024-04-07 11:50:38 +01:00
parent 9ce06264bd
commit e7b216cdfb
7 changed files with 119 additions and 58 deletions

39
lib/line.ml Normal file
View File

@ -0,0 +1,39 @@
type styles = ANSITerminal.style list
type chunk = {
styles : styles;
string : string;
}
let fmt_chunk { styles; string } = ANSITerminal.sprintf styles "%s" string
let padding_chunk width =
let padding = String.make width ' ' in
{ styles = []; string = padding }
type t = {
chunks : chunk list;
length : int;
}
let length line = line.length
let of_chunks chunks =
let length =
List.fold_left
(fun acc { string; _ } -> acc + String_extra.graphemes_len string)
0 chunks
in
{ chunks; length }
let prepend_chunk chunk line =
let chunks = chunk :: line.chunks in
let length = String_extra.graphemes_len chunk.string + line.length in
{ chunks; length }
let append line1 line2 =
let chunks = line1.chunks @ line2.chunks in
let length = line1.length + line2.length in
{ chunks; length }
let fmt line = line.chunks |> List.map fmt_chunk |> String.concat ""

32
lib/line.mli Normal file
View File

@ -0,0 +1,32 @@
(** Formatting of a string. *)
type styles = ANSITerminal.style list
(** A part of a line with the formatting added to it. *)
type chunk = {
styles : styles;
string : string;
}
(** [padding_chunk n] created a [chunk] without formatting of [n] spaces. *)
val padding_chunk : int -> chunk
(** A type defining a single line of text with different parts ("chunks") having
potentially different formatting. *)
type t
(** Returns the length of a string as in the number of graphemes (before
formatting applied). *)
val length : t -> int
(** Smart constructor for a line from a list of chunks to calculate the final
[length] automatically as well. *)
val of_chunks : chunk list -> t
(** Add a chunk to the beginning of a line. *)
val prepend_chunk : chunk -> t -> t
(** Append two lines into a single line. *)
val append : t -> t -> t
(** Format a single line as string, applying formatting. *)
val fmt : t -> string

View File

@ -1,9 +1,6 @@
(* A simple pretty printing combinator library *)
type styles = ANSITerminal.style list
type doc =
| Str of styles * string
| Str of Line.styles * string
| Horizontal_fill
| Vertical of doc list
| Horizontal of doc list
@ -11,29 +8,10 @@ let str string = Str ([], string)
let fmt styles string = Str (styles, string)
let horizontal cols = Horizontal cols
let vertical rows = Vertical rows
let horizontal_fill = Horizontal_fill
type chunk = {
styles : styles;
string : string;
}
let fmt_chunk { styles; string } = ANSITerminal.sprintf styles "%s" string
let mk_padding_chunk n =
let padding = String.make n ' ' in
{ styles = []; string = padding }
type line = { chunks : chunk list }
let fmt_line line = line.chunks |> List.map fmt_chunk |> String.concat ""
let line_len line =
List.fold_left
(fun acc { string; _ } -> acc + String_extra.graphemes_len string)
0 line.chunks
let zip_lines (l : line list) (r : line list) =
let max_len_l = List.map line_len l |> List.fold_left max 0 in
let zip_lines (l : Line.t list) (r : Line.t list) =
let max_len_l = List.map Line.length l |> List.fold_left max 0 in
let rec zip l r =
match (l, r) with
@ -41,37 +19,38 @@ let zip_lines (l : line list) (r : line list) =
| [], r ->
(* Optimisation: Add extra chunk only if padding is needed *)
if max_len_l > 0 then
let padding_chunk = mk_padding_chunk max_len_l in
List.map (fun line -> { chunks = padding_chunk :: line.chunks }) r
let padding_chunk = Line.padding_chunk max_len_l in
List.map (Line.prepend_chunk padding_chunk) r
else r
| hd_l :: tl_l, hd_r :: tl_r ->
let left_len = line_len hd_l in
let left_len = Line.length hd_l in
(* Optimisation: Combine chunks when left is already max len *)
if left_len >= max_len_l then
let new_line = { chunks = hd_l.chunks @ hd_r.chunks } in
let new_line = Line.append hd_l hd_r in
new_line :: zip tl_l tl_r
else
let padding_chunk = mk_padding_chunk (max_len_l - left_len) in
let padding_chunk = Line.padding_chunk (max_len_l - left_len) in
let new_line =
{ chunks = hd_l.chunks @ [ padding_chunk ] @ hd_r.chunks }
Line.append hd_l (Line.append (Line.of_chunks [ padding_chunk ]) hd_r)
in
new_line :: zip tl_l tl_r
in
zip l r
let rec render_to_lines = function
| Str (styles, string) -> [ { chunks = [ { styles; string } ] } ]
| Vertical rows -> List.concat_map render_to_lines rows
let rec render_to_lines ~width = function
| Str (styles, string) -> [ Line.of_chunks [ { styles; string } ] ]
| Horizontal_fill -> [ Line.of_chunks [ Line.padding_chunk width ] ]
| Vertical rows -> List.concat_map (render_to_lines ~width) rows
| Horizontal cols -> (
match cols with
| [] -> []
(* TODO: This is potentially really slow; optimise *)
| hd :: tl ->
List.fold_left
(fun acc col -> zip_lines acc (render_to_lines col))
(render_to_lines hd) tl)
(fun acc col -> zip_lines acc (render_to_lines ~width col))
(render_to_lines ~width hd) tl)
let render doc =
doc |> render_to_lines |> List.map fmt_line |> String_extra.unlines
let render ~width doc =
doc |> render_to_lines ~width |> List.map Line.fmt |> String_extra.unlines

View File

@ -1,14 +1,11 @@
(** A type representing a structured document. *)
type doc
(** Formatting of a string. *)
type styles = ANSITerminal.style list
(** Create a single chunk of string without formatting. *)
val str : string -> doc
(** Create a single chunk of string with formatting. *)
val fmt : styles -> string -> doc
val fmt : Line.styles -> string -> doc
(** Put all documents in a list horizontally, automatically adding required padding. *)
val horizontal : doc list -> doc
@ -16,5 +13,19 @@ val horizontal : doc list -> doc
(** Put all documents in a list vertically after each other with a line separator. *)
val vertical : doc list -> doc
(** Render the resulting document. *)
val render : doc -> string
(** Fill all the empty horizontal space with spaces. Useful for alignment.
Only one [horizontal_fill] is allowed per [horizontal] element. The first one
will be used, the remaining ones will be ignored.
*)
val horizontal_fill : doc
(** Render the resulting document.
Parameters:
* [width]: the max allowed width for the document. Passed recursively and
currently only used for [horizontal_fill].
*)
val render : width:int -> doc -> string

View File

@ -7,11 +7,11 @@ let init ~repo ~root_dir_path : Model.initial_data =
exit 1
| Fs.Dir (_, files) -> files
in
let terminal_rows = Option.value (Terminal_size.get_rows ()) ~default:120 in
let terminal_cols =
let height = Option.value (Terminal_size.get_rows ()) ~default:120 in
let width =
Option.value (Terminal_size.get_columns ()) ~default:140
in
{ repo; root_dir_path; files; terminal_rows; terminal_cols }
{ repo; root_dir_path; files; width; height }
let app = Minttea.app ~init:Init.init ~update:Update.update ~view:View.view ()

View File

@ -11,8 +11,8 @@ type tab =
| PullRequests
type t = {
terminal_rows : int;
terminal_cols : int;
width : int;
height : int;
repo : string;
current_tab : tab;
code_tab : code_tab;
@ -22,14 +22,14 @@ type initial_data = {
repo : string;
root_dir_path : string;
files : Fs.tree array;
terminal_rows : int;
terminal_cols : int;
width : int;
height : int;
}
let initial_model { repo; root_dir_path; files; terminal_rows; terminal_cols } =
let initial_model { repo; root_dir_path; files; width; height } =
{
terminal_rows;
terminal_cols;
width;
height;
repo;
current_tab = Code;
code_tab = { root_dir_path; fs = Fs.zip_it files };

View File

@ -4,7 +4,7 @@ let style_directory = ANSITerminal.[ Bold; magenta ]
let debug_section (model : Model.t) =
let debug_info =
Printf.sprintf "%dw x %dh" model.terminal_cols model.terminal_rows
Printf.sprintf "%dw x %dh" model.width model.height
in
Pretty.str debug_info
@ -42,4 +42,4 @@ let to_doc (model : Model.t) =
vertical [ horizontal [ repo; str " "; debug ]; empty; tabs; content; empty ]
let view (model : Model.t) = model |> to_doc |> Pretty.render
let view (model : Model.t) = model |> to_doc |> Pretty.render ~width:model.width