1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
type t = { rows : int; cols : int; cells : Vt.cell array array; cursor : (int * int) option }
let create ~rows ~cols = { rows; cols; cells = Array.make rows (Array.make cols Vt.blank); cursor = None }
let rows (t : t) = t.rows
let cols (t : t) = t.cols
let cursor (c : (int * int) option) (t : t) : t = { t with cursor = c }
let cell (t : t) r c = if r >= 0 && r < t.rows && c >= 0 && c < t.cols then t.cells.(r).(c) else Vt.blank
let glyphs (s : string) : string list =
let n = String.length s in
let rec go i acc =
if i >= n then List.rev acc
else
let b = Char.code s.[i] in
let len = if b land 0xE0 = 0xC0 then 2 else if b land 0xF0 = 0xE0 then 3 else if b land 0xF8 = 0xF0 then 4 else 1 in
let len = min len (n - i) in
go (i + len) (String.sub s i len :: acc)
in
go 0 []
let put ?(attrs = Vt.plain) (r : int) (c : int) (text : string) (t : t) : t =
if r < 0 || r >= t.rows then t
else begin
let row = Array.copy t.cells.(r) in
List.iteri (fun i g -> let c = c + i in if c >= 0 && c < t.cols then row.(c) <- { Vt.glyph = g; attrs }) (glyphs text);
let cells = Array.copy t.cells in
cells.(r) <- row;
{ t with cells }
end
let box ?attrs (top : int) (left : int) (height : int) (width : int) (t : t) : t =
let edge = "+" ^ String.make (max 0 (width - 2)) '-' ^ "+" in
let t = put ?attrs top left edge t |> put ?attrs (top + height - 1) left edge in
List.fold_left (fun t r -> t |> put ?attrs r left "|" |> put ?attrs r (left + width - 1) "|") t (List.init (max 0 (height - 2)) (fun i -> top + 1 + i))
let text (t : t) : string list =
Array.to_list t.cells
|> List.map (fun row ->
let s = String.concat "" (Array.to_list (Array.map (fun (c : Vt.cell) -> c.glyph) row)) in
let n = ref (String.length s) in
while !n > 0 && s.[!n - 1] = ' ' do decr n done;
String.sub s 0 !n)
let cup (r : int) (c : int) : string = Printf.sprintf "\x1b[%d;%dH" (r + 1) (c + 1)
let sgr (a : Vt.attrs) : string =
let color base (c : Vt.color) =
match c with
| Default -> []
| Black -> [ base ] | Red -> [ base + 1 ] | Green -> [ base + 2 ] | Yellow -> [ base + 3 ]
| Blue -> [ base + 4 ] | Magenta -> [ base + 5 ] | Cyan -> [ base + 6 ] | White -> [ base + 7 ]
in
let codes = (0 :: (if a.bold then [ 1 ] else [])) @ (if a.reverse then [ 7 ] else []) @ color 30 a.fg @ color 40 a.bg in
"\x1b[" ^ String.concat ";" (List.map string_of_int codes) ^ "m"
let refresh ~(before : t) (after : t) : string =
let b = Buffer.create 256 in
let at = ref before.cursor and pen = ref Vt.plain in
let send (r : int) (c : int) (cell : Vt.cell) =
if cell.attrs <> !pen then begin
Buffer.add_string b (sgr cell.attrs);
pen := cell.attrs
end;
Buffer.add_string b cell.glyph;
at := if c + 1 < after.cols then Some (r, c + 1) else None
in
for r = 0 to after.rows - 1 do
if r >= before.rows || before.cells.(r) != after.cells.(r) then
for c = 0 to after.cols - 1 do
let now = after.cells.(r).(c) in
if cell before r c <> now then begin
(match !at with
| Some (r', c') when r' = r && c' = c -> ()
| Some (r', c')
when r' = r && c' < c
&& c - c' < String.length (cup r c)
&& List.for_all (fun i -> after.cells.(r).(i).attrs = !pen) (List.init (c - c') (fun i -> c' + i)) ->
for i = c' to c - 1 do
send r i after.cells.(r).(i)
done
| _ -> Buffer.add_string b (cup r c));
send r c now
end
done
done;
if !pen <> Vt.plain then Buffer.add_string b (sgr Vt.plain);
(match after.cursor with
| Some (r, c) ->
if !at <> Some (r, c) then Buffer.add_string b (cup r c);
if before.cursor = None then Buffer.add_string b "\x1b[?25h"
| None -> if before.cursor <> None then Buffer.add_string b "\x1b[?25l");
Buffer.contents b
let redraw (t : t) : string =
let blank = { (create ~rows:t.rows ~cols:t.cols) with cursor = Some (0, 0) } in
"\x1b[0m\x1b[2J\x1b[H\x1b[?25h" ^ refresh ~before:blank t