package notty

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file notty_unix.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
   See LICENSE.md. *)

open Notty

external c_winsize : Unix.file_descr -> int = "caml_notty_winsize" [@@noalloc]
external winch_number : unit -> int = "caml_notty_winch_number" [@@noalloc]

let iter f = function Some x -> f x | _ -> ()
let value x = function Some a -> a | _ -> x

let winsize fd = match c_winsize fd with
  | 0  -> None
  | wh -> Some (wh lsr 16, wh lsr 1 land 0x7fff)

module Private = struct

  let once f = let v = lazy (f ()) in fun () -> Lazy.force v

  let cap_for_fd =
    let open Cap in
    match Sys.getenv "TERM" with
    | exception Not_found -> fun _ -> dumb
    | (""|"dumb")         -> fun _ -> dumb
    | _                   -> fun fd -> if Unix.isatty fd then ansi else dumb

  let setup_tcattr ~nosig fd =
    let open Unix in try
      let tc = tcgetattr fd in
      let tc1 = { tc with c_icanon = false; c_echo = false } in
      tcsetattr fd TCSANOW
        ( if nosig then { tc1 with c_isig = false; c_ixon = false } else tc1 );
      `Revert (once @@ fun _ -> tcsetattr fd TCSANOW tc)
    with Unix_error (ENOTTY, _, _) -> `Revert ignore

  let set_winch_handler f =
    let signum = winch_number () in
    let old_hdl = Sys.(signal signum (Signal_handle (fun _ -> f ()))) in
    `Revert (once @@ fun () -> Sys.set_signal signum old_hdl)

  module Gen_output (O : sig
    type fd
    type k
    val def   : fd
    val to_fd : fd -> Unix.file_descr
    val write : fd -> Buffer.t -> k
  end) = struct

    let scratch = lazy (Buffer.create 4096)

    let output ?cap ?(fd = O.def) f =
      let cap = cap |> value (cap_for_fd (O.to_fd fd)) in
      let buf = Lazy.force scratch in
      Buffer.reset buf; f buf cap fd; O.write fd buf

    let output_image_size ?cap ?fd f =
      output ?cap ?fd @@ fun buf cap fd ->
        let size = winsize (O.to_fd fd) in
        let i = f (value (80, 24) size) in
        let dim = match size with
          | Some (w, _) -> I.(w, height i)
          | None        -> I.(width i, height i) in
        Render.to_buffer buf cap (0, 0) dim i

    let show_cursor ?cap ?fd x =
      output ?cap ?fd @@ fun buf cap _ -> Direct.show_cursor buf cap x

    let move_cursor ?cap ?fd x =
      output ?cap ?fd @@ fun buf cap _ -> Direct.move_cursor buf cap x

    let output_image ?cap ?fd i = output_image_size ?cap ?fd (fun _ -> i)

    let eol i = I.(i <-> void 0 1)
  end
end

open Private

module Term = struct

  module Winch = struct

    let h  = Hashtbl.create 3
    and id = ref 0

    let add fd f =
      let n = !id in
      set_winch_handler (fun () -> Hashtbl.iter (fun _ f -> f ()) h) |> ignore;
      Hashtbl.add h n (fun () -> winsize fd |> iter f); incr id;
      `Revert (fun () -> Hashtbl.remove h n)
  end

  module Input = struct

    type t = {
      fd      : Unix.file_descr
    ; flt     : Unescape.t
    ; ibuf    : bytes
    ; cleanup : unit -> unit
    }

    let bsize = 1024

    let create ~nosig fd =
      let flt  = Unescape.create ()
      and ibuf = Bytes.create bsize
      and `Revert cleanup = setup_tcattr ~nosig fd in
      { fd; flt; ibuf; cleanup }

    let rec event t =
      match Unescape.next t.flt with
      | #Unescape.event | `End as r -> r
      | `Await ->
          let n = Unix.read t.fd t.ibuf 0 bsize in
          Unescape.input t.flt t.ibuf 0 n; event t
  end

  type t = {
    output   : out_channel
  ; trm      : Tmachine.t
  ; buf      : Buffer.t
  ; input    : Input.t
  ; fds      : Unix.file_descr * Unix.file_descr
  ; unwinch  : (unit -> unit) Lazy.t
  ; mutable winched : bool
  }

  let write t =
    Buffer.clear t.buf;
    Tmachine.output t.trm t.buf;
    Buffer.output_buffer t.output t.buf; flush t.output

  let set_size t dim = Tmachine.set_size t.trm dim
  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 size t         = Tmachine.size t.trm

  let release t =
    if Tmachine.release t.trm then
      ( Lazy.force t.unwinch ();
        t.input.Input.cleanup ();
        write t )

  let create ?(dispose=true) ?(nosig=true) ?(mouse=true) ?(bpaste=true)
             ?(input=Unix.stdin) ?(output=Unix.stdout) () =
    let rec t = {
        output  = Unix.out_channel_of_descr output
      ; trm     = Tmachine.create ~mouse ~bpaste (cap_for_fd input)
      ; buf     = Buffer.create 4096
      ; input   = Input.create ~nosig input
      ; fds     = (input, output)
      ; winched = false
      ; unwinch = lazy (
          let `Revert f = Winch.add output @@ fun dim ->
            Buffer.reset t.buf; t.winched <- true; set_size t dim in f)
    } in
    winsize output |> iter (set_size t);
    (Lazy.force t.unwinch |> ignore) [@ocaml.warning "-5"];
    if dispose then at_exit (fun () -> release t);
    write t;
    t

  let rec event = function
    | t when Tmachine.dead t.trm -> `End
    | t when t.winched -> t.winched <- false; `Resize (size t)
    | t -> Unix.(try Input.event t.input with Unix_error (EINTR, _, _) -> event t)

  let pending t =
    not (Tmachine.dead t.trm) &&
    (t.winched || Unescape.pending t.input.Input.flt)

  let fds t = t.fds
end

include Gen_output (struct
  type fd = out_channel and k = unit
  let def   = stdout
  and to_fd = Unix.descr_of_out_channel
  and write = Buffer.output_buffer
end)
OCaml

Innovation. Community. Security.