package zed

  1. Overview
  2. Docs

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
(*
 * zed_lines.ml
 * ------------
 * Copyright : (c) 2011, Jeremie Dimino <jeremie@dimino.org>
 * Copyright : (c) 2019, ZAN DoYe <zandoye@gmail.com>
 * Licence   : BSD3
 *
 * This file is a part of Zed, an editor engine.
 *)

open Result

exception Out_of_bounds

(* +-----------------------------------------------------------------+
   | Representation                                                  |
   +-----------------------------------------------------------------+ *)

(* Sets are represented by ropes. *)

type line=
  {
    length: int;
    width: int;
    width_info: int array;
  }

type t =
  | String of line
      (* [String len] is a string of length [len] without newline
         character. *)
  | Return
      (* A newline character. *)
  | Concat of t * t * int * int * int
      (* [Concat(t1, t2, len, count, depth)] *)

(* +-----------------------------------------------------------------+
   | Basic functions                                                 |
   +-----------------------------------------------------------------+ *)

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

(* +-----------------------------------------------------------------+
   | Offset/line resolution                                          |
   +-----------------------------------------------------------------+ *)

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


(* +-----------------------------------------------------------------+
   | Operations on sets                                              |
   +-----------------------------------------------------------------+ *)

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)))

(* +-----------------------------------------------------------------+
   | Sets from ropes                                                 |
   +-----------------------------------------------------------------+ *)

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

(* +-----------------------------------------------------------------+
   | Index and width                                                 |
   +-----------------------------------------------------------------+ *)

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 (* the width of the current char covers the column *)
      else get (idx+1) (acc_width + curr_width)
  in
  get start 0

OCaml

Innovation. Community. Security.