Skip to content

Commit

Permalink
feat: prepare 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
leostera committed Dec 14, 2023
0 parents commit 64fe6b0
Show file tree
Hide file tree
Showing 17 changed files with 410 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_build
Empty file added .ocamlformat
Empty file.
13 changes: 13 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changes

## 0.0.1

Initial release, including:

* High-level `Terminal` module for controlling a terminal
* Async-input with UTF-8 support in the `Stdin` module
* Terminal `Profile`s for determining what color palettes are available
* A `Color` module for parsing and working with RGB/ANSI/ANSI256 colors
* A collection of 60 escape sequence functions in `Escape_seq`
* a lot of room for improvement!

19 changes: 19 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2023, Leandro Ostera

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# TTY

TTY is a pure OCaml library for directly interacting with the
terminal, including escape sequences, color profiles, colors, and
consuming stdin.

It is the main backend for [MintTea][minttea].

[minttea]: https://github.com/leostera/minttea

## Getting Started

```
$ opam install tty
```
24 changes: 24 additions & 0 deletions dune-project
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(lang dune 3.11)

(name tty)

(generate_opam_files true)

(source (github leostera/tty))

(authors "Leandro Ostera <[email protected]>")

(maintainers "Leandro Ostera <[email protected]>")

(license MIT)

(package
(name tty)
(synopsis "A library for interacting with teletype and terminal emulators")
(description "TTY is a library for directly interacting with teletypes and terminal emulators, including escape sequences, colors, and consuming stdin")
(depends
(ocaml (>= "5.1"))
(dune (>= "3.11"))
(uutf (>= "1.0.3")))
(tags (terminal ansi tty teletype utf8)))

32 changes: 32 additions & 0 deletions tty.opam
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "A library for interacting with teletype and terminal emulators"
description:
"TTY is a library for directly interacting with teletypes and terminal emulators, including escape sequences, colors, and consuming stdin"
maintainer: ["Leandro Ostera <[email protected]>"]
authors: ["Leandro Ostera <[email protected]>"]
license: "MIT"
tags: ["terminal" "ansi" "tty" "teletype" "utf8"]
homepage: "https://github.com/leostera/tty"
bug-reports: "https://github.com/leostera/tty/issues"
depends: [
"ocaml" {>= "5.1"}
"dune" {>= "3.11" & >= "3.11"}
"uutf" {>= "1.0.3"}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/leostera/tty.git"
45 changes: 45 additions & 0 deletions tty/color.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
type t = RGB of int * int * int | ANSI of int | ANSI256 of int | No_color

exception Invalid_color of string
exception Invalid_color_param of string
exception Invalid_color_num of string * int

let to_255 str =
match int_of_string_opt ("0x" ^ str) with
| None -> raise (Invalid_color_param str)
| Some c -> c

let rgb r g b = RGB (to_255 r, to_255 g, to_255 b)

let rgb str =
match String.to_seq str |> List.of_seq |> List.map (String.make 1) with
| [ "#"; r1; r2; g1; g2; b1; b2 ] -> rgb (r1 ^ r2) (g1 ^ g2) (b1 ^ b2)
| [ "#"; r1; g1; b1 ] -> rgb r1 g1 b1
| _ -> raise (Invalid_color str)

let ansi i = ANSI i
let ansi256 i = ANSI256 i
let no_color = No_color

let make str =
if String.starts_with ~prefix:"#" str then rgb str
else
match int_of_string_opt str with
| None -> raise (Invalid_color str)
| Some i when i < 16 -> ansi i
| Some i -> ansi256 i

let to_escape_seq ~mode t =
match t with
| RGB (r, g, b) -> Format.sprintf "2;%d;%d;%d" r g b
| ANSI c ->
let bg_mod x = if mode = `bg then x + 10 else x in
let c = if c < 8 then bg_mod c + 30 else bg_mod (c - 8) + 90 in
Int.to_string c
| ANSI256 c -> Format.sprintf "5;%d" c
| No_color -> ""

let is_no_color t = t = No_color
let is_rgb t = match t with RGB _ -> true | _ -> false
let is_ansi t = match t with ANSI _ -> true | _ -> false
let is_ansi256 t = match t with ANSI256 _ -> true | _ -> false
18 changes: 18 additions & 0 deletions tty/color.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type t = private
| RGB of int * int * int
| ANSI of int
| ANSI256 of int
| No_color

val make : string -> t

exception Invalid_color of string
exception Invalid_color_param of string
exception Invalid_color_num of string * int

val no_color : t
val is_no_color : t -> bool
val is_rgb : t -> bool
val is_ansi : t -> bool
val is_ansi256 : t -> bool
val to_escape_seq : mode:[> `bg | `fg ] -> t -> string
4 changes: 4 additions & 0 deletions tty/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(library
(public_name tty)
(name tty)
(libraries uutf unix))
73 changes: 73 additions & 0 deletions tty/escape_seq.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
let csi = "\x1b["
let reset_seq = "0"
let bold_seq = "1"
let faint_seq = "2"
let italics_seq = "3"
let underline_seq = "4"
let blink_seq = "5"
let reverse_seq = "7"
let cross_out_seq = "9"
let overline_seq = "53"
let foreground_seq = "38"
let background_seq = "48"
let escape code () = Printf.printf "%s%s%!" csi code

(* Cursor positioning. *)
let cursor_up_seq x = escape (Printf.sprintf "%dA" x)
let cursor_down_seq x = escape (Printf.sprintf "%dB" x)
let cursor_forward_seq x = escape (Printf.sprintf "%dC" x)
let cursor_back_seq x = escape (Printf.sprintf "%dD" x)
let cursor_next_line_seq x = escape (Printf.sprintf "%dE" x)
let cursor_previous_line_seq x = escape (Printf.sprintf "%dF" x)
let cursor_horizontal_seq x = escape (Printf.sprintf "%dG" x)
let cursor_position_seq x y = escape (Printf.sprintf "%d;%dH" x y)
let erase_display_seq x = escape (Printf.sprintf "%dJ" x)
let erase_line_seq x = escape (Printf.sprintf "%dK" x)
let scroll_up_seq x = escape (Printf.sprintf "%dS" x)
let scroll_down_seq x = escape (Printf.sprintf "%dT" x)
let save_cursor_position_seq = escape "s"
let restore_cursor_position_seq = escape "u"
let change_scrolling_region_seq x y = escape (Printf.sprintf "%d;%dr" x y)
let insert_line_seq x = escape (Printf.sprintf "%dL" x)
let delete_line_seq x = escape (Printf.sprintf "%dM" x)

(* Explicit values for EraseLineSeq. *)
let erase_line_right_seq = escape "0K"
let erase_line_left_seq = escape "1K"
let erase_entire_line_seq = escape "2K"

(* Mouse. *)
let enable_mouse_press_seq = escape "?9h"
let disable_mouse_press_seq = escape "?9l"
let enable_mouse_seq = escape "?1000h"
let disable_mouse_seq = escape "?1000l"
let enable_mouse_hilite_seq = escape "?1001h"
let disable_mouse_hilite_seq = escape "?1001l"
let enable_mouse_cell_motion_seq = escape "?1002h"
let disable_mouse_cell_motion_seq = escape "?1002l"
let enable_mouse_all_motion_seq = escape "?1003h"
let disable_mouse_all_motion_seq = escape "?1003l"
let enable_mouse_extended_mode_seq = escape "?1006h"
let disable_mouse_extended_mode_seq = escape "?1006l"
let enable_mouse_pixels_mode_seq = escape "?1016h"
let disable_mouse_pixels_mode_seq = escape "?1016l"

(* Screen. *)
let restore_screen_seq = escape "?47l"
let save_screen_seq = escape "?47h"
let alt_screen_seq = escape "?1049h"
let exit_alt_screen_seq = escape "?1049l"

(* Bracketed paste. *)
let enable_bracketed_paste_seq = escape "?2004h"
let disable_bracketed_paste_seq = escape "?2004l"
let start_bracketed_paste_seq = escape "200~"
let end_bracketed_paste_seq = escape "201~"

(* Session. *)
let set_window_title_seq s = escape (Printf.sprintf "2;%s" s)
let set_foreground_color_seq s = escape (Printf.sprintf "10;%s" s)
let set_background_color_seq s = escape (Printf.sprintf "11;%s" s)
let set_cursor_color_seq s = escape (Printf.sprintf "12;%s" s)
let show_cursor_seq = escape "?25h"
let hide_cursor_seq = escape "?25l"
60 changes: 60 additions & 0 deletions tty/escape_seq.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
val csi : string
val reset_seq : string
val bold_seq : string
val faint_seq : string
val italics_seq : string
val underline_seq : string
val blink_seq : string
val reverse_seq : string
val cross_out_seq : string
val overline_seq : string
val foreground_seq : string
val background_seq : string
val alt_screen_seq : unit -> unit
val change_scrolling_region_seq : int -> int -> unit -> unit
val cursor_back_seq : int -> unit -> unit
val cursor_down_seq : int -> unit -> unit
val cursor_forward_seq : int -> unit -> unit
val cursor_horizontal_seq : int -> unit -> unit
val cursor_next_line_seq : int -> unit -> unit
val cursor_position_seq : int -> int -> unit -> unit
val cursor_previous_line_seq : int -> unit -> unit
val cursor_up_seq : int -> unit -> unit
val delete_line_seq : int -> unit -> unit
val disable_bracketed_paste_seq : unit -> unit
val disable_mouse_all_motion_seq : unit -> unit
val disable_mouse_cell_motion_seq : unit -> unit
val disable_mouse_extended_mode_seq : unit -> unit
val disable_mouse_hilite_seq : unit -> unit
val disable_mouse_pixels_mode_seq : unit -> unit
val disable_mouse_press_seq : unit -> unit
val disable_mouse_seq : unit -> unit
val enable_bracketed_paste_seq : unit -> unit
val enable_mouse_all_motion_seq : unit -> unit
val enable_mouse_cell_motion_seq : unit -> unit
val enable_mouse_extended_mode_seq : unit -> unit
val enable_mouse_hilite_seq : unit -> unit
val enable_mouse_pixels_mode_seq : unit -> unit
val enable_mouse_press_seq : unit -> unit
val enable_mouse_seq : unit -> unit
val end_bracketed_paste_seq : unit -> unit
val erase_display_seq : int -> unit -> unit
val erase_entire_line_seq : unit -> unit
val erase_line_left_seq : unit -> unit
val erase_line_right_seq : unit -> unit
val erase_line_seq : int -> unit -> unit
val exit_alt_screen_seq : unit -> unit
val hide_cursor_seq : unit -> unit
val insert_line_seq : int -> unit -> unit
val restore_cursor_position_seq : unit -> unit
val restore_screen_seq : unit -> unit
val save_cursor_position_seq : unit -> unit
val save_screen_seq : unit -> unit
val scroll_down_seq : int -> unit -> unit
val scroll_up_seq : int -> unit -> unit
val set_background_color_seq : string -> unit -> unit
val set_cursor_color_seq : string -> unit -> unit
val set_foreground_color_seq : string -> unit -> unit
val set_window_title_seq : string -> unit -> unit
val show_cursor_seq : unit -> unit
val start_bracketed_paste_seq : unit -> unit
42 changes: 42 additions & 0 deletions tty/profile.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
type t = No_color | ANSI | ANSI256 | True_color

let from_env () =
let term = Sys.getenv_opt "TERM" in
let color_term = Sys.getenv_opt "COLORTERM" in
let term_program = Sys.getenv_opt "TERM_PROGRAM" in

let is_screen =
match term with
| Some term -> String.starts_with ~prefix:"screen" term
| None -> false
in

let is_tmux = match term_program with Some "tmux" -> true | _ -> false in

(* TODO(@leostera): String.contains "256color" "color" "ansi" *)
let is_256color = false in
let is_color = false in
let is_ansi = false in

match (term, color_term) with
| _, Some "true" -> ANSI256
| _, Some "truecolor" when is_screen && not is_tmux -> ANSI256
| _, Some "truecolor" -> True_color
| Some ("xterm-kitty" | "wezterm"), _ -> True_color
| Some "linux", _ -> ANSI
| Some _, _ when is_256color -> ANSI256
| Some _, _ when is_color || is_ansi -> ANSI
| _ -> No_color

let default = from_env ()

let convert profile color =
match (color, profile) with
| _, No_color -> Color.no_color
| Color.No_color, _ -> Color.no_color
| Color.ANSI _, _ -> color
| Color.ANSI256 _, ANSI -> color
| Color.ANSI256 _, _ -> color
| Color.RGB _, ANSI -> color
| Color.RGB _, ANSI256 -> color
| Color.RGB _, True_color -> color
5 changes: 5 additions & 0 deletions tty/profile.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type t

val from_env : unit -> t
val default : t
val convert : t -> Color.t -> Color.t
Loading

0 comments on commit 64fe6b0

Please sign in to comment.