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.

apply shared debouncer to selection reset and status updates

+56 -44
+13 -23
jj_tui/bin/global_vars.ml
··· 143 143 let term_width_height : (int * int) Lwd.var = Lwd.var (0, 0) 144 144 let get_term () = Option.get !term 145 145 146 - let reset_selection () = 147 - Flock.fork (fun _ -> 148 - Picos_std_structured.Control.sleep ~seconds:0.7; 149 - [%log info "Resetting selection"]; 150 - ui_state.reset_selection |> Signal.trigger) 146 + let reset_selection_debouncer = 147 + Jj_tui.Debounce.make 148 + ~delay:0.7 149 + ~merge:(fun () () -> ()) 150 + ~run:(fun () -> 151 + [%log info "Resetting selection"]; 152 + ui_state.reset_selection |> Signal.trigger) 153 + () 151 154 ;; 155 + 156 + let reset_selection () = Jj_tui.Debounce.push reset_selection_debouncer () 152 157 153 158 (**Gets an id for the currently hovered revision. If the change_id is unique we use that, if it's not we return a commit_id instead*) 154 159 let get_hovered_rev () = Lwd.peek ui_state.hovered_revision |> get_unique_id ··· 186 191 ;; 187 192 188 193 let get_rebase_preview_active () = Lwd.peek ui_state.rebase_preview_active 189 - 190 194 let get_rebase_preview_mode () = Lwd.peek ui_state.rebase_preview_mode 191 - 192 195 let get_rebase_preview_mode_lwd () = Lwd.get ui_state.rebase_preview_mode 193 - 194 196 let get_rebase_preview_source_mode () = Lwd.peek ui_state.rebase_preview_source_mode 195 - 196 197 let get_rebase_preview_source_mode_lwd () = Lwd.get ui_state.rebase_preview_source_mode 197 198 198 199 let set_rebase_preview_source_mode mode = ··· 200 201 ;; 201 202 202 203 let get_rebase_preview_targets () = Lwd.peek ui_state.rebase_preview_targets 203 - 204 204 let get_rebase_preview_sources () = Lwd.peek ui_state.rebase_preview_sources 205 - 206 - let set_rebase_preview_active active = 207 - Lwd.set ui_state.rebase_preview_active active 208 - ;; 209 - 210 - let set_rebase_preview_targets targets = 211 - Lwd.set ui_state.rebase_preview_targets targets 212 - ;; 213 - 214 - let set_rebase_preview_sources sources = 215 - Lwd.set ui_state.rebase_preview_sources sources 216 - ;; 217 - 205 + let set_rebase_preview_active active = Lwd.set ui_state.rebase_preview_active active 206 + let set_rebase_preview_targets targets = Lwd.set ui_state.rebase_preview_targets targets 207 + let set_rebase_preview_sources sources = Lwd.set ui_state.rebase_preview_sources sources 218 208 let set_rebase_preview_invalid msg = Lwd.set ui_state.rebase_preview_invalid msg 219 209 220 210 let clear_rebase_preview () =
+12 -21
jj_tui/bin/show_view.ml
··· 16 16 let statusStream = Stream.create () 17 17 let lastMessage = ref None 18 18 19 + let push_status_debouncer = 20 + Jj_tui.Debounce.make 21 + ~delay:0.05 22 + ~merge:(fun _ new_ -> new_) 23 + ~run:(fun status -> Stream.push statusStream status) 24 + () 25 + ;; 26 + 19 27 let push_status status = 20 28 lastMessage := Some status; 21 - Stream.push statusStream status 29 + Jj_tui.Debounce.push push_status_debouncer status 22 30 ;; 23 31 24 32 (** pushes the last message to the queue again to re-render everything *) ··· 79 87 res 80 88 ;; 81 89 82 - let get_latest_message cursor = 83 - let rec seek_latest last cursor = 84 - let peeked = Stream.peek_opt cursor in 85 - match peeked with 86 - | Some (last, new_cursor) -> 87 - seek_latest last new_cursor 88 - | None -> 89 - [%log debug "skipping to next status because two were queued"]; 90 - last, cursor 91 - in 92 - let msg, new_cursor = cursor |> Stream.read in 93 - (*little 50ms delay to let us move to the next one if it's ready*) 94 - Picos.Fiber.sleep ~seconds:0.05; 95 - (*if the queue isn't empty just skip the current because we really only ever want the newest*) 96 - seek_latest msg new_cursor 97 - ;; 98 - 99 90 (* Wait for messages to come in the stream. 100 - When a message comes, we try to render it. 101 - If a new message comes, we cancel the current computation and then start the new rendering 91 + When a message comes, we try to render it. 92 + If a new message comes, we cancel the current computation and then start the new rendering 102 93 *) 103 94 let render_loop stream = 104 95 let current_summary_computation = ref (Promise.of_value ()) in ··· 107 98 let cursor = ref (Stream.tap stream) in 108 99 while true do 109 100 [%log debug "waiting for next status"]; 110 - let msg, new_cursor = get_latest_message !cursor in 101 + let msg, new_cursor = Stream.read !cursor in 111 102 cursor := new_cursor; 112 103 [%log debug "cancelling older status because of new message"]; 113 104 Promise.terminate_after ~seconds:0. !current_summary_computation;
+31
jj_tui/lib/debounce.ml
··· 1 1 open Picos_std_structured 2 2 3 + (** 4 + A small, reusable debouncer for Picos fibers. 5 + 6 + The debouncer coalesces rapid [push] calls into a single delayed execution. 7 + Callers provide a [merge] function to combine queued values and a [run] 8 + function for the final work item. 9 + 10 + Internally, both the debounce timer and the active run are represented as 11 + cancelable promises. This keeps latest-wins behavior explicit and avoids 12 + leaking background work. 13 + 14 + Warning: 15 + - This module uses [Flock.fork_as_promise], so calls to {!make} and {!push} 16 + must happen inside a running flock scope. 17 + - The [run] callback should not swallow [Control.Terminate], otherwise 18 + cancellation cannot stop stale work promptly. 19 + *) 3 20 type 'a t = { 4 21 delay : float 5 22 ; merge : 'a -> 'a -> 'a ··· 9 26 ; current_computation : unit Promise.t ref 10 27 } 11 28 29 + (** [make ~delay ~merge ~run ()] creates a debouncer. 30 + 31 + - [delay] is the debounce window in seconds. 32 + - [merge old new_] combines queued values when multiple pushes arrive. 33 + - [run value] performs the debounced action. 34 + 35 + [run] executes in a fiber forked from the current flock. 36 + *) 12 37 let make ~delay ~merge ~run () = 13 38 { 14 39 delay ··· 20 45 } 21 46 ;; 22 47 48 + (** [push t value] enqueues a new value for debounced processing. 49 + 50 + Repeated pushes during the debounce window are merged with [merge]. When the 51 + timer elapses, only the latest merged value is executed. Any previously 52 + running [run] fiber is canceled before a new one is started. 53 + *) 23 54 let push t value = 24 55 t.pending 25 56 := Some