-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathcli_support.ml
More file actions
293 lines (267 loc) · 9.22 KB
/
Copy pathcli_support.ml
File metadata and controls
293 lines (267 loc) · 9.22 KB
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
open Notty
(* Some utilities on top of Notty which I need (might be useful elsewhere?) *)
module Char = struct
let hdash a w =
if !Utils.unicode then
I.uchar a 0x2500 w 1
else
I.char a '-' w 1
and vdash a h =
if !Utils.unicode then
I.uchar a 0x2502 1 h
else
I.char a '|' 1 h
and star a w =
if !Utils.unicode then
I.uchar a 0x2605 w 1
else
I.char a '*' w 1
end
(* line wrapping is a bit tricky, since we prefer to wrap at word boundaries.
this is not always possible (if the word length exceeds the line width).
once we hit such a word, we split at grapheme clusters, the first part of the
word is put next to the last word, then a list of lines follow, and a last
partial line.
while we're there, we also strip whitespaces at the beginning of the line
example for line width = 5
input: "foo bar baz"
foo |
bar |
baz |
input: "foobar bar baz"
fooba|
r bar|
baz |
input: "foo foobar baz"
foo f|
oobar|
baz |
input: "foobarbazbar boo"
fooba|
rbazb|
ar |
boo |
The function graphemes get's the leftover width of the current line, the width
of a standard line and the list of characters of the word, returns the
first line, a list of lines, and the last partial line -- splits the word at
grapheme clusters to wrap it properly.
The function words strips leading whitespaces and delegates to graphemes if
word is too wide.
*)
let graphemes slen linelen chars =
let seg = Uuseg.create `Grapheme_cluster in
(* sum up width of chars till len is reached, only split on graphemes.. *)
let rec go tlen ucs llen line fline lines evt left =
match Uuseg.add seg evt with
| `Await ->
(match left with
| [] -> go tlen ucs llen line fline lines `End []
| x::xs -> go tlen ucs llen line fline lines (`Uchar x) xs)
| `Boundary ->
let clen = List.fold_left (+) 0 (List.map Uucp.Break.tty_width_hint ucs) in
if clen + llen < tlen then
go tlen [] (clen + llen) (ucs @ line) fline lines `Await left
else if clen + llen = tlen then
let l = List.rev (ucs @ line) in
if fline = None then
go linelen [] 0 [] (Some l) lines `Await left
else
go linelen [] 0 [] fline (l::lines) `Await left
else
go tlen [] clen ucs fline (List.rev line :: lines) `Await left
| `Uchar u ->
go tlen (u::ucs) llen line fline lines `Await left
| `End -> match fline with
| Some l -> l, lines, line
| None -> [], lines, line
in
go slen [] 0 [] None [] `Await chars
let words strip a l str =
let dec = Uutf.decoder ~encoding:`UTF_8 (`String str)
and seg = Uuseg.create `Word
in
let rec go llen word line lines evt =
match Uuseg.add seg evt with
| `Await -> (match Uutf.decode dec with
| `Await | `Malformed _ -> assert false
| `End | `Uchar _ as evt -> go llen word line lines evt)
| `Boundary ->
let wlen w = List.fold_left (+) 0 (List.map Uucp.Break.tty_width_hint w) in
let wordlen = wlen word in
let is_ws = match word with
| [w] -> Uucp.White.is_white_space w
| _ -> false
in
if llen + wordlen <= l then
(* word fits, all good *)
if strip && llen = 0 && is_ws then
(* strip leading ws *)
go llen [] line lines `Await
else
go (llen + wordlen) [] (word @ line) lines `Await
else if wordlen > l then
(* need to cut in the middle of a word! *)
let fl, ls, la = graphemes (l - llen) l (List.rev word) in
go (wlen la) [] la (ls @ ((List.rev line @ fl) :: lines)) `Await
else
(* wrap around, start new line (strip ws) *)
let word = if strip && is_ws then [] else word in
go (wlen word) [] word (List.rev line::lines) `Await
| `Uchar u -> go llen (u::word) line lines `Await
| `End -> if line = [] then lines else List.rev line::lines
in
let lines = go 0 [] [] [] `Await in
List.map (fun l -> I.uchars a (Array.of_list l)) (List.rev lines)
let split_unicode strip len a str =
(* first try to fit str in len *)
let whole = I.string a str in
if I.width whole <= len then
[ whole ]
else
words strip a len str
let rec split_ascii strip len str acc =
let open Astring.String in
if length str <= len then
List.rev (str :: acc)
else
let l, r =
match find ~rev:true ~start:len ((=) ' ') str with
| None -> with_range ~len str, with_range ~first:len str
| Some idx ->
let first = if strip then succ idx else idx in
with_range ~len:idx str, with_range ~first str
in
split_ascii strip len r (l :: acc)
let render_wrapped_list strip_ws width formatted =
(* that's a list of a * string! *)
let lines = List.fold_right (fun (a, m) acc ->
let lines = Astring.String.cuts ~sep:"\n" ~empty:false m in
(* now, for each line:
- if all ASCII -> look for width, maybe split in multiple at spaces
- if not, gather words (via segmenter) until width reached, start new line *)
let wrapped = List.fold_right (fun l acc ->
let els =
if Astring.String.Ascii.is_valid l then
let lines = split_ascii strip_ws width l [] in
List.map (I.string a) lines
else
split_unicode strip_ws width a l
in
els @ acc)
lines []
in
wrapped @ acc)
formatted []
in
I.vcat lines
let split_on_nl a m =
List.map (I.string a) (Astring.String.cuts ~sep:"\n" ~empty:false m)
let v_space f width left right =
let len = width - I.(width left + width right) in
if len <= 0 then
I.hpad 0 len I.(left <|> right)
else
let fill = I.tile len 1 f in
I.hcat [ left ; fill ; right ]
let v_center left right width =
let lw = I.width left
and rw = I.width right
in
match rw, lw >= width with
| 0, true -> (I.hcrop (lw - width + 1) 0 left, width)
| 0, false -> (left, succ lw)
| _, _ ->
if lw + rw >= width then
let leftw = min (max (width - rw) (width / 2)) lw in
let rightw = width - leftw in
let l = I.hcrop (lw - leftw) 0 left
and r = I.hcrop 0 (rw - rightw) right
in
(I.(l <|> r), succ leftw)
else
(I.(left <|> right), succ lw)
let str_to_char_list str : int list =
let open Uutf in
List.rev (String.fold_utf_8 (fun acc _ -> function `Uchar i -> i :: acc | `Malformed _ -> acc) [] str)
let char_list_to_str xs =
let inp = Array.of_list xs in
let buf = Buffer.create (Array.length inp) in
Array.iter (Uutf.Buffer.add_utf_8 buf) inp ;
Buffer.contents buf
let readline_input = function
| `Key (`Backspace, []) ->
`Ok (fun (pre, post) ->
match List.rev pre with
| [] -> (pre, post)
| _::tl -> (List.rev tl, post))
| `Key (`Delete, []) ->
`Ok (fun (pre, post) ->
match post with
| [] -> (pre, post)
| _::tl -> (pre, tl))
| `Key (`Home, []) -> `Ok (fun (pre, post) -> [], pre @ post)
| `Key (`End, []) -> `Ok (fun (pre, post) -> pre @ post, [])
| `Key (`Arrow `Right, []) ->
`Ok (fun (pre, post) ->
match post with
| [] -> (pre, post)
| hd::tl -> (pre @ [hd], tl))
| `Key (`Arrow `Left, []) ->
`Ok (fun (pre, post) ->
match List.rev pre with
| [] -> ([], post)
| hd::tl -> (List.rev tl, hd :: post))
| `Key (`Uchar chr, []) -> `Ok (fun (pre, post) -> pre @ [chr], post)
| k -> `Unhandled k
let emacs_bindings = function
| `Key (`Uchar 0x41, [`Ctrl]) (* C-a *) -> `Ok (fun (pre, post) -> ([], pre @ post))
| `Key (`Uchar 0x45, [`Ctrl]) (* C-e *) -> `Ok (fun (pre, post) -> (pre @ post, []))
| `Key (`Uchar 0x4b, [`Ctrl]) (* C-k *) -> `Ok (fun (pre, _) -> (pre, []))
| `Key (`Uchar 0x55, [`Ctrl]) (* C-u *) -> `Ok (fun (_, post) -> ([], post))
| `Key (`Uchar 0x46, [`Ctrl]) (* C-f *) ->
`Ok (fun (pre, post) ->
match post with
| [] -> (pre, post)
| hd::tl -> (pre @ [hd], tl))
| `Key (`Uchar 0x42, [`Ctrl]) (* C-b *) ->
`Ok (fun (pre, post) ->
match List.rev pre with
| [] -> ([], post)
| hd::tl -> (List.rev tl, hd :: post))
| `Key (`Arrow `Left, [`Ctrl]) ->
`Ok (fun (pre, post) ->
let inp, middle = match List.rev pre with
| ws::xs -> (xs, [ws])
| [] -> ([], [])
in
let _, pre, prep =
List.fold_left (fun (found, rp, rpp) char ->
if found then
(found, char :: rp, rpp)
else if Uucp.White.is_white_space char then
(true, char :: rp, rpp)
else
(false, rp, char :: rpp) )
(false, [], [])
inp
in
(pre, prep @ middle @ post))
| `Key (`Arrow `Right, [`Ctrl]) ->
`Ok (fun (pre, post) ->
let inp, middle = match post with
| ws::xs -> (xs, [ws])
| [] -> ([], [])
in
let _, prep, post =
List.fold_left (fun (found, rp, rpp) char ->
if found then
(found, rp, char :: rpp)
else if Uucp.White.is_white_space char then
(true, rp, char :: rpp)
else
(false, char :: rp, rpp) )
(false, [], [])
inp
in
(pre @ middle @ (List.rev prep), List.rev post))
| k -> `Unhandled k