Source file text_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
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
type span = { text : string; style : Ansi.Style.t }
module Highlight = struct
type t = {
start_offset : int;
end_offset : int;
style : Ansi.Style.t;
priority : int;
ref_id : int;
}
let make ~start_offset ~end_offset ~style ?(priority = 0) ~ref_id () =
{ start_offset; end_offset; style; priority; ref_id }
let start_offset h = h.start_offset
let end_offset h = h.end_offset
let style h = h.style
let priority h = h.priority
let ref_id h = h.ref_id
end
type line_cache = {
line_count : int;
line_widths : int array;
max_line_width : int;
bounds : (int * int) array;
}
type t = {
mutable spans : span list;
mutable spans_rev : bool;
mutable default_style : Ansi.Style.t;
mutable highlights : Highlight.t list;
mutable tab_width : int;
mutable width_method : Glyph.width_method;
mutable cached_plain_text : string option;
mutable cached_lines : line_cache option;
mutable cached_grapheme_count : int option;
mutable cached_grapheme_offsets : int array option;
mutable version : int;
}
let create ?(default_style = Ansi.Style.default) ?(width_method = `Unicode)
?(tab_width = 2) () =
{
spans = [];
spans_rev = false;
default_style;
highlights = [];
tab_width = max 1 tab_width;
width_method;
cached_plain_text = None;
cached_lines = None;
cached_grapheme_count = None;
cached_grapheme_offsets = None;
version = 0;
}
let invalidate t =
t.cached_plain_text <- None;
t.cached_lines <- None;
t.cached_grapheme_count <- None;
t.cached_grapheme_offsets <- None;
t.version <- t.version + 1
let ensure_span_order t =
if t.spans_rev then begin
t.spans <- List.rev t.spans;
t.spans_rev <- false
end
let ensure_spans_rev t =
if not t.spans_rev then begin
t.spans <- List.rev t.spans;
t.spans_rev <- true
end
let set_text t s =
t.spans <- [ { text = s; style = t.default_style } ];
t.spans_rev <- false;
invalidate t
let set_styled_text t spans =
t.spans <- spans;
t.spans_rev <- false;
invalidate t
let append t s =
ensure_spans_rev t;
t.spans <- { text = s; style = t.default_style } :: t.spans;
invalidate t
let append_styled t new_spans =
ensure_spans_rev t;
t.spans <- List.rev_append new_spans t.spans;
invalidate t
let clear t =
t.spans <- [];
t.spans_rev <- false;
t.highlights <- [];
invalidate t
let plain_text t =
match t.cached_plain_text with
| Some s -> s
| None ->
ensure_span_order t;
let s =
match t.spans with
| [] -> ""
| [ s ] -> s.text
| spans ->
let buf = Buffer.create 256 in
List.iter (fun s -> Buffer.add_string buf s.text) spans;
Buffer.contents buf
in
t.cached_plain_text <- Some s;
s
let ensure_grapheme_offsets t =
match t.cached_grapheme_offsets with
| Some offsets -> offsets
| None ->
let full = plain_text t in
let n = Glyph.String.grapheme_count full in
let offsets = Array.make (n + 1) (String.length full) in
let idx = ref 0 in
Glyph.String.iter_graphemes
(fun ~offset ~len:_ ->
offsets.(!idx) <- offset;
incr idx)
full;
t.cached_grapheme_offsets <- Some offsets;
t.cached_grapheme_count <- Some n;
offsets
let grapheme_count t =
match t.cached_grapheme_count with
| Some n -> n
| None ->
let offsets = ensure_grapheme_offsets t in
Array.length offsets - 1
let default_style t = t.default_style
let set_default_style t s = t.default_style <- s
let compute_lines t =
let tab_width = t.tab_width in
let full_text = plain_text t in
let text_len = String.length full_text in
if text_len = 0 then
{
line_count = 1;
line_widths = [| 0 |];
max_line_width = 0;
bounds = [| (0, 0) |];
}
else begin
let breaks = ref [] in
Glyph.String.iter_line_breaks
(fun ~pos ~kind -> breaks := (pos, kind) :: !breaks)
full_text;
let breaks = List.rev !breaks in
let bounds_rev = ref [] in
let line_start = ref 0 in
List.iter
(fun (brk_pos, kind) ->
let line_end =
match kind with `CRLF -> brk_pos - 1 | `LF | `CR -> brk_pos
in
bounds_rev := (!line_start, line_end) :: !bounds_rev;
line_start := brk_pos + 1)
breaks;
bounds_rev := (!line_start, text_len) :: !bounds_rev;
let bounds = Array.of_list (List.rev !bounds_rev) in
let width_method = t.width_method in
let n = Array.length bounds in
let widths = Array.make n 0 in
let max_w = ref 0 in
for i = 0 to n - 1 do
let pos, end_ = bounds.(i) in
let len = end_ - pos in
let w =
if len = 0 then 0
else Glyph.String.measure_sub ~width_method ~tab_width full_text ~pos ~len
in
widths.(i) <- w;
if w > !max_w then max_w := w
done;
{
line_count = n;
line_widths = widths;
max_line_width = !max_w;
bounds;
}
end
let ensure_line_cache t =
match t.cached_lines with
| Some _ -> ()
| None -> t.cached_lines <- Some (compute_lines t)
let line_count t =
ensure_line_cache t;
(Option.get t.cached_lines).line_count
let line_width t n =
ensure_line_cache t;
let cache = Option.get t.cached_lines in
if n < 0 || n >= cache.line_count then 0 else cache.line_widths.(n)
let max_line_width t =
ensure_line_cache t;
(Option.get t.cached_lines).max_line_width
let line_spans t line_idx =
ensure_span_order t;
let full_text = plain_text t in
let text_len = String.length full_text in
if text_len = 0 then
if line_idx = 0 then [ { text = ""; style = t.default_style } ] else []
else begin
ensure_line_cache t;
let cache = Option.get t.cached_lines in
if line_idx < 0 || line_idx >= cache.line_count then []
else begin
let start_byte, end_byte = cache.bounds.(line_idx) in
if start_byte >= end_byte then [ { text = ""; style = t.default_style } ]
else begin
let result = ref [] in
let offset = ref 0 in
List.iter
(fun (s : span) ->
let slen = String.length s.text in
let s_start = !offset in
let s_end = s_start + slen in
offset := s_end;
let lo = max s_start start_byte in
let hi = min s_end end_byte in
if lo < hi then begin
let sub = String.sub s.text (lo - s_start) (hi - lo) in
result := { text = sub; style = s.style } :: !result
end)
t.spans;
List.rev !result
end
end
end
let text_in_range t ~start ~len =
if len <= 0 then ""
else
let offsets = ensure_grapheme_offsets t in
let n = Array.length offsets - 1 in
if start >= n then ""
else
let full = plain_text t in
let byte_start = offsets.(start) in
let byte_end =
if start + len >= n then String.length full
else offsets.(start + len)
in
String.sub full byte_start (byte_end - byte_start)
let add_highlight t h = t.highlights <- h :: t.highlights
let remove_highlights_by_ref t ref_id =
t.highlights <-
List.filter (fun h -> Highlight.ref_id h <> ref_id) t.highlights
let clear_highlights t = t.highlights <- []
let highlights_in_range t ~start ~len =
let end_offset = start + len in
t.highlights
|> List.filter (fun h ->
Highlight.start_offset h < end_offset && Highlight.end_offset h > start)
|> List.sort (fun a b ->
compare (Highlight.priority a) (Highlight.priority b))
let tab_width t = t.tab_width
let set_tab_width t w =
let w = max 1 w in
if t.tab_width <> w then begin
t.tab_width <- w;
t.cached_lines <- None;
t.version <- t.version + 1
end
let width_method t = t.width_method
let set_width_method t m =
if t.width_method <> m then begin
t.width_method <- m;
t.cached_lines <- None;
t.version <- t.version + 1
end
let version t = t.version