184 lines
4.8 KiB
OCaml
184 lines
4.8 KiB
OCaml
open Unix
|
|
|
|
(* type coords = int * int *)
|
|
type offset = { x : int; y : int }
|
|
type ansirgb = int * int * int
|
|
|
|
type cursor_ops = Hide | Show | Move of offset
|
|
type screen_ops = Clear
|
|
|
|
type color =
|
|
| Black
|
|
| Red
|
|
| Green
|
|
| Yellow
|
|
| Blue
|
|
| Magenta
|
|
| Cyan
|
|
| White
|
|
| Gray
|
|
| BrightRed
|
|
| BrightGreen
|
|
| BrightYellow
|
|
| BrightBlue
|
|
| BrightMagenta
|
|
| BrightCyan
|
|
| BrightWhite
|
|
| Default
|
|
| Custom of ansirgb
|
|
|
|
type color_style =
|
|
| Foreground of color
|
|
| Background of color
|
|
|
|
type style =
|
|
| Reset
|
|
| Bold
|
|
| Underlined
|
|
| Blink
|
|
| Inverse
|
|
| Hidden
|
|
|
|
type ansi =
|
|
| Cursor of cursor_ops
|
|
| Screen of screen_ops
|
|
| ColorStyle of color_style
|
|
| Style of style
|
|
|
|
let top_block = "▀"
|
|
let bot_block = "▄"
|
|
let block = "█"
|
|
|
|
let black = Foreground Black
|
|
let red = Foreground Red
|
|
let green = Foreground Green
|
|
let yellow = Foreground Yellow
|
|
let blue = Foreground Blue
|
|
let magenta = Foreground Magenta
|
|
let cyan = Foreground Cyan
|
|
let white = Foreground White
|
|
let default = Foreground Default
|
|
let bg_black = Background Black
|
|
let bg_red = Background Red
|
|
let bg_green = Background Green
|
|
let bg_yellow = Background Yellow
|
|
let bg_blue = Background Blue
|
|
let bg_magenta = Background Magenta
|
|
let bg_cyan = Background Cyan
|
|
let bg_white = Background White
|
|
let bg_default = Background Default
|
|
|
|
let is_valid_rgb = function x when x > 5 || x < 0 -> false | _ -> true
|
|
|
|
let rgb_to_ansi (rgb : ansirgb) =
|
|
let rr, gg, bb = rgb in
|
|
if is_valid_rgb rr && is_valid_rgb gg && is_valid_rgb bb then
|
|
Ok (16 + (36 * rr) + (6 * gg) + bb)
|
|
else Error "Invalid r, g, or b value provided (must be between 0-5)"
|
|
|
|
let screenop_to_ansi = function Clear -> "\027[2J"
|
|
|
|
let cursorop_to_ansi = function
|
|
| Show -> "\027[?25h"
|
|
| Hide -> "\027[?25l"
|
|
| Move coord -> Printf.sprintf "\027[%d;%dH" coord.y coord.x
|
|
|
|
(* Function to get the ANSI escape code for a color *)
|
|
let color_to_ansi = function
|
|
| Black -> "\027[30m"
|
|
| Red -> "\027[31m"
|
|
| Green -> "\027[32m"
|
|
| Yellow -> "\027[33m"
|
|
| Blue -> "\027[34m"
|
|
| Magenta -> "\027[35m"
|
|
| Cyan -> "\027[36m"
|
|
| White -> "\027[37m"
|
|
| Gray -> "\027[90m"
|
|
| BrightRed -> "\027[91m"
|
|
| BrightGreen -> "\027[92m"
|
|
| BrightYellow -> "\027[9m"
|
|
| BrightBlue -> "\027[94m"
|
|
| BrightMagenta -> "\027[95m"
|
|
| BrightCyan -> "\027[96m"
|
|
| BrightWhite -> "\027[97m"
|
|
| Default -> "\027[39m" (* Reset to default foreground color *)
|
|
| Custom rgb -> (
|
|
let ansi = rgb_to_ansi rgb in
|
|
match ansi with Ok s -> Printf.sprintf "\027[38m;5;%d" s | Error _ -> "")
|
|
|
|
let bgcolor_to_ansi = function
|
|
| Black -> "\027[40m"
|
|
| Red -> "\027[41m"
|
|
| Green -> "\027[42m"
|
|
| Yellow -> "\027[4m"
|
|
| Blue -> "\027[44m"
|
|
| Magenta -> "\027[45m"
|
|
| Cyan -> "\027[46m"
|
|
| White -> "\027[47m"
|
|
| Gray -> "\027[100m"
|
|
| BrightRed -> "\027[101m"
|
|
| BrightGreen -> "\027[102m"
|
|
| BrightYellow -> "\027[10m"
|
|
| BrightBlue -> "\027[10m"
|
|
| BrightMagenta -> "\027[105m"
|
|
| BrightCyan -> "\027[106m"
|
|
| BrightWhite -> "\027[107m"
|
|
| Default -> "\027[49m" (* Reset to default background color *)
|
|
| Custom rgb -> (
|
|
let ansi = rgb_to_ansi rgb in
|
|
match ansi with Ok s -> Printf.sprintf "\027[48m;5;%d" s | Error _ -> "")
|
|
|
|
(* Function to get the ANSI escape code for a style *)
|
|
let style_to_ansi = function
|
|
| Reset -> "\027[0m"
|
|
| Bold -> "\027[1m"
|
|
| Underlined -> "\027[4m"
|
|
| Blink -> "\027[5m"
|
|
| Inverse -> "\027[7m"
|
|
| Hidden -> "\027[8m"
|
|
|
|
let color_style_to_ansi = function
|
|
| Foreground color -> color_to_ansi color
|
|
| Background color -> bgcolor_to_ansi color
|
|
(* You would define background colors as well *)
|
|
|
|
let string_of_ansi = function
|
|
| ColorStyle op -> color_style_to_ansi op
|
|
| Cursor op -> cursorop_to_ansi op
|
|
| Style op -> style_to_ansi op
|
|
| Screen op -> screenop_to_ansi op
|
|
|
|
(* let cus_for = color_to_ansi Foreground Custom (3; 4; 1)) *)
|
|
let pixel_ansi offset color_style =
|
|
let color_ansi = color_style_to_ansi color_style in
|
|
let move_ansi = cursorop_to_ansi (Move offset) in
|
|
let ansi = Printf.sprintf "%s%s%s" move_ansi color_ansi block in
|
|
ansi
|
|
|
|
(* Gets the terminal height and width from running `stty size` *)
|
|
let termsize: offset =
|
|
let command = "stty size" in
|
|
let in_channel = open_process_in command in
|
|
try
|
|
let line = input_line in_channel in
|
|
close_in in_channel;
|
|
let parts = String.split_on_char ' ' line in
|
|
match parts with
|
|
| [height; width] ->
|
|
{x = (int_of_string width); y = (int_of_string height)}
|
|
| _ ->
|
|
let msg = Printf.sprintf "Unexpected output from stty size: %s" line in
|
|
failwith msg
|
|
with
|
|
| End_of_file ->
|
|
close_in in_channel;
|
|
failwith "No output from `stty size`"
|
|
| Sys_error msg ->
|
|
close_in in_channel;
|
|
Printf.eprintf "Error reading input: %s\n" msg;
|
|
failwith "Failed to get terminal size"
|
|
| Failure msg ->
|
|
Printf.eprintf "Error converting size to int: %s\n" msg;
|
|
failwith "Failed to parse terminal size"
|
|
|