Skip to content

Instantly share code, notes, and snippets.

@alexforsale
Forked from progfolio/general-spacemacs.org
Created July 10, 2023 13:52
Show Gist options
  • Save alexforsale/94c9a5f28b988476f675963e8c4d0c42 to your computer and use it in GitHub Desktop.
Save alexforsale/94c9a5f28b988476f675963e8c4d0c42 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

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