package lambda-term

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file lTerm_edit.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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
(*
 * lTerm_edit.ml
 * -------------
 * Copyright : (c) 2011, Jeremie Dimino <jeremie@dimino.org>
 * Licence   : BSD3
 *
 * This file is a part of Lambda-Term.
 *)

let pervasives_compare= compare

open CamomileLibraryDefault.Camomile
open Zed_edit
open LTerm_key
open LTerm_geom
open Lwt_react

(* +-----------------------------------------------------------------+
   | Actions                                                         |
   +-----------------------------------------------------------------+ *)

type action =
  | Zed of Zed_edit.action
  | Start_macro
  | Stop_macro
  | Cancel_macro
  | Play_macro
  | Insert_macro_counter
  | Set_macro_counter
  | Add_macro_counter
  | Custom of (unit -> unit)

let doc_of_action = function
  | Zed action -> Zed_edit.doc_of_action action
  | Start_macro -> "start a new macro."
  | Stop_macro -> "end the current macro."
  | Cancel_macro -> "cancel the current macro."
  | Play_macro -> "play the last recorded macro."
  | Insert_macro_counter -> "insert the current value of the macro counter."
  | Set_macro_counter -> "sets the value of the macro counter."
  | Add_macro_counter -> "adds a value to the macro counter."
  | Custom _ -> "programmer defined action."

let actions = [
  Start_macro, "start-macro";
  Stop_macro, "stop-macro";
  Cancel_macro, "cancel-macro";
  Play_macro, "play-macro";
  Insert_macro_counter, "insert-macro-counter";
  Set_macro_counter, "set-macro-counter";
  Add_macro_counter, "add-macro-counter";
]

let actions_to_names = Array.of_list (List.sort (fun (a1, _) (a2, _) -> pervasives_compare a1 a2) actions)
let names_to_actions = Array.of_list (List.sort (fun (_, n1) (_, n2) -> pervasives_compare n1 n2) actions)

let action_of_name x =
  let rec loop a b =
    if a = b then
      Zed (Zed_edit.action_of_name x)
    else
      let c = (a + b) / 2 in
      let action, name = Array.unsafe_get names_to_actions c in
      match pervasives_compare x name with
        | d when d < 0 ->
            loop a c
        | d when d > 0 ->
            loop (c + 1) b
        | _ ->
            action
  in
  loop 0 (Array.length names_to_actions)

let name_of_action x =
  let rec loop a b =
    if a = b then
      raise Not_found
    else
      let c = (a + b) / 2 in
      let action, name = Array.unsafe_get actions_to_names c in
      match pervasives_compare x action with
        | d when d < 0 ->
            loop a c
        | d when d > 0 ->
            loop (c + 1) b
        | _ ->
            name
  in
  match x with
    | Zed x -> Zed_edit.name_of_action x
    | Custom _ -> "custom"
    | _ -> loop 0 (Array.length actions_to_names)

module Bindings = Zed_input.Make (LTerm_key)

let bindings = ref Bindings.empty

let bind seq actions = bindings := Bindings.add seq actions !bindings
let unbind seq = bindings := Bindings.remove seq !bindings

let () =
  bind [{ control = false; meta = false; shift = false; code = Left }] [Zed Prev_char];
  bind [{ control = false; meta = false; shift = false; code = Right }] [Zed Next_char];
  bind [{ control = false; meta = false; shift = false; code = Up }] [Zed Prev_line];
  bind [{ control = false; meta = false; shift = false; code = Down }] [Zed Next_line];
  bind [{ control = false; meta = false; shift = false; code = Home }] [Zed Goto_bol];
  bind [{ control = false; meta = false; shift = false; code = End }] [Zed Goto_eol];
  bind [{ control = false; meta = false; shift = false; code = Insert }] [Zed Switch_erase_mode];
  bind [{ control = false; meta = false; shift = false; code = Delete }] [Zed Delete_next_char];
  bind [{ control = false; meta = false; shift = false; code = Enter }] [Zed Newline];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char ' ') }] [Zed Set_mark];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'a') }] [Zed Goto_bol];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'e') }] [Zed Goto_eol];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'd') }] [Zed Delete_next_char];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'h') }] [Zed Delete_prev_char];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'k') }] [Zed Kill_next_line];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'u') }] [Zed Kill_prev_line];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'n') }] [Zed Next_line];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'p') }] [Zed Prev_line];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'w') }] [Zed Kill];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'y') }] [Zed Yank];
  bind [{ control = false; meta = false; shift = false; code = Backspace }] [Zed Delete_prev_char];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'w') }] [Zed Copy];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'c') }] [Zed Capitalize_word];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'l') }] [Zed Lowercase_word];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'u') }] [Zed Uppercase_word];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'b') }] [Zed Prev_word];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'f') }] [Zed Next_word];
  bind [{ control = false; meta = true; shift = false; code = Right }] [Zed Next_word];
  bind [{ control = false; meta = true; shift = false; code = Left }] [Zed Prev_word];
  bind [{ control = true; meta = false; shift = false; code = Right }] [Zed Next_word];
  bind [{ control = true; meta = false; shift = false; code = Left }] [Zed Prev_word];
  bind [{ control = false; meta = true; shift = false; code = Backspace }] [Zed Kill_prev_word];
  bind [{ control = false; meta = true; shift = false; code = Delete }] [Zed Kill_prev_word];
  bind [{ control = true; meta = false; shift = false; code = Delete }] [Zed Kill_next_word];
  bind [{ control = false; meta = true; shift = false; code = Char(UChar.of_char 'd') }] [Zed Kill_next_word];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char '_') }] [Zed Undo];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'x') }; { control = false; meta = false; shift = false; code = Char(UChar.of_char '(') }] [Start_macro];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'x') }; { control = false; meta = false; shift = false; code = Char(UChar.of_char ')') }] [Stop_macro];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'x') }; { control = false; meta = false; shift = false; code = Char(UChar.of_char 'e') }] [Play_macro];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'g') }] [Cancel_macro];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'x') };
        { control = true; meta = false; shift = false; code = Char(UChar.of_char 'k') };
        { control = false; meta = false; shift = false; code = Tab }] [Insert_macro_counter];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'x') };
        { control = true; meta = false; shift = false; code = Char(UChar.of_char 'k') };
        { control = true; meta = false; shift = false; code = Char(UChar.of_char 'a') }] [Add_macro_counter];
  bind [{ control = true; meta = false; shift = false; code = Char(UChar.of_char 'x') };
        { control = true; meta = false; shift = false; code = Char(UChar.of_char 'k') };
        { control = true; meta = false; shift = false; code = Char(UChar.of_char 'c') }] [Set_macro_counter]

(* +-----------------------------------------------------------------+
   | Widgets                                                         |
   +-----------------------------------------------------------------+ *)

let clipboard = Zed_edit.new_clipboard ()
let macro = Zed_macro.create []

let regexp_word =
  let set = UCharInfo.load_property_set `Alphabetic in
  let set = List.fold_left (fun set ch -> USet.add (UChar.of_char ch) set) set ['0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'] in
  Zed_re.Core.compile (`Repn(`Set set, 1, None))

let dummy_engine = Zed_edit.create ()
let dummy_cursor = Zed_edit.new_cursor dummy_engine
let dummy_context = Zed_edit.context dummy_engine dummy_cursor
let newline = Zed_char.unsafe_of_uChar (UChar.of_char '\n')

class scrollable = object
  inherit LTerm_widget.scrollable
  method! calculate_range page_size document_size = (document_size - page_size/2)
end

class edit ?(clipboard = clipboard) ?(macro = macro) ?(size = { cols = 1; rows = 1 }) () =
  let locale, set_locale = S.create None in
object(self)
  inherit LTerm_widget.t "edit" as super

  val vscroll = new scrollable
  method vscroll = vscroll

  method clipboard = clipboard
  method macro = macro

  method! can_focus = true

  val mutable engine = dummy_engine
  method engine = engine

  val mutable cursor = dummy_cursor
  method cursor = cursor

  val mutable context = dummy_context
  method context = context

  method text = Zed_rope.to_string (Zed_edit.text engine)

  val mutable style = LTerm_style.none
  val mutable marked_style = LTerm_style.none
  val mutable current_line_style = LTerm_style.none
  method! update_resources =
    let rc = self#resource_class and resources = self#resources in
    style <- LTerm_resources.get_style rc resources;
    marked_style <- LTerm_resources.get_style (rc ^ ".marked") resources;
    current_line_style <- LTerm_resources.get_style (rc ^ ".current-line") resources

  method editable _pos _len = true
  method match_word text pos = match_by_regexp_core regexp_word text pos
  method locale = S.value locale
  method set_locale locale = set_locale locale

  val mutable event = E.never
  val mutable resolver = None

  val mutable local_bindings = Bindings.empty
  method bind keys actions = local_bindings <- Bindings.add keys actions local_bindings

  val mutable shift_width = 0
  val mutable start = 0
  val mutable start_line = 0
  val mutable size = size

  method! size_request = size

  method private update_window_position =
    let line_set = Zed_edit.lines engine in
    let line_count = Zed_lines.count line_set in
    let cursor_offset = Zed_cursor.get_position cursor in
    let cursor_line = Zed_lines.line_index line_set cursor_offset in
    let cursor_column = cursor_offset - Zed_lines.line_start line_set cursor_line in
    let column_display= Zed_lines.force_width line_set (Zed_lines.line_start line_set cursor_line) cursor_column in


    (*** check cursor position is in view *)

    (* Horizontal check *)
    if column_display < shift_width || column_display >= shift_width + size.cols then begin

      shift_width <- max 0 (column_display - size.cols / 2);
    end;

    (* Vertical check *)
    let start_line' = Zed_lines.line_index line_set start in
    let start_line' =
      if cursor_line < start_line' || cursor_line >= start_line' + size.rows then begin
        (*let start_line' = max 0 (cursor_line - size.rows / 2) in*)
        let line_count = Zed_lines.count line_set in
        let start_line' = min line_count (max 0 (cursor_line - size.rows / 2)) in
        start <- Zed_lines.line_start line_set start_line';
        start_line'
      end else
        start_line'
    in
    (* document size *)
    if start_line <> start_line' then begin
      start_line <- start_line';
      vscroll#set_offset ~trigger_callback:false start_line
    end;
    vscroll#set_document_size (line_count+1);
    ()

  initializer
    engine <- (
      Zed_edit.create
        ~editable:(fun pos len -> self#editable pos len)
        ~match_word:(fun text pos -> self#match_word text pos)
        ~clipboard
        ~locale
        ()
    );
    cursor <- Zed_edit.new_cursor engine;
    context <- Zed_edit.context engine cursor;
    Zed_edit.set_data engine (self :> edit);
    event <- E.map (fun _ ->
      self#update_window_position;
      self#queue_draw) (Zed_edit.update engine [cursor]);
    self#on_event
      (function
         | LTerm_event.Key key -> begin
             let res =
               match resolver with
               | Some res -> res
               | None -> Bindings.resolver [ Bindings.pack (fun x -> x) local_bindings
                                           ; Bindings.pack (fun x -> x) !bindings
                                           ]
             in
             match Bindings.resolve key res with
               | Bindings.Accepted actions ->
                   resolver <- None;
                   let rec exec = function
                     | Custom f :: actions ->
                         Zed_macro.add macro (Custom f);
                         f ();
                         exec actions
                     | Zed action :: actions ->
                         Zed_macro.add macro (Zed action);
                         Zed_edit.get_action action context;
                         exec actions
                     | Start_macro :: actions ->
                         Zed_macro.set_recording macro true;
                         exec actions
                     | Stop_macro :: actions ->
                         Zed_macro.set_recording macro false;
                         exec actions
                     | Cancel_macro :: actions ->
                         Zed_macro.cancel macro;
                         exec actions
                     | Play_macro :: actions ->
                         Zed_macro.cancel macro;
                         exec (Zed_macro.contents macro @ actions)
                     | Insert_macro_counter :: actions ->
                         Zed_macro.add macro Insert_macro_counter;
                         Zed_edit.insert context (Zed_rope.of_string (Zed_string.unsafe_of_utf8 (string_of_int (Zed_macro.get_counter macro))));
                         Zed_macro.add_counter macro 1;
                         exec actions
                     | (Add_macro_counter | Set_macro_counter) :: actions ->
                         exec actions
                     | [] ->
                         true
                   in
                   exec actions
               | Bindings.Continue res ->
                   resolver <- Some res;
                   true
               | Bindings.Rejected ->
                   if resolver = None then
                     match key with
                       | { control = false; meta = false; shift = false; code = Char ch } ->
                           Zed_edit.insert_char context ch;
                           true
                       | _ ->
                           false
                   else begin
                     resolver <- None;
                     false
                   end
           end
         | _ ->
             false)


  method! set_allocation rect =
    size <- size_of_rect rect;
    super#set_allocation rect;
    vscroll#set_page_size size.rows;
    start <- 0; shift_width <- 0; start_line <- 0;
    self#update_window_position

  initializer vscroll#on_offset_change (fun n ->

    (* find what line the cursor is currently on. *)
    let line_set = Zed_edit.lines engine in
    let cursor_offset = Zed_cursor.get_position cursor in
    let cursor_line = Zed_lines.line_index line_set cursor_offset in

    start_line <- n;
    start <- Zed_lines.line_start line_set start_line;

    if cursor_line < start_line then begin
      let d = start_line - cursor_line in
      Zed_edit.move_line context d (* first row *)
    end else if cursor_line >= start_line + size.rows then begin
      let line_count = Zed_lines.count line_set in
      let line = max 0 (min (line_count+1) (start_line + size.rows - 1)) in (* last row *)
      let d = line - cursor_line in
      Zed_edit.move_line context d
    end;
    self#queue_draw;
  )


  method! draw ctx _focused =
    let open LTerm_draw in

    let size = LTerm_draw.size ctx in

    let line_set = Zed_edit.lines engine in
    let cursor_offset = Zed_cursor.get_position cursor in
    let cursor_line = Zed_lines.line_index line_set cursor_offset in
    let cursor_column = cursor_offset - Zed_lines.line_start line_set cursor_line in

    (*** Drawing ***)

    (* Initialises points with the text style and spaces. *)
    fill ctx (Zed_char.unsafe_of_char ' ');
    fill_style ctx style;

    (*** Text drawing ***)

    let rec draw_line row col zip =
      if Zed_rope.Zip.at_eos zip then
        draw_eoi (row + 1)
      else
        let char, zip = Zed_rope.Zip.next zip in
        if char = newline then begin
          let row = row + 1 in
          if row < size.rows then begin_line row zip
        end else begin
          if col > size.cols then begin
            let row = row + 1 in
            if row < size.rows then skip_eol row zip
          end else begin
            draw_char ctx row col char;
            draw_line row (col + (Zed_char.width char)) zip
          end
        end

    and skip_eol row zip =
      if Zed_rope.Zip.at_eos zip then
        draw_eoi (row + 1)
      else
        let char, zip = Zed_rope.Zip.next zip in
        if char = newline then
          begin_line row zip
        else
          skip_eol row zip

    and skip_bol row zip remaining =
      if remaining <= 0 then
        draw_line row (-remaining) zip
      else if Zed_rope.Zip.at_eos zip then
        draw_eoi (row + 1)
      else
        let char, zip = Zed_rope.Zip.next zip in
        if char = newline then begin
          let row = row + 1 in
          if row < size.rows then begin_line row zip
        end else
          skip_bol row zip (remaining - (Zed_char.width char))

    and begin_line row zip =
      if Zed_rope.Zip.at_eos zip then
        draw_eoi row
      else if shift_width <> 0 then begin
        skip_bol row zip shift_width
      end else
        draw_line row 0 zip

    and draw_eoi _row =
      ()
    in

    let text = Zed_edit.text engine in

    begin_line 0 (Zed_rope.Zip.make_f text start);

    (* Colorize the current line. *)
    for col = 0 to size.cols - 1 do
      set_style (point ctx (cursor_line - start_line) col) current_line_style
    done;

    (* Colorize the selection if needed *)
    if Zed_edit.get_selection engine then begin
      let sel_offset = Zed_cursor.get_position (Zed_edit.mark engine) in
      let sel_line = Zed_lines.line_index line_set sel_offset in
      let sel_column = sel_offset - Zed_lines.line_start line_set sel_line in
      let line_a, column_a, line_b, column_b =
        if sel_offset < cursor_offset then
          (sel_line, sel_column, cursor_line, cursor_column)
        else
          (cursor_line, cursor_column, sel_line, sel_column)
      in
      let line_a, column_a =
        if line_a < start_line then
          (start_line, 0)
        else
          (line_a, column_a)
      in
      let line_b, column_b =
        if line_b >= start_line + size.rows then
          (start_line + size.rows - 1, size.cols - 1)
        else
          (line_b, column_b)
      in
      if line_a < start_line + size.rows && line_b >= start_line then begin
        let line_a = line_a - start_line and line_b = line_b - start_line in
        let column_a = column_a and column_b = column_b in
        if line_a = line_b then
          for column = column_a to column_b - 1 do
            set_style (point ctx line_a column) marked_style
          done
        else begin
          for column = column_a to size.cols - 1 do
            set_style (point ctx line_a column) marked_style
          done;
          for line = line_a + 1 to line_b - 1 do
            for column = 0 to size.cols - 1 do
              set_style (point ctx line column) marked_style
            done
          done;
          for column = 0 to column_b - 1 do
            set_style (point ctx line_b column) marked_style
          done
        end
      end
    end

  method! cursor_position =
    let line_set = Zed_edit.lines engine in
    let cursor_offset = Zed_cursor.get_position cursor in
    let cursor_line = Zed_lines.line_index line_set cursor_offset in
    let line_start= Zed_lines.line_start line_set cursor_line  in
    let start_line = Zed_lines.line_index line_set start in
    let col= Zed_lines.force_width line_set line_start (cursor_offset - line_start)
        - shift_width in
    Some { row = cursor_line - start_line; col }
end
OCaml

Innovation. Community. Security.