Skip to content

Instantly share code, notes, and snippets.

@progfolio
Last active March 20, 2024 17:04
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save progfolio/1c96a67fcec7584b31507ef664de36cc to your computer and use it in GitHub Desktop.
Save progfolio/1c96a67fcec7584b31507ef664de36cc to your computer and use it in GitHub Desktop.
Spacemacs-like menus using general.el

Spacemacs-like menus using general.el

Global keybindings

First, we define a global prefix key:

(general-create-definer global-definer
  :keymaps 'override
  :states  '(insert emacs normal hybrid motion visual operator)
  :prefix  "SPC"
  :non-normal-prefix "S-SPC")

Bind top level, global commands like so:

(global-definer
  "!"   'shell-command
  ":"   'eval-expression)

Nested menus

To ease the creation of nested menus, we define a macro:

(defmacro +general-global-menu! (name infix-key &rest body)
  "Create a definer named +general-global-NAME wrapping global-definer.
Create prefix map: +general-global-NAME. Prefix bindings in BODY with INFIX-KEY."
  (declare (indent 2))
  `(progn
     (general-create-definer ,(intern (concat "+general-global-" name))
       :wrapping global-definer
       :prefix-map (quote ,(intern (concat "+general-global-" name "-map")))
       :infix ,infix-key
       :wk-full-keys nil
       "" '(:ignore t :which-key ,name))
     (,(intern (concat "+general-global-" name))
      ,@body)))

This macro defines top-level nested menus. e.g. a menu for “buffer” bindings:

(+general-global-menu! "buffer" "b"
  "d"  'kill-current-buffer
  "o" '((lambda () (interactive) (switch-to-buffer nil))
        :which-key "other-buffer")
  "p"  'previous-buffer
  "r"  'rename-buffer
  "M" '((lambda () (interactive) (switch-to-buffer "*Messages*"))
        :which-key "messages-buffer")
  "n"  'next-buffer
  "s" '((lambda () (interactive) (switch-to-buffer "*scratch*"))
        :which-key "scratch-buffer")
  "TAB" '((lambda () (interactive) (switch-to-buffer nil))
          :which-key "other-buffer"))

You can specify per-package keybindings with the :general use-package keyword. For example, here’s a minimal helm configuration that binds SPC bb to helm-mini:

(use-package helm
  :init (require 'helm-config)
  :defer 1
  :config
  (helm-mode)

  :general
  (+general-global-buffer
    "b" 'helm-mini))

Contextual (mode specific) leader key

We define a global-leader definer to access major-mode specific bindings:

(general-create-definer global-leader
  :keymaps 'override
  :states '(emacs normal hybrid motion visual operator)
  :prefix "SPC m"
  "" '(:ignore t :which-key (lambda (arg) `(,(cadr (split-string (car arg) " ")) . ,(replace-regexp-in-string "-mode$" "" (symbol-name major-mode))))))

Again, we use the :general keyword to bind mode-specific bindings. For example, with the following, we can eval-buffer when in emacs-lisp-mode or lisp-interaction-mode by pressing SPC m e b:

(use-package elisp-mode
  ;;this is a built in package, so we don't want to try and install it
  :ensure nil
  :general
  (global-leader
    ;;specify the major modes these should apply to:
    :major-modes
    '(emacs-lisp-mode lisp-interaction-mode t)
    ;;and the keymaps:
    :keymaps
    '(emacs-lisp-mode-map lisp-interaction-mode-map)
    "e" '(:ignore t :which-key "eval")
    "eb" 'eval-buffer
    "ed" 'eval-defun
    "ee" 'eval-expression
    "ep" 'pp-eval-last-sexp
    "es" 'eval-last-sexp
    "i" 'elisp-index-search))

Conclusion

Hope this helps. ~ Nicholas Vollmer

@progfolio
Copy link
Author

progfolio commented Dec 1, 2021

From the comment I linked:

There may be some configuration context missing, etc. For example, the last test you've posted contains Org noweb syntax:

<<general-config>>

This is not valid elisp. It's a syntax used in Org src blocks which only results in a valid elisp file after tangling.

@hongyi-zhao
Copy link

hongyi-zhao commented Dec 1, 2021

So this line should be deleted from the testing configuration used by me. I've taken it for granted that all the stuff in your linked comment is generated by yodel automatically, which makes me lost the opportunity to check its content further exhaustively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment