package miaou-driver-matrix

  1. Overview
  2. Docs
Miaou high-performance terminal driver with diff rendering

Install

dune-project
 Dependency

Authors

Maintainers

Sources

v0.5.2.tar.gz
md5=60a3b9f181f24572a06a9492532bfdda
sha512=fcc35a275066be2900e6201782faf47503076fa4640f08cf78067835a6f447b74613009e55b2ac799adb7ca46f1bffa261fc5971753f2cc3c6bef327511c7ef6

doc/src/miaou-driver-matrix.driver/matrix_buffer.ml.html

Source file matrix_buffer.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
(*****************************************************************************)
(*                                                                           *)
(* SPDX-License-Identifier: MIT                                              *)
(* Copyright (c) 2025 Nomadic Labs <contact@nomadic-labs.com>                *)
(*                                                                           *)
(*****************************************************************************)

[@@@warning "-69"]

type t = {
  mutable rows : int;
  mutable cols : int;
  mutable front : Matrix_cell.t array array;
  mutable back : Matrix_cell.t array array;
  mutex : Mutex.t;
  dirty : bool Atomic.t;
}

let make_grid ~rows ~cols =
  Array.init rows (fun _ -> Array.init cols (fun _ -> Matrix_cell.empty ()))

let create ~rows ~cols =
  let rows = max 1 rows in
  let cols = max 1 cols in
  {
    rows;
    cols;
    front = make_grid ~rows ~cols;
    back = make_grid ~rows ~cols;
    mutex = Mutex.create ();
    dirty = Atomic.make true;
  }

let with_lock t f =
  Mutex.lock t.mutex ;
  match f () with
  | result ->
      Mutex.unlock t.mutex ;
      result
  | exception e ->
      Mutex.unlock t.mutex ;
      raise e

let resize t ~rows ~cols =
  with_lock t (fun () ->
      let rows = max 1 rows in
      let cols = max 1 cols in
      (* On resize, create fresh empty buffers - don't copy old content.
         The UI layout changes completely on resize, so we need a full redraw.
         Leaving front buffer empty ensures diff will redraw everything. *)
      let new_front = make_grid ~rows ~cols in
      let new_back = make_grid ~rows ~cols in
      t.rows <- rows ;
      t.cols <- cols ;
      t.front <- new_front ;
      t.back <- new_back ;
      Atomic.set t.dirty true)

let rows t = t.rows

let cols t = t.cols

let size t = with_lock t (fun () -> (t.rows, t.cols))

let in_bounds t ~row ~col = row >= 0 && row < t.rows && col >= 0 && col < t.cols

let set t ~row ~col cell =
  with_lock t (fun () ->
      if in_bounds t ~row ~col then t.back.(row).(col) <- cell)

let set_from t ~row ~col cell =
  with_lock t (fun () ->
      if in_bounds t ~row ~col then begin
        t.back.(row).(col).char <- cell.Matrix_cell.char ;
        t.back.(row).(col).style <- cell.Matrix_cell.style
      end)

let get_back t ~row ~col =
  with_lock t (fun () ->
      if in_bounds t ~row ~col then t.back.(row).(col) else Matrix_cell.empty ())

let clear_back t =
  with_lock t (fun () ->
      for r = 0 to t.rows - 1 do
        for c = 0 to t.cols - 1 do
          Matrix_cell.reset t.back.(r).(c)
        done
      done)

let set_char t ~row ~col ~char ~style =
  with_lock t (fun () ->
      if in_bounds t ~row ~col then begin
        t.back.(row).(col).char <- char ;
        t.back.(row).(col).style <- style
      end)

let get_front t ~row ~col =
  if in_bounds t ~row ~col then t.front.(row).(col) else Matrix_cell.empty ()

let swap t =
  with_lock t (fun () ->
      let tmp = t.front in
      t.front <- t.back ;
      t.back <- tmp)

let cell_changed t ~row ~col =
  if in_bounds t ~row ~col then
    not (Matrix_cell.equal t.front.(row).(col) t.back.(row).(col))
  else false

let mark_all_dirty t =
  with_lock t (fun () ->
      (* Clear front buffer so all cells appear changed *)
      for r = 0 to t.rows - 1 do
        for c = 0 to t.cols - 1 do
          Matrix_cell.reset t.front.(r).(c)
        done
      done ;
      Atomic.set t.dirty true)

let mark_region_dirty t ~row_start ~row_end ~col_start ~col_end =
  with_lock t (fun () ->
      (* Clear front buffer region so those cells appear changed *)
      for r = max 0 row_start to min (t.rows - 1) row_end do
        for c = max 0 col_start to min (t.cols - 1) col_end do
          Matrix_cell.reset t.front.(r).(c)
        done
      done ;
      Atomic.set t.dirty true)

let mark_dirty t = Atomic.set t.dirty true

let is_dirty t = Atomic.get t.dirty

let clear_dirty t = Atomic.set t.dirty false

(** Batch operations record - provides unlocked accessors within with_back_buffer *)
type batch_ops = {
  clear : unit -> unit;
  set_char :
    row:int -> col:int -> char:string -> style:Matrix_cell.style -> unit;
  get : row:int -> col:int -> Matrix_cell.t;
  rows : int;
  cols : int;
}

(* Execute a function with the buffer lock held - for batch operations.
   If force_full_redraw is true, invalidates the front buffer so all cells
   appear changed - this is done atomically to avoid race conditions.
   We use invalidate (sets char to \x00) rather than reset (sets to space)
   because if the new content also has spaces, reset would compare equal
   and no change would be emitted, leaving stale content on screen. *)
let with_back_buffer ?(force_full_redraw = false) t f =
  with_lock t (fun () ->
      (* If forcing full redraw, invalidate front buffer first (while holding lock) *)
      if force_full_redraw then
        for r = 0 to t.rows - 1 do
          for c = 0 to t.cols - 1 do
            Matrix_cell.invalidate t.front.(r).(c)
          done
        done ;
      let ops =
        {
          clear =
            (fun () ->
              for r = 0 to t.rows - 1 do
                for c = 0 to t.cols - 1 do
                  Matrix_cell.reset t.back.(r).(c)
                done
              done);
          set_char =
            (fun ~row ~col ~char ~style ->
              if in_bounds t ~row ~col then begin
                t.back.(row).(col).char <- char ;
                t.back.(row).(col).style <- style
              end);
          get =
            (fun ~row ~col ->
              if in_bounds t ~row ~col then t.back.(row).(col)
              else Matrix_cell.empty ());
          rows = t.rows;
          cols = t.cols;
        }
      in
      let result = f ops in
      Atomic.set t.dirty true ;
      result)

(* Execute a read operation with the buffer lock held - for atomic diff computation *)
let with_read_lock t f = with_lock t f

(* Get front and back cells without locking - for use inside with_read_lock *)
let get_front_unlocked (t : t) ~row ~col =
  if in_bounds t ~row ~col then t.front.(row).(col) else Matrix_cell.empty ()

let get_back_unlocked (t : t) ~row ~col =
  if in_bounds t ~row ~col then t.back.(row).(col) else Matrix_cell.empty ()

let rows_unlocked (t : t) = t.rows

let cols_unlocked (t : t) = t.cols

(* Swap without locking - for use inside with_read_lock after diff *)
let swap_unlocked (t : t) =
  let tmp = t.front in
  t.front <- t.back ;
  t.back <- tmp

(* Clear dirty without locking - for use inside with_read_lock after swap *)
let clear_dirty_unlocked (t : t) = Atomic.set t.dirty false

(* Generate SGR sequence for a style - used for dump_to_string *)
let style_to_sgr style =
  let open Matrix_cell in
  let buf = Buffer.create 32 in
  Buffer.add_string buf "\027[0" ;
  if style.bold then Buffer.add_string buf ";1" ;
  if style.dim then Buffer.add_string buf ";2" ;
  if style.underline then Buffer.add_string buf ";4" ;
  if style.reverse then Buffer.add_string buf ";7" ;
  if style.fg >= 0 && style.fg <= 255 then
    Buffer.add_string buf (Printf.sprintf ";38;5;%d" style.fg) ;
  if style.bg >= 0 && style.bg <= 255 then
    Buffer.add_string buf (Printf.sprintf ";48;5;%d" style.bg) ;
  Buffer.add_char buf 'm' ;
  Buffer.contents buf

(* Dump front buffer to string with ANSI formatting - for preserving screen on exit *)
let dump_to_string t =
  with_lock t (fun () ->
      let buf = Buffer.create (t.rows * t.cols * 2) in
      let current_style = ref Matrix_cell.default_style in
      for row = 0 to t.rows - 1 do
        for col = 0 to t.cols - 1 do
          let cell = t.front.(row).(col) in
          (* Update style if changed *)
          if not (Matrix_cell.style_equal cell.style !current_style) then begin
            if Matrix_cell.style_equal cell.style Matrix_cell.default_style then
              Buffer.add_string buf "\027[0m"
            else Buffer.add_string buf (style_to_sgr cell.style) ;
            current_style := cell.style
          end ;
          Buffer.add_string buf cell.char
        done ;
        (* Add newline after each row except the last *)
        if row < t.rows - 1 then Buffer.add_char buf '\n'
      done ;
      (* Reset style at end *)
      Buffer.add_string buf "\027[0m" ;
      Buffer.contents buf)