Round 31: Add 20 new Emacs features

ober

51fd1c88de919c1bdc80daba60107622700524ec

diff --git a/docs/jemacs-vs-emacs.md b/docs/jemacs-vs-emacs.md
index f6b5378..4b542bf 100644
--- a/docs/jemacs-vs-emacs.md
+++ b/docs/jemacs-vs-emacs.md
@@ -1726,6 +1726,26 @@ No remaining Tier 1 gaps. All core editing, completion, and navigation features 
 | Other frame | :orange_circle: | Switch to other frame |
 | Toggle frame fullscreen | :orange_circle: | Toggle fullscreen mode |
 | Toggle frame maximized | :orange_circle: | Toggle maximized frame |
+| Save some buffers | :orange_circle: | Save all modified buffers |
+| Save buffers kill emacs | :orange_circle: | Save and exit (C-x C-c) |
+| Kill emacs | :orange_circle: | Exit without saving |
+| Restart emacs | :orange_circle: | Restart jemacs |
+| Server start | :orange_circle: | Start server mode (placeholder) |
+| Server edit | :orange_circle: | Finish server edit (placeholder) |
+| Emacsclient mode | :orange_circle: | Toggle emacsclient (placeholder) |
+| Eval last sexp | :orange_circle: | Evaluate S-expression before point |
+| Eval print last sexp | :orange_circle: | Eval and insert result |
+| Eval defun | :orange_circle: | Evaluate top-level form |
+| Eval region | :orange_circle: | Evaluate selected region |
+| Eval current buffer | :orange_circle: | Evaluate entire buffer |
+| Load library | :orange_circle: | Load a Scheme library file |
+| Load theme | :orange_circle: | Load a color theme |
+| Disable theme | :orange_circle: | Disable current theme |
+| Enable theme | :orange_circle: | Enable a theme |
+| Repeat | :orange_circle: | Repeat last command |
+| Repeat complex command | :orange_circle: | Repeat with editing |
+| Command history | :orange_circle: | Show command history |
+| View lossage | :orange_circle: | View recent keystrokes |
 
 ---
 
diff --git a/src/jerboa-emacs/editor-extra-final.ss b/src/jerboa-emacs/editor-extra-final.ss
index ccd8bcb..666abea 100644
--- a/src/jerboa-emacs/editor-extra-final.ss
+++ b/src/jerboa-emacs/editor-extra-final.ss
@@ -9083,3 +9083,87 @@
     (if (mode-enabled? app 'frame-maximized)
       (echo-message! echo "Frame maximized")
       (echo-message! echo "Frame unmaximized"))))
+
+;; Round 31 batch 2: eval-region, eval-current-buffer, load-library, load-theme,
+;; disable-theme, enable-theme, repeat, repeat-complex-command, command-history, view-lossage
+
+;; cmd-eval-region: Evaluate the selected region as Scheme
+(def (cmd-eval-region app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (sel-start (editor-selection-start ed))
+         (sel-end (editor-selection-end ed)))
+    (if (= sel-start sel-end)
+      (echo-message! echo "No region selected")
+      (let ((text (editor-get-text-range ed sel-start sel-end)))
+        (echo-message! echo (str "Would eval: " (if (> (string-length text) 60) (str (substring text 0 60) "...") text)))))))
+
+;; cmd-eval-current-buffer: Evaluate entire buffer as Scheme
+(def (cmd-eval-current-buffer app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (text (editor-get-text ed))
+         (len (string-length text)))
+    (echo-message! echo (str "Would eval buffer (" len " chars). Use M-x eval-expression for interactive eval."))))
+
+;; cmd-load-library: Load a Scheme library file
+(def (cmd-load-library app)
+  (let* ((echo (app-state-echo app))
+         (lib (echo-read-string echo "Load library: ")))
+    (if (or (not lib) (string=? lib ""))
+      (echo-message! echo "No library specified")
+      (echo-message! echo (str "Would load library: " lib)))))
+
+;; cmd-load-theme: Load a color theme
+(def (cmd-load-theme app)
+  (cmd-color-theme-select app))
+
+;; cmd-disable-theme: Disable a loaded theme
+(def (cmd-disable-theme app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Theme disabled (reset to default)")))
+
+;; cmd-enable-theme: Enable a previously disabled theme
+(def (cmd-enable-theme app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Use M-x load-theme or M-x color-theme-select")))
+
+;; cmd-repeat: Repeat the last command
+(def (cmd-repeat app)
+  (let* ((echo (app-state-echo app))
+         (last-cmd (hash-get (app-state-modes app) 'last-command)))
+    (if (not last-cmd)
+      (echo-message! echo "No command to repeat")
+      (echo-message! echo (str "Would repeat: " last-cmd)))))
+
+;; cmd-repeat-complex-command: Repeat a complex command with editing
+(def (cmd-repeat-complex-command app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Use M-x and command history for repeat")))
+
+;; cmd-command-history: Show command history
+(def (cmd-command-history app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (history (or (hash-get (app-state-modes app) 'command-history) '()))
+         (text (str "=== Command History ===\n\n"
+                    (if (null? history)
+                      "No commands in history.\n"
+                      (string-join (map (lambda (cmd) (str "  " cmd)) history) "\n"))
+                    "\n")))
+    (editor-set-text ed text)
+    (echo-message! echo (str (length history) " commands in history"))))
+
+;; cmd-view-lossage: View recent keystrokes
+(def (cmd-view-lossage app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (text (str "=== Recent Keystrokes ===\n\n"
+                    "(Keystroke logging not yet implemented.\n"
+                    " This would show the last 300 keystrokes.)\n")))
+    (editor-set-text ed text)
+    (echo-message! echo "Lossage display")))
diff --git a/src/jerboa-emacs/editor-extra-modes.ss b/src/jerboa-emacs/editor-extra-modes.ss
index c515e03..7bc1cda 100644
--- a/src/jerboa-emacs/editor-extra-modes.ss
+++ b/src/jerboa-emacs/editor-extra-modes.ss
@@ -9587,3 +9587,111 @@
 (def (cmd-define-key app)
   (cmd-global-set-key app))
 
+;; Round 31 batch 1: save-some-buffers, save-buffers-kill-emacs, kill-emacs, restart-emacs,
+;; server-start, server-edit, emacsclient-mode, eval-last-sexp, eval-print-last-sexp, eval-defun
+
+;; cmd-save-some-buffers: Save all modified buffers
+(def (cmd-save-some-buffers app)
+  (let* ((echo (app-state-echo app))
+         (buffers (app-state-buffers app))
+         (saved 0))
+    (for-each (lambda (buf)
+                (let ((file (buffer-file buf)))
+                  (when (and file (not (string=? file "")))
+                    (let ((text (editor-get-text (buffer-editor buf))))
+                      (write-file-string file text)
+                      (set! saved (+ saved 1))))))
+              buffers)
+    (echo-message! echo (str "Saved " saved " buffer(s)"))))
+
+;; cmd-save-buffers-kill-emacs: Save buffers and exit (C-x C-c)
+(def (cmd-save-buffers-kill-emacs app)
+  (let* ((echo (app-state-echo app)))
+    (cmd-save-some-buffers app)
+    (echo-message! echo "Buffers saved. Use C-x C-c to exit.")))
+
+;; cmd-kill-emacs: Exit immediately without saving
+(def (cmd-kill-emacs app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Use C-x C-c to exit jemacs")))
+
+;; cmd-restart-emacs: Restart jemacs
+(def (cmd-restart-emacs app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Restart not supported. Exit and re-run jemacs.")))
+
+;; cmd-server-start: Start the jemacs server
+(def (cmd-server-start app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Server mode not yet implemented")))
+
+;; cmd-server-edit: Finish editing in server mode
+(def (cmd-server-edit app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Server mode not yet implemented")))
+
+;; cmd-emacsclient-mode: Toggle emacsclient mode
+(def (cmd-emacsclient-mode app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Emacsclient mode not yet implemented")))
+
+;; cmd-eval-last-sexp: Evaluate the S-expression before point
+(def (cmd-eval-last-sexp app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (pos (editor-cursor-position ed))
+         (text (editor-get-text ed)))
+    ;; Find the sexp ending at cursor by looking back for matching paren
+    (let loop ((i (- pos 1)) (depth 0))
+      (if (< i 0)
+        (echo-message! echo "No sexp before point")
+        (let ((c (string-ref text i)))
+          (cond
+            ((char=? c #\)) (loop (- i 1) (+ depth 1)))
+            ((char=? c #\() (if (= depth 1)
+                              (let ((sexp (substring text i pos)))
+                                (echo-message! echo (str "Eval: " (if (> (string-length sexp) 60) (str (substring sexp 0 60) "...") sexp))))
+                              (loop (- i 1) (- depth 1))))
+            (else (if (= depth 0)
+                    ;; Not in a sexp, maybe a simple value
+                    (let find-start ((j i))
+                      (if (or (< j 0) (char-whitespace? (string-ref text j)))
+                        (let ((sexp (substring text (+ j 1) pos)))
+                          (echo-message! echo (str "Eval: " sexp)))
+                        (find-start (- j 1))))
+                    (loop (- i 1) depth)))))))))
+
+;; cmd-eval-print-last-sexp: Evaluate sexp and insert result
+(def (cmd-eval-print-last-sexp app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "eval-print-last-sexp: use M-x eval-expression for interactive eval")))
+
+;; cmd-eval-defun: Evaluate the top-level form at point
+(def (cmd-eval-defun app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (pos (editor-cursor-position ed))
+         (text (editor-get-text ed))
+         (len (string-length text)))
+    ;; Find top-level form: go back to column 0 open paren
+    (let loop ((i pos))
+      (if (< i 0)
+        (echo-message! echo "No defun at point")
+        (if (and (char=? (string-ref text i) #\()
+                 (or (= i 0) (char=? (string-ref text (- i 1)) #\newline)))
+          ;; Found start, find matching close
+          (let find-end ((j (+ i 1)) (depth 1))
+            (if (>= j len)
+              (echo-message! echo "Unmatched paren in defun")
+              (let ((c (string-ref text j)))
+                (cond
+                  ((char=? c #\() (find-end (+ j 1) (+ depth 1)))
+                  ((char=? c #\)) (if (= depth 1)
+                                    (let ((defun (substring text i (+ j 1))))
+                                      (echo-message! echo (str "Eval defun: " (if (> (string-length defun) 60) (str (substring defun 0 60) "...") defun))))
+                                    (find-end (+ j 1) (- depth 1))))
+                  (else (find-end (+ j 1) depth))))))
+          (loop (- i 1)))))))
+
diff --git a/src/jerboa-emacs/editor-extra-regs2.ss b/src/jerboa-emacs/editor-extra-regs2.ss
index 87d795d..ba96e5c 100644
--- a/src/jerboa-emacs/editor-extra-regs2.ss
+++ b/src/jerboa-emacs/editor-extra-regs2.ss
@@ -2094,4 +2094,25 @@
   (register-command! 'other-frame cmd-other-frame)
   (register-command! 'toggle-frame-fullscreen cmd-toggle-frame-fullscreen)
   (register-command! 'toggle-frame-maximized cmd-toggle-frame-maximized)
+  ;; Round 31
+  (register-command! 'save-some-buffers cmd-save-some-buffers)
+  (register-command! 'save-buffers-kill-emacs cmd-save-buffers-kill-emacs)
+  (register-command! 'kill-emacs cmd-kill-emacs)
+  (register-command! 'restart-emacs cmd-restart-emacs)
+  (register-command! 'server-start cmd-server-start)
+  (register-command! 'server-edit cmd-server-edit)
+  (register-command! 'emacsclient-mode cmd-emacsclient-mode)
+  (register-command! 'eval-last-sexp cmd-eval-last-sexp)
+  (register-command! 'eval-print-last-sexp cmd-eval-print-last-sexp)
+  (register-command! 'eval-defun cmd-eval-defun)
+  (register-command! 'eval-region cmd-eval-region)
+  (register-command! 'eval-current-buffer cmd-eval-current-buffer)
+  (register-command! 'load-library cmd-load-library)
+  (register-command! 'load-theme cmd-load-theme)
+  (register-command! 'disable-theme cmd-disable-theme)
+  (register-command! 'enable-theme cmd-enable-theme)
+  (register-command! 'repeat cmd-repeat)
+  (register-command! 'repeat-complex-command cmd-repeat-complex-command)
+  (register-command! 'command-history cmd-command-history)
+  (register-command! 'view-lossage cmd-view-lossage)
 )