package tiny_appkits

  1. Overview
  2. Docs
Application engines from scratch: a spreadsheet, rich text, paint, draw, CAD, editors and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_appkits.appkit_editor/Gap_buffer.ml.html

Source file Gap_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
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Gap_buffer.mli *)

(*****************************************************************************)
(* Stacks shared by their versions *)
(*****************************************************************************)

(* an array, and how much of it the newest version filled *)
type store = { mutable bytes : Bytes.t; mutable used : int }

(* a version: the first [len] bytes of [store] *)
type stack = { store : store; len : int }

let empty_stack () = { store = { bytes = Bytes.create 64; used = 0 }; len = 0 }

let push (s : stack) (text : string) : stack =
  let n = String.length text in
  if s.len = s.store.used && s.len + n <= Bytes.length s.store.bytes then begin
    (* the end of the used part: nobody reads past it, write in place *)
    Bytes.blit_string text 0 s.store.bytes s.len n;
    s.store.used <- s.len + n;
    { s with len = s.len + n }
  end
  else begin
    (* shared with a newer version, or full: a copy, twice as large *)
    let bytes = Bytes.create (max 64 (2 * (s.len + n))) in
    Bytes.blit s.store.bytes 0 bytes 0 s.len;
    Bytes.blit_string text 0 bytes s.len n;
    { store = { bytes; used = s.len + n }; len = s.len + n }
  end

let pop (s : stack) (n : int) : stack = { s with len = s.len - n }

(* the top [n] bytes, the topmost last *)
let top (s : stack) (n : int) : string = Bytes.sub_string s.store.bytes (s.len - n) n

let rev (s : string) : string = String.init (String.length s) (fun i -> s.[String.length s - 1 - i])

(*****************************************************************************)
(* The text *)
(*****************************************************************************)

(* after is reversed: its top is the byte just after the gap *)
type t = { before : stack; after : stack }

let length t = t.before.len + t.after.len
let gap t = t.before.len
let of_string s = { before = push (empty_stack ()) s; after = empty_stack () }

let get t i =
  if i < t.before.len then Bytes.get t.before.store.bytes i
  else Bytes.get t.after.store.bytes (t.after.len - 1 - (i - t.before.len))

let sub t i j = String.init (j - i) (fun k -> get t (i + k))
let to_string t = sub t 0 (length t)

(* the gap moved to [pos]: the bytes it passes over change stacks *)
let move_gap (t : t) (pos : int) : t =
  let g = gap t in
  if pos < g then
    let moved = top t.before (g - pos) in
    { before = pop t.before (g - pos); after = push t.after (rev moved) }
  else if pos > g then
    let moved = rev (top t.after (pos - g)) in
    { before = push t.before moved; after = pop t.after (pos - g) }
  else t

let insert t pos s =
  let t = move_gap t pos in
  { t with before = push t.before s }

let delete t i j =
  let t = move_gap t i in
  { t with after = pop t.after (j - i) }

let index_from t pos c =
  let n = length t in
  let rec go i = if i >= n then None else if get t i = c then Some i else go (i + 1) in
  go pos

let rindex_before t pos c =
  let rec go i = if i < 0 then None else if get t i = c then Some i else go (i - 1) in
  go (pos - 1)