terminal user interface to jujutsu. Focused on speed and clarity
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

format

+143 -378
+2 -2
jj_tui/bin/file_view.ml
··· 34 34 "squash" 35 35 ; "-u" 36 36 ; "--from" 37 - ; get_selected_rev() 37 + ; get_selected_rev () 38 38 ; "--into" 39 39 ; rev 40 40 ; Lwd.peek selected_file ··· 73 73 (**Get the status for the currently selected file*) 74 74 let file_status () = 75 75 let$ selected = Lwd.get selected_file 76 - and$ rev = Vars.get_selected_rev_lwd()in 76 + and$ rev = Vars.get_selected_rev_lwd () in 77 77 if selected != "" then jj_no_log [ "diff"; "-r"; rev; selected ] else "" 78 78 ;; 79 79 end
+1 -1
jj_tui/bin/global_funcs.ml
··· 46 46 This should be called after any command that performs a change *) 47 47 let update_views ?(cause_snapshot = false) () = 48 48 safe_jj (fun () -> 49 - let rev = Vars.get_selected_rev() in 49 + let rev = Vars.get_selected_rev () in 50 50 Eio.Switch.run @@ fun sw -> 51 51 let log_res = 52 52 jj_no_log ~snapshot:cause_snapshot [ "show"; "-s"; "--color-words"; "-r"; rev ]
+23 -13
jj_tui/bin/jj_commands.ml
··· 9 9 (** Regular jj command *) 10 10 type command_variant = 11 11 | Cmd of cmd_args (** Regular jj command *) 12 - | Cmd_r of cmd_args (** Regular jj command that should operate on the selected revison *) 12 + | Cmd_r of cmd_args 13 + (** Regular jj command that should operate on the selected revison *) 13 14 | Dynamic of (unit -> command_variant) 14 - | Dynamic_r of (string-> command_variant) 15 + | Dynamic_r of (string -> command_variant) 15 16 (** Wraps a command so that the content will be regenerated each time it's run. Usefull if you wish to read some peice of ui state *) 16 17 | Cmd_I of cmd_args 17 18 (** Command that will open interactively. Used for diff editing to hand control over to the jj process *) ··· 47 48 open! Jj_tui.Util 48 49 module Wd = Jj_tui.Widgets 49 50 50 - 51 51 exception Handled 52 52 53 53 let render_command_line ~indent_level key desc = ··· 68 68 | { 69 69 key 70 70 ; description 71 - ; cmd = Cmd _ | Cmd_I _ | Prompt _ | Prompt_I _ | Fun _ | PromptThen _ | Dynamic _ |Cmd_r _|Prompt_r _|Dynamic_r _ 71 + ; cmd = 72 + ( Cmd _ 73 + | Cmd_I _ 74 + | Prompt _ 75 + | Prompt_I _ 76 + | Fun _ 77 + | PromptThen _ 78 + | Dynamic _ 79 + | Cmd_r _ 80 + | Prompt_r _ 81 + | Dynamic_r _ ) 72 82 } -> 73 83 [ render_command_line ~indent_level [| key |> Uchar.of_char |] description ] 74 84 | { key; description; cmd = SubCmd subs } -> ··· 76 86 :: render_commands ~indent_level:(indent_level + 1) subs 77 87 ;; 78 88 79 - 80 - 81 89 let commands_list_ui commands = 82 90 let move_command = 83 91 render_command_line ··· 132 140 raise Handled 133 141 | Cmd_r args -> 134 142 ui_state.show_popup $= None; 135 - noOut (args@["-r";Vars.get_selected_rev()]); 143 + noOut (args @ [ "-r"; Vars.get_selected_rev () ]); 136 144 raise Handled 137 145 | Prompt (str, args) -> 138 146 ui_state.show_popup $= None; ··· 140 148 raise Handled 141 149 | Prompt_r (str, args) -> 142 150 ui_state.show_popup $= None; 143 - prompt str (`Cmd (args@["-r";Vars.get_selected_rev()])); 151 + prompt str (`Cmd (args @ [ "-r"; Vars.get_selected_rev () ])); 144 152 raise Handled 145 153 | PromptThen (label, next) -> 146 154 ui_state.show_popup $= None; ··· 163 171 | Dynamic f -> 164 172 f () |> handleCommand description 165 173 | Dynamic_r f -> 166 - f (Vars.get_selected_rev()) |> handleCommand description 174 + f (Vars.get_selected_rev ()) |> handleCommand description 167 175 168 176 (** Try mapching the command mapping to the provided key and run the command if it matches *) 169 177 and command_input ~is_sub keymap key = ··· 177 185 | Handled -> 178 186 if is_sub then ui_state.input $= `Normal; 179 187 `Handled 180 - | JJError (cmd,error) -> 188 + | JJError (cmd, error) -> 181 189 handle_jj_error cmd error; 182 190 `Unhandled 183 191 ··· 204 212 open Intern (Vars) 205 213 module Wd = Jj_tui.Widgets 206 214 include Shared 207 - (** A handy command_list that just has this help command for areas that don't have any commands to still show help*) 208 - let rec default_list= 215 + 216 + (** A handy command_list that just has this help command for areas that don't have any commands to still show help*) 217 + let rec default_list = 209 218 [ 210 219 { 211 220 key = 'h' ··· 216 225 ui_state.show_popup $= Some (commands_list_ui default_list, "Help"); 217 226 ui_state.input $= `Mode (fun _ -> `Unhandled)) 218 227 } 219 - ] 228 + ] 229 + ;; 220 230 221 231 (**Generate a UI object with all the commands nicely formatted and layed out. Useful for help text*) 222 232 let commands_list_ui = commands_list_ui
+22 -12
jj_tui/bin/jj_process.ml
··· 60 60 Mutex.lock access_lock; 61 61 true) 62 62 else false 63 - in 63 + in 64 64 let res = 65 65 cmdArgs 66 66 "jj" ··· 75 75 res 76 76 ;; 77 77 78 - exception JJError of string*string 78 + exception JJError of string * string 79 79 80 80 (** Run a jj command without outputting to the command_log. 81 81 @param ?snapshot=true 82 82 When true snapshots the state when running the command and also aquires a lock before running it. Set to false for commands you wish to run concurrently. like those for generating content in the UI 83 83 @param ?color=true When true output will have terminal escape codes for color *) 84 84 let jj_no_log ?(snapshot = true) ?(color = true) args = 85 - 86 85 match jj_no_log_errorable ~snapshot ~color args with 87 86 | Ok a -> 88 87 a 89 88 | Error (`BadExit (code, str)) -> 90 - raise (JJError( "jj"::args|>String.concat " " ,Printf.sprintf "Exited with code %i; Message:\n%s" code str)) 89 + raise 90 + (JJError 91 + ( "jj" :: args |> String.concat " " 92 + , Printf.sprintf "Exited with code %i; Message:\n%s" code str )) 91 93 | Error (`EioErr a) -> 92 - raise (JJError ("jj"::args|>String.concat " ",Printf.sprintf 93 - "Error running jj process:\n%a" 94 - (fun _ -> Base.Error.to_string_hum) 95 - a)) 94 + raise 95 + (JJError 96 + ( "jj" :: args |> String.concat " " 97 + , Printf.sprintf 98 + "Error running jj process:\n%a" 99 + (fun _ -> Base.Error.to_string_hum) 100 + a )) 96 101 ;; 97 102 98 103 let jj args = ··· 115 120 ; "-T" 116 121 ; {|"::"++current_working_copy++"::\n"++description++"\n::end::\n"|} 117 122 ] 118 - |>String.trim 123 + |> String.trim 124 + in 125 + let current, prev = 126 + output |> Jj_tui.OutputParsing.parse_descriptions |> Result.get_ok 119 127 in 120 - let current, prev = output |>Jj_tui.OutputParsing.parse_descriptions|>Result.get_ok in 121 128 current |> String.concat "", prev |> String.concat "" 122 129 ;; 130 + 123 131 open Vars 124 132 open Nottui 125 133 open Lwd_infix 134 + 126 135 (*handle exception from jj*) 127 136 let handle_jj_error cmd error = 128 137 ui_state.show_prompt $= None; ··· 133 142 |> Ui.atom 134 143 |> Ui.resize ~sw:1 ~sh:1 135 144 |> Lwd.pure 136 - , Printf.sprintf"An error occured running %s" cmd ); 145 + , Printf.sprintf "An error occured running %s" cmd ); 137 146 ui_state.input $= `Mode (fun _ -> `Unhandled) 138 147 ;; 148 + 139 149 (*catch any exceptions from jj*) 140 - let safe_jj f = try f () with JJError (cmd,error) -> handle_jj_error cmd error 150 + let safe_jj f = try f () with JJError (cmd, error) -> handle_jj_error cmd error 141 151 end
+5 -4
jj_tui/bin/jj_ui.ml
··· 79 79 ~pad:Wd.neutral_grav 80 80 |> inputs 81 81 ;; 82 + 82 83 (** The primary view for the UI with the file_view graph_view and summary*) 83 84 let main_view ~sw = 84 85 let file_focus = Focus.make () in ··· 135 136 136 137 (** Shows the op log *) 137 138 let log_view () = 138 - jj_no_log [ "op"; "log"; "--limit";"200" ] 139 + jj_no_log [ "op"; "log"; "--limit"; "200" ] 139 140 |> AnsiReverse.colored_string 140 141 |> Ui.atom 141 - |>Ui.resize ~mh:1000 ~mw:10000 142 + |> Ui.resize ~mh:1000 ~mw:10000 142 143 |> Lwd.pure 143 - |>Wd.scroll_area 144 + |> Wd.scroll_area 144 145 |> Wd.border_box ~pad_w:1 ~pad_h:0 145 - |>inputs 146 + |> inputs 146 147 ;; 147 148 148 149 let mainUi ~sw env =
+19 -17
jj_tui/bin/jj_widgets.ml
··· 145 145 (* else () *)) 146 146 ;; 147 147 148 - (** Function to tag duplicated items in a list *) 149 - let tag_duplicates lst = 150 - (* Create a frequency map to count occurrences of each element *) 151 - let freq_map = 152 - List.fold_left (fun acc {change_id;_} -> 153 - let count = try List.assoc change_id acc with Not_found -> 0 in 154 - (change_id, count + 1) :: List.remove_assoc change_id acc 155 - ) [] lst 156 - in 157 - (* Tag each item in the list based on the frequency map *) 158 - List.map (fun ({change_id;_} as x) -> 159 - if List.assoc change_id freq_map > 1 then Duplicate x else Unique x 160 - ) lst 148 + (** Function to tag duplicated items in a list *) 149 + let tag_duplicates lst = 150 + (* Create a frequency map to count occurrences of each element *) 151 + let freq_map = 152 + List.fold_left 153 + (fun acc { change_id; _ } -> 154 + let count = try List.assoc change_id acc with Not_found -> 0 in 155 + (change_id, count + 1) :: List.remove_assoc change_id acc) 156 + [] 157 + lst 158 + in 159 + (* Tag each item in the list based on the frequency map *) 160 + List.map 161 + (fun ({ change_id; _ } as x) -> 162 + if List.assoc change_id freq_map > 1 then Duplicate x else Unique x) 163 + lst 164 + ;; 161 165 162 166 (**Returns a list of revs with both the change_id and commit_id*) 163 167 let get_revs () = 164 - jj_no_log 165 - ~color:false 166 - [ "log"; "-T"; {|"|"++change_id++"|"++commit_id++"\n"|} ] 168 + jj_no_log ~color:false [ "log"; "-T"; {|"|"++change_id++"|"++commit_id++"\n"|} ] 167 169 |> String.split_on_char '\n' 168 170 |> List.filter_map (fun x -> 169 171 let items = x |> String.split_on_char '|' in ··· 172 174 Some { change_id; commit_id } 173 175 | _ -> 174 176 None) 175 - |>tag_duplicates 177 + |> tag_duplicates 176 178 |> Array.of_list 177 179 ;; 178 180
+9 -5
jj_tui/lib/ansiReverse.ml
··· 140 140 ;; 141 141 142 142 let string_to_image ?(extra_attr = A.empty) str = 143 - let str= 143 + let str = 144 144 (* replace any carrriage returns becasue notty doesn't know what to do with them*) 145 - Base.String.Search_pattern.replace_all (Base.String.Search_pattern.create "\r\n") ~in_:str ~with_:"\n" 146 - |> 147 - Base.String.Search_pattern.replace_all (Base.String.Search_pattern.create "\r") ~with_:"\n" 148 - in 145 + Base.String.Search_pattern.replace_all 146 + (Base.String.Search_pattern.create "\r\n") 147 + ~in_:str 148 + ~with_:"\n" 149 + |> Base.String.Search_pattern.replace_all 150 + (Base.String.Search_pattern.create "\r") 151 + ~with_:"\n" 152 + in 149 153 match parse_ansi_escape_codes str with 150 154 | Error a -> 151 155 Printf.printf "restut: %s" a;
-263
jj_tui/lib/box_widget.ml
··· 1 - open Notty 2 - open Nottui 3 - open Lwd_infix 4 - open! Util 5 - open! Widgets_citty 6 - module W = Nottui_widgets 7 - 8 - (** This is for shifting something away from the edge it is pushed against *) 9 - let pad_edge x_pad y_pad grav ui = 10 - let y_pad = 11 - match grav |> Gravity.v with 12 - | `Negative -> 13 - -y_pad 14 - | `Neutral -> 15 - 0 16 - | `Positive -> 17 - y_pad 18 - in 19 - match grav |> Gravity.h with 20 - | `Negative -> 21 - ui |> Ui.shift_area (-x_pad) y_pad 22 - | `Neutral -> 23 - ui 24 - | `Positive -> 25 - ui |> Ui.shift_area x_pad y_pad 26 - ;; 27 - 28 - let neutral_grav = Gravity.make ~h:`Neutral ~v:`Neutral 29 - 30 - module Intern = struct 31 - let make_even num = num + (num mod 2 * 1) 32 - let upPipe = Uchar.of_int 0x2503 33 - let tlPipe = Uchar.of_int 0x250f 34 - let trPipe = Uchar.of_int 0x2513 35 - let blPipe = Uchar.of_int 0x2517 36 - let brPipe = Uchar.of_int 0x251b 37 - let sidePipe = Uchar.of_int 0x2501 38 - 39 - (** makes a line of chars with a specific start , midlle and end*) 40 - let make_with_ends start mid ending width = 41 - Array.init width (fun i -> 42 - let lastIdx = width - 1 in 43 - match i with 0 -> start | a when a == lastIdx -> ending | _ -> mid) 44 - |> I.uchars A.empty 45 - ;; 46 - 47 - let make_top width = make_with_ends tlPipe sidePipe trPipe width 48 - let make_bot width = make_with_ends blPipe sidePipe brPipe width 49 - let grid xxs = xxs |> List.map I.hcat |> I.vcat 50 - 51 - (** Truncate a string to a given length, adding an ellipsis if truncated. *) 52 - let truncate_string len str = 53 - if String.length str > len 54 - then if len <= 3 then "" else String.sub str 0 (len - 3) ^ "..." 55 - else str 56 - ;; 57 - 58 - (** top border*) 59 - let outline_top attr w label = 60 - let chr x = I.uchar attr (Uchar.of_int x) 1 1 in 61 - let hbar = I.uchar attr (Uchar.of_int 0x2500) w 1 62 - and label = if label |> I.width > w - 2 then I.empty else label |> I.hpad 2 0 63 - and a, b = chr 0x256d, chr 0x256e in 64 - I.zcat [ label; I.hcat [ a; hbar; b ]; label ] 65 - ;; 66 - 67 - (** bottom border*) 68 - let outline_bot attr w label = 69 - let chr x = I.uchar attr (Uchar.of_int x) 1 1 in 70 - let hbar = I.uchar attr (Uchar.of_int 0x2500) w 1 in 71 - let label = 72 - if label |> I.width > w - 2 73 - then I.empty 74 - else label |> I.hpad (w - (label |> I.width |> ( + ) 1)) 0 75 - in 76 - let c, d = chr 0x256f, chr 0x2570 in 77 - I.zcat [ label; I.hcat [ d; hbar; c ] ] 78 - ;; 79 - 80 - let make_label max_width label_str = 81 - I.strf " %s " (truncate_string (max_width - 2) label_str) 82 - ;; 83 - 84 - end 85 - 86 - open Intern 87 - (** Internal function for rendering a border box with known dimensions and padding.*) 88 - let border_box_intern 89 - ?(border_attr = A.empty) 90 - ?(label_top = I.empty) 91 - ?(label_bottom = I.empty) 92 - w 93 - h 94 - pad 95 - pad_w 96 - pad_h 97 - input 98 - = 99 - (*can't go below 1 internal width or things get weird*) 100 - let h = if pad_h < 1 then Int.max h 1 else h in 101 - let w = if pad_w < 1 then Int.max w 1 else w in 102 - (* this is a weird quirk, but we have to be careful of runaway size expansion. 103 - If we increase the width of the space by making the vbar longer than the input ui element it will be able to expand to fill that space. 104 - That will then increase the vbar and increase the height etc etc untill the max height is reached*) 105 - let vbar = 106 - I.uchar border_attr (Uchar.of_int 0x2502) 1 (h + (pad_h * 2)) 107 - |> Ui.atom 108 - |> Ui.resize ~h:0 109 - in 110 - Ui.vcat 111 - [ 112 - outline_top border_attr w label_top |> Ui.atom |> Ui.resize ~w:0 113 - ; Ui.hcat 114 - [ 115 - vbar 116 - ; I.void pad_w 1 |> Ui.atom 117 - ; Ui.vcat 118 - [ 119 - I.void 1 pad_h |> Ui.atom 120 - ; input |> Ui.resize ~pad 121 - ; I.void 1 pad_h |> Ui.atom 122 - ] 123 - ; I.void pad_w 1 |> Ui.atom 124 - ; vbar 125 - ] 126 - ; outline_bot border_attr w label_bottom |> Ui.atom |> Ui.resize ~w:0 127 - ] 128 - ;; 129 - 130 - let border_box_custom_border 131 - ?(scaling = `Static) 132 - ?(pad = neutral_grav) 133 - ?(pad_w = 2) 134 - ?(pad_h = 1) 135 - ?label_top 136 - ?label_bottom 137 - get_border 138 - input 139 - = 140 - let max_width, min_width, sw = 141 - match scaling with 142 - | `Static -> 143 - None, None, None 144 - (* This allows the input to expand to fill space*) 145 - | `Expand sw -> 146 - Some 1000, None, Some sw 147 - in 148 - let size = Lwd.var (0, 0) in 149 - let layout_width = Lwd.var 0 in 150 - let input = 151 - let$ input = input |>$ Ui.resize ?sw ?mw:max_width ?sh:sw ?mh:max_width in 152 - (*We need this later to determine the max with*) 153 - layout_width $= (input |> Ui.layout_width); 154 - input 155 - (*This lets us tell the input to be a flexible size*) 156 - |> Ui.size_sensor (fun ~w ~h -> if Lwd.peek size <> (w, h) then Lwd.set size (w, h)) 157 - in 158 - (*This is original width and height of the input before padding or anything *) 159 - let$ o_w, o_h = Lwd.get size 160 - and$ input = input 161 - and$ border_attr = get_border in 162 - let w, h = o_w + (pad_w * 2), o_h in 163 - let h = h in 164 - let bbox = 165 - border_box_intern 166 - ~border_attr 167 - ?label_top:(label_top |> Option.map (make_label (w - 2))) 168 - ?label_bottom:(label_bottom |> Option.map (make_label (w - 2))) 169 - (* ~label_bottom:(if has_focus then I.strf "focused" else I.strf "unfocused") *) 170 - w 171 - h 172 - pad 173 - pad_w 174 - pad_h 175 - input 176 - in 177 - (*If we want the input to be shrinkable we make it expandable, set it's width to something small and then set a max width for the whole box*) 178 - bbox 179 - ;; 180 - 181 - (** Creates a bordered box around the given [input] widget. This box will change colour when focused 182 - 183 - @param scaling 184 - Controls how the input widget is sized within the border box. Can be: 185 - - [`Static] - The input widget is not resized. 186 - - [`Expand sw] - The input widget is allowed to expand to fill the available space, with a stretch width [sw]. 187 - - [`Shrinkable (min_width, sw)] - The input widget is allowed to shrink to a minimum width of [min_width], and expand with a stretch width [sw]. 188 - @param pad The padding around the input widget within the border box. 189 - @param pad_w The horizontal padding around the input widget. 190 - @param pad_h The vertical padding around the input widget. 191 - @param label An optional label to display within the border box. 192 - @param input The input widget to be bordered. 193 - @param border_attr Style for the border, defaults to [A.empty]. 194 - @param focus Focus handle for the box . 195 - @param focus_attr Style for the border when focused, defaults to [A.fg A.blue]. 196 - @param on_key Callback called when a key is pressed while the box is focused. Useful for performing actions when the box is selected . 197 - *) 198 - let border_box_focusable 199 - ?scaling 200 - ?pad 201 - ?pad_w 202 - ?pad_h 203 - ?label_top 204 - ?label_bottom 205 - ?(border_attr = A.empty) 206 - ?(focus_attr = A.fg A.blue) 207 - ?(focus = Focus.make ()) 208 - ?(on_key=(fun _->`Unhandled)) 209 - input 210 - = 211 - let attr = Lwd.var border_attr in 212 - let input = 213 - input 214 - |> Lwd.map2 (focus |> Focus.status) ~f:(fun focus ui -> 215 - ui |> Ui.keyboard_area ~focus on_key) 216 - in 217 - border_box_custom_border 218 - ?scaling 219 - ?pad 220 - ?pad_w 221 - ?pad_h 222 - ?label_top 223 - ?label_bottom 224 - (let$ focus = Focus.status focus in 225 - if Focus.has_focus focus then focus_attr else border_attr) 226 - input 227 - ;; 228 - 229 - (** Creates a bordered box around the given [input] widget. 230 - 231 - @param scaling 232 - Controls how the input widget is sized within the border box. Can be: 233 - - [`Static] - The input widget is not resized. 234 - - [`Expand sw] - The input widget is allowed to expand to fill the available space, with a stretch width [sw]. 235 - - [`Shrinkable (min_width, sw)] - The input widget is allowed to shrink to a minimum width of [min_width], and expand with a stretch width [sw]. 236 - @param pad The padding around the input widget within the border box. 237 - @param pad_w The horizontal padding around the input widget. 238 - @param pad_h The vertical padding around the input widget. 239 - @param label An optional label to display within the border box. 240 - @param input The input widget to be bordered. 241 - @param border_attr Style for the border, defaults to [A.empty]. *) 242 - let border_box 243 - ?scaling 244 - ?pad 245 - ?pad_w 246 - ?pad_h 247 - ?label_top 248 - ?label_bottom 249 - ?(border_attr = A.empty) 250 - input 251 - = 252 - border_box_custom_border 253 - ?scaling 254 - ?pad 255 - ?pad_w 256 - ?pad_h 257 - ?label_top 258 - ?label_bottom 259 - (border_attr |> Lwd.pure) 260 - input 261 - ;; 262 - 263 - ;;
+48 -47
jj_tui/lib/widgets/border_box.ml
··· 80 80 let make_label max_width label_str = 81 81 I.strf " %s " (truncate_string (max_width - 2) label_str) 82 82 ;; 83 - 84 83 end 85 84 86 85 open Intern 87 - (** Internal function for rendering a border box with known dimensions and padding.*) 88 - let border_box_intern 89 - ?(border_attr = A.empty) 90 - ?(label_top = I.empty) 91 - ?(label_bottom = I.empty) 92 - w 93 - h 94 - pad 95 - pad_w 96 - pad_h 97 - input 98 - = 99 - (*can't go below 1 internal width or things get weird*) 100 - let h = if pad_h < 1 then Int.max h 1 else h in 101 - let w = if pad_w < 1 then Int.max w 1 else w in 102 - (* this is a weird quirk, but we have to be careful of runaway size expansion. 103 - If we increase the width of the space by making the vbar longer than the input ui element it will be able to expand to fill that space. 104 - That will then increase the vbar and increase the height etc etc untill the max height is reached*) 105 - let vbar = 106 - I.uchar border_attr (Uchar.of_int 0x2502) 1 (h + (pad_h * 2)) 107 - |> Ui.atom 108 - |> Ui.resize ~h:0 109 - in 110 - Ui.vcat 111 - [ 112 - outline_top border_attr w label_top |> Ui.atom |> Ui.resize ~w:0 113 - ; Ui.hcat 114 - [ 115 - vbar 116 - ; I.void pad_w 1 |> Ui.atom 117 - ; Ui.vcat 118 - [ 119 - I.void 1 pad_h |> Ui.atom 120 - ; input |> Ui.resize ~pad 121 - ; I.void 1 pad_h |> Ui.atom 122 - ] 123 - ; I.void pad_w 1 |> Ui.atom 124 - ; vbar 125 - ] 126 - ; outline_bot border_attr w label_bottom |> Ui.atom |> Ui.resize ~w:0 127 - ] 128 - ;; 86 + 87 + (** Internal function for rendering a border box with known dimensions and padding.*) 88 + let border_box_intern 89 + ?(border_attr = A.empty) 90 + ?(label_top = I.empty) 91 + ?(label_bottom = I.empty) 92 + w 93 + h 94 + pad 95 + pad_w 96 + pad_h 97 + input 98 + = 99 + (*can't go below 1 internal width or things get weird*) 100 + let h = if pad_h < 1 then Int.max h 1 else h in 101 + let w = if pad_w < 1 then Int.max w 1 else w in 102 + (* this is a weird quirk, but we have to be careful of runaway size expansion. 103 + If we increase the width of the space by making the vbar longer than the input ui element it will be able to expand to fill that space. 104 + That will then increase the vbar and increase the height etc etc untill the max height is reached*) 105 + let vbar = 106 + I.uchar border_attr (Uchar.of_int 0x2502) 1 (h + (pad_h * 2)) 107 + |> Ui.atom 108 + |> Ui.resize ~h:0 109 + in 110 + Ui.vcat 111 + [ 112 + outline_top border_attr w label_top |> Ui.atom |> Ui.resize ~w:0 113 + ; Ui.hcat 114 + [ 115 + vbar 116 + ; I.void pad_w 1 |> Ui.atom 117 + ; Ui.vcat 118 + [ 119 + I.void 1 pad_h |> Ui.atom 120 + ; input |> Ui.resize ~pad 121 + ; I.void 1 pad_h |> Ui.atom 122 + ] 123 + ; I.void pad_w 1 |> Ui.atom 124 + ; vbar 125 + ] 126 + ; outline_bot border_attr w label_bottom |> Ui.atom |> Ui.resize ~w:0 127 + ] 128 + ;; 129 129 130 130 let border_box_custom_border 131 131 ?(scaling = `Static) ··· 192 192 @param input The input widget to be bordered. 193 193 @param border_attr Style for the border, defaults to [A.empty]. 194 194 @param focus Focus handle for the box . 195 - @param focus_attr Style for the border when focused, defaults to [A.fg A.blue]. *) 195 + @param focus_attr Style for the border when focused, defaults to [A.fg A.blue]. 196 + @param on_key 197 + Callback called when a key is pressed while the box is focused. Useful for performing actions when the box is selected . *) 196 198 let border_box_focusable 197 199 ?scaling 198 200 ?pad ··· 203 205 ?(border_attr = A.empty) 204 206 ?(focus_attr = A.fg A.blue) 205 207 ?(focus = Focus.make ()) 208 + ?(on_key = fun _ -> `Unhandled) 206 209 input 207 210 = 208 211 let attr = Lwd.var border_attr in 209 212 let input = 210 213 input 211 214 |> Lwd.map2 (focus |> Focus.status) ~f:(fun focus ui -> 212 - ui |> Ui.keyboard_area ~focus (fun _ -> `Unhandled)) 215 + ui |> Ui.keyboard_area ~focus on_key) 213 216 in 214 217 border_box_custom_border 215 218 ?scaling ··· 256 259 (border_attr |> Lwd.pure) 257 260 input 258 261 ;; 259 - 260 - ;;
+14 -14
jj_tui/lib/widgets/widgets.ml
··· 3 3 open Lwd_infix 4 4 open! Util 5 5 open! Widgets_citty 6 - include Box_widget 6 + include Border_box 7 7 include Shared 8 8 include Selection_list 9 9 module Wip = Wip ··· 353 353 |> border_box ~pad_w:1 ~pad_h:0 354 354 in 355 355 W.vbox [ tab_bar; f () ] 356 - |>$ Ui.keyboard_area (function 357 - | `ASCII key, _ -> 358 - key 359 - |> char_to_int 360 - |> Option.map (fun i -> 361 - if i >= 1 && i <= List.length tabs 362 - then ( 363 - cur $= i-1; 364 - `Handled) 365 - else `Unhandled) 366 - |> Option.value ~default:`Unhandled 367 - | _ -> 368 - `Unhandled) 356 + |>$ Ui.keyboard_area (function 357 + | `ASCII key, _ -> 358 + key 359 + |> char_to_int 360 + |> Option.map (fun i -> 361 + if i >= 1 && i <= List.length tabs 362 + then ( 363 + cur $= i - 1; 364 + `Handled) 365 + else `Unhandled) 366 + |> Option.value ~default:`Unhandled 367 + | _ -> 368 + `Unhandled) 369 369 ;;