Kitty Graphics Protocol in OCaml
terminal graphics ocaml

restructure types

+2 -2
example/anim_test.ml
···
let image_id = 500 in
(* Clear any existing image *)
-
send (K.Command.delete ~quiet:`Errors_only (`All_visible_and_free)) ~data:"";
(* Step 1: Transmit base frame (red) *)
let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in
···
~data:"";
(* Clean up *)
-
send (K.Command.delete ~quiet:`Errors_only (`All_visible_and_free)) ~data:"";
print_endline "Done."
···
let image_id = 500 in
(* Clear any existing image *)
+
send (K.Command.delete ~free:true ~quiet:`Errors_only `All_visible) ~data:"";
(* Step 1: Transmit base frame (red) *)
let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in
···
~data:"";
(* Clean up *)
+
send (K.Command.delete ~free:true ~quiet:`Errors_only `All_visible) ~data:"";
print_endline "Done."
example/anim_test.mli

This is a binary file and will not be displayed.

example/debug_anim.mli

This is a binary file and will not be displayed.

example/test_output.mli

This is a binary file and will not be displayed.

+2 -2
example/tiny_anim.ml
···
let image_id = 999 in
(* Clear any existing images *)
-
send (K.Command.delete ~quiet:`Errors_only (`All_visible_and_free)) ~data:"";
(* Step 1: Transmit base frame (red) - matching Go's sequence *)
let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in
···
~data:"";
(* Clean up *)
-
send (K.Command.delete ~quiet:`Errors_only (`All_visible_and_free)) ~data:"";
print_endline "Done."
···
let image_id = 999 in
(* Clear any existing images *)
+
send (K.Command.delete ~free:true ~quiet:`Errors_only `All_visible) ~data:"";
(* Step 1: Transmit base frame (red) - matching Go's sequence *)
let red_frame = solid_color_rgba ~width ~height ~r:255 ~g:0 ~b:0 ~a:255 in
···
~data:"";
(* Clean up *)
+
send (K.Command.delete ~free:true ~quiet:`Errors_only `All_visible) ~data:"";
print_endline "Done."
example/tiny_anim.mli

This is a binary file and will not be displayed.

-10
lib/kgp.ml
···
module Delete = Kgp_delete
module Animation_state = Kgp_animation_state
-
(* Type aliases *)
-
type format = Format.t
-
type transmission = Transmission.t
-
type compression = Compression.t
-
type quiet = Quiet.t
-
type cursor = Cursor.t
-
type composition = Composition.t
-
type delete = Delete.t
-
type animation_state = Animation_state.t
-
(* Configuration modules *)
module Placement = Kgp_placement
module Frame = Kgp_frame
···
module Delete = Kgp_delete
module Animation_state = Kgp_animation_state
(* Configuration modules *)
module Placement = Kgp_placement
module Frame = Kgp_frame
+9 -5
lib/kgp.mli
···
(** {2 Deletion}
Images and placements can be deleted to free terminal resources.
-
Lowercase delete commands remove placements but keep image data;
-
uppercase variants also free the image data. *)
-
val delete : ?quiet:Quiet.t -> Delete.t -> command
(** Delete images or placements.
See {!Delete} for the full list of deletion targets.
Examples:
{[
-
(* Delete all visible images *)
Kgp.delete `All_visible
(* Delete specific image, keeping data for reuse *)
Kgp.delete (`By_id (42, None))
(* Delete specific image and free its data *)
-
Kgp.delete (`By_id_and_free (42, None))
(* Delete all placements at a specific cell *)
Kgp.delete (`At_cell (10, 5))
···
(** {2 Deletion}
Images and placements can be deleted to free terminal resources.
+
By default, only placements are removed and image data is retained
+
for potential reuse. Use [~free:true] to also release the image data. *)
+
val delete : ?free:bool -> ?quiet:Quiet.t -> Delete.t -> command
(** Delete images or placements.
See {!Delete} for the full list of deletion targets.
+
@param free If true, also free the image data from memory (default: false).
+
Without [~free:true], only placements are removed and the image data
+
can be reused for new placements.
+
Examples:
{[
+
(* Delete all visible images, keep data *)
Kgp.delete `All_visible
(* Delete specific image, keeping data for reuse *)
Kgp.delete (`By_id (42, None))
(* Delete specific image and free its data *)
+
Kgp.delete ~free:true (`By_id (42, None))
(* Delete all placements at a specific cell *)
Kgp.delete (`At_cell (10, 5))
+16 -15
lib/kgp_command.ml
···
image_number : int option;
placement : Kgp_placement.t option;
delete : Kgp_delete.t option;
frame : Kgp_frame.t option;
animation : Kgp_animation.t option;
compose : Kgp_compose.t option;
···
image_number = None;
placement = None;
delete = None;
frame = None;
animation = None;
compose = None;
···
let display ?image_id ?image_number ?placement ?quiet () =
{ (make `Display) with image_id; image_number; placement; quiet }
-
let delete ?quiet del = { (make `Delete) with quiet; delete = Some del }
let frame ?image_id ?image_number ?format ?transmission ?compression ?width
?height ?quiet ~frame () =
···
kv_int_if w 'C' ~default:0 (Some (Kgp_cursor.to_int c)));
if Kgp_placement.unicode_placeholder p then kv_int w 'U' 1
-
let write_delete w (d : Kgp_delete.t) =
-
kv_char w 'd' (Kgp_delete.to_char d);
match d with
-
| `By_id (id, pid) | `By_id_and_free (id, pid) ->
kv_int w 'i' id;
kv_int_opt w 'p' pid
-
| `By_number (n, pid) | `By_number_and_free (n, pid) ->
kv_int w 'I' n;
kv_int_opt w 'p' pid
-
| `At_cell (x, y) | `At_cell_and_free (x, y) ->
kv_int w 'x' x;
kv_int w 'y' y
-
| `At_cell_z (x, y, z) | `At_cell_z_and_free (x, y, z) ->
kv_int w 'x' x;
kv_int w 'y' y;
kv_int w 'z' z
-
| `By_column c | `By_column_and_free c -> kv_int w 'x' c
-
| `By_row r | `By_row_and_free r -> kv_int w 'y' r
-
| `By_z_index z | `By_z_index_and_free z -> kv_int w 'z' z
-
| `By_id_range (min_id, max_id) | `By_id_range_and_free (min_id, max_id) ->
kv_int w 'x' min_id;
kv_int w 'y' max_id
-
| `All_visible | `All_visible_and_free | `At_cursor | `At_cursor_and_free
-
| `Frames | `Frames_and_free ->
-
()
let write_frame w (f : Kgp_frame.t) =
kv_int_opt w 'x' (Kgp_frame.x f);
···
kv_int_opt w 'I' cmd.image_number;
(* Complex options *)
cmd.placement |> Option.iter (write_placement w);
-
cmd.delete |> Option.iter (write_delete w);
cmd.frame |> Option.iter (write_frame w);
cmd.animation |> Option.iter (write_animation w);
cmd.compose |> Option.iter (write_compose w);
···
image_number : int option;
placement : Kgp_placement.t option;
delete : Kgp_delete.t option;
+
delete_free : bool;
frame : Kgp_frame.t option;
animation : Kgp_animation.t option;
compose : Kgp_compose.t option;
···
image_number = None;
placement = None;
delete = None;
+
delete_free = false;
frame = None;
animation = None;
compose = None;
···
let display ?image_id ?image_number ?placement ?quiet () =
{ (make `Display) with image_id; image_number; placement; quiet }
+
let delete ?(free = false) ?quiet del =
+
{ (make `Delete) with quiet; delete = Some del; delete_free = free }
let frame ?image_id ?image_number ?format ?transmission ?compression ?width
?height ?quiet ~frame () =
···
kv_int_if w 'C' ~default:0 (Some (Kgp_cursor.to_int c)));
if Kgp_placement.unicode_placeholder p then kv_int w 'U' 1
+
let write_delete w ~free (d : Kgp_delete.t) =
+
kv_char w 'd' (Kgp_delete.to_char ~free d);
match d with
+
| `By_id (id, pid) ->
kv_int w 'i' id;
kv_int_opt w 'p' pid
+
| `By_number (n, pid) ->
kv_int w 'I' n;
kv_int_opt w 'p' pid
+
| `At_cell (x, y) ->
kv_int w 'x' x;
kv_int w 'y' y
+
| `At_cell_z (x, y, z) ->
kv_int w 'x' x;
kv_int w 'y' y;
kv_int w 'z' z
+
| `By_column c -> kv_int w 'x' c
+
| `By_row r -> kv_int w 'y' r
+
| `By_z_index z -> kv_int w 'z' z
+
| `By_id_range (min_id, max_id) ->
kv_int w 'x' min_id;
kv_int w 'y' max_id
+
| `All_visible | `At_cursor | `Frames -> ()
let write_frame w (f : Kgp_frame.t) =
kv_int_opt w 'x' (Kgp_frame.x f);
···
kv_int_opt w 'I' cmd.image_number;
(* Complex options *)
cmd.placement |> Option.iter (write_placement w);
+
cmd.delete |> Option.iter (write_delete w ~free:cmd.delete_free);
cmd.frame |> Option.iter (write_frame w);
cmd.animation |> Option.iter (write_animation w);
cmd.compose |> Option.iter (write_compose w);
+6 -2
lib/kgp_command.mli
···
(** {1 Deletion} *)
-
val delete : ?quiet:Kgp_quiet.t -> Kgp_delete.t -> t
-
(** Delete images or placements. *)
(** {1 Animation} *)
···
(** {1 Deletion} *)
+
val delete : ?free:bool -> ?quiet:Kgp_quiet.t -> Kgp_delete.t -> t
+
(** Delete images or placements.
+
+
@param free If true, also free the image data from memory (default: false).
+
Without [~free:true], only placements are removed and the image data
+
can be reused for new placements. *)
(** {1 Animation} *)
+18 -35
lib/kgp_delete.ml
···
type t =
[ `All_visible
-
| `All_visible_and_free
| `By_id of int * int option
-
| `By_id_and_free of int * int option
| `By_number of int * int option
-
| `By_number_and_free of int * int option
| `At_cursor
-
| `At_cursor_and_free
| `At_cell of int * int
-
| `At_cell_and_free of int * int
| `At_cell_z of int * int * int
-
| `At_cell_z_and_free of int * int * int
| `By_column of int
-
| `By_column_and_free of int
| `By_row of int
-
| `By_row_and_free of int
| `By_z_index of int
-
| `By_z_index_and_free of int
| `By_id_range of int * int
-
| `By_id_range_and_free of int * int
-
| `Frames
-
| `Frames_and_free ]
-
let to_char : t -> char = function
-
| `All_visible -> 'a'
-
| `All_visible_and_free -> 'A'
-
| `By_id _ -> 'i'
-
| `By_id_and_free _ -> 'I'
-
| `By_number _ -> 'n'
-
| `By_number_and_free _ -> 'N'
-
| `At_cursor -> 'c'
-
| `At_cursor_and_free -> 'C'
-
| `At_cell _ -> 'p'
-
| `At_cell_and_free _ -> 'P'
-
| `At_cell_z _ -> 'q'
-
| `At_cell_z_and_free _ -> 'Q'
-
| `By_column _ -> 'x'
-
| `By_column_and_free _ -> 'X'
-
| `By_row _ -> 'y'
-
| `By_row_and_free _ -> 'Y'
-
| `By_z_index _ -> 'z'
-
| `By_z_index_and_free _ -> 'Z'
-
| `By_id_range _ -> 'r'
-
| `By_id_range_and_free _ -> 'R'
-
| `Frames -> 'f'
-
| `Frames_and_free -> 'F'
···
type t =
[ `All_visible
| `By_id of int * int option
| `By_number of int * int option
| `At_cursor
| `At_cell of int * int
| `At_cell_z of int * int * int
| `By_column of int
| `By_row of int
| `By_z_index of int
| `By_id_range of int * int
+
| `Frames ]
+
let to_char ~free : t -> char =
+
let base = function
+
| `All_visible -> 'a'
+
| `By_id _ -> 'i'
+
| `By_number _ -> 'n'
+
| `At_cursor -> 'c'
+
| `At_cell _ -> 'p'
+
| `At_cell_z _ -> 'q'
+
| `By_column _ -> 'x'
+
| `By_row _ -> 'y'
+
| `By_z_index _ -> 'z'
+
| `By_id_range _ -> 'r'
+
| `Frames -> 'f'
+
in
+
fun t ->
+
let c = base t in
+
if free then Char.uppercase_ascii c else c
+16 -28
lib/kgp_delete.mli
···
{2 Placements vs Image Data}
-
Each deletion type has two variants:
-
- {b Lowercase}: Removes placements only. The image data remains in
-
memory and can be displayed again later.
-
- {b Uppercase}: Removes placements AND frees the image data. The
-
image cannot be displayed again without retransmitting.
-
-
Example: [{`By_id (42, None)}] removes all placements of image 42 but
-
keeps the data. [{`By_id_and_free (42, None)}] removes placements and
-
frees the image data.
{2 Placement IDs}
···
{2 Virtual Placements}
Virtual placements (used for Unicode placeholder mode) are only affected
-
by: [{`By_id}], [{`By_id_and_free}], [{`By_number}], [{`By_number_and_free}],
-
[{`By_id_range}], and [{`By_id_range_and_free}]. Other deletion commands
-
do not affect virtual placements. *)
type t =
[ `All_visible
-
| `All_visible_and_free
| `By_id of int * int option
-
| `By_id_and_free of int * int option
| `By_number of int * int option
-
| `By_number_and_free of int * int option
| `At_cursor
-
| `At_cursor_and_free
| `At_cell of int * int
-
| `At_cell_and_free of int * int
| `At_cell_z of int * int * int
-
| `At_cell_z_and_free of int * int * int
| `By_column of int
-
| `By_column_and_free of int
| `By_row of int
-
| `By_row_and_free of int
| `By_z_index of int
-
| `By_z_index_and_free of int
| `By_id_range of int * int
-
| `By_id_range_and_free of int * int
-
| `Frames
-
| `Frames_and_free ]
(** Deletion target specification.
{b Screen-based:}
···
{b Animation:}
- [`Frames] - Animation frames only (not the base image)
-
All variants have an [_and_free] version that also releases image data. *)
-
val to_char : t -> char
(** Convert to protocol character for the delete command.
-
Returns the character used in the [d=] control data key. Lowercase
-
for placement-only deletion, uppercase for deletion with data free. *)
···
{2 Placements vs Image Data}
+
Each deletion type can optionally free image data (controlled by the
+
[~free] parameter in the delete command):
+
- {b Without free}: Removes placements only. The image data remains in
+
memory and can be displayed again later. (Protocol: lowercase char)
+
- {b With free}: Removes placements AND frees the image data. The
+
image cannot be displayed again without retransmitting. (Protocol:
+
uppercase char)
{2 Placement IDs}
···
{2 Virtual Placements}
Virtual placements (used for Unicode placeholder mode) are only affected
+
by: [{`By_id}], [{`By_number}], and [{`By_id_range}]. Other deletion
+
commands do not affect virtual placements. *)
type t =
[ `All_visible
| `By_id of int * int option
| `By_number of int * int option
| `At_cursor
| `At_cell of int * int
| `At_cell_z of int * int * int
| `By_column of int
| `By_row of int
| `By_z_index of int
| `By_id_range of int * int
+
| `Frames ]
(** Deletion target specification.
{b Screen-based:}
···
{b Animation:}
- [`Frames] - Animation frames only (not the base image)
+
Use the [~free] parameter in the delete command to also release
+
image data from memory. *)
+
val to_char : free:bool -> t -> char
(** Convert to protocol character for the delete command.
+
Returns the character used in the [d=] control data key.
+
@param free If true, returns uppercase (frees data); if false,
+
returns lowercase (keeps data). *)