package tiny_appkits
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
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_richtext/Rich.ml.html
Source file Rich.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(* 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 Rich.mli *) (*****************************************************************************) (* Types *) (*****************************************************************************) type t = { (* the characters, the caret, the selection *) edit : Text_edit.t; (* the looks: (length in bytes, look), in order, covering the text *) runs : (int * Style.t) list; (* a look set with nothing selected, waiting for the next character *) pending : Style.t option; (* what an empty text looks like, so that its first character has a look to take *) base : Style.t; } let of_string ?(style = Style.plain) s = { edit = Text_edit.of_string s; runs = (if s = "" then [] else [ (String.length s, style) ]); pending = None; base = style; } let to_string t = Text_edit.to_string t.edit let length t = Text_edit.length t.edit let edit t = t.edit (*****************************************************************************) (* The run table *) (*****************************************************************************) (* everything before [pos] and everything after, splitting the run it * falls inside -- the piece table's own surgery, on looks *) let split runs pos = let rec go acc pos = function | [] -> (List.rev acc, []) | ((len, st) as r) :: rest -> if pos <= 0 then (List.rev acc, r :: rest) else if pos >= len then go (r :: acc) (pos - len) rest else (List.rev ((pos, st) :: acc), (len - pos, st) :: rest) in go [] pos runs (* neighbours that have come to look the same are one run again: the * table stays as short as the text's looks really are *) let merge runs = let rec go = function | (l1, s1) :: (l2, s2) :: rest when s1 = s2 -> go ((l1 + l2, s1) :: rest) | r :: rest -> r :: go rest | [] -> [] in go (List.filter (fun (l, _) -> l > 0) runs) let cut runs a b = let before, rest = split runs a in let _, after = split rest (b - a) in merge (before @ after) let put runs a len st = let before, after = split runs a in merge (before @ [ (len, st) ] @ after) let runs t = let _, out = List.fold_left (fun (pos, acc) (len, st) -> (pos + len, (pos, len, st) :: acc)) (0, []) t.runs in List.rev out let style_at t i = let rec go pos = function | [] -> t.base | [ (_, st) ] -> st | (len, st) :: rest -> if i < pos + len then st else go (pos + len) rest in go 0 t.runs (*****************************************************************************) (* The caret and the selection *) (*****************************************************************************) let caret t = Text_edit.caret t.edit let range t = Text_edit.range t.edit (* moving the caret forgets a look that was waiting for it *) let at pos t = { t with edit = Text_edit.at pos t.edit; pending = None } let select ~anchor ~caret t = { t with edit = Text_edit.select ~anchor ~caret t.edit; pending = None } let to_ pos t = { t with edit = Text_edit.to_ pos t.edit; pending = None } let typing_style t = match t.pending with | Some st -> st | None -> let a, b = range t in if length t = 0 then t.base else if a <> b then style_at t a (* over a selection: its first character *) else if a > 0 then style_at t (a - 1) (* what is before the caret *) else style_at t 0 (* at the very start: what comes after *) (*****************************************************************************) (* Editing *) (*****************************************************************************) let insert s t = let style = typing_style t in let a, b = range t in let runs = put (cut t.runs a b) a (String.length s) style in { t with edit = Text_edit.insert s t.edit; runs; pending = None } (* the same bytes Text_edit is about to delete, so both tables lose the * same stretch *) let delete_backward t = let a, b = range t in let from, upto = if a <> b then (a, b) else if a = 0 then (0, 0) else (Text.prev_char (to_string t) a, a) in if from = upto then t else { t with edit = Text_edit.delete_backward t.edit; runs = cut t.runs from upto; pending = None } let delete_forward t = let a, b = range t in let s = to_string t in let from, upto = if a <> b then (a, b) else if a >= String.length s then (a, a) else (a, Text.next_char s a) in if from = upto then t else { t with edit = Text_edit.delete_forward t.edit; runs = cut t.runs from upto; pending = None } (*****************************************************************************) (* Looks *) (*****************************************************************************) let restyle f t = let a, b = range t in if a = b then { t with pending = Some (f (typing_style t)) } else let before, rest = split t.runs a in let mid, after = split rest (b - a) in { t with runs = merge (before @ List.map (fun (l, st) -> (l, f st)) mid @ after) }
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>