Add scroller

This commit is contained in:
Dmitrii Kovanikov 2024-06-22 18:11:10 +01:00
parent b42f13afcd
commit 98b9b48d6f
7 changed files with 98 additions and 27 deletions

View File

@ -13,6 +13,14 @@ A TUI interface to GitHub.
> `github-tui` is in _alpha_ stage of development!
> Expect missing features and horrible bugs.
## Prerequisites
To use `github-tui`, you need to have the following installed:
1. OCaml toolchain: to build the project
1. `bat` version ⩾ 0.19.0
1. [Hack Mono Nerd Font](https://www.nerdfonts.com/)
## Development
Initialise the project when building for the first time:

View File

@ -1,5 +1,10 @@
type file_contents = {
lines : Pretty.doc array;
offset : int;
}
type tree =
| File of string * string lazy_t
| File of string * file_contents lazy_t
| Dir of string * tree array
let file_name = function
@ -26,10 +31,18 @@ let rec sort_tree = function
(* Reads file contents using 'bat' to have pretty syntax highlighting *)
let read_file_contents path =
let cmd =
"bat --plain --color=always --italic-text=always --paging=never \
--terminal-width=80 " ^ path
"bat --style=numbers,changes --color=always --italic-text=always \
--paging=never --terminal-width=80 " ^ path
in
Shell.proc_stdout cmd
let contents = Shell.proc_stdout cmd in
let lines =
contents
|> String.split_on_char '\n'
|> List.map Pretty.str
|> Array.of_list
in
let offset = 0 in
{ lines; offset }
let rec to_tree path =
if Sys.is_directory path then

View File

@ -1,6 +1,12 @@
(** File contents as an array of lines, where each line is wrapped into a document (for rendering efficiency) *)
type file_contents = {
lines : Pretty.doc array;
offset : int;
}
(** A definition of a file tree. *)
type tree =
| File of string * string lazy_t
| File of string * file_contents lazy_t
| Dir of string * tree array
(** Return the name of a given tree node. *)

View File

@ -7,13 +7,11 @@ let in_between ~sep list =
| [] | [ _ ] -> list
| x :: xs -> x :: loop xs
let generate n f =
let rec loop i = if i = n then [] else f i :: loop (i + 1) in
let of_sub_array ~offset ~len arr =
let len =
if offset + len > Array.length arr then Array.length arr - offset else len
in
let rec loop i = if i >= len then [] else arr.(offset + i) :: loop (i + 1) in
loop 0
let rec take n = function
| _ when n <= 0 -> []
| [] -> []
| x :: xs -> x :: take (n - 1) xs
let max_on f list = List.fold_left (fun acc x -> max acc (f x)) 0 list

21
lib/scroll.ml Normal file
View File

@ -0,0 +1,21 @@
type t = {
height : int;
span : int;
lines : int;
offset : int;
}
type sections = {
before : int;
scroll : int;
after : int;
}
let make ~height ~span ~lines ~offset =
if lines <= height then None else Some { height; span; lines; offset }
let to_sections { height; span; lines; offset } =
let before = offset * height / lines in
let scroll = span * height / lines in
let after = (lines - span - offset) * height / lines in
{ before; scroll; after }

10
lib/scroll.mli Normal file
View File

@ -0,0 +1,10 @@
type t
type sections = {
before : int;
scroll : int;
after : int;
}
val make : height:int -> span:int -> lines:int -> offset:int -> t option
val to_sections : t -> sections

View File

@ -41,6 +41,19 @@ let pull_requests_tab ~is_selected =
"┴───────────────┴";
] [@@ocamlformat "disable"]
let scroll ~lines ~span ~offset =
let height = 40 in
let scroll = Scroll.make ~height ~span ~lines ~offset in
match scroll with
| None -> Pretty.str " "
| Some scroll ->
let sections = Scroll.to_sections scroll in
let before = List.init sections.before (fun _ -> Pretty.str "") in
let scroll = List.init sections.scroll (fun _ -> Pretty.str "") in
let after = List.init sections.after (fun _ -> Pretty.str "") in
let scroll_bar = before @ scroll @ after in
Pretty.vertical scroll_bar
let pwd_char = "\u{e5fd}"
let dir_char = "\u{f4d4}"
let empty_dir_char = "\u{f413}"
@ -55,16 +68,20 @@ let pwd root_dir_path parents =
let full_path = pwd_char ^ " " ^ Filename.concat root_dir_name pwd_path in
Pretty.fmt style_directory full_path
let file_contents_to_doc ~file_name:_ ~file_contents =
let file_contents_preview =
file_contents
|> Lazy.force
|> String.split_on_char '\n'
|> List_extra.take 30
|> List.map Pretty.str
|> Pretty.vertical
let file_contents_to_doc ~file_contents =
let (file_contents : Fs.file_contents) = Lazy.force file_contents in
let lines = Array.length file_contents.lines in
let span = 40 in
let offset = file_contents.offset in
let contents_span =
List_extra.of_sub_array ~offset ~len:span file_contents.lines
in
Pretty.(horizontal [ str " "; file_contents_preview ])
let scroll_doc = scroll ~lines ~span ~offset in
let contents_doc = Pretty.vertical contents_span in
Pretty.(horizontal [ scroll_doc; str " "; contents_doc ])
(* Extra padding for:
@ -132,7 +149,7 @@ let children_to_doc ~prev_total ~pos children =
let prev_rows_count = (2 * prev_total) + 1 in
let connect_pos = (2 * pos) + 1 in
let connector_doc =
List_extra.generate prev_rows_count (fun i ->
List.init prev_rows_count (fun i ->
let is_current_pos = i = connect_pos in
if is_current_pos then str "" else str " ")
|> vertical
@ -159,9 +176,7 @@ let children_to_doc ~prev_total ~pos children =
|> List_extra.in_between ~sep:mid
|> (fun lines -> [ top ] @ lines @ [ bot ])
|> (fun lines ->
let pad_before =
List_extra.generate (max (connect_pos - 1) 0) (fun _ -> "")
in
let pad_before = List.init (max (connect_pos - 1) 0) (fun _ -> "") in
pad_before @ lines)
|> List.map str
|> vertical
@ -182,8 +197,8 @@ let next_level_to_doc ~prev_total ~pos (selected_file : Fs.tree) =
(* Get the next level files *)
match selected_file with
(* No children of a file *)
| File (file_name, file_contents) ->
File_contents (file_contents_to_doc ~file_name ~file_contents)
| File (_file_name, file_contents) ->
File_contents (file_contents_to_doc ~file_contents)
(* No children of a directory without children *)
| Dir (_, [||]) -> Empty_directory
(* Non-empty array of children *)