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_editor/Emacs_editor.ml.html
Source file Emacs_editor.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(* 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. *) open Lisp (* See Emacs_editor.mli *) type undo = Inserted of int * int | Deleted of int * string | Boundary type buffer = { name : string; text : Gap_buffer.t; point : int; mark : int option; file : string option; modified : bool; undo : undo list; undoing : undo list option; top : int; mode : string; } type request = Call_interactively of Lisp.t | Isearch of bool | Universal_argument type t = { buffers : buffer list; disk : (string * string) list; kill_ring : string list; message : string; quit : bool; request : request option; } type lisp = t Lisp_eval.state let window_height = 22 (*****************************************************************************) (* Buffers *) (*****************************************************************************) let mode_of (name : string) : string = if name = "*scratch*" then "Lisp Interaction" else if Filename.check_suffix name ".el" || name = ".emacs" then "Emacs-Lisp" else if Filename.check_suffix name ".txt" || name = "TUTORIAL" then "Text" else "Fundamental" let make_buffer ?file (name : string) (text : string) : buffer = { name; text = Gap_buffer.of_string text; point = 0; mark = None; file; modified = false; undo = []; undoing = None; top = 0; mode = mode_of name } let current (e : t) : buffer = List.hd e.buffers let set_current (b : buffer) (e : t) : t = { e with buffers = b :: List.filter (fun (x : buffer) -> x.name <> b.name) e.buffers } (* the buffer [name] made current, the others keeping their order *) let select (name : string) (e : t) : t option = match List.find_opt (fun (b : buffer) -> b.name = name) e.buffers with Some b -> Some (set_current b e) | None -> None (*****************************************************************************) (* Changes, and their undo *) (*****************************************************************************) (* a position after a change: past the insertion if it was after it (at it, it stays before, as a marker does), pulled back into a deletion's start *) let after_insert (pos : int) (at : int) (n : int) : int = if pos > at then pos + n else pos let after_delete (pos : int) (i : int) (j : int) : int = if pos >= j then pos - (j - i) else if pos > i then i else pos let insert_at (b : buffer) (pos : int) (s : string) : buffer = if s = "" then b else let n = String.length s in (* typing merges into the insertion just before, one entry per run *) let undo = match b.undo with Inserted (i, j) :: rest when j = pos -> Inserted (i, j + n) :: rest | u -> Inserted (pos, pos + n) :: u in { b with text = Gap_buffer.insert b.text pos s; point = after_insert b.point pos n; mark = Option.map (fun m -> after_insert m pos n) b.mark; modified = true; undo } let delete_range (b : buffer) (i : int) (j : int) : buffer = let i, j = (min i j, max i j) in if i = j then b else { b with text = Gap_buffer.delete b.text i j; point = after_delete b.point i j; mark = Option.map (fun m -> after_delete m i j) b.mark; modified = true; undo = Deleted (i, Gap_buffer.sub b.text i j) :: b.undo } let boundary (e : t) : t = let b = current e in match b.undo with Boundary :: _ | [] -> e | u -> set_current { b with undo = Boundary :: u } e (* one command's worth undone: the entries up to the next boundary, each undone by a change of its own, recorded *) let undo_step (b : buffer) (entries : undo list) : buffer * undo list = let rec go (b : buffer) = function | Boundary :: rest -> (b, rest) | [] -> (b, []) | Inserted (i, j) :: rest -> go { (delete_range b i j) with point = i } rest | Deleted (i, s) :: rest -> go { (insert_at b i s) with point = i + String.length s } rest in go b entries (*****************************************************************************) (* Lines *) (*****************************************************************************) let line_start_at (b : buffer) (pos : int) : int = match Gap_buffer.rindex_before b.text pos '\n' with Some i -> i + 1 | None -> 0 let line_end_at (b : buffer) (pos : int) : int = match Gap_buffer.index_from b.text pos '\n' with Some i -> i | None -> Gap_buffer.length b.text let line_of (b : buffer) (pos : int) : int = let n = ref 0 in for i = 0 to pos - 1 do if Gap_buffer.get b.text i = '\n' then incr n done; !n let line_start (b : buffer) (line : int) : int = let rec go pos k = if k = 0 then pos else match Gap_buffer.index_from b.text pos '\n' with Some i -> go (i + 1) (k - 1) | None -> Gap_buffer.length b.text in go 0 line (*****************************************************************************) (* Keys *) (*****************************************************************************) let key_name (k : string) : string = let control c = "C-" ^ String.make 1 (Char.lowercase_ascii (Char.chr (Char.code c + 64))) in match k with | "\r" -> "RET" | "\t" -> "TAB" | "\x7f" -> "DEL" | " " -> "SPC" | "\x1b" -> "ESC" | "\x00" -> "C-@" | "\x1b[A" -> "<up>" | "\x1b[B" -> "<down>" | "\x1b[C" -> "<right>" | "\x1b[D" -> "<left>" | "\x1b[3~" -> "<deletechar>" | "\x1b[5~" -> "<prior>" | "\x1b[6~" -> "<next>" | _ when String.length k = 1 && Char.code k.[0] < 32 -> control k.[0] | _ when String.length k = 2 && k.[0] = '\x1b' -> let c = k.[1] in "M-" ^ if Char.code c < 32 then control c else if c = '\x7f' then "DEL" else String.make 1 c | _ -> k let key_description (keys : string) : string = String.concat " " (List.map key_name (Line_discipline.split_keys keys)) (*****************************************************************************) (* The primitives *) (*****************************************************************************) let signal = Lisp_eval.signal let error = Lisp_eval.error let wrong_type pred v = signal "wrong-type-argument" [ Sym pred; v ] let int_of v = match v with Int n -> n | _ -> wrong_type "integer-or-marker-p" v let string_of v = match v with Str s -> s | _ -> wrong_type "stringp" v (* the current buffer, and the state with it changed *) let buf (st : lisp) : buffer = current st.host let with_buf (st : lisp) (b : buffer) : lisp = { st with host = set_current b st.host } let with_host (st : lisp) (e : t) : lisp = { st with host = e } (* a Lisp position (from 1) into the text's (from 0), inside it *) let pos_of (b : buffer) (v : Lisp.t) : int = max 0 (min (Gap_buffer.length b.text) (int_of v - 1)) let lisp_pos (p : int) : Lisp.t = Int (p + 1) (* an optional count: the first argument, 1 when missing or nil *) let count (args : Lisp.t list) : int = match args with Int n :: _ -> n | _ -> 1 let message (st : lisp) (s : string) : lisp = with_host st { st.host with message = s } let move (st : lisp) (n : int) : Lisp.t * lisp = let b = buf st in let p = b.point + n in if p < 0 then signal "beginning-of-buffer" [] else if p > Gap_buffer.length b.text then signal "end-of-buffer" [] else (nil, with_buf st { b with point = p }) (* forward-line: to the start of the line [n] lines down (up when negative), and how many lines were missing to get there *) let forward_line (b : buffer) (n : int) : buffer * int = if n > 0 then let rec go p k = if k = 0 then (p, 0) else match Gap_buffer.index_from b.text p '\n' with Some i -> go (i + 1) (k - 1) | None -> (Gap_buffer.length b.text, k) in let p, short = go b.point n in ({ b with point = p }, short) else let rec go p k = if k = 0 then (p, 0) else if p = 0 then (0, k) else go (line_start_at b (p - 1)) (k - 1) in let p, short = go (line_start_at b b.point) (-n) in ({ b with point = p }, short) let column (b : buffer) : int = b.point - line_start_at b b.point let search (b : buffer) (s : string) ~(forward : bool) ~(bound : int option) : int option = let len = Gap_buffer.length b.text and n = String.length s in let at i = i >= 0 && i + n <= len && Gap_buffer.sub b.text i (i + n) = s in if forward then let limit = Option.value bound ~default:len in let rec go i = if i + n > limit then None else if at i then Some (i + n) else go (i + 1) in go b.point else let limit = Option.value bound ~default:0 in let rec go i = if i < limit then None else if at i then Some i else go (i - 1) in go (b.point - n) (* the start of the balanced expression before [pos]: a list, a string, or a symbol, a quote before it included (lisp.el's backward-sexp, without syntax tables) *) let sexp_start (b : buffer) (pos : int) : int = let get i = Gap_buffer.get b.text i in let rec skip_blank i = if i > 0 && (get (i - 1) = ' ' || get (i - 1) = '\n' || get (i - 1) = '\t') then skip_blank (i - 1) else i in let i = skip_blank pos in if i = 0 then signal "scan-error" [ Str "Containing expression ends prematurely" ] else let start = match get (i - 1) with | ')' -> let rec go j depth = if j < 0 then signal "scan-error" [ Str "Unbalanced parentheses" ] else match get j with ')' -> go (j - 1) (depth + 1) | '(' -> if depth = 1 then j else go (j - 1) (depth - 1) | _ -> go (j - 1) depth in go (i - 1) 0 | '"' -> ( match Gap_buffer.rindex_before b.text (i - 1) '"' with Some j -> j | None -> signal "scan-error" [ Str "Unbalanced quotes" ]) | _ -> let rec go j = if j > 0 && not (String.contains " \n\t()\"'" (get (j - 1))) then go (j - 1) else j in go (i - 1) in if start > 0 && get (start - 1) = '\'' then start - 1 else start let switch_to (st : lisp) (name : string) : lisp = match select name st.host with | Some e -> with_host st e | None -> with_host st { st.host with buffers = make_buffer name "" :: st.host.buffers } let visit (st : lisp) (file : string) : lisp = match List.find_opt (fun (b : buffer) -> b.file = Some file) st.host.buffers with | Some b -> with_host st (set_current b st.host) | None -> let text, msg = match List.assoc_opt file st.host.disk with Some text -> (text, "") | None -> ("", "(New file)") in let name = if List.exists (fun (b : buffer) -> b.name = file) st.host.buffers then file ^ "<2>" else file in message (with_host st { st.host with buffers = make_buffer ~file name text :: st.host.buffers }) msg let write (st : lisp) (file : string) : lisp = let b = buf st in let e = { st.host with disk = (file, Gap_buffer.to_string b.text) :: List.remove_assoc file st.host.disk } in message (with_buf (with_host st e) { b with file = Some file; modified = false }) ("Wrote " ^ file) let region (b : buffer) : int * int = match b.mark with Some m -> (min m b.point, max m b.point) | None -> error "The mark is not set now, so there is no region" (* name, interactive spec when it is a command too, documentation, and the function *) let primitives : (string * string option * string * t Lisp_eval.subr) list = [ (* positions *) ("point", None, "Return the value of point, an integer.", fun st _ -> (lisp_pos (buf st).point, st)); ("point-min", None, "Return the minimum permissible value of point.", fun st _ -> (Int 1, st)); ("point-max", None, "Return the maximum permissible value of point.", fun st _ -> (lisp_pos (Gap_buffer.length (buf st).text), st)); ("buffer-size", None, "Return the number of characters in the buffer.", fun st _ -> (Int (Gap_buffer.length (buf st).text), st)); ( "goto-char", None, "Set point to POSITION.", fun st args -> match args with [ p ] -> let b = buf st in (p, with_buf st { b with point = pos_of b p }) | _ -> error "goto-char takes a position" ); ( "char-after", None, "Return the character at POS (point by default), or nil at the end.", fun st args -> let b = buf st in let p = match args with p :: _ when p <> nil -> int_of p - 1 | _ -> b.point in ((if p >= 0 && p < Gap_buffer.length b.text then Int (Char.code (Gap_buffer.get b.text p)) else nil), st) ); ( "char-before", None, "Return the character before POS (point by default), or nil at the start.", fun st args -> let b = buf st in let p = match args with p :: _ when p <> nil -> int_of p - 1 | _ -> b.point in ((if p > 0 && p <= Gap_buffer.length b.text then Int (Char.code (Gap_buffer.get b.text (p - 1))) else nil), st) ); ("bobp", None, "Return t if point is at the beginning of the buffer.", fun st _ -> (of_bool ((buf st).point = 0), st)); ("eobp", None, "Return t if point is at the end of the buffer.", fun st _ -> let b = buf st in (of_bool (b.point = Gap_buffer.length b.text), st)); ("bolp", None, "Return t if point is at the beginning of a line.", fun st _ -> let b = buf st in (of_bool (b.point = line_start_at b b.point), st)); ("eolp", None, "Return t if point is at the end of a line.", fun st _ -> let b = buf st in (of_bool (b.point = line_end_at b b.point), st)); ("line-beginning-position", None, "Return the position of the start of the current line.", fun st _ -> let b = buf st in (lisp_pos (line_start_at b b.point), st)); ("line-end-position", None, "Return the position of the end of the current line.", fun st _ -> let b = buf st in (lisp_pos (line_end_at b b.point), st)); ("current-column", None, "Return the horizontal position of point, from 0.", fun st _ -> (Int (column (buf st)), st)); ( "move-to-column", None, "Move point to COLUMN in the current line, or its end if shorter.", fun st args -> let b = buf st in let start = line_start_at b b.point in let p = min (line_end_at b b.point) (start + int_of (List.hd args)) in (Int (p - start), with_buf st { b with point = p }) ); (* moving: commands in C in Emacs too *) ("forward-char", Some "p", "Move point N characters forward (backward if N is negative).", fun st args -> move st (count args)); ("backward-char", Some "p", "Move point N characters backward (forward if N is negative).", fun st args -> move st (-count args)); ( "forward-line", Some "p", "Move N lines forward, to the start of a line; return how many lines could not be moved.", fun st args -> let b, short = forward_line (buf st) (count args) in (Int short, with_buf st b) ); ("beginning-of-line", Some "", "Move point to the beginning of the current line.", fun st _ -> let b = buf st in (nil, with_buf st { b with point = line_start_at b b.point })); ("end-of-line", Some "", "Move point to the end of the current line.", fun st _ -> let b = buf st in (nil, with_buf st { b with point = line_end_at b b.point })); (* changing *) ( "insert", None, "Insert the strings or characters at point, point moving after them.", fun st args -> let s = String.concat "" (List.map (function Str s -> s | Int c -> String.make 1 (Char.chr (c land 255)) | v -> wrong_type "char-or-string-p" v) args) in let b = buf st in (nil, with_buf st { (insert_at b b.point s) with point = b.point + String.length s }) ); ( "delete-region", None, "Delete the text between START and END.", fun st args -> match args with [ a; z ] -> let b = buf st in (nil, with_buf st (delete_range b (pos_of b a) (pos_of b z))) | _ -> error "delete-region takes two positions" ); ( "delete-char", Some "p", "Delete the N characters after point (before it if N is negative).", fun st args -> let b = buf st in let n = count args in let p = b.point + n in if p < 0 then signal "beginning-of-buffer" [] else if p > Gap_buffer.length b.text then signal "end-of-buffer" [] else (nil, with_buf st (delete_range b b.point p)) ); ("erase-buffer", None, "Delete the entire contents of the current buffer.", fun st _ -> let b = buf st in (nil, with_buf st (delete_range b 0 (Gap_buffer.length b.text)))); ( "buffer-substring", None, "Return the text between START and END as a string.", fun st args -> match args with [ a; z ] -> let b = buf st in let i = pos_of b a and j = pos_of b z in (Str (Gap_buffer.sub b.text (min i j) (max i j)), st) | _ -> error "buffer-substring takes two positions" ); ("buffer-string", None, "Return the whole text of the buffer.", fun st _ -> (Str (Gap_buffer.to_string (buf st).text), st)); ( "search-forward", Some "sSearch: ", "Search forward from point for STRING; point goes after it. BOUND limits the search, and NOERROR returns nil instead of failing.", fun st args -> let b = buf st in match args with | s :: rest -> ( let bound = match rest with bd :: _ when bd <> nil -> Some (pos_of b bd) | _ -> None in match search b (string_of s) ~forward:true ~bound with | Some p -> (lisp_pos p, with_buf st { b with point = p }) | None -> (match rest with [ _; noerror ] when noerror <> nil -> (nil, st) | _ -> signal "search-failed" [ s ])) | [] -> error "search-forward takes a string" ); ( "search-backward", Some "sSearch backward: ", "Search backward from point for STRING; point goes to its start.", fun st args -> let b = buf st in match args with | s :: rest -> ( let bound = match rest with bd :: _ when bd <> nil -> Some (pos_of b bd) | _ -> None in match search b (string_of s) ~forward:false ~bound with | Some p -> (lisp_pos p, with_buf st { b with point = p }) | None -> (match rest with [ _; noerror ] when noerror <> nil -> (nil, st) | _ -> signal "search-failed" [ s ])) | [] -> error "search-backward takes a string" ); ( "backward-sexp", Some "", "Move backward across one balanced expression.", fun st _ -> let b = buf st in (nil, with_buf st { b with point = sexp_start b b.point }) ); (* the mark and the region *) ("mark", None, "Return the position of the mark, or nil.", fun st _ -> ((match (buf st).mark with Some m -> lisp_pos m | None -> nil), st)); ( "set-mark", None, "Set the mark at POS (nil: no mark).", fun st args -> let b = buf st in (nil, with_buf st { b with mark = (match args with [ p ] when p <> nil -> Some (pos_of b p) | _ -> None) }) ); ("region-beginning", None, "Return the start of the region.", fun st _ -> (lisp_pos (fst (region (buf st))), st)); ("region-end", None, "Return the end of the region.", fun st _ -> (lisp_pos (snd (region (buf st))), st)); (* the kill ring *) ("kill-new", None, "Make STRING the latest kill in the kill ring.", fun st args -> (nil, with_host st { st.host with kill_ring = string_of (List.hd args) :: st.host.kill_ring })); ( "kill-append", None, "Append STRING to the latest kill (prepend it if BEFORE-P).", fun st args -> let s = string_of (List.hd args) in let before = match args with [ _; b ] -> b <> nil | _ -> false in let ring = match st.host.kill_ring with k :: rest -> (if before then s ^ k else k ^ s) :: rest | [] -> [ s ] in (nil, with_host st { st.host with kill_ring = ring }) ); ( "current-kill", None, "Rotate the kill ring N places, and return the latest kill.", fun st args -> match st.host.kill_ring with | [] -> error "Kill ring is empty" | ring -> let n = count args mod List.length ring in let ring = List.filteri (fun i _ -> i >= n) ring @ List.filteri (fun i _ -> i < n) ring in (Str (List.hd ring), with_host st { st.host with kill_ring = ring }) ); (* undo *) ( "undo", Some "", "Undo some previous changes; repeat to undo more. Anything else in between makes the undos undoable in turn.", fun st _ -> let b = buf st in let entries = match (st.vars |> List.assoc_opt "last-command", b.undoing) with Some (Sym "undo"), Some rest -> rest | _ -> (match b.undo with Boundary :: rest -> rest | u -> u) in if entries = [] then error "No further undo information" else let b, rest = undo_step b entries in (nil, message (with_buf st { b with undoing = Some rest }) "Undo") ); (* buffers and files *) ("buffer-name", None, "Return the name of the current buffer.", fun st _ -> (Str (buf st).name, st)); ("buffer-list", None, "Return the names of the buffers, the current one first.", fun st _ -> (list (List.map (fun (b : buffer) -> Str b.name) st.host.buffers), st)); ("buffer-file-name", None, "Return the name of the file the buffer visits, or nil.", fun st _ -> ((match (buf st).file with Some f -> Str f | None -> nil), st)); ("buffer-modified-p", None, "Return t if the buffer was modified since it was read or saved.", fun st _ -> (of_bool (buf st).modified, st)); ("set-buffer-modified-p", None, "Mark the buffer as modified or not, by FLAG.", fun st args -> let b = buf st in (nil, with_buf st { b with modified = List.hd args <> nil })); ( "switch-to-buffer", Some "BSwitch to buffer: ", "Make BUFFER-NAME current, creating it if there is none.", fun st args -> (nil, switch_to st (string_of (List.hd args))) ); ( "kill-buffer", Some "bKill buffer: ", "Kill the buffer BUFFER-NAME.", fun st args -> let name = string_of (List.hd args) in match List.filter (fun (b : buffer) -> b.name <> name) st.host.buffers with | [] -> error "Can't kill the only buffer" | rest -> (nil, with_host st { st.host with buffers = rest }) ); ("find-file", Some "FFind file: ", "Edit the file FILENAME, in a buffer of its own.", fun st args -> (nil, visit st (string_of (List.hd args)))); ( "save-buffer", Some "", "Save the current buffer in its file.", fun st _ -> let b = buf st in match b.file with | None -> error "Buffer is not visiting a file (C-x C-w: write-file)" | Some f -> if b.modified then (nil, write st f) else (nil, message st "(No changes need to be saved)") ); ("write-file", Some "FWrite file: ", "Write the buffer into the file FILENAME, which it visits from then on.", fun st args -> (nil, write st (string_of (List.hd args)))); ("directory-files", None, "Return the names of the files on the disk.", fun st _ -> (list (List.map (fun (f, _) -> Str f) (List.sort compare st.host.disk)), st)); ("kill-emacs", Some "", "Exit the Emacs job.", fun st _ -> (nil, with_host st { st.host with quit = true })); (* what only the command loop can do, asked of it *) ( "call-interactively", None, "Run COMMAND as if from a key: its arguments asked for as its interactive spec says.", fun st args -> (nil, with_host st { st.host with request = Some (Call_interactively (List.hd args)) }) ); ( "isearch-forward", Some "", "Search as you type: each character typed extends the search string; C-s finds the next one, RET or a command key stops, C-g goes back.", fun st _ -> (nil, with_host st { st.host with request = Some (Isearch true) }) ); ("isearch-backward", Some "", "Search backward as you type.", fun st _ -> (nil, with_host st { st.host with request = Some (Isearch false) })); ( "universal-argument", Some "", "Begin a numeric argument for the next command: 4, and 16 after C-u C-u.", fun st _ -> (nil, with_host st { st.host with request = Some Universal_argument }) ); (* the window and the echo area *) ( "message", None, "Show the formatted string in the echo area, and return it.", fun st args -> let s = match (List.assoc "format" st.subrs) st args with Str s, _ -> s | _ -> "" in (Str s, message st s) ); ( "recenter", Some "", "Scroll the window so that point's line is in its middle.", fun st _ -> let b = buf st in (nil, with_buf st { b with top = max 0 (line_of b b.point - (window_height / 2)) }) ); ( "scroll-up-command", Some "", "Scroll the text up a windowful, less two lines.", fun st _ -> let b = buf st in let top = b.top + window_height - 2 in let last = line_of b (Gap_buffer.length b.text) in if b.top >= last then signal "end-of-buffer" [] else (nil, with_buf st { b with top; point = max b.point (line_start b (min top last)) }) ); ( "scroll-down-command", Some "", "Scroll the text down a windowful, less two lines.", fun st _ -> let b = buf st in if b.top = 0 then signal "beginning-of-buffer" [] else let top = max 0 (b.top - window_height + 2) in let last_shown = line_start b (top + window_height - 1) in (nil, with_buf st { b with top; point = (if b.point >= last_shown then line_start b (top + window_height - 2) else b.point) }) ); (* Lisp over text *) ( "read", None, "Read one Lisp expression from STRING.", fun st args -> match Lisp_read.read (string_of (List.hd args)) 0 with | v, _ -> (v, st) | exception Lisp_read.Error msg -> signal (if msg = "end of input" then "end-of-file" else "invalid-read-syntax") [ Str msg ] ); ("key-description", None, "Return a pretty description of the key sequence KEYS: C-x C-f.", fun st args -> (Str (key_description (string_of (List.hd args))), st)) ] (* (save-excursion body...): body, then point and the current buffer put back, even after an error *) let save_excursion (st : lisp) (body : Lisp.t list) : Lisp.t * lisp = let b = buf st in let restore (st : lisp) : lisp = let st = match select b.name st.host with Some e -> with_host st e | None -> st in let now = buf st in if now.name = b.name then with_buf st { now with point = min b.point (Gap_buffer.length now.text) } else st in match Lisp_eval.protect st (fun st -> Lisp_eval.progn st body) with | Ok v, st -> (v, restore st) | Error e, st -> Lisp_eval.raise_error (restore st) e let scratch_text = ";; This buffer is for text that is not saved, and for Lisp evaluation.\n;; To create a file, visit it with C-x C-f and enter text in its buffer.\n\n" let create ~(disk : (string * string) list) ~(simple : string) : lisp = let e = { buffers = [ make_buffer "*scratch*" scratch_text ]; disk; kill_ring = []; message = ""; quit = false; request = None } in let st = Lisp_eval.create e in let st = List.fold_left (fun st (name, interactive, doc, f) -> Lisp_eval.define_subr ?interactive ~doc name f st) st primitives in let st = Lisp_eval.define_special "save-excursion" save_excursion st in let st = Lisp_eval.load st simple in let b = buf st in with_buf st { b with point = Gap_buffer.length b.text }
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>