From 2c32b3bce394ec1a0e2266b8b5601703a8eff9a6 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 21 Jun 2026 13:28:14 -0500 Subject: [PATCH 01/13] add a variable for overriding the describe output medium --- src/commands/help.lisp | 51 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index e67b41cad..71cbea477 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -12,15 +12,44 @@ (define-key *global-keymap* "C-x ?" 'describe-key) +(declaim (type (member nil :buffer :popup :message) *describe-output-override*)) +(defvar *describe-output-type-override* nil + "When non nil, describe commands will always + send their information to this type of output") + +(defmacro with-describe-output-stream + ((var requested-output-type &optional (buffer-name "*Description*")) &body body) + (alexandria:with-gensyms (output-buffer) + `(ecase (or *describe-output-type-override* ,requested-output-type) + ((:message) + (let ((,var (make-string-output-stream))) + (unwind-protect + (progn + ,@body) + (show-message (get-output-stream-string ,var))))) + ((:popup) + (with-pop-up-typeout-window (,var (make-buffer ,buffer-name) :erase t) + ,@body)) + ((:buffer) + (let ((,var (make-string-output-stream))) + (unwind-protect + (progn + ,@body) + (let ((,output-buffer (lem:make-buffer ,buffer-name))) + (erase-buffer ,output-buffer) + (insert-string (buffer-point ,output-buffer) (get-output-stream-string ,var)) + (pop-to-buffer ,output-buffer)))))))) + (define-command describe-key () () "Tell what is the command associated to a keybinding." (show-message "describe-key: ") (redraw-display) (let* ((kseq (read-key-sequence)) (cmd (find-keybind kseq))) - (show-message (format nil "describe-key: ~a ~(~a~)" - (keyseq-to-string kseq) - cmd)))) + (with-describe-output-stream (s :message) + (format s "describe-key: ~a ~(~a~)" + (keyseq-to-string kseq) + cmd)))) (defun describe-bindings-internal (s name keymap &optional first-p) (unless first-p @@ -45,7 +74,7 @@ "Describe the bindings of the buffer's current major mode." (let ((buffer (current-buffer)) (firstp t)) - (with-pop-up-typeout-window (s (make-buffer "*bindings*") :erase t) + (with-describe-output-stream (s :popup "*bindings*") (describe-bindings-internal s (mode-name (buffer-major-mode buffer)) (setf firstp (mode-keymap (buffer-major-mode buffer))) @@ -65,7 +94,7 @@ (define-command list-modes () () "Output all available major and minor modes." - (with-pop-up-typeout-window (s (make-buffer "*all-modes*") :erase t) + (with-describe-output-stream (s :popup "*all-modes*") (let ((major-modes (major-modes)) (minor-modes (minor-modes))) (labels ((is-technical-mode (mode) @@ -84,6 +113,9 @@ (print-modes "Major modes" major-modes) (print-modes "Minor modes" minor-modes))))) +(define-command describe-all-modes () () + (lem:call-command 'list-modes nil)) + (define-command describe-mode () () "Show information about current major mode and enabled minor modes." (let* ((buffer (current-buffer)) @@ -93,7 +125,7 @@ :when (and (mode-active-p buffer mode) (not (find mode minor-modes))) :collect mode))) - (with-pop-up-typeout-window (s (make-buffer "*modes*") :erase t) + (with-describe-output-stream (s :popup "*modes*") (format s "Major mode is: ~A~@[ – ~A~]~%" (mode-name major-mode) (mode-description major-mode)) @@ -116,7 +148,7 @@ "Apropos: " :completion-function (lambda (str) (completion str (all-command-names)))))) - (with-pop-up-typeout-window (out (make-buffer "*Apropos*") :erase t) + (with-describe-output-stream (out :popup "*Apropos*") (dolist (name (all-command-names)) (when (search str name) (describe (command-name (find-command name)) out)))))) @@ -124,11 +156,12 @@ (define-command lem-version () () "Display Lem's version." (let ((version (get-version-string))) - (show-message (princ-to-string version)))) + (with-describe-output-stream (s :message "*Version*") + (format s "~a" version)))) (define-command help () () "Show some help." - (with-pop-up-typeout-window (s (make-buffer "*Help*") :erase t) + (with-describe-output-stream (s :popup "*Help*") (format s "Welcome to Lem.~&") (format s "You are running ~a.~&" (get-version-string)) (format s "~%") From 2271ab944058a31d9ad6e53266c46d82df4b65cf Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 21 Jun 2026 13:34:13 -0500 Subject: [PATCH 02/13] minor fixes --- src/commands/help.lisp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index 71cbea477..e959be7ef 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -35,7 +35,7 @@ (unwind-protect (progn ,@body) - (let ((,output-buffer (lem:make-buffer ,buffer-name))) + (let ((,output-buffer (make-buffer ,buffer-name))) (erase-buffer ,output-buffer) (insert-string (buffer-point ,output-buffer) (get-output-stream-string ,var)) (pop-to-buffer ,output-buffer)))))))) @@ -114,7 +114,8 @@ (print-modes "Minor modes" minor-modes))))) (define-command describe-all-modes () () - (lem:call-command 'list-modes nil)) + "Alias for list-modes" + (call-command 'list-modes nil)) (define-command describe-mode () () "Show information about current major mode and enabled minor modes." @@ -153,6 +154,10 @@ (when (search str name) (describe (command-name (find-command name)) out)))))) +(define-command describe-command () () + "Alias for apropos-command" + (call-command 'apropos-command nil)) + (define-command lem-version () () "Display Lem's version." (let ((version (get-version-string))) From fad63fb0d3ab2dc4c2ec41df628d1b1f38601247 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 21 Jun 2026 14:45:31 -0500 Subject: [PATCH 03/13] get basic bindings working --- .../emacs-help-mode/lem-emacs-help-mode.asd | 10 ++++ extensions/emacs-help-mode/main.lisp | 47 +++++++++++++++++++ lem.asd | 3 +- src/internal-packages.lisp | 1 + src/key.lisp | 11 +++++ src/keymap.lisp | 3 +- 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 extensions/emacs-help-mode/lem-emacs-help-mode.asd create mode 100644 extensions/emacs-help-mode/main.lisp diff --git a/extensions/emacs-help-mode/lem-emacs-help-mode.asd b/extensions/emacs-help-mode/lem-emacs-help-mode.asd new file mode 100644 index 000000000..662fb153a --- /dev/null +++ b/extensions/emacs-help-mode/lem-emacs-help-mode.asd @@ -0,0 +1,10 @@ +(in-package #:asdf-user) + +(defsystem "lem-emacs-help-mode" + :description "Emacs Style Help Keybindings for Lem" + :author "Robert Wess Burnett" + :license "MIT" + :depends-on ("lem/core") + :serial t + :components ((:file "main"))) + diff --git a/extensions/emacs-help-mode/main.lisp b/extensions/emacs-help-mode/main.lisp new file mode 100644 index 000000000..20cd00898 --- /dev/null +++ b/extensions/emacs-help-mode/main.lisp @@ -0,0 +1,47 @@ +(defpackage #:lem-emacs-help-mode + (:use #:cl #:lem)) +(in-package #:lem-emacs-help-mode) + +(defvar *previous-ctrl-h-suffix* nil) +(defvar *ctrl-h-keymap* (make-keymap)) + +(define-key *ctrl-h-keymap* "k" 'describe-key) +(define-key *ctrl-h-keymap* "b" 'describe-bindings) +(define-key *ctrl-h-keymap* "m" 'describe-mode) +(define-key *ctrl-h-keymap* "a" 'apropos-command) + +;; TODO add describe-variable command for "v" and +;; add describe-function command for "f" + +(defun enable (&optional recursed-p) + "Enables emacs help mode" + (let* ((prefixes (keymap-prefixes *global-keymap*)) + (ctrl-h (first (parse-keyspec "C-h"))) + (index (position ctrl-h prefixes :key #'prefix-key :test #'key-equal))) + + (cond + (index + (setf *previous-ctrl-h-suffix* (prefix-suffix (nth index prefixes))) + (setf (prefix-suffix (nth index prefixes)) *ctrl-h-keymap*)) + (recursed-p + (error "Infinite loop")) + (t + (push (make-prefix :key ctrl-h) (keymap-prefixes *global-keymap*)) + (enable t))))) + +(defun disable () + "Disables emacs help mode" + (let* ((prefixes (keymap-prefixes *global-keymap*)) + (ctrl-h (first (parse-keyspec "C-h"))) + (index (position ctrl-h prefixes :key #'prefix-key + :test #'lem-core:key-equal))) + (assert index) + (setf (prefix-suffix (nth index prefixes)) *previous-ctrl-h-suffix*) + (setf *previous-ctrl-h-suffix* nil))) + +(define-minor-mode emacs-help-mode + (:name "EHelp" + :description "Adds Emacs Style C-h Bindings." + :global t + :enable-hook 'enable + :disable-hook 'disable)) diff --git a/lem.asd b/lem.asd index 1ddff52c0..e187505f4 100644 --- a/lem.asd +++ b/lem.asd @@ -303,7 +303,8 @@ "lem-living-canvas" "lem-tree-sitter" "lem-git-gutter" - "lem-skk-mode")) + "lem-skk-mode" + "lem-emacs-help-mode")) (defsystem "lem" :version "2.3.0" diff --git a/src/internal-packages.lisp b/src/internal-packages.lisp index 6dfa60cc2..24c1063bb 100644 --- a/src/internal-packages.lisp +++ b/src/internal-packages.lisp @@ -99,6 +99,7 @@ :key-shift :key-sym :match-key + :key-equal :insertion-key-sym-p :named-key-sym-p :define-named-key diff --git a/src/key.lisp b/src/key.lisp index b32dfcf0b..5526c7b08 100644 --- a/src/key.lisp +++ b/src/key.lisp @@ -86,6 +86,17 @@ (eq (key-shift key) shift) (equal (key-sym key) sym))) +(defun key-equal (key-one key-two) + "Returns t if key-one == key-two, nil otherwise" + (match-key key-one + :ctrl (key-ctrl key-two) + :meta (key-meta key-two) + :super (key-super key-two) + :hyper (key-hyper key-two) + :shift (key-shift key-two) + :sym (key-sym key-two))) + + (defun insertion-key-sym-p (sym) (= 1 (length sym))) diff --git a/src/keymap.lisp b/src/keymap.lisp index 0e16e2706..647712e54 100644 --- a/src/keymap.lisp +++ b/src/keymap.lisp @@ -639,4 +639,5 @@ for KEYMAP, populating and reusing the keymap's binding-cache slot." (defmacro with-special-keymap ((keymap) &body body) `(let ((*special-keymap* (or ,keymap *special-keymap*))) - ,@body)) \ No newline at end of file + ,@body)) + From ee5f8d85c267ae05c93296bd05d50244395d879d Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 21 Jun 2026 14:54:05 -0500 Subject: [PATCH 04/13] change the describe output type when using emacs help mode --- extensions/emacs-help-mode/main.lisp | 11 +++++++++-- src/commands/help.lisp | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions/emacs-help-mode/main.lisp b/extensions/emacs-help-mode/main.lisp index 20cd00898..59a1e39de 100644 --- a/extensions/emacs-help-mode/main.lisp +++ b/extensions/emacs-help-mode/main.lisp @@ -3,6 +3,7 @@ (in-package #:lem-emacs-help-mode) (defvar *previous-ctrl-h-suffix* nil) +(defvar *previous-describe-output-override* nil) (defvar *ctrl-h-keymap* (make-keymap)) (define-key *ctrl-h-keymap* "k" 'describe-key) @@ -22,7 +23,10 @@ (cond (index (setf *previous-ctrl-h-suffix* (prefix-suffix (nth index prefixes))) - (setf (prefix-suffix (nth index prefixes)) *ctrl-h-keymap*)) + (setf (prefix-suffix (nth index prefixes)) *ctrl-h-keymap*) + (setf *previous-describe-output-override* + lem-core/commands/help:*describe-output-type-override*) + (setf lem-core/commands/help:*describe-output-type-override* :buffer)) (recursed-p (error "Infinite loop")) (t @@ -37,7 +41,10 @@ :test #'lem-core:key-equal))) (assert index) (setf (prefix-suffix (nth index prefixes)) *previous-ctrl-h-suffix*) - (setf *previous-ctrl-h-suffix* nil))) + (setf *previous-ctrl-h-suffix* nil) + (setf lem-core/commands/help:*describe-output-type-override* + *previous-describe-output-override*) + (setf *previous-describe-output-override* nil))) (define-minor-mode emacs-help-mode (:name "EHelp" diff --git a/src/commands/help.lisp b/src/commands/help.lisp index e959be7ef..e39142434 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -5,14 +5,16 @@ :describe-mode :apropos-command :lem-version - :list-modes) + :list-modes + :*describe-output-type-override*) #+sbcl (:lock t)) (in-package :lem-core/commands/help) (define-key *global-keymap* "C-x ?" 'describe-key) -(declaim (type (member nil :buffer :popup :message) *describe-output-override*)) +(declaim (type (member nil :buffer :popup :message) + *describe-output-type-override*)) (defvar *describe-output-type-override* nil "When non nil, describe commands will always send their information to this type of output") From 42d7d6740e469917ee124db7278be3eb444b8da9 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 21 Jun 2026 21:06:50 -0500 Subject: [PATCH 05/13] add an initial implementation of apropos-variable (describe variable) --- extensions/emacs-help-mode/main.lisp | 4 +- src/commands/help.lisp | 58 +++++++++++++++++++++++++++- src/common/var.lisp | 1 + 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/extensions/emacs-help-mode/main.lisp b/extensions/emacs-help-mode/main.lisp index 59a1e39de..e383f5086 100644 --- a/extensions/emacs-help-mode/main.lisp +++ b/extensions/emacs-help-mode/main.lisp @@ -10,9 +10,9 @@ (define-key *ctrl-h-keymap* "b" 'describe-bindings) (define-key *ctrl-h-keymap* "m" 'describe-mode) (define-key *ctrl-h-keymap* "a" 'apropos-command) +(define-key *ctrl-h-keymap* "v" 'apropos-variable) -;; TODO add describe-variable command for "v" and -;; add describe-function command for "f" +;; TODO add describe-function command for "f" (defun enable (&optional recursed-p) "Enables emacs help mode" diff --git a/src/commands/help.lisp b/src/commands/help.lisp index e39142434..0fded61cd 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -4,6 +4,9 @@ :describe-bindings :describe-mode :apropos-command + :describe-command + :apropos-variable + :describe-variable :lem-version :list-modes :*describe-output-type-override*) @@ -20,7 +23,8 @@ send their information to this type of output") (defmacro with-describe-output-stream - ((var requested-output-type &optional (buffer-name "*Description*")) &body body) + ((var requested-output-type &optional (buffer-name "*Description*")) + &body body) (alexandria:with-gensyms (output-buffer) `(ecase (or *describe-output-type-override* ,requested-output-type) ((:message) @@ -146,7 +150,7 @@ (mode-description mode))))))) (define-command apropos-command () () - "Find all symbols in the running Lisp image whose names match a given string." + "Find all commands in the running Lisp image whose names match a given string." (let ((str (prompt-for-string "Apropos: " :completion-function @@ -160,6 +164,56 @@ "Alias for apropos-command" (call-command 'apropos-command nil)) +(defun lem-symbol-p (symbol) + (let* ((pkg (symbol-package symbol)) + (name (package-name pkg))) + (string-equal "LEM" (subseq name 0 3)))) + +(defun is-variable-p (symbol) + "Returns true if the symbol is a lem variable or an editor variable" + (or (lem/common/var:editor-variable-p (get symbol 'editor-variable)) + (and (lem-symbol-p symbol) + (boundp symbol) + (not (constantp symbol))))) + +(defparameter *all-variables-cache* nil + "Cached list of all strings that represent variables") +(defun all-variables () + (unless *all-variables-cache* + (loop + :for pkg :in (list-all-packages) + :appending + (loop + :for sym :being :the external-symbols :of (find-package pkg) + :when (is-variable-p sym) + :collect (string-downcase + (format nil "~a:~a" + (package-name (find-package pkg)) + (symbol-name sym)))) + :into syms + :finally (setf *all-variables-cache* syms))) + *all-variables-cache*) + +(define-command apropos-variable () () + (let* ((str (prompt-for-string + "Enter Variable: " + :completion-function + (lambda (str) (completion str (all-variables)))))) + (with-describe-output-stream (out :popup "*variable-description*") + ;; TODO get a better description than just describe + (let* ((sym (read-from-string str)) + (editor-variable (get sym 'editor-variable))) + (cond + ((lem/common/var:editor-variable-p editor-variable) + (format + out + "~a is an EDITOR VARIABLE bound to the following value: ~%~%" sym) + (describe editor-variable out)) + ((is-variable-p sym) + (describe sym out)) + (t + (format out "~a does not describe any variable" sym))))))) + (define-command lem-version () () "Display Lem's version." (let ((version (get-version-string))) diff --git a/src/common/var.lisp b/src/common/var.lisp index a237f3a3c..e9a0932fe 100644 --- a/src/common/var.lisp +++ b/src/common/var.lisp @@ -11,6 +11,7 @@ :variable-documentation :find-editor-variable :editor-variables + :editor-variable-p :with-global-variable-value)) (in-package :lem/common/var) From d87a5f287276110bc9e6614f152fddc9aba4a585 Mon Sep 17 00:00:00 2001 From: Robert Wess Burnett Date: Mon, 22 Jun 2026 10:15:37 -0500 Subject: [PATCH 06/13] clean up some docs and exports --- src/commands/help.lisp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index 0fded61cd..da7c6ceb3 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -9,6 +9,7 @@ :describe-variable :lem-version :list-modes + :describe-all-modes :*describe-output-type-override*) #+sbcl (:lock t)) @@ -214,6 +215,10 @@ (t (format out "~a does not describe any variable" sym))))))) +(define-command describe-variable () () + "Alias for apropos-variable" + (call-command 'apropos-variable nil)) + (define-command lem-version () () "Display Lem's version." (let ((version (get-version-string))) From 126c549ccd25b8d7b4160c17acebbc40560bee6f Mon Sep 17 00:00:00 2001 From: Robert Wess Burnett Date: Mon, 22 Jun 2026 10:27:18 -0500 Subject: [PATCH 07/13] refactor code to use the call-with-* pattern --- src/commands/help.lisp | 48 ++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index da7c6ceb3..beb5054d4 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -23,29 +23,37 @@ "When non nil, describe commands will always send their information to this type of output") +(defun call-with-describe-output-stream (requested-output-type buffer-name function) + (ecase (or *describe-output-type-override* requested-output-type) + ((:message) + (let ((stream (make-string-output-stream))) + (unwind-protect + (funcall function stream) + (show-message (get-output-stream-string stream))))) + ((:popup) + (with-pop-up-typeout-window (stream (make-buffer buffer-name) :erase t) + (funcall function stream))) + ((:buffer) + (let ((stream (make-string-output-stream))) + (unwind-protect + (funcall function stream) + (let ((output-buffer (make-buffer buffer-name))) + (erase-buffer output-buffer) + (insert-string (buffer-point output-buffer) (get-output-stream-string stream)) + (pop-to-buffer output-buffer))))))) + (defmacro with-describe-output-stream ((var requested-output-type &optional (buffer-name "*Description*")) &body body) - (alexandria:with-gensyms (output-buffer) - `(ecase (or *describe-output-type-override* ,requested-output-type) - ((:message) - (let ((,var (make-string-output-stream))) - (unwind-protect - (progn - ,@body) - (show-message (get-output-stream-string ,var))))) - ((:popup) - (with-pop-up-typeout-window (,var (make-buffer ,buffer-name) :erase t) - ,@body)) - ((:buffer) - (let ((,var (make-string-output-stream))) - (unwind-protect - (progn - ,@body) - (let ((,output-buffer (make-buffer ,buffer-name))) - (erase-buffer ,output-buffer) - (insert-string (buffer-point ,output-buffer) (get-output-stream-string ,var)) - (pop-to-buffer ,output-buffer)))))))) + "Executes body in a lexical context where a documentation output stream called + 'var' has been established. The documentation output stream type will be either + a :message, a :popup, or a :buffer. If *describe-output-type-override* is set, + then its value will override the requested-output-type, allowing the user to, + for example, always output documentation descriptions to a full buffer, rather + then just a temporary message or popup" + `(call-with-describe-output-stream ,requested-output-type ,buffer-name + (lambda (,var) + ,@body))) (define-command describe-key () () "Tell what is the command associated to a keybinding." From 9909e0f4103ebb5da0d3c27399d0c7524b6f238d Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Mon, 22 Jun 2026 14:51:55 -0500 Subject: [PATCH 08/13] I realized minor mode keymap can shadow global bindings which is what I needed --- extensions/emacs-help-mode/main.lisp | 41 +++++++++------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/extensions/emacs-help-mode/main.lisp b/extensions/emacs-help-mode/main.lisp index e383f5086..90234141c 100644 --- a/extensions/emacs-help-mode/main.lisp +++ b/extensions/emacs-help-mode/main.lisp @@ -2,53 +2,38 @@ (:use #:cl #:lem)) (in-package #:lem-emacs-help-mode) -(defvar *previous-ctrl-h-suffix* nil) -(defvar *previous-describe-output-override* nil) +(defvar *emacs-help-mode-keymap* (make-keymap)) (defvar *ctrl-h-keymap* (make-keymap)) +(defvar *previous-describe-output-override* nil) (define-key *ctrl-h-keymap* "k" 'describe-key) (define-key *ctrl-h-keymap* "b" 'describe-bindings) (define-key *ctrl-h-keymap* "m" 'describe-mode) (define-key *ctrl-h-keymap* "a" 'apropos-command) (define-key *ctrl-h-keymap* "v" 'apropos-variable) - ;; TODO add describe-function command for "f" +(define-key *emacs-help-mode-keymap* "C-h" *ctrl-h-keymap*) + (defun enable (&optional recursed-p) "Enables emacs help mode" - (let* ((prefixes (keymap-prefixes *global-keymap*)) - (ctrl-h (first (parse-keyspec "C-h"))) - (index (position ctrl-h prefixes :key #'prefix-key :test #'key-equal))) - (cond - (index - (setf *previous-ctrl-h-suffix* (prefix-suffix (nth index prefixes))) - (setf (prefix-suffix (nth index prefixes)) *ctrl-h-keymap*) - (setf *previous-describe-output-override* - lem-core/commands/help:*describe-output-type-override*) - (setf lem-core/commands/help:*describe-output-type-override* :buffer)) - (recursed-p - (error "Infinite loop")) - (t - (push (make-prefix :key ctrl-h) (keymap-prefixes *global-keymap*)) - (enable t))))) + ;; TODO, figure out a more functional way to overwrite this + ;; variable which does not cause mutable global state + (setf *previous-describe-output-override* + lem-core/commands/help:*describe-output-type-override*) + (setf lem-core/commands/help:*describe-output-type-override* :buffer)) (defun disable () "Disables emacs help mode" - (let* ((prefixes (keymap-prefixes *global-keymap*)) - (ctrl-h (first (parse-keyspec "C-h"))) - (index (position ctrl-h prefixes :key #'prefix-key - :test #'lem-core:key-equal))) - (assert index) - (setf (prefix-suffix (nth index prefixes)) *previous-ctrl-h-suffix*) - (setf *previous-ctrl-h-suffix* nil) - (setf lem-core/commands/help:*describe-output-type-override* - *previous-describe-output-override*) - (setf *previous-describe-output-override* nil))) + (setf lem-core/commands/help:*describe-output-type-override* + *previous-describe-output-override*) + (setf *previous-describe-output-override* nil)) (define-minor-mode emacs-help-mode (:name "EHelp" :description "Adds Emacs Style C-h Bindings." :global t + :keymap *emacs-help-mode-keymap* :enable-hook 'enable :disable-hook 'disable)) From a90643b3305be1301335b9b11fc13905deab15ee Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Mon, 22 Jun 2026 14:53:33 -0500 Subject: [PATCH 09/13] Remove superfluous parameter --- extensions/emacs-help-mode/main.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/emacs-help-mode/main.lisp b/extensions/emacs-help-mode/main.lisp index 90234141c..5555fc5c4 100644 --- a/extensions/emacs-help-mode/main.lisp +++ b/extensions/emacs-help-mode/main.lisp @@ -15,7 +15,7 @@ (define-key *emacs-help-mode-keymap* "C-h" *ctrl-h-keymap*) -(defun enable (&optional recursed-p) +(defun enable () "Enables emacs help mode" ;; TODO, figure out a more functional way to overwrite this From ce8529025a5afd438a5542c6b4ce5a03e0af266e Mon Sep 17 00:00:00 2001 From: Robert Wess Burnett Date: Tue, 23 Jun 2026 09:41:25 -0500 Subject: [PATCH 10/13] begin fixing issues identified in code review --- src/commands/help.lisp | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index beb5054d4..c9a60dc29 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -1,11 +1,10 @@ -(defpackage :lem-core/commands/help +(uiop:define-package :lem-core/commands/help (:use :cl :lem-core) (:export :describe-key :describe-bindings :describe-mode :apropos-command :describe-command - :apropos-variable :describe-variable :lem-version :list-modes @@ -21,7 +20,8 @@ *describe-output-type-override*)) (defvar *describe-output-type-override* nil "When non nil, describe commands will always - send their information to this type of output") + send their information to this type of output. + Acceptable values: (nil :buffer :popup :message) ") (defun call-with-describe-output-stream (requested-output-type buffer-name function) (ecase (or *describe-output-type-override* requested-output-type) @@ -178,36 +178,38 @@ (name (package-name pkg))) (string-equal "LEM" (subseq name 0 3)))) -(defun is-variable-p (symbol) +(defun lem-variable-p (symbol) "Returns true if the symbol is a lem variable or an editor variable" (or (lem/common/var:editor-variable-p (get symbol 'editor-variable)) (and (lem-symbol-p symbol) (boundp symbol) (not (constantp symbol))))) -(defparameter *all-variables-cache* nil - "Cached list of all strings that represent variables") -(defun all-variables () +(defparameter *all-lem-variables-list-cache* nil + "Cached list of all strings that represent lem variables") +(defun list-all-lem-variables () + "Returns a list of all strings that represent lem variables" (unless *all-variables-cache* (loop :for pkg :in (list-all-packages) :appending (loop :for sym :being :the external-symbols :of (find-package pkg) - :when (is-variable-p sym) + :when (lem-variable-p sym) :collect (string-downcase (format nil "~a:~a" (package-name (find-package pkg)) (symbol-name sym)))) :into syms - :finally (setf *all-variables-cache* syms))) - *all-variables-cache*) + :finally (setf *all-lem-variables-list-cache* syms))) + *all-lem-variables-list-cache*) -(define-command apropos-variable () () +(define-command describe-variable () () + "Describe a lem variable who's name matches a given string." (let* ((str (prompt-for-string "Enter Variable: " :completion-function - (lambda (str) (completion str (all-variables)))))) + (lambda (str) (completion str (list-all-lem-variables)))))) (with-describe-output-stream (out :popup "*variable-description*") ;; TODO get a better description than just describe (let* ((sym (read-from-string str)) @@ -218,15 +220,11 @@ out "~a is an EDITOR VARIABLE bound to the following value: ~%~%" sym) (describe editor-variable out)) - ((is-variable-p sym) + ((lem-variable-p sym) (describe sym out)) (t (format out "~a does not describe any variable" sym))))))) -(define-command describe-variable () () - "Alias for apropos-variable" - (call-command 'apropos-variable nil)) - (define-command lem-version () () "Display Lem's version." (let ((version (get-version-string))) From 16eecabc6df6fbc39d07af42cc96fac4e28d8d37 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Tue, 23 Jun 2026 11:37:36 -0500 Subject: [PATCH 11/13] Remove caching --- src/commands/help.lisp | 37 ++++++++++++++++++------------------- src/internal-packages.lisp | 1 - src/key.lisp | 11 ----------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index c9a60dc29..d67298155 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -1,4 +1,4 @@ -(uiop:define-package :lem-core/commands/help +(defpackage :lem-core/commands/help (:use :cl :lem-core) (:export :describe-key :describe-bindings @@ -185,31 +185,30 @@ (boundp symbol) (not (constantp symbol))))) -(defparameter *all-lem-variables-list-cache* nil - "Cached list of all strings that represent lem variables") (defun list-all-lem-variables () "Returns a list of all strings that represent lem variables" - (unless *all-variables-cache* - (loop - :for pkg :in (list-all-packages) - :appending - (loop - :for sym :being :the external-symbols :of (find-package pkg) - :when (lem-variable-p sym) - :collect (string-downcase - (format nil "~a:~a" - (package-name (find-package pkg)) - (symbol-name sym)))) - :into syms - :finally (setf *all-lem-variables-list-cache* syms))) - *all-lem-variables-list-cache*) + (loop + :for pkg :in (list-all-packages) + :appending + (loop + :for sym :being :the external-symbols :of (find-package pkg) + :when (lem-variable-p sym) + :collect (string-downcase + (format nil "~a:~a" + (package-name (find-package pkg)) + (symbol-name sym)))) + :into syms + :finally (return syms))) + + (define-command describe-variable () () "Describe a lem variable who's name matches a given string." - (let* ((str (prompt-for-string + (let* ((all-lem-variables (list-all-lem-variables)) + (str (prompt-for-string "Enter Variable: " :completion-function - (lambda (str) (completion str (list-all-lem-variables)))))) + (lambda (str) (completion str all-lem-variables))))) (with-describe-output-stream (out :popup "*variable-description*") ;; TODO get a better description than just describe (let* ((sym (read-from-string str)) diff --git a/src/internal-packages.lisp b/src/internal-packages.lisp index 24c1063bb..6dfa60cc2 100644 --- a/src/internal-packages.lisp +++ b/src/internal-packages.lisp @@ -99,7 +99,6 @@ :key-shift :key-sym :match-key - :key-equal :insertion-key-sym-p :named-key-sym-p :define-named-key diff --git a/src/key.lisp b/src/key.lisp index 5526c7b08..b32dfcf0b 100644 --- a/src/key.lisp +++ b/src/key.lisp @@ -86,17 +86,6 @@ (eq (key-shift key) shift) (equal (key-sym key) sym))) -(defun key-equal (key-one key-two) - "Returns t if key-one == key-two, nil otherwise" - (match-key key-one - :ctrl (key-ctrl key-two) - :meta (key-meta key-two) - :super (key-super key-two) - :hyper (key-hyper key-two) - :shift (key-shift key-two) - :sym (key-sym key-two))) - - (defun insertion-key-sym-p (sym) (= 1 (length sym))) From dffa0a29099813cd1f25d1b413cdf55ee4eb1828 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Tue, 23 Jun 2026 11:41:45 -0500 Subject: [PATCH 12/13] Add error check --- src/commands/help.lisp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index d67298155..5ad1a7a4b 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -24,23 +24,26 @@ Acceptable values: (nil :buffer :popup :message) ") (defun call-with-describe-output-stream (requested-output-type buffer-name function) - (ecase (or *describe-output-type-override* requested-output-type) - ((:message) - (let ((stream (make-string-output-stream))) - (unwind-protect - (funcall function stream) - (show-message (get-output-stream-string stream))))) - ((:popup) - (with-pop-up-typeout-window (stream (make-buffer buffer-name) :erase t) - (funcall function stream))) - ((:buffer) - (let ((stream (make-string-output-stream))) - (unwind-protect - (funcall function stream) - (let ((output-buffer (make-buffer buffer-name))) - (erase-buffer output-buffer) - (insert-string (buffer-point output-buffer) (get-output-stream-string stream)) - (pop-to-buffer output-buffer))))))) + (let ((chosen-output-type (or *describe-output-type-override* requested-output-type))) + (case chosen-output-type + ((:message) + (let ((stream (make-string-output-stream))) + (unwind-protect + (funcall function stream) + (show-message (get-output-stream-string stream))))) + ((:popup) + (with-pop-up-typeout-window (stream (make-buffer buffer-name) :erase t) + (funcall function stream))) + ((:buffer) + (let ((stream (make-string-output-stream))) + (unwind-protect + (funcall function stream) + (let ((output-buffer (make-buffer buffer-name))) + (erase-buffer output-buffer) + (insert-string (buffer-point output-buffer) (get-output-stream-string stream)) + (pop-to-buffer output-buffer))))) + (otherwise + (lem:editor-error "Invalid describe-output-type: ~a" chosen-output-type))))) (defmacro with-describe-output-stream ((var requested-output-type &optional (buffer-name "*Description*")) From 303f11b1685474ab7f98951de92ef4ad8a23cdba Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Tue, 23 Jun 2026 11:45:06 -0500 Subject: [PATCH 13/13] fix compile error --- src/commands/help.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/help.lisp b/src/commands/help.lisp index 5ad1a7a4b..d44cd482f 100644 --- a/src/commands/help.lisp +++ b/src/commands/help.lisp @@ -43,7 +43,7 @@ (insert-string (buffer-point output-buffer) (get-output-stream-string stream)) (pop-to-buffer output-buffer))))) (otherwise - (lem:editor-error "Invalid describe-output-type: ~a" chosen-output-type))))) + (editor-error "Invalid describe-output-type: ~a" chosen-output-type))))) (defmacro with-describe-output-stream ((var requested-output-type &optional (buffer-name "*Description*"))