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.

Added logging system

+153 -40
+4
flake.nix
··· 235 235 236 236 picos_aux 237 237 238 + ocamlPackages.logs 239 + ocamlPackages.logs-ppx 240 + 241 + ocamlPackages.ppx_deriving 238 242 239 243 ocamlPackages.mtime 240 244 ocamlPackages.multicore-magic
+4 -1
jj_tui/bin/dune
··· 2 2 (public_name jj_tui) 3 3 (name main) 4 4 (libraries jj_tui nottui base stdio picos_io picos_mux.multififo picos_std.sync picos_std.finally picos_std.structured eio_main eio-process spawn ) 5 - ) 5 + 6 + (preprocess 7 + (pps logs-ppx ppx_deriving.std)) 8 + ) 6 9 7 10 (env 8 11 (static
+4 -3
jj_tui/bin/graph_view.ml
··· 1 + open Jj_tui.Logging 2 + 1 3 module Make (Vars : Global_vars.Vars) = struct 2 4 open Lwd_infix 3 5 open Vars ··· 415 417 items 416 418 |> W.Lists.selection_list_exclusions 417 419 ~on_selection_change:(fun revision -> 418 - (* Eio.Fiber.fork ~sw @@ fun _ -> *) 419 - (* Vars.update_ui_state @@ fun _ -> *) 420 - (* TODO: Do i need this now that we have the concurrency safeguards?*) 420 + (*Respond to change in selected revision*) 421 421 Lwd.set Vars.ui_state.selected_revision revision; 422 422 Show_view.(pushStatus (Graph_preview (Vars.get_selected_rev ()))); 423 + [%log debug "Selected revision: '%s'" (Global_vars.get_unique_id revision)]; 423 424 Picos_std_structured.Flock.fork (fun () -> Global_funcs.update_views ())) 424 425 ~custom_handler:(fun _ key -> handleKeys key) 425 426 in
+7 -2
jj_tui/bin/jj_commands.ml
··· 2 2 It allows us to define a command list: A list of keys, commands and descriptions 3 3 We can then run a command matching a key or generate a documentation UI element showing all available commands *) 4 4 5 + open Jj_tui.Logging 6 + 5 7 (** Internal to this module. I'm trying this out as a way to avoid .mli files*) 6 8 module Shared = struct 7 - type cmd_args = string list 9 + type cmd_args = string list [@@deriving show] 8 10 9 11 (** Regular jj command *) 10 12 type 'a command_variant = ··· 32 34 (** Allows nesting of commands, shows a popup with command options and waits for the user to press the appropriate key*) 33 35 | Fun of (unit -> unit) 34 36 (** Execute an arbitrary function. Prefer other command types if possible *) 37 + [@@deriving show] 35 38 36 39 (** A command that should be run when it's key is pressed*) 37 40 and 'a command = { ··· 39 42 ; description : string 40 43 ; cmd : 'a command_variant 41 44 } 45 + [@@deriving show] 42 46 end 43 47 44 48 (** Internal to this module. I'm trying this out as a way to avoid .mli files*) ··· 105 109 ;; 106 110 107 111 let rec handleCommand description cmd = 112 + [%log info "Handling command: %s" description]; 108 113 let noOut args = 109 114 let _ = args in 110 - let _result = jj (args ) in 115 + let _result = jj args in 111 116 Global_funcs.update_status ~cause_snapshot:false (); 112 117 () 113 118 in
+10 -7
jj_tui/bin/jj_process.ml
··· 2 2 open Picos_std_sync 3 3 open Picos_std_finally 4 4 open Spawn 5 + open Jj_tui.Logging 5 6 6 7 module type t = sig 7 8 val jj : ?snapshot:bool -> string list -> string ··· 115 116 (* This should ensure that all children processes are killed before we cleanup the pipes*) 116 117 Flock.join_after @@ fun () -> 117 118 let pid = 118 - Picos_io.Unix.create_process_env 119 - cmd 120 - (cmd :: args |> Array.of_list) 121 - (Unix.environment ()) 122 - stdin_o 123 - stdout_i 124 - stderr_i 119 + Picos_io.Unix.create_process_env 120 + cmd 121 + (cmd :: args |> Array.of_list) 122 + (Unix.environment ()) 123 + stdin_o 124 + stdout_i 125 + stderr_i 125 126 in 126 127 let prom = Flock.fork_as_promise (fun () -> Picos_io.Unix.waitpid [] pid) in 127 128 (* Close unused pipe ends in the parent process *) ··· 165 166 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 166 167 @param ?color=true When true output will have terminal escape codes for color *) 167 168 let jj_no_log_errorable ?(snapshot = true) ?(color = true) args = 169 + [%log debug "Running 'jj %s'" (args |> String.concat " ")]; 168 170 let locked = 169 171 if snapshot 170 172 then ( ··· 186 188 | Picos_std_structured.Control.Terminate as e -> 187 189 raise e 188 190 | e -> 191 + [%log warn "Exception running jj: %s" (Printexc.to_string e)]; 189 192 Error (`Exception (Printexc.to_string e)) 190 193 in 191 194 if locked then Mutex.unlock access_lock;
+15 -5
jj_tui/bin/main.ml
··· 3 3 open Nottui 4 4 module Jj_ui = Jj_ui.Make (Vars) 5 5 open Picos_std_structured 6 + open Jj_tui.Logging 7 + 8 + (* let file_logger ~logs_stream= 9 + let logs_crs=Picos_std_sync.Stream.tap logs_stream in 10 + let file=Picos_io.Unix.openfile "" in 11 + let handle_log cursor= 12 + let log,cursor =Picos_std_sync.Stream.read cursor in 13 + 14 + Picos_io.Unix.wri 15 + *) 6 16 7 17 let ui_loop ~quit ~term root = 8 18 print_endline "starting loop"; ··· 12 22 root 13 23 |> Nottui.Ui.event_filter (fun x -> 14 24 match x with 15 - | `Key (`ASCII 'q', [`Ctrl]) -> 25 + | `Key (`ASCII 'q', [ `Ctrl ]) -> 16 26 Lwd.set quit true; 17 27 `Handled 18 28 | _ -> ··· 33 43 ~renderer 34 44 term 35 45 (Lwd.observe @@ root); 36 - 37 46 (*Sleep for a bit to stop spinning the cpu 38 - TODO: May not be needed, nottui may sleep for a bit anyway 47 + TODO: May not be needed, nottui may sleep for a bit anyway 39 48 *) 40 49 let end_time = Sys.time () in 41 50 let elapsed = end_time -. start_time in ··· 53 62 Vars.term := Some term; 54 63 ui_loop ~quit:Vars.quit ~term (Jj_ui.mainUi ()); 55 64 Flock.terminate () 56 - 57 65 ;; 58 66 59 67 let start () = 60 68 Picos_mux_multififo.run_on ~n_domains:8 (fun _ -> 61 - Flock.join_after @@ fun () -> start_ui ()) 69 + Flock.join_after @@ fun () -> 70 + init_logging (); 71 + start_ui ()) 62 72 (* Picos_mux_multififo.run (fun () -> Flock.join_after (fun _ -> start_ui ())) *) 63 73 ;; 64 74
+3
jj_tui/bin/show_view.ml
··· 1 1 open Picos_std_sync 2 2 open Picos_std_structured 3 + open Jj_tui.Logging 3 4 4 5 type status_state = 5 6 | File_preview of (string * string) (*revision,filepath*) 6 7 | Graph_preview of string (*revision*) 8 + [@@deriving show] 7 9 8 10 let statusStream = Stream.create () 9 11 let lastMessage = None ··· 57 59 Promise.terminate_after ~seconds:0. !current_computation; 58 60 current_computation 59 61 := Flock.fork_as_promise (fun () -> 62 + [%log debug "Rendering status view with: %a" pp_status_state msg]; 60 63 viewState 61 64 $= 62 65 match msg with
+3 -3
jj_tui/lib/dune
··· 1 - (include_subdirs unqualified) 1 + (include_subdirs unqualified) 2 2 (library 3 3 4 4 (name jj_tui) 5 5 (inline_tests) 6 - (libraries stdio notty nottui angstrom bigstringaf eio_main eio-process) 6 + (libraries stdio notty nottui angstrom bigstringaf picos_std.sync picos_std.finally picos_std.structured logs) 7 7 (preprocess 8 - (pps ppx_expect)) 8 + (pps ppx_expect logs-ppx ppx_deriving.std)) 9 9 )
+100
jj_tui/lib/logging.ml
··· 1 + (** A version of the logging module that adds timestamps to the logs *) 2 + module Log = struct 3 + let timestamp_tag = 4 + let now () = Unix.gettimeofday () |> Unix.localtime in 5 + let time_to_string tm = 6 + Printf.sprintf 7 + "%04d-%02d-%02d %02d:%02d:%02d" 8 + (tm.Unix.tm_year + 1900) 9 + (tm.Unix.tm_mon + 1) 10 + tm.Unix.tm_mday 11 + tm.Unix.tm_hour 12 + tm.Unix.tm_min 13 + tm.Unix.tm_sec 14 + in 15 + Logs.Tag.def "timestamp" ~doc:"Timestamp" (fun fmt tm -> 16 + time_to_string tm |> Format.pp_print_string fmt) 17 + ;; 18 + 19 + let timestamp_wrap fn : ('a, 'b) Logs.msgf = 20 + fun m -> 21 + fn (fun ?header ?(tags = Logs.Tag.empty) fmt -> 22 + let timestamp = Unix.gettimeofday () |> Unix.localtime in 23 + let tags = Logs.Tag.add timestamp_tag timestamp Logs.Tag.empty in 24 + m ?header ~tags fmt) 25 + ;; 26 + 27 + let debug ?src fn = Logs.debug ?src (timestamp_wrap fn) 28 + let info ?src fn = Logs.info ?src (timestamp_wrap fn) 29 + let warn ?src fn = Logs.warn ?src (timestamp_wrap fn) 30 + let err ?src fn = Logs.err ?src (timestamp_wrap fn) 31 + let app ?src fn = Logs.app ?src (timestamp_wrap fn) 32 + end 33 + 34 + module Internal =struct 35 + let reporter ppf = 36 + let report src level ~over k msgf = 37 + let k _ = over (); k () in 38 + let with_stamp h tags k ppf fmt = 39 + let stamp = match tags with 40 + | None -> None 41 + | Some tags -> Logs.Tag.find Log.timestamp_tag tags 42 + in 43 + let dt = Format.pp_print_option (Logs.Tag.printer Log.timestamp_tag) in 44 + Format.kfprintf k ppf ("%a[%a] @["^^fmt^^"@]@.") 45 + Logs.pp_header (level, h) dt stamp 46 + in 47 + msgf @@ fun ?header ?tags fmt -> with_stamp header tags k ppf fmt 48 + in 49 + { Logs.report = report } 50 + (** Removes old log files, keeping only the 20 most recent *) 51 + let cleanup_logs () = 52 + let state_home = Unix.getenv "XDG_STATE_HOME" in 53 + let jj_tui_dir = Filename.concat state_home "jj_tui" in 54 + let log_files = Sys.readdir jj_tui_dir 55 + |> Array.to_list 56 + |> List.filter (fun file -> Filename.check_suffix file ".log") 57 + |> List.sort (fun a b -> String.compare b a) in 58 + if List.length log_files > 20 then 59 + List.iteri (fun i file -> 60 + if i >= 20 then 61 + let file_path = Filename.concat jj_tui_dir file in 62 + Unix.unlink file_path 63 + ) log_files 64 + ;; 65 + let init_logging () = 66 + (*creates or opens the log file*) 67 + let get_log_file_channel () = 68 + let state_home = Unix.getenv "XDG_STATE_HOME" in 69 + let jj_tui_dir = Filename.concat state_home "jj_tui" in 70 + (try Unix.mkdir jj_tui_dir 0o755 with Unix.Unix_error (Unix.EEXIST, _, _) -> ()); 71 + let timestamp = Unix.time () |> Unix.localtime in 72 + let timestamp_str = Printf.sprintf "%04d%02d%02d_%02d%02d%02d" 73 + (timestamp.tm_year + 1900) (timestamp.tm_mon + 1) timestamp.tm_mday 74 + timestamp.tm_hour timestamp.tm_min timestamp.tm_sec in 75 + let log_file = Filename.concat jj_tui_dir (Printf.sprintf "log_%s.log" timestamp_str) in 76 + let log_channel = open_out_gen [ Open_append; Open_creat ] 0o644 log_file in 77 + log_channel 78 + in 79 + (*Make a mutex for logging to prevent concurrency and threading issues *) 80 + let logging_mutex = Picos_std_sync.Mutex.create () in 81 + Logs.set_reporter_mutex 82 + ~lock:(fun () -> Picos_std_sync.Mutex.lock logging_mutex) 83 + ~unlock:(fun () -> Picos_std_sync.Mutex.unlock logging_mutex); 84 + (* log our logs into the log file*) 85 + let log_chan = get_log_file_channel () in 86 + let log_formatter = Format.formatter_of_out_channel log_chan in 87 + let reporter = reporter log_formatter in 88 + Logs.set_level (Some Debug); 89 + Logs.set_reporter reporter; 90 + (*make sure everything is working*) 91 + [%log info "Logging initialized"]; 92 + cleanup_logs (); 93 + [%log debug "Old logs cleaned up"] 94 + ;; 95 + 96 + end 97 + (** 98 + Initialize the logging system 99 + *) 100 + let init_logging= Internal.init_logging
-1
jj_tui/lib/process.ml
··· 1 - 2 1 type rev_id = { 3 2 change_id : string 4 3 ; commit_id : string
+3 -2
jj_tui/lib/process_wrappers.ml
··· 5 5 open Lwd_infix 6 6 open! Util 7 7 open Process 8 + open Logging 8 9 9 10 exception FoundStart 10 11 exception FoundFiller ··· 45 46 | 0x200A (* Hair Space *) 46 47 | 0x2028 (* Line Separator *) 47 48 | 0x2029 (* Paragraph Separator *) 48 - | 0x202F (* Narrow No-Break Space *) 49 + | 0x202F (* Narrow No-Brpeak Space *) 49 50 | 0x205F (* Medium Mathematical Space *) 50 51 | 0x3000 (* Ideographic Space *) -> 51 52 true ··· 101 102 || uchar |> Uchar.equal rev_symbol 102 103 || uchar |> Uchar.equal elieded_symbol 103 104 || uchar |> Uchar.equal elieded_symbol_alt 104 - || uchar |> Uchar.equal (make_uchar"×") 105 + || uchar |> Uchar.equal (make_uchar "×") 105 106 || char == '@') 106 107 && not (line |> Base.String.is_substring ~substring:"(elided revisions)") 107 108 then raise FoundStart)
-16
jj_tui/lib/widgets_citty.ml
··· 149 149 join3 (wrap_line pa) ub (wrap_line sb))) 150 150 ;; 151 151 152 - (* Grab the mouse and repeat an event until button is released *) 153 - let grab_and_repeat ~sw f = 154 - let stop = ref false in 155 - let rec step delay () = 156 - if not !stop 157 - then ( 158 - f (); 159 - (*TODO: this should not be a clock*) 160 - Eio_unix.sleep delay; 161 - step 0.025 ()) 162 - else () 163 - in 164 - Eio.Fiber.fork ~sw (step 0.4); 165 - `Grab ((fun ~x:_ ~y:_ -> ()), fun ~x:_ ~y:_ -> stop := true) 166 - ;; 167 - 168 152 let on_click f ~x:_ ~y:_ = function 169 153 | `Left -> 170 154 f ();