Source file keyseq_terminal.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

let hide_cursor = "\027[?25l"
let show_cursor = "\027[?25h"

let write (out : out_channel) (s : string) : unit =
  output_string out s;
  flush out

let render_insert (out : out_channel) (u : Uchar.t) : unit =
  let buf = Buffer.create 4 in
  Buffer.add_utf_8_uchar buf u;
  write out (Buffer.contents buf)

let render_delete (out : out_channel) : unit = write out "\b \b"

let render_idle (out : out_channel) (d : float) : unit =
  let half = 0.5 in
  let hidden = ref false in
  let remaining = ref d in
  while !remaining >= half do
    Unix.sleepf half;
    remaining := !remaining -. half;
    if !hidden then (
      write out show_cursor;
      hidden := false)
    else (
      write out hide_cursor;
      hidden := true)
  done;
  if !remaining > 0.0 then Unix.sleepf !remaining;
  if !hidden then write out show_cursor

let run ?(out = stdout) ?(on_sound = fun (_ : Keyseq.sound) -> ())
    (events : Keyseq.event list) : unit =
  List.iter
    (fun (e : Keyseq.event) ->
      match e with
      | Keyseq.Insert u -> render_insert out u
      | Keyseq.Delete -> render_delete out
      | Keyseq.Sleep d -> Unix.sleepf d
      | Keyseq.Idle d -> render_idle out d
      | Keyseq.Sound s -> on_sound s)
    events

let is_ws (c : char) : bool =
  match c with ' ' | '\t' | '\n' | '\r' -> true | _ -> false

let split_words (text : string) : string list =
  let len = String.length text in
  let rec loop i acc =
    if i >= len then List.rev acc
    else if is_ws text.[i] then loop (i + 1) acc
    else
      let j = ref i in
      while !j < len && not (is_ws text.[!j]) do
        incr j
      done;
      loop !j (String.sub text i (!j - i) :: acc)
  in
  loop 0 []

let utf8_length (s : string) : int =
  let len = String.length s in
  let rec loop i n =
    if i >= len then n
    else
      let d = String.get_utf_8_uchar s i in
      loop (i + Uchar.utf_decode_length d) (n + 1)
  in
  loop 0 0

let wrap ~(width : int) (text : string) : string =
  if width <= 0 then invalid_arg "Keyseq_terminal.wrap: width must be > 0";
  let buf = Buffer.create (String.length text) in
  let col = ref 0 in
  let first_on_line = ref true in
  List.iter
    (fun word ->
      let wlen = utf8_length word in
      if (not !first_on_line) && !col + 1 + wlen > width then begin
        Buffer.add_char buf '\n';
        col := 0;
        first_on_line := true
      end;
      if not !first_on_line then begin
        Buffer.add_char buf ' ';
        incr col
      end;
      Buffer.add_string buf word;
      col := !col + wlen;
      first_on_line := false)
    (split_words text);
  Buffer.contents buf