Source file widget.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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
open Base
open! Async

module type S = Widget_intf.S

module M = Widget_intf.M
module Waves = Hardcaml_waveterm_kernel.Waves

module Make (Data : Hardcaml_waveterm_kernel.Data.S) = struct
  module Render = Hardcaml_waveterm_kernel.Render.Make (Data)
  module Waveform_window = Waveform_window.Make (Data) (Render)
  module Help_window = Help_window.Make (Data) (Render)
  module Wave = Hardcaml_waveterm_kernel.Wave

  module Context = struct
    type t =
      { term : Notty_async.Term.t
      ; keys : Key_actions.Key.t Key_actions.t
      ; mutable rows : int
      ; mutable cols : int
      ; waves : Data.t Wave.t array
      ; mutable waveform : Waveform_window.t
      ; mutable help : Help_window.t
      ; events : [ Notty.Unescape.event | `Resize of int * int ] Pipe.Reader.t
      ; stop : unit Deferred.t
      ; mutable draw_ctx : Draw_notty.ctx
      ; mutable active : [ `Waveform | `Help ]
      }

    (* Look for a hardcaml waveterm rc file which stores key bindings. In order:

       - Look at HARDCAMLWAVETERMRC envvar
       - Look in local dir
       - Look in home dir (via HOME envvar)
    *)

    let keys () =
      let rcfilename = ".hardcamlwavetermrc" in
      let parse rcfilename =
        match Core.Sexp.load_sexp_conv rcfilename Key_actions.File_format.t_of_sexp with
        | exception _ -> None
        | `Error _ -> None
        | `Result keys -> Some keys
      in
      let load_from_envvar () =
        let%bind.Option rcfilename = Sys.getenv "HARDCAMLWAVETERMRC" in
        parse rcfilename
      in
      let load_from_localdir () = parse rcfilename in
      let load_from_homedir () =
        let%bind.Option homedir = Sys.getenv "HOME" in
        parse (homedir ^ "/" ^ rcfilename)
      in
      List.fold
        ~init:None
        [ load_from_envvar; load_from_localdir; load_from_homedir ]
        ~f:(fun r f -> if Option.is_none r then f () else r)
      |> Option.value ~default:Key_actions.default_keys
    ;;

    let create ~ui_state_file waves =
      let%bind term = Notty_async.Term.create () in
      let keys = keys () in
      let cols, rows = Notty_async.Term.size term in
      let waveform = Waveform_window.create ~cols ~rows ~ui_state_file waves keys in
      let help = Help_window.create ~cols ~rows keys in
      let events = Notty_async.Term.events term in
      let stop = Pipe.closed events in
      let%bind () = Notty_async.Term.cursor term None in
      let draw_ctx = Draw_notty.init ~rows ~cols in
      return
        { term
        ; keys
        ; rows
        ; cols
        ; waves = waves.waves
        ; events
        ; stop
        ; waveform
        ; help
        ; draw_ctx
        ; active = `Waveform
        }
    ;;

    let resize ~rows ~cols t =
      let waveform =
        Waveform_window.create
          ~waveform:t.waveform
          ~cols
          ~rows
          ~ui_state_file:(Waveform_window.ui_state_file t.waveform)
          { waves = t.waves; cfg = (Waveform_window.hierarchy t.waveform).cfg }
          t.keys
      in
      let help = Help_window.create ~cols ~rows t.keys in
      let draw_ctx = Draw_notty.init ~rows ~cols in
      t.rows <- rows;
      t.cols <- cols;
      t.waveform <- waveform;
      t.help <- help;
      t.draw_ctx <- draw_ctx
    ;;

    let draw (ctx : t) =
      (match ctx.active with
       | `Waveform -> Waveform_window.draw ~ctx:ctx.draw_ctx ctx.waveform
       | `Help -> Help_window.draw ~ctx:ctx.draw_ctx ctx.help);
      let image = Draw_notty.to_image ctx.draw_ctx in
      Notty_async.Term.image ctx.term image
    ;;

    let handle_event ctx event =
      let handler event =
        match ctx.active with
        | `Waveform -> Waveform_window.handler ctx.waveform event
        | `Help -> Help_window.handler ctx.help event
      in
      let response =
        match event with
        | `Mouse m -> handler (`Mouse m)
        | `Key key -> handler (`Key key)
        | `Resize (cols, rows) ->
          resize ~rows ~cols ctx;
          Redraw_window
        | `Paste _ -> No_action
      in
      match response with
      | Escape ->
        Pipe.close_read ctx.events;
        false
      | Recreate_window ->
        let hierarchy = Waveform_window.hierarchy ctx.waveform in
        ctx.waveform
        <- Waveform_window.create
             ~waveform:ctx.waveform
             ~cols:ctx.cols
             ~rows:ctx.rows
             ~ui_state_file:(Waveform_window.ui_state_file ctx.waveform)
             { waves = ctx.waves; cfg = hierarchy.cfg }
             ctx.keys;
        true
      | Show_help ->
        ctx.active <- `Help;
        true
      | Show_waveform ->
        ctx.active <- `Waveform;
        true
      | No_action -> false
      | Redraw_window -> true
    ;;

    let iter_events ctx =
      (* process events in batches and draw at the end. Given rendering can be slow, this
         behaves much better - especially over a ssh connection. *)
      Pipe.iter' ctx.events ~f:(fun q ->
        let redraw =
          Core.Queue.fold q ~init:false ~f:(fun redraw event ->
            if handle_event ctx event then true else redraw)
        in
        if redraw then draw ctx else return ())
    ;;
  end

  let run_waves ?(ui_state_file = "/tmp/hardcaml_waveterm_state.sexp") (waves : _ Waves.t)
    =
    Array.iter waves.waves ~f:(function
      | Empty _ -> ()
      | Divider { style; _ } -> style.style <- Waveform_window.default_waves_style
      | Clock { style; _ } -> style.style <- Waveform_window.default_waves_style
      | Binary { style; _ } -> style.style <- Waveform_window.default_waves_style
      | Data { style; _ } -> style.style <- Waveform_window.default_waves_style);
    let%bind ctx = Context.create ~ui_state_file waves in
    let%bind () = Context.draw ctx in
    don't_wait_for (Context.iter_events ctx);
    ctx.stop
  ;;

  let run_and_close ?ui_state_file waves =
    don't_wait_for
      (Monitor.protect
         (fun () -> run_waves ?ui_state_file waves)
         ~finally:(fun _ ->
           shutdown 0;
           return ()));
    Core.never_returns (Scheduler.go ())
  ;;

  let create_waves
    ?(signals_width = 20)
    ?(values_width = 20)
    ?(start_cycle = 0)
    ?(wave_width = 3)
    ?display_rules
    t
    =
    { Hardcaml_waveterm.Waves.cfg =
        { Waves.Config.default with start_cycle; wave_width; signals_width; values_width }
    ; waves = Hardcaml_waveterm_kernel.Waveform.sort_ports_and_formats t display_rules
    }
  ;;

  let run
    ?ui_state_file
    ?signals_width
    ?values_width
    ?start_cycle
    ?wave_width
    ?display_rules
    t
    =
    run_and_close
      ?ui_state_file
      (create_waves
         ?signals_width
         ?values_width
         ?start_cycle
         ?wave_width
         ?display_rules
         t)
  ;;

  let run_async
    ?ui_state_file
    ?signals_width
    ?values_width
    ?start_cycle
    ?wave_width
    ?display_rules
    t
    =
    run_waves
      ?ui_state_file
      (create_waves
         ?signals_width
         ?values_width
         ?start_cycle
         ?wave_width
         ?display_rules
         t)
  ;;
end

module By_cycle = Make (Hardcaml.Wave_data_in_cycles)
module By_event = Make (Hardcaml.Wave_data_in_events.Bits)

let run_async
  ?ui_state_file
  ?signals_width
  ?values_width
  ?start_cycle
  ?wave_width
  ?display_rules
  (t : Hardcaml.Wave_data.t)
  =
  match t with
  | By_cycle cycles ->
    By_cycle.run_async
      ?ui_state_file
      ?signals_width
      ?values_width
      ?start_cycle
      ?wave_width
      ?display_rules
      cycles
  | By_event events ->
    By_event.run_async
      ?ui_state_file
      ?signals_width
      ?values_width
      ?start_cycle
      ?wave_width
      ?display_rules
      events
;;

let run
  ?ui_state_file
  ?signals_width
  ?values_width
  ?start_cycle
  ?wave_width
  ?display_rules
  (t : Hardcaml.Wave_data.t)
  =
  match t with
  | By_cycle cycles ->
    By_cycle.run
      ?ui_state_file
      ?signals_width
      ?values_width
      ?start_cycle
      ?wave_width
      ?display_rules
      cycles
  | By_event events ->
    By_event.run
      ?ui_state_file
      ?signals_width
      ?values_width
      ?start_cycle
      ?wave_width
      ?display_rules
      events
;;