-
Notifications
You must be signed in to change notification settings - Fork 0
/
notty_mirage.ml
147 lines (131 loc) · 4.19 KB
/
notty_mirage.ml
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
let src = Logs.Src.create "notty.mirage"
module Log = (val Logs.src_log src : Logs.LOG)
open Lwt.Infix
open Notty
let ( </> ) a b = Lwt.pick [ a >|= Either.left; b >|= Either.right ]
let ( <??> ) a b = a >|= Either.left <?> (b >|= Either.right)
module Make (Time : Mirage_time.S) = struct
module Lwt_condition = struct
include Lwt_condition
let map f v =
let v' = create () in
let rec go () =
wait v >>= fun x ->
broadcast v' (f x);
go ()
in
Lwt.async go;
v'
let unburst ~timeout v =
let v' = create () in
let rec delay x =
Time.sleep_ns timeout </> wait v >>= function
| Either.Left () ->
broadcast v' x;
start ()
| Either.Right x -> delay x
and start () = wait v >>= delay in
Lwt.async start;
v'
end
module Term = struct
let input_stream ic stop =
let flt = Unescape.create () in
let ibuf = Bytes.create 1024 in
let rec next () =
match Unescape.next flt with
| #Unescape.event as r -> Lwt.return_some r
| `End -> Lwt.return_none
| `Await -> (
ic () <??> stop >>= function
| Either.Right _ -> Lwt.return_none
| Either.Left `Eof ->
Unescape.input flt ibuf 0 0;
next ()
| Either.Left (`Data cs) ->
let rec go cs =
if Cstruct.length cs > 0 then (
let len = min (Bytes.length ibuf) (Cstruct.length cs) in
Cstruct.blit_to_bytes cs 0 ibuf 0 len;
Unescape.input flt ibuf 0 len;
go (Cstruct.shift cs len))
else Lwt.return_unit
in
go cs >>= next)
in
Lwt_stream.from next
type t =
{ oc : Cstruct.t -> unit Lwt.t
; trm : Notty.Tmachine.t
; buf : Buffer.t
; events : [ Unescape.event | `Resize of int * int ] Lwt_stream.t
; stop : unit -> unit
}
let write t =
Tmachine.output t.trm t.buf;
let out = Buffer.contents t.buf in
Buffer.clear t.buf;
t.oc (Cstruct.of_string out)
let refresh t =
Tmachine.refresh t.trm;
write t
let image t image =
Tmachine.image t.trm image;
write t
let cursor t curs =
Tmachine.cursor t.trm curs;
write t
let set_size t dim = Tmachine.set_size t.trm dim
let size t = Tmachine.size t.trm
let release t =
if Tmachine.release t.trm then (
t.stop ();
write t (* TODO(dinosaure): send [`Eof] *))
else Lwt.return_unit
let resize dim stop on_resize =
(* TODO(dinosaure): we can save some allocations here but I mostly followed `notty-lwt`. *)
let rcond =
Lwt_condition.unburst ~timeout:1000L dim
|> Lwt_condition.map Option.some
in
let rec monitor () =
Lwt_condition.wait rcond <?> stop >>= function
| Some dim ->
on_resize dim;
monitor ()
| None -> Lwt.return_unit
in
Lwt.dont_wait monitor (fun exn ->
Logs.err @@ fun m ->
m "Got an exception from the resizer: %S" (Printexc.to_string exn));
Lwt_stream.from (fun () -> Lwt_condition.wait rcond <?> stop)
|> Lwt_stream.map (fun dim -> `Resize dim)
let create ?(dispose = true) ?(bpaste = true) ?(mouse = true)
(size, sigwinch) ic oc =
let stop, do_stop = Lwt.wait () in
let rec t =
lazy
{ trm =
Tmachine.create ~mouse ~bpaste
Cap.ansi (* XXX(dinosaure): we assume! *)
; oc
; buf = Buffer.create 4096
; stop = (fun () -> Lwt.wakeup do_stop None)
; events =
Lwt_stream.choose
[ input_stream ic stop
; ( resize sigwinch stop @@ fun dim ->
let t = Lazy.force t in
Buffer.reset t.buf;
set_size t dim )
]
}
in
let t = Lazy.force t in
set_size t size;
Lwt.async (fun () -> write t);
if dispose then Mirage_runtime.at_exit (fun () -> release t);
t
let events t = t.events
end
end