A CLI and OCaml library for managing contacts
0
fork

Configure Feed

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

sync

+44 -43
+1 -1
bin/dune
··· 1 1 (executable 2 2 (name sortal_cli) 3 3 (public_name sortal) 4 - (libraries eio eio_main sortal xdge cmdliner logs logs.cli logs.fmt fmt fmt.tty kgp)) 4 + (libraries eio eio_main sortal xdge cmdliner logs logs.cli logs.fmt fmt fmt.tty kgp kgp-cmd))
+4 -2
bin/sortal_cli.ml
··· 32 32 ] 33 33 in 34 34 35 - let list_cmd_term = Term.(const (fun () -> Sortal.Cmd.list_cmd ()) $ const ()) in 35 + let graphics_term = Kgp_cmdliner.graphics_term in 36 + 37 + let list_cmd_term = Term.(const Sortal.Cmd.list_cmd $ graphics_term) in 36 38 let list_cmd = run ~info:Sortal.Cmd.list_info list_cmd_term in 37 39 38 - let show_cmd_term = Term.(const (fun handle -> Sortal.Cmd.show_cmd handle) $ Sortal.Cmd.handle_arg) in 40 + let show_cmd_term = Term.(const Sortal.Cmd.show_cmd $ graphics_term $ Sortal.Cmd.handle_arg) in 39 41 let show_cmd = run ~info:Sortal.Cmd.show_info show_cmd_term in 40 42 41 43 let search_cmd_term = Term.(const (fun query -> Sortal.Cmd.search_cmd query) $ Sortal.Cmd.query_arg) in
+33 -36
lib/sortal_cmd.ml
··· 1 1 open Cmdliner 2 2 3 - let image_columns = 9 (* columns for 4-row square-ish thumbnail + 3 padding *) 3 + let image_columns = 11 (* columns for 4-row thumbnail (8 cols) + 3 padding *) 4 4 5 - let supports_kitty_graphics () = 6 - let check_env var = 7 - match Sys.getenv_opt var with 8 - | Some _ -> true 9 - | None -> false 10 - in 11 - let check_env_contains var substr = 12 - match Sys.getenv_opt var with 13 - | Some v -> String.lowercase_ascii v |> fun s -> 14 - String.length s >= String.length substr && 15 - let rec check i = 16 - if i > String.length s - String.length substr then false 17 - else if String.sub s i (String.length substr) = substr then true 18 - else check (i + 1) 19 - in check 0 20 - | None -> false 21 - in 22 - check_env "KITTY_WINDOW_ID" || 23 - check_env "WEZTERM_PANE" || 24 - check_env "GHOSTTY_RESOURCES_DIR" || 25 - check_env_contains "TERM_PROGRAM" "kitty" || 26 - check_env_contains "TERM_PROGRAM" "wezterm" || 27 - check_env_contains "TERM_PROGRAM" "ghostty" || 28 - check_env_contains "TERM" "kitty" 5 + (* Resolve graphics mode to determine if we should use graphics output *) 6 + let use_graphics mode = 7 + Kgp.Terminal.supports_graphics mode 8 + 9 + let next_image_id = ref 1 29 10 30 11 let display_png_thumbnail path = 31 12 let png_data = Eio.Path.load path in 32 - let placement = Kgp.Placement.make ~rows:4 ~cursor:`Static () in 33 - let cmd = Kgp.transmit_and_display ~format:`Png ~placement () in 34 - Kgp.to_string cmd ~data:png_data 13 + let rows = 4 in 14 + let cols = 8 in 15 + (* Direct rendering - cursor stays in place after image *) 16 + let placement = Kgp.Placement.make ~rows ~columns:cols ~cursor:`Static () in 17 + let cmd = Kgp.transmit_and_display ~format:`Png ~placement ~quiet:`Silent () in 18 + let buf = Buffer.create 4096 in 19 + Kgp.write_tmux buf cmd ~data:png_data; 20 + Buffer.contents buf 35 21 36 22 let display_block_placeholder () = 37 23 (* 4 rows of patterned block characters as fallback *) ··· 48 34 49 35 (* 1-row thumbnail for listings *) 50 36 let display_small_thumbnail path = 37 + let image_id = !next_image_id in 38 + incr next_image_id; 51 39 let png_data = Eio.Path.load path in 52 - let placement = Kgp.Placement.make ~rows:1 () in 53 - let cmd = Kgp.transmit_and_display ~format:`Png ~placement () in 54 - Kgp.to_string cmd ~data:png_data 40 + let rows = 1 in 41 + let cols = 2 in 42 + (* Transmit image with unicode placeholder virtual placement, suppress responses *) 43 + let placement = Kgp.Placement.make ~rows ~columns:cols ~unicode_placeholder:true () in 44 + let cmd = Kgp.transmit_and_display ~image_id ~format:`Png ~placement ~quiet:`Silent () in 45 + let buf = Buffer.create 4096 in 46 + (* Use tmux-aware write that wraps for passthrough if needed *) 47 + Kgp.write_tmux buf cmd ~data:png_data; 48 + (* Write unicode placeholders (these are just text, no wrapping needed) *) 49 + Kgp.Unicode_placeholder.write buf ~image_id ~rows ~cols (); 50 + Buffer.add_char buf ' '; (* spacing after thumbnail *) 51 + Buffer.contents buf 55 52 56 53 let small_placeholder () = "▓░ " (* 1-row patterned placeholder *) 57 54 58 - let list_cmd () xdg = 55 + let list_cmd mode xdg = 59 56 let store = Sortal_store.create_from_xdg xdg in 60 57 let contacts = Sortal_store.list store in 61 58 let sorted = List.sort Sortal_contact.compare contacts in 62 - let use_graphics = supports_kitty_graphics () in 59 + let graphics = use_graphics mode in 63 60 Printf.printf "Total contacts: %d\n" (List.length sorted); 64 61 List.iter (fun c -> 65 62 (match Sortal_store.png_thumbnail_path store c with 66 63 | Some path -> 67 - if use_graphics then 64 + if graphics then 68 65 print_string (display_small_thumbnail path) 69 66 else 70 67 print_string (small_placeholder ()) ··· 74 71 ) sorted; 75 72 0 76 73 77 - let show_cmd handle xdg = 74 + let show_cmd mode handle xdg = 78 75 let store = Sortal_store.create_from_xdg xdg in 79 76 match Sortal_store.lookup store handle with 80 77 | Some c -> 81 78 let has_thumbnail = match Sortal_store.png_thumbnail_path store c with 82 79 | Some path -> 83 - if supports_kitty_graphics () then 80 + if use_graphics mode then 84 81 print_string (display_png_thumbnail path) 85 82 else 86 83 print_string (display_block_placeholder ());
+6 -4
lib/sortal_cmd.mli
··· 5 5 6 6 (** {1 Command Implementations} *) 7 7 8 - (** [list_cmd] is a Cmdliner command that lists all contacts. 8 + (** [list_cmd mode] is a Cmdliner command that lists all contacts. 9 9 10 + @param mode The graphics mode to use for thumbnails. 10 11 Usage: Integrate into your CLI with [Cmd.group] or use standalone. 11 12 Returns a function that takes an XDG context and returns an exit code. *) 12 - val list_cmd : unit -> (Xdge.t -> int) 13 + val list_cmd : Kgp.Terminal.graphics_mode -> (Xdge.t -> int) 13 14 14 - (** [show_cmd handle] creates a command to show detailed contact information. 15 + (** [show_cmd mode handle] creates a command to show detailed contact information. 15 16 17 + @param mode The graphics mode to use for thumbnails. 16 18 @param handle The contact handle to display *) 17 - val show_cmd : string -> (Xdge.t -> int) 19 + val show_cmd : Kgp.Terminal.graphics_mode -> string -> (Xdge.t -> int) 18 20 19 21 (** [search_cmd query] creates a command to search contacts by name. 20 22