mirror of
https://github.com/chshersh/github-tui.git
synced 2024-10-05 14:57:53 +03:00
Add scroller
This commit is contained in:
parent
b42f13afcd
commit
98b9b48d6f
@ -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:
|
||||
|
21
lib/fs.ml
21
lib/fs.ml
@ -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
|
||||
|
@ -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. *)
|
||||
|
@ -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
21
lib/scroll.ml
Normal 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
10
lib/scroll.mli
Normal 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
|
@ -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 *)
|
||||
|
Loading…
Reference in New Issue
Block a user