···2020 | Empty
2121 | Handle of int * var
2222 | Conflict of int
2323+ val peek_has_focus:handle->bool
23242425 val empty : status
2526 (*val is_empty : status -> bool*)
···5253 let has_focus = function
5354 | Empty -> false
5455 | Handle (i, _) | Conflict i -> i > 0
5656+5757+ let peek_has_focus (h : handle) : bool= fst h|>Lwd.peek>0
55585659 let clock = ref 0
5760
+2
forks/nottui/lib/nottui/nottui_main.mli
···3636 (** Check if this [status] corresponds to an active focus *)
3737 val has_focus : status -> bool
38383939+ (** EXPERIMENTAL: Check if the handle is focused.*)
4040+ val peek_has_focus:handle->bool
3941 (** TODO
4042 This implements a more general concept of "reactive auction":
4143
···77 open! Jj_tui.Util
88 open Jj_commands.Make (Vars)
99 open Global_vars
1010+ open Jj_tui
1111+ open Picos_std_structured
10121113 let selected_file = Lwd.var ""
1214···9193 ]
9294 ;;
93959494- let file_view sw () =
9696+ let file_view focus =
9597 let file_uis =
9698 let$ files = Lwd.get Vars.ui_state.jj_change_files in
9799 files
98100 |> List.map (fun (_modifier, file) ->
99101 W.Lists.{ data = file; ui = W.Lists.selectable_item (W.string file) })
100102 in
103103+ let show_diff_promise = ref @@ Promise.of_value () in
104104+ (*TODO:
105105+ This should be redesigned completely
106106+ There will be a new function that renders the show state
107107+ It will have a cancellation system just like this one.
108108+ when any of the dependencies change, selected file, selected rev, focus etc, it will re-render if needed and cancel the current rendering.
109109+110110+111111+ *)
112112+ let show_selected_file_diff ()=
113113+ (* kill any existing process writing to the show buffer*)
114114+ !show_diff_promise |> Promise.terminate;
115115+ (* set self as current process writing to show buffer*)
116116+ show_diff_promise
117117+ := Picos_std_structured.Flock.fork_as_promise (fun () ->
118118+ let rev = Vars.get_selected_rev () in
119119+ let selected = Lwd.peek selected_file in
120120+ Vars.ui_state.jj_show
121121+ $=
122122+ if selected != ""
123123+ then
124124+ let log=jj_no_log [ "diff"; "-r"; rev; selected ] in
125125+ Control.yield();
126126+ let res=log|> AnsiReverse.colored_string in
127127+ Control.yield();
128128+ res
129129+ else I.string A.empty "")
130130+ in
101131 W.Lists.selection_list_custom
102102- ~on_selection_change:(fun x ->
103103- Eio.Fiber.fork ~sw @@ fun _ ->
104104- Vars.update_ui_state @@ fun _ -> Lwd.set selected_file x)
132132+ ~on_selection_change:(fun selected ->
133133+ Lwd.set selected_file selected;
134134+ if Focus.peek_has_focus focus then show_selected_file_diff())
105135 ~custom_handler:(fun _ key ->
106136 match key with `ASCII k, [] -> handleInputs command_mapping k | _ -> `Unhandled)
107137 file_uis
108108- ;;
109109-110110- (**Get the status for the currently selected file*)
111111- let file_status () =
112112- let$ selected = Lwd.get selected_file
113113- and$ rev = Vars.get_selected_rev_lwd () in
114114- if selected != "" then jj_no_log [ "diff"; "-r"; rev; selected ] else ""
115138 ;;
116139end
+15-14
jj_tui/bin/global_funcs.ml
···11open Global_vars
22open Lwd_infix
33open Jj_process.Make (Global_vars.Vars)
44+open Picos_std_structured
4556let colored_string = Jj_tui.AnsiReverse.colored_string
67···2021 if str |> Base.String.is_substring ~substring:"There is no jj repo"
2122 then `NotInRepo
2223 else `OtherError str
2323- | Error (`EioErr a) ->
2424+ | Error (`Exception _) ->
2425 `CantStartProcess
2526;;
2627···3839let update_views ?(cause_snapshot = false) () =
3940 safe_jj (fun () ->
4041 let rev = Vars.get_selected_rev () in
4141- Eio.Switch.run @@ fun sw ->
4242- let log_res =
4343- jj_no_log ~snapshot:cause_snapshot [ "show"; "-s"; "--color-words"; "-r"; rev ]
4444- |> colored_string
4242+ Flock.join_after @@ fun () ->
4343+ let tree =
4444+ jj_no_log ~snapshot:cause_snapshot [ "log"; "-r"; rev ] |> colored_string
4545 in
4646 (* From now on we use ignore-working-copy so we don't re-snapshot the state and so
4747 we can operate in paralell *)
4848- let tree =
4949- Eio.Fiber.fork_promise ~sw (fun _ ->
5050- jj_no_log ~snapshot:false [ "log"; "-r"; rev ] |> colored_string)
5148 (* TODO: stop using dop last twice *)
4949+ let log_res =
5050+ Flock.fork_as_promise (fun _ ->
5151+ jj_no_log ~snapshot:false [ "show"; "-s"; "--color-words"; "-r"; rev ]
5252+ |> colored_string)
5253 and branches =
5353- Eio.Fiber.fork_promise ~sw (fun _ ->
5454+ Flock.fork_as_promise (fun _ ->
5455 jj_no_log ~snapshot:false [ "branch"; "list"; "-a" ] |> colored_string)
5555- and files_list = Eio.Fiber.fork_promise ~sw (fun _ -> list_files ~rev ()) in
5656+ and files_list = Flock.fork_as_promise (fun _ -> list_files ~rev ()) in
5657 (*wait for all our tasks*)
5757- let tree = Eio.Promise.await_exn tree
5858- and files_list = Eio.Promise.await_exn files_list
5959- and branches = Eio.Promise.await_exn branches in
5858+ let log_res= Promise.await log_res
5959+ and files_list = Promise.await files_list
6060+ and branches = Promise.await branches in
6061 (*now we can assign our results*)
6161- Vars.ui_state.jj_show $= log_res;
6262+ (* Vars.ui_state.jj_show $= log_res; *)
6263 Vars.ui_state.jj_branches $= branches;
6364 Vars.ui_state.jj_tree $= tree;
6465 Vars.ui_state.jj_change_files $= files_list)
+6-4
jj_tui/bin/graph_view.ml
···348348349349 (*TODO:make a custom widget the renders the commit with and without selection.
350350 with selection replace the dot with a blue version and slightly blue tint the background *)
351351- let graph_view ~sw () =
351351+ let graph_view () =
352352 (*We have a seperate error var here instead of using a result type. This allows us to avoid using Lwd.bind which would cause our list selection to get reset anytime the content changes *)
353353 let error_var = Lwd.var None in
354354 let revset_ui =
···415415 items
416416 |> W.Lists.selection_list_exclusions
417417 ~on_selection_change:(fun revision ->
418418- Eio.Fiber.fork ~sw @@ fun _ ->
419419- Vars.update_ui_state @@ fun _ ->
418418+ (* Eio.Fiber.fork ~sw @@ fun _ -> *)
419419+ (* Vars.update_ui_state @@ fun _ -> *)
420420+ (* TODO: Do i need this now that we have the concurrency safeguards?*)
421421+ Picos_std_structured.Flock.fork(fun ()->
420422 Lwd.set Vars.ui_state.selected_revision revision;
421421- Global_funcs.update_views ())
423423+ Global_funcs.update_views ()))
422424 ~custom_handler:(fun _ key -> handleKeys key)
423425 in
424426 let final_ui =
+44-46
jj_tui/bin/jj_process.ml
···11-open Eio
11+open Picos_std_structured
22+open Picos_std_sync
33+open Picos_std_finally
2435module type t = sig
46 val jj : string list -> string
55- val switch_to_process : Eio_unix.Stdenv.base -> string list -> Process.exit_status
77+ val switch_to_process : string list -> Unix.process_status
68end
79810exception JJError of string * string
···1012module Make (Vars : Global_vars.Vars) = struct
1113 (** Makes a new process that has acess to all input and output
1214 This should be used for running other tui sub-programs *)
1313- let switch_to_process env command =
1414- Switch.run @@ fun sw ->
1515- let mgr = Eio.Stdenv.process_mgr env in
1616- let stdout = Eio.Stdenv.stdout env in
1717- let stdin = Eio.Stdenv.stdin env in
1818- let stderr = Eio.Stdenv.stderr env in
1919- let proc = Eio.Process.spawn ~sw mgr ~stderr ~stdin ~stdout command in
2020- proc |> Eio.Process.await
1515+ let switch_to_process command =
1616+ let stdout = Unix.stdout in
1717+ let stdin = Unix.stdin in
1818+ let stderr = Unix.stderr in
1919+ let pid = Unix.create_process command.(0) command stdin stdout stderr in
2020+ let _, status = Unix.waitpid [] pid in
2121+ status
2122 ;;
22232324 (* Ui_loop.run (Lwd.pure (W.printf "Hello world"));; *)
2425 let cmdArgs cmd args =
2525- let Vars.{ cwd; mgr; _ } = Vars.get_eio_vars () in
2626- let out =
2727- Eio_process.run
2828- ~cwd
2929- ~process_mgr:mgr
3030- ~prog:cmd
3131- ~args
3232- ~f:(fun x ->
3333- (match x.exit_status with
3434- | `Exited i ->
3535- if i == 0 then `Ok (x.stdout, x.stderr) else `BadExit (i, x.stderr)
3636- | `Signaled i ->
3737- `BadExit (i, x.stderr))
3838- |> Base.Or_error.return)
3939- ()
2626+ let stdout, stdin, stderr =
2727+ Unix.open_process_args_full cmd (Array.of_list (cmd::args)) (Unix.environment ())
4028 in
4141- match out with
4242- | Error a ->
4343- Error (`EioErr a)
4444- | Ok (`Ok a) ->
4545- Ok a
4646- | Ok (`BadExit _ as a) ->
4747- Error a
2929+ let out_content = In_channel.input_all stdout in
3030+ let err_content = In_channel.input_all stderr in
3131+ (* TODO: may need to wait before calling close*)
3232+ let status = Unix.close_process_full (stdout, stdin, stderr) in
3333+ let exit_code =
3434+ match status with
3535+ | Unix.WEXITED code ->
3636+ code
3737+ | Unix.WSIGNALED _ | Unix.WSTOPPED _ ->
3838+ -1
3939+ in
4040+ match exit_code with
4141+ | 0 ->
4242+ Ok (out_content, err_content)
4343+ | _ ->
4444+ Error (`BadExit (exit_code, err_content ^ "\n" ^ out_content))
4845 ;;
49465047 (** Prevents concurrent acess to jj when running commands that cause snapshotting.
5148 jj can get currupted otherwise *)
5252- let access_lock = Eio.Mutex.create ()
4949+ let access_lock = Mutex.create ()
53505451 (** Run a jj command without outputting to the command_log.
5552 @param ?snapshot=true
···6461 else false
6562 in
6663 let res =
6767- cmdArgs
6868- "jj"
6969- (List.concat
7070- [
7171- args
7272- ; (if snapshot then [] else [ "--ignore-working-copy" ])
7373- ; (if color then [ "--color"; "always" ] else [ "--color"; "never" ])
7474- ])
6464+ try
6565+ cmdArgs
6666+ "jj"
6767+ (List.concat
6868+ [
6969+ args
7070+ ; (if snapshot then [] else [ "--ignore-working-copy" ])
7171+ ; (if color then [ "--color"; "always" ] else [ "--color"; "never" ])
7272+ ])
7373+ with
7474+ | e ->
7575+ Error (`Exception (Printexc.to_string e))
7576 in
7677 if locked then Mutex.unlock access_lock;
7778 res
···9091 (JJError
9192 ( "jj" :: args |> String.concat " "
9293 , Printf.sprintf "Exited with code %i; Message:\n%s" code str ))
9393- | Error (`EioErr a) ->
9494+ | Error (`Exception a) ->
9495 raise
9596 (JJError
9697 ( "jj" :: args |> String.concat " "
9797- , Printf.sprintf
9898- "Error running jj process:\n%a"
9999- (fun _ -> Base.Error.to_string_hum)
100100- a ))
9898+ , Printf.sprintf "Error running jj process:\n%s" a ))
10199 ;;
102100103101 let jj args =
+22-30
jj_tui/bin/jj_ui.ml
···11+open Picos_std_structured
12open Notty
23open Nottui
34open Lwd_infix
45open Global_funcs
56open Jj_tui.Util
67open Jj_tui
77-88+module Pio = Picos_io
89910module Ui = struct
1011 include Nottui.Ui
···2728 let full_term_sized_background =
2829 let$ term_width, term_height = Lwd.get Vars.term_width_height in
2930 Notty.I.void term_width term_height |> Nottui.Ui.atom
3030- let blue= I.string A.((fg blue)++(bg blue)++(st bold)) "blue"
3131 ;;
3232+3333+ let blue = I.string A.(fg blue ++ bg blue ++ st bold) "blue"
32343335 let _quitButton =
3436 W.button (Printf.sprintf "quit ") (fun () -> Vars.quit $= true) |> Lwd.pure
···7072 W.string message
7173 |> Lwd.pure
7274 |> W.Box.box
7373- |>$ Ui.resize
7474- ~sw:1
7575- ~sh:1
7676- ~mw:10000
7777- ~mh:10000
7878- ~crop:W.neutral_grav
7979- ~pad:W.neutral_grav
7575+ |>$ Ui.resize ~sw:1 ~sh:1 ~mw:10000 ~mh:10000 ~crop:W.neutral_grav ~pad:W.neutral_grav
8076 |> inputs
8177 ;;
82788379 (** The primary view for the UI with the file_view graph_view and summary*)
8484- let main_view ~sw =
8080+ let main_view =
8581 let file_focus = Focus.make () in
8682 let graph_focus = Focus.make () in
8783 Focus.request graph_focus;
···9288 (*left side window stack*)
9389 W.vbox
9490 [
9595- File_view.file_view sw ()
9191+ File_view.file_view file_focus
9692 |>$ Ui.resize ~w:5 ~sw:1 ~mw:1000
9793 |> W.Box.focusable ~focus:file_focus ~pad_h:0 ~pad_w:1
9898- ; Graph_view.graph_view ~sw ()
9494+ ; Graph_view.graph_view ()
9995 |>$ Ui.resize ~sh:3 ~w:5 ~sw:1 ~mw:1000 ~h:10 ~mh:1000
10096 |> W.Box.focusable ~focus:graph_focus ~pad_h:0 ~pad_w:1
101101- ; W.Scroll.area(ui_state.jj_branches $-> Ui.atom)
9797+ ; W.Scroll.area (ui_state.jj_branches $-> Ui.atom)
10298 |> W.is_focused ~focus:branch_focus (fun ui focused ->
10399 ui
104100 |> Ui.keyboard_area (function
···116112 |> W.Box.focusable ~focus:branch_focus ~pad_h:0 ~pad_w:1
117113 ]
118114 ; (*Right side summary/status/fileinfo view*)
119119- (let$* file_focus = file_focus |> Focus.status in
120120- if file_focus |> Focus.has_focus
121121- then
122122- let$ status = File_view.file_status () in
123123- status |> AnsiReverse.colored_string |> Ui.atom
124124- else (fun x -> x |> Ui.atom) <-$ ui_state.jj_show)
115115+ ui_state.jj_show
116116+ $-> Ui.atom
125117 |> W.Scroll.area
126118 (* let mw=Int.max (Ui.layout_max_width ui) 100 in *)
127119 |>$ Ui.resize ~w:0 ~sh:3 ~sw:2 ~mw:10000 ~mh:10000
···131123 (*These outer prompts can popup and show them selves over the main view*)
132124 |> W.Overlay.text_prompt ~char_count:true ~show_prompt_var:ui_state.show_prompt
133125 |> W.Overlay.popup ~show_popup_var:ui_state.show_popup
134134- |> W.Overlay.selection_list_prompt_filterable ~show_prompt_var:(ui_state.show_string_selection_prompt)
126126+ |> W.Overlay.selection_list_prompt_filterable
127127+ ~show_prompt_var:ui_state.show_string_selection_prompt
135128 |> inputs
136129 ;;
137130···147140 |> inputs
148141 ;;
149142150150- let mainUi ~sw env =
143143+ let mainUi () =
151144 (*we want to initialize our states and keep them up to date*)
152145 match check_startup () with
153146 | `Good ->
154147 update_status ~cause_snapshot:true ();
155155- Eio.Fiber.fork_daemon ~sw (fun _ ->
156156- let clock = Eio.Stdenv.clock env in
148148+ Flock.fork (fun () ->
157149 while true do
158158- Eio.Time.sleep clock 5.0;
150150+ Picos.Fiber.sleep ~seconds:5.0;
159151 (*we need to lock this becasue we could end up updating while the ui is rendering*)
160160- Vars.render_mutex |> Eio.Mutex.lock;
161161- update_status ~cause_snapshot:true ();
162162- Vars.render_mutex |> Eio.Mutex.unlock
152152+ (* Vars.render_mutex |> Eio.Mutex.lock; *)
153153+ update_status ~cause_snapshot:true ()
154154+ (* Vars.render_mutex |> Eio.Mutex.unlock *)
163155 done;
164164- `Stop_daemon);
156156+ ());
165157 let$* running = Lwd.get ui_state.view in
166158 (match running with
167159 | `Cmd_I cmd ->
···169161 Lwd.set ui_state.view @@ `RunCmd cmd;
170162 full_term_sized_background
171163 | `RunCmd cmd ->
172172- Jj_widgets.interactive_process env ("jj" :: cmd)
164164+ Jj_widgets.interactive_process ("jj" :: cmd)
173165 | `Main ->
174174- W.keyboard_tabs [ ("Main", fun _ -> main_view ~sw); "Op log", log_view ])
166166+ W.keyboard_tabs [ ("Main", fun _ -> main_view); "Op log", log_view ])
175167 | (`CantStartProcess | `NotInRepo | `OtherError _) as other ->
176168 render_startup_error other
177169 ;;
+5-4
jj_tui/bin/jj_widgets.ml
···139139140140 (** Start a process that will take full control of both stdin and stdout.
141141 This is used for interactive diffs and such*)
142142- let interactive_process env cmd =
142142+ let interactive_process cmd =
143143 let post_change new_view =
144144 Global_funcs.update_status ();
145145 Lwd.set ui_state.view new_view
146146 in
147147- let exit_status_to_str y =
148148- match match y with `Exited x -> x | `Signaled x -> x with
147147+ let exit_status_to_str (y : Unix.process_status) =
148148+ let open Unix in
149149+ match match y with WSTOPPED x -> x | WEXITED x -> x | WSIGNALED x -> x with
149150 | 0 ->
150151 "success"
151152 | 1 ->
···153154 | a ->
154155 Printf.sprintf "unknown code %d" a
155156 in
156156- let res = switch_to_process env cmd in
157157+ let res = switch_to_process (cmd |> Array.of_list) in
157158 let$ ui =
158159 W.vbox
159160 [
+17-11
jj_tui/bin/main.ml
···11-open Eio.Std
21open Lwd_infix
32module Vars = Global_vars.Vars
43open Nottui
54module Jj_ui = Jj_ui.Make (Vars)
66-55+open Picos_std_structured
7687let ui_loop ~quit ~term root =
98 print_endline "starting loop";
···2221 let rec loop () =
2322 if not (Lwd.peek quit)
2423 then (
2424+ let start_time = Sys.time () in
2525 let term_width, term_height = Notty_unix.Term.size (Vars.get_term ()) in
2626 let prev_term_width, prev_term_height = Lwd.peek Vars.term_width_height in
2727 if term_width <> prev_term_width || term_height <> prev_term_height
2828 then Lwd.set Vars.term_width_height (term_width, term_height);
2929- Vars.render_mutex |> Eio.Mutex.lock;
2929+ (* Vars.render_mutex |> Eio.Mutex.lock; *)
3030 Nottui.Ui_loop.step
3131 ~process_event:true
3232- ~timeout:0.05
3232+ ~timeout:0.01
3333 ~renderer
3434 term
3535 (Lwd.observe @@ root);
3636- Vars.render_mutex |> Eio.Mutex.unlock;
3737- Eio.Fiber.yield ();
3636+ (* Vars.render_mutex |> Eio.Mutex.unlock; *)
3737+ let end_time = Sys.time () in
3838+ let elapsed = end_time -. start_time in
3939+ let sleep_time = max 0.01 (0.01 -. elapsed) in
4040+ Unix.sleepf sleep_time;
4141+ Picos.Fiber.yield ();
3842 loop ())
3943 in
4044 loop ()
4145;;
42464347(*TODO:For hosting a subprocess i should look into using EIO and Ui_loop.step like some of the other libraries built with nottui*)
4444-let start_ui env =
4545- Switch.run @@ fun sw ->
4848+let start_ui () =
4649 (*initialse the state*)
4750 let term = Notty_unix.Term.create () in
4851 Vars.term := Some term;
4949- Vars.set_eio_env env;
5050- ui_loop ~quit:Vars.quit ~term (Jj_ui.mainUi ~sw env)
5252+ ui_loop ~quit:Vars.quit ~term (Jj_ui.mainUi ())
5153;;
52545353-let start () = Eio_main.run @@ fun env -> Fiber.all [ (fun _ -> start_ui env) ];;
5555+let start () =
5656+ Picos_mux_multififo.run_on ~n_domains:8 (fun _ ->
5757+ Flock.join_after @@ fun () -> start_ui ())
5858+(* Picos_mux_multififo.run (fun () -> Flock.join_after (fun _ -> start_ui ())) *)
5959+;;
54605561start ()
+30-21
jj_tui/lib/ansiReverse.ml
···146146 It parses the escape codes and then creates notty images from that by applying the styles*)
147147let ansi_string_to_image ?(extra_attr = A.empty) str =
148148 let str =
149149- (* replace any carrriage returns becasue notty doesn't know what to do with them*)
150150- Base.String.Search_pattern.replace_all
151151- (Base.String.Search_pattern.create "\r\n")
152152- ~in_:str
153153- ~with_:"\n"
154154- |> Base.String.Search_pattern.replace_all
155155- (Base.String.Search_pattern.create "\r")
156156- ~with_:"\n"
157157- (*tabs cause issues too*)
158158- |> Base.String.Search_pattern.replace_all
159159- (Base.String.Search_pattern.create "\t")
160160- ~with_:" "
161161- (*delete control char*)
162162- |> Base.String.Search_pattern.replace_all
163163- (Base.String.Search_pattern.create "\u{7f}")
164164- ~with_:" "
165165- (*replace form feed with a symbol: https://codepoints.net/U+000C?lang=en*)
166166- |> Base.String.Search_pattern.replace_all
167167- (Base.String.Search_pattern.create "")
168168- ~with_:"↡"
149149+ let buffer = Buffer.create (String.length str) in
150150+ let last_char = ref '\000' in
151151+ String.iter
152152+ (fun c ->
153153+ match c with
154154+ | '\r' ->
155155+ last_char := '\r'
156156+ | '\n' when !last_char <> '\r' ->
157157+ Buffer.add_char buffer '\n'
158158+ | '\n' ->
159159+ Buffer.add_char buffer '\n';
160160+ last_char := '\000'
161161+ | '\t' ->
162162+ Buffer.add_string buffer " "
163163+ | '\x7F' ->
164164+ Buffer.add_string buffer " "
165165+ | '\x0C' ->
166166+ Buffer.add_string buffer "↡"
167167+ | _ ->
168168+ Buffer.add_char buffer c)
169169+ str;
170170+ Buffer.contents buffer
169171 in
170172 match Parser.parse_ansi_escape_codes str with
171173 | Error a ->
···198200;;
199201200202(** Same as ansi_string_to_image, but can throw if a parsing error occurs. I have not seen it fail, should be safe. *)
201201-let colored_string ?extra_attr s = s |> ansi_string_to_image ?extra_attr |> Result.get_ok
203203+let colored_string ?extra_attr ?(max_length = 50000) s =
204204+ let truncated_s =
205205+ if String.length s > max_length
206206+ then String.sub s 0 max_length ^ "\n...truncated"
207207+ else s
208208+ in
209209+ truncated_s |> ansi_string_to_image ?extra_attr |> Result.get_ok
210210+;;