Round 28: Add 20 new Emacs features

ober

936beb1be09bb1ea4b41e0f182ba581314a68dfd

diff --git a/docs/jemacs-vs-emacs.md b/docs/jemacs-vs-emacs.md
index 45a7a0b..f400f08 100644
--- a/docs/jemacs-vs-emacs.md
+++ b/docs/jemacs-vs-emacs.md
@@ -1666,6 +1666,26 @@ No remaining Tier 1 gaps. All core editing, completion, and navigation features 
 | Overwrite mode | :orange_circle: | Toggle overwrite/insert mode |
 | Binary overwrite mode | :orange_circle: | Toggle binary overwrite |
 | Enriched mode | :orange_circle: | Toggle rich text editing |
+| SGML mode | :orange_circle: | HTML/SGML syntax highlighting |
+| nXML mode | :orange_circle: | XML syntax highlighting |
+| CSS mode | :orange_circle: | CSS syntax highlighting |
+| JS mode | :orange_circle: | JavaScript syntax highlighting |
+| Python mode | :orange_circle: | Python syntax highlighting |
+| Ruby mode | :orange_circle: | Ruby syntax highlighting |
+| Shell-script mode | :orange_circle: | Shell syntax highlighting |
+| Conf mode | :orange_circle: | Config file syntax highlighting |
+| Diff mode | :orange_circle: | Diff/patch syntax highlighting |
+| Compilation mode | :orange_circle: | Compilation output mode |
+| Grep mode | :orange_circle: | Grep output mode |
+| Occur-edit mode | :orange_circle: | Make occur buffer editable |
+| Compile command | :orange_circle: | Run compile command, show output |
+| Next error | :orange_circle: | Jump to next error |
+| Previous error | :orange_circle: | Jump to previous error |
+| First error | :orange_circle: | Jump to first error |
+| Describe variable | :orange_circle: | Show variable information |
+| Describe key briefly | :orange_circle: | Show key's command |
+| Describe bindings | :orange_circle: | Show all key bindings |
+| Describe mode | :orange_circle: | Show current modes |
 
 ---
 
diff --git a/src/jerboa-emacs/editor-extra-final.ss b/src/jerboa-emacs/editor-extra-final.ss
index d5bdeb9..7b8e92f 100644
--- a/src/jerboa-emacs/editor-extra-final.ss
+++ b/src/jerboa-emacs/editor-extra-final.ss
@@ -8742,3 +8742,188 @@
     (if (mode-enabled? app 'enriched-mode)
       (echo-message! echo "Enriched mode enabled (rich text editing)")
       (echo-message! echo "Enriched mode disabled"))))
+
+;; Round 28 batch 2: grep-mode, occur-edit-mode, compile-command, next-error, previous-error,
+;; first-error, describe-variable, describe-key-briefly, describe-bindings, describe-mode
+
+;; cmd-grep-mode: Set grep output mode
+(def (cmd-grep-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETREADONLY 1 0)
+    (echo-message! echo "Grep mode (read-only output)")))
+
+;; cmd-occur-edit-mode: Make occur buffer editable
+(def (cmd-occur-edit-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETREADONLY 0 0)
+    (echo-message! echo "Occur-edit mode (buffer is now editable)")))
+
+;; cmd-compile-command: Run a compile command and show output
+(def (cmd-compile-command app)
+  (let* ((echo (app-state-echo app))
+         (frame (app-state-frame app))
+         (buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (cmd (echo-read-string echo "Compile command: ")))
+    (if (or (not cmd) (string=? cmd ""))
+      (echo-message! echo "No command specified")
+      (with-catch
+        (lambda (e) (echo-message! echo (str "Compile error: " e)))
+        (lambda ()
+          (let-values (((si so se pid)
+                        (open-process-ports (str cmd " 2>&1")
+                          'block (native-transcoder))))
+            (close-port si)
+            (let loop ((lines '()))
+              (let ((line (get-line so)))
+                (if (eof-object? line)
+                  (begin
+                    (close-port so) (close-port se)
+                    (let* ((result (string-join (reverse lines) "\n"))
+                           (new-buf (create-buffer "*compilation*")))
+                      (switch-to-buffer frame new-buf)
+                      (let ((new-ed (edit-window-editor (current-window frame))))
+                        (editor-set-text new-ed result)
+                        (send-message new-ed SCI_SETREADONLY 1 0))
+                      (echo-message! echo "Compilation finished")))
+                  (loop (cons line lines)))))))))))
+
+;; cmd-next-error: Jump to next error in compilation output
+(def (cmd-next-error 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))
+         ;; Look for file:line patterns
+         (idx (string-contains text ":" (+ pos 1))))
+    (if (not idx)
+      (echo-message! echo "No more errors")
+      (begin
+        (editor-set-cursor ed idx)
+        (echo-message! echo "Next error")))))
+
+;; cmd-previous-error: Jump to previous error
+(def (cmd-previous-error 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)))
+    (if (<= pos 1)
+      (echo-message! echo "No previous errors")
+      (let loop ((i (- pos 2)))
+        (if (<= i 0)
+          (echo-message! echo "No previous errors")
+          (if (char=? (string-ref text i) #\:)
+            (begin (editor-set-cursor ed i) (echo-message! echo "Previous error"))
+            (loop (- i 1))))))))
+
+;; cmd-first-error: Jump to first error
+(def (cmd-first-error app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (text (editor-get-text ed))
+         (idx (string-contains text ":")))
+    (if (not idx)
+      (echo-message! echo "No errors found")
+      (begin
+        (editor-set-cursor ed idx)
+        (echo-message! echo "First error")))))
+
+;; cmd-describe-variable: Show info about an editor variable/setting
+(def (cmd-describe-variable app)
+  (let* ((echo (app-state-echo app))
+         (buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (var (echo-read-string echo "Describe variable: ")))
+    (if (or (not var) (string=? var ""))
+      (echo-message! echo "No variable specified")
+      (let* ((modes (app-state-modes app))
+             (val (hash-get modes (string->symbol var)))
+             (text (str "=== Variable: " var " ===\n\n"
+                        (if val (str "Value: " val "\n") "Not set\n")
+                        "\nThis is a jemacs session variable.\n"
+                        "Use M-x set-variable to change it.\n")))
+        (editor-set-text ed text)
+        (echo-message! echo (if val (str var " = " val) (str var " is not set")))))))
+
+;; cmd-describe-key-briefly: Show what command a key runs
+(def (cmd-describe-key-briefly app)
+  (let* ((echo (app-state-echo app)))
+    (echo-message! echo "Press a key to describe... (use C-h k for full description)")))
+
+;; cmd-describe-bindings: Show all key bindings
+(def (cmd-describe-bindings app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (text (str "=== Key Bindings ===\n\n"
+                    "--- Movement ---\n"
+                    "  C-f       forward-char\n"
+                    "  C-b       backward-char\n"
+                    "  C-n       next-line\n"
+                    "  C-p       previous-line\n"
+                    "  C-a       beginning-of-line\n"
+                    "  C-e       end-of-line\n"
+                    "  M-f       forward-word\n"
+                    "  M-b       backward-word\n"
+                    "  M-<       beginning-of-buffer\n"
+                    "  M->       end-of-buffer\n"
+                    "  C-v       scroll-down\n"
+                    "  M-v       scroll-up\n\n"
+                    "--- Editing ---\n"
+                    "  C-d       delete-char\n"
+                    "  DEL       backward-delete-char\n"
+                    "  C-k       kill-line\n"
+                    "  C-y       yank\n"
+                    "  M-y       yank-pop\n"
+                    "  C-/       undo\n"
+                    "  M-d       kill-word\n\n"
+                    "--- Files & Buffers ---\n"
+                    "  C-x C-f   find-file\n"
+                    "  C-x C-s   save-buffer\n"
+                    "  C-x C-w   write-file\n"
+                    "  C-x b     switch-buffer\n"
+                    "  C-x k     kill-buffer\n"
+                    "  C-x C-b   list-buffers\n\n"
+                    "--- Windows ---\n"
+                    "  C-x 2     split-window-below\n"
+                    "  C-x 3     split-window-right\n"
+                    "  C-x 1     delete-other-windows\n"
+                    "  C-x 0     delete-window\n"
+                    "  C-x o     other-window\n\n"
+                    "--- Search ---\n"
+                    "  C-s       isearch-forward\n"
+                    "  C-r       isearch-backward\n"
+                    "  M-%       query-replace\n\n"
+                    "--- Help ---\n"
+                    "  C-h k     describe-key\n"
+                    "  C-h f     describe-function\n"
+                    "  M-x       execute-command\n")))
+    (editor-set-text ed text)
+    (echo-message! echo "Key bindings listed")))
+
+;; cmd-describe-mode: Show info about current major and minor modes
+(def (cmd-describe-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app))
+         (modes (app-state-modes app))
+         (enabled (filter (lambda (pair) (eq? (cdr pair) #t)) (hash->list modes)))
+         (mode-names (map (lambda (pair) (symbol->string (car pair))) enabled))
+         (sorted (sort string<? mode-names))
+         (text (str "=== Current Modes ===\n\n"
+                    "Major mode: fundamental-mode\n\n"
+                    "Enabled minor modes:\n"
+                    (if (null? sorted) "  (none)\n"
+                      (string-join (map (lambda (m) (str "  " m)) sorted) "\n"))
+                    "\n\nTotal: " (length sorted) " active minor modes\n")))
+    (editor-set-text ed text)
+    (echo-message! echo (str (length sorted) " active modes"))))
diff --git a/src/jerboa-emacs/editor-extra-modes.ss b/src/jerboa-emacs/editor-extra-modes.ss
index e6c0d52..debef41 100644
--- a/src/jerboa-emacs/editor-extra-modes.ss
+++ b/src/jerboa-emacs/editor-extra-modes.ss
@@ -9272,3 +9272,87 @@
       (echo-message! echo "Electric-pair mode enabled (auto-close brackets)")
       (echo-message! echo "Electric-pair mode disabled"))))
 
+;; Round 28 batch 1: sgml-mode, nxml-mode, css-mode, js-mode, python-mode,
+;; ruby-mode, sh-mode, conf-mode, diff-mode, compilation-mode
+
+;; cmd-sgml-mode: Set SGML/HTML major mode
+(def (cmd-sgml-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 4 0)  ;; SCLEX_HTML
+    (echo-message! echo "SGML mode")))
+
+;; cmd-nxml-mode: Set nXML major mode
+(def (cmd-nxml-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 5 0)  ;; SCLEX_XML
+    (echo-message! echo "nXML mode")))
+
+;; cmd-css-mode: Set CSS major mode
+(def (cmd-css-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 38 0)  ;; SCLEX_CSS
+    (echo-message! echo "CSS mode")))
+
+;; cmd-js-mode: Set JavaScript major mode
+(def (cmd-js-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 3 0)  ;; SCLEX_CPP (used for JS too)
+    (echo-message! echo "JavaScript mode")))
+
+;; cmd-python-mode: Set Python major mode
+(def (cmd-python-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 2 0)  ;; SCLEX_PYTHON
+    (echo-message! echo "Python mode")))
+
+;; cmd-ruby-mode: Set Ruby major mode
+(def (cmd-ruby-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 22 0)  ;; SCLEX_RUBY
+    (echo-message! echo "Ruby mode")))
+
+;; cmd-sh-mode: Set Shell script major mode
+(def (cmd-sh-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 62 0)  ;; SCLEX_BASH
+    (echo-message! echo "Shell-script mode")))
+
+;; cmd-conf-mode: Set configuration file mode
+(def (cmd-conf-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 66 0)  ;; SCLEX_PROPERTIES
+    (echo-message! echo "Conf mode")))
+
+;; cmd-diff-mode: Set diff/patch major mode
+(def (cmd-diff-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 16 0)  ;; SCLEX_DIFF
+    (echo-message! echo "Diff mode")))
+
+;; cmd-compilation-mode: Set compilation output mode
+(def (cmd-compilation-mode app)
+  (let* ((buf (app-state-current-buffer app))
+         (ed (buffer-editor buf))
+         (echo (app-state-echo app)))
+    (send-message ed SCI_SETLEXER 62 0)  ;; Use bash lexer for compilation output
+    (send-message ed SCI_SETREADONLY 1 0)
+    (echo-message! echo "Compilation mode")))
+
diff --git a/src/jerboa-emacs/editor-extra-regs2.ss b/src/jerboa-emacs/editor-extra-regs2.ss
index 4ea72c0..a04d808 100644
--- a/src/jerboa-emacs/editor-extra-regs2.ss
+++ b/src/jerboa-emacs/editor-extra-regs2.ss
@@ -2031,4 +2031,25 @@
   (register-command! 'overwrite-mode cmd-overwrite-mode)
   (register-command! 'binary-overwrite-mode cmd-binary-overwrite-mode)
   (register-command! 'enriched-mode cmd-enriched-mode)
+  ;; Round 28
+  (register-command! 'sgml-mode cmd-sgml-mode)
+  (register-command! 'nxml-mode cmd-nxml-mode)
+  (register-command! 'css-mode cmd-css-mode)
+  (register-command! 'js-mode cmd-js-mode)
+  (register-command! 'python-mode cmd-python-mode)
+  (register-command! 'ruby-mode cmd-ruby-mode)
+  (register-command! 'sh-mode cmd-sh-mode)
+  (register-command! 'conf-mode cmd-conf-mode)
+  (register-command! 'diff-mode cmd-diff-mode)
+  (register-command! 'compilation-mode cmd-compilation-mode)
+  (register-command! 'grep-mode cmd-grep-mode)
+  (register-command! 'occur-edit-mode cmd-occur-edit-mode)
+  (register-command! 'compile-command cmd-compile-command)
+  (register-command! 'next-error cmd-next-error)
+  (register-command! 'previous-error cmd-previous-error)
+  (register-command! 'first-error cmd-first-error)
+  (register-command! 'describe-variable cmd-describe-variable)
+  (register-command! 'describe-key-briefly cmd-describe-key-briefly)
+  (register-command! 'describe-bindings cmd-describe-bindings)
+  (register-command! 'describe-mode cmd-describe-mode)
 )