perf(core): use fixed-size ring vector for key lossage

ober

c31f23803c40df7bab48a3c645b9f47f1cba540a

diff --git a/src/jerboa-emacs/core.ss b/src/jerboa-emacs/core.ss
index 47d80ec..a66b5c7 100644
--- a/src/jerboa-emacs/core.ss
+++ b/src/jerboa-emacs/core.ss
@@ -1409,9 +1409,9 @@
    #f                    ; key-handler
    '()                    ; winner-history
    0                     ; winner-history-idx
-   (list (list "Tab 1" '("*scratch*") 0)) ; tabs - initial tab
-   0                     ; current-tab-idx
-   '()))                  ; key-lossage
+    (list (list "Tab 1" '("*scratch*") 0)) ; tabs - initial tab
+    0                     ; current-tab-idx
+    (make-key-lossage-ring))) ; key-lossage
 
 (def (get-prefix-arg app (default 1))
   "Get the numeric value of the current prefix argument."
@@ -1448,28 +1448,42 @@
 
 (def *key-lossage-max* 300)
 
+;; Lossage ring: vector #(buf head count)
+;;   buf   — fixed vector of *key-lossage-max* slots (key string or #f)
+;;   head  — index of the next write slot
+;;   count — number of valid entries (0..*key-lossage-max*)
+;; Recording is O(1); no per-keystroke list copy.
+(def (make-key-lossage-ring)
+  (vector (make-vector *key-lossage-max* #f) 0 0))
+
 (def (key-lossage-record! app key-str)
   "Record a keystroke in the lossage ring."
-  (let ((lossage (app-state-key-lossage app)))
-    (set! (app-state-key-lossage app)
-      (if (>= (length lossage) *key-lossage-max*)
-        (cons key-str (list-head lossage (- *key-lossage-max* 1)))
-        (cons key-str lossage)))))
+  (let* ((ring (app-state-key-lossage app))
+         (buf (vector-ref ring 0))
+         (head (vector-ref ring 1)))
+    (vector-set! buf head key-str)
+    (vector-set! ring 1 (modulo (+ head 1) *key-lossage-max*))
+    (when (< (vector-ref ring 2) *key-lossage-max*)
+      (vector-set! ring 2 (+ (vector-ref ring 2) 1)))))
 
 (def (key-lossage->string app)
   "Format key lossage for display, 10 keys per line."
-  (let ((keys (reverse (app-state-key-lossage app))))
-    (if (null? keys)
+  (let* ((ring (app-state-key-lossage app))
+         (buf (vector-ref ring 0))
+         (head (vector-ref ring 1))
+         (count (vector-ref ring 2)))
+    (if (= count 0)
       "(no keystrokes recorded)"
-      (let loop ((ks keys) (col 0) (acc ""))
-        (if (null? ks)
+      (let loop ((i 0) (col 0) (acc ""))
+        (if (>= i count)
           acc
-          (let* ((k (car ks))
+          (let* ((idx (modulo (+ (- head count) i) *key-lossage-max*))
+                 (k (vector-ref buf idx))
                  (sep (if (and (> col 0) (= (modulo col 10) 0)) "\n" " "))
                  (new-acc (if (string=? acc "")
-                            k
-                            (string-append acc sep k))))
-            (loop (cdr ks) (+ col 1) new-acc)))))))
+                             k
+                             (string-append acc sep k))))
+            (loop (+ i 1) (+ col 1) new-acc)))))))
 
 (def (list-head lst n)
   "Return the first n elements of lst."