Source file zed_lines.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
339
340
341
342
343
open Result
exception Out_of_bounds
type line=
{
length: int;
width: int;
width_info: int array;
}
type t =
| String of line
| Return
| Concat of t * t * int * int * int
let empty_line ()= { length= 0; width= 0; width_info= [||] }
let length = function
| String line -> line.length
| Return -> 1
| Concat(_, _, len, _, _) -> len
let count = function
| String _ -> 0
| Return -> 1
| Concat(_, _, _, count, _) -> count
let depth = function
| String _ | Return -> 0
| Concat(_, _, _, _, d) -> d
let empty = String (empty_line ())
let unsafe_width ?(tolerant=false) set idx len=
let start= idx
and len_all= len
and acc=
if tolerant then
fun a b-> (+)
(if a < 0 then 1 else a)
(if b < 0 then 1 else b)
else
(+)
in
let rec unsafe_width set idx len=
if len = 0 then
Ok 0
else
match set with
| Return-> Error (start + len_all - len)
| String line->
Ok (Array.fold_left acc 0 (Array.sub line.width_info idx len))
| Concat (set1, set2, _,_,_)->
let len1= length set1 in
if idx + len <= len1 then
unsafe_width set1 idx len
else if idx >= len1 then
unsafe_width set2 (idx-len1) len
else
let r1= unsafe_width set1 idx (len1 - idx)
and r2= unsafe_width set2 0 (len - len1 + idx) in
match r1, r2 with
| Error ofs, _-> Error ofs
| Ok _, Error ofs-> Error ofs
| Ok w1, Ok w2-> Ok (w1 + w2)
in
unsafe_width set idx len
let width ?(tolerant=false) set idx len =
if idx < 0 || len < 0 || idx + len > length set then
raise Out_of_bounds
else
unsafe_width ~tolerant set idx len
let force_width set idx len=
let acc a b= (+)
(if a < 0 then 1 else a)
(if b < 0 then 1 else b)
in
let rec force_width set idx len=
if len = 0 then
0
else
match set with
| Return-> 0
| String line->
Array.fold_left acc 0 (Array.sub line.width_info idx len)
| Concat (set1, set2, _,_,_)->
let len1= length set1 in
if idx + len <= len1 then
force_width set1 idx len
else if idx >= len1 then
force_width set2 (idx-len1) len
else
let r1= force_width set1 idx (len1 - idx)
and r2= force_width set2 0 (len - len1 + idx) in
r1 + r2
in
if idx < 0 || len < 0 || idx + len > length set then
raise Out_of_bounds
else
force_width set idx len
let rec line_index_rec set ofs acc =
match set with
| String _ ->
acc
| Return ->
if ofs = 0 then
acc
else
acc + 1
| Concat(s1, s2, _, _, _) ->
let len1 = length s1 in
if ofs < len1 then
line_index_rec s1 ofs acc
else
line_index_rec s2 (ofs - len1) (acc + count s1)
let line_index set ofs =
if ofs < 0 || ofs > length set then
raise Out_of_bounds
else
line_index_rec set ofs 0
let rec line_start_rec set idx acc =
match set with
| String _ ->
acc
| Return ->
if idx = 0 then
acc
else
acc + 1
| Concat(s1, s2, _, _, _) ->
let count1 = count s1 in
if idx <= count1 then
line_start_rec s1 idx acc
else
line_start_rec s2 (idx - count1) (acc + length s1)
let line_start set idx =
if idx < 0 || idx > count set then
raise Out_of_bounds
else
line_start_rec set idx 0
let line_stop set idx =
if idx = count set
then length set
else line_start set (idx + 1) - 1
let line_length set idx =
line_stop set idx - line_start set idx
let concat set1 set2 =
Concat(
set1, set2,
length set1 + length set2,
count set1 + count set2,
1 + max (depth set1) (depth set2))
let append_line l1 l2=
{ length= l1.length + l2.length;
width= l1.width + l2.width;
width_info= Array.append l1.width_info l2.width_info
}
let append set1 set2 =
match set1, set2 with
| String {length= 0;_}, _ -> set2
| _, String {length= 0;_} -> set1
| String l1, String l2 -> String (append_line l1 l2)
| String l1, Concat(String l2, set, len, count, h) ->
Concat(String (append_line l1 l2), set, len + l1.length, count, h)
| Concat(set, String l1, len, count, h), String l2 ->
Concat(set, String(append_line l1 l2), len + l2.length, count, h)
| _ ->
let d1 = depth set1 and d2 = depth set2 in
if d1 > d2 + 2 then begin
match set1 with
| String _ | Return ->
assert false
| Concat(set1_1, set1_2, _, _, _) ->
if depth set1_1 >= depth set1_2 then
concat set1_1 (concat set1_2 set2)
else begin
match set1_2 with
| String _ | Return ->
assert false
| Concat(set1_2_1, set1_2_2, _, _, _) ->
concat (concat set1_1 set1_2_1) (concat set1_2_2 set2)
end
end else if d2 > d1 + 2 then begin
match set2 with
| String _ | Return ->
assert false
| Concat(set2_1, set2_2, _, _, _) ->
if depth set2_2 >= depth set2_1 then
concat (concat set1 set2_1) set2_2
else begin
match set2_1 with
| String _ | Return ->
assert false
| Concat(set2_1_1, set2_1_2, _, _, _) ->
concat (concat set1 set2_1_1) (concat set2_1_2 set2_2)
end
end else
concat set1 set2
let rec unsafe_sub set idx len =
match set with
| String line ->
let length= len in
let width_info= Array.sub line.width_info idx length in
let width= Array.fold_left (+) 0 width_info in
String { length; width; width_info }
| Return ->
if len = 1 then
Return
else
String (empty_line ())
| Concat(set_l, set_r, len', _, _) ->
let len_l = length set_l in
if len = len' then
set
else if idx >= len_l then
unsafe_sub set_r (idx - len_l) len
else if idx + len <= len_l then
unsafe_sub set_l idx len
else
append
(unsafe_sub set_l idx (len_l - idx))
(unsafe_sub set_r 0 (len - len_l + idx))
let sub set idx len =
if idx < 0 || len < 0 || idx + len > length set then
raise Out_of_bounds
else
unsafe_sub set idx len
let break set ofs =
let len = length set in
if ofs < 0 || ofs > len then
raise Out_of_bounds
else
(unsafe_sub set 0 ofs, unsafe_sub set ofs (len - ofs))
let insert set ofs set' =
let set1, set2 = break set ofs in
append set1 (append set' set2)
let remove set ofs len =
append (sub set 0 ofs) (sub set (ofs + len) (length set - ofs - len))
let replace set ofs len repl =
append (sub set 0 ofs) (append repl (sub set (ofs + len) (length set - ofs - len)))
let of_rope rope =
let calc_widths widths=
let width_info= widths |> List.rev |> Array.of_list in
let width= Array.fold_left (+) 0 width_info in
(width, width_info)
in
let rec loop zip (length, widths) acc =
if Zed_rope.Zip.at_eos zip then
let width, width_info= calc_widths widths in
append acc (String { length; width; width_info })
else
let ch, zip = Zed_rope.Zip.next zip in
if Uchar.to_int (Zed_char.core ch) = 10 then
let width, width_info= calc_widths widths in
loop0 zip (append (append acc (String { length; width; width_info })) Return)
else
loop zip (length + 1, Zed_char.width ch::widths) acc
and loop0 zip acc =
if Zed_rope.Zip.at_eos zip then
acc
else
let ch, zip = Zed_rope.Zip.next zip in
if Uchar.to_int (Zed_char.core ch) = 10 then
loop0 zip (append acc Return)
else
loop zip (1, [Zed_char.width ch]) acc
in
loop0 (Zed_rope.Zip.make_f rope 0) empty
let get_idx_by_width set row column=
let start= line_start set row in
let stop= line_stop set row in
let rec get idx acc_width=
if acc_width >= column || idx >= stop then
idx
else
let curr_width= force_width set idx 1 in
if acc_width + curr_width > column
then idx
else get (idx+1) (acc_width + curr_width)
in
get start 0