Skip to content

Latest commit

 

History

History
568 lines (431 loc) · 20.1 KB

wal-key-bindings.org

File metadata and controls

568 lines (431 loc) · 20.1 KB

Key Bindings

I use many[fn:1] custom keybindings.

Overview

Control

There are some non-standard control sequences. Anywhere:

  • C-> expands region
  • C-. marks “next like this” using multiple-cursors.

User-reserved combinations are used for (mostly built-in) commands and command maps:

  • C-c a for org-agenda
  • C-c b for bookmark
  • C-c c for org-capture (C-c M-c captures project tasks)
  • C-c d is bound to outline-minor-mode-map (if mode is enabled)
  • C-c e for eshell
  • C-c f for flymake
  • C-c ` for eww
  • C-c g for smerge
  • C-c p for puni
  • C-c q to do a quick-calc (inserted if called with C-u)
  • C-c r to compile (C-c M-r to recompile)
  • C-c t to flop frames (command map of transpose-frame with C-c M-t)
  • C-c w mimics pressing the hyper key (customizable through wal-hyper-mock)
  • C-c x opens a scratch buffer (can be called with numeric argument)
  • C-c z for a heavy pulse

Meta

  • M-o switches to most recently used window (C-M-o switches to other-buffer)

Hyper

I rebound my <CAPS> (caps-lock) key to Hyper_L to use the hyper bindings below. Therefore, all following keys should be right hand keys.

Most hyper[fn:2] bindings are quick-access actions, some have an alternate action using prefix M, some bind a command map or transient with prefix C:

BindingContextActionAlternateCommand Map
H-]dap-debugdap-breakpoint-toggle
H-[Buffers with lsp-mode.wal-lsp-dwimlsp-execute-code-action
H-'wal-consult-projectwal-project-switch-to-parent-project-
H-porg-roam-captureorg-roam-dailies-capture-today
H-hproject-find=filewal-project-find-in-here(use C-x p)
H-kGoes to headings for org-mode.wal-consult-place--
H-kDuring compatible consult completion.Preview selection.--
H-;consult-linewal-consult-line-symbol-at-point-
H-;During vertico completion.marginalia-cycle--
H-jwal-avy-goto-wordavy-goto-char-timer-
H-jDuring vertico completion.vertico-quick-exit--
H-jDuring corfu completion.corfu-quick-complete--
H-yjump-to-registerwal-point-to-register(use C-x r)
H-.embark-actembark-dwim-
H-lwal-avy-goto-lineconsult-goto-line-
H-lDuring vertico completion.vertico-multiform=vertical--
H-mmagit-statusmagit-find-file
H-<mouse3>mc/add-cursor-on-click--
H-nrg-project-literalrg-literal
H-oace-windowtab-bar-switch-to-tab-
H-,ship-mate-rerun-commandship-mate-quick-command-map
H-<SPC>wal-consult-clockwal-consult-agenda-take-note-
H-uconsult-bufferconsult-find-file
H-{up,down-left,right}windmove-{up,down,left-right}--
H-ipartial-recall-switch-to-bufferpartial-recall-menu
H-/cape-dabbrevtempel-complete
H-\wal-supernovawal-consult-display-buffer-

Rebinding Hyper

XServer

Assuming you use Xorg Display server, create an .Xmodmap file in your home folder containing the following lines.

! Assign Hyper_L to Caps_Lock
keycode 66 = Hyper_L
! Remove caps lock
remove lock = Caps_Lock
! Set hyper to mod3 from mod4
remove mod4 = Hyper_L
add mod3 = Hyper_L

Add a script (also in your home folder) containing the following command and call it during start-up.

[[ -f ~/.Xmodmap ]] && xmodmap ~/.Xmodmap

This assumes that Hyper_L was assigned to modifier Mod4 that’s already used by Super_L and modifier Mod3 is an empty group.

Unsafe Alternative

A much riskier[fn:1] way, provided the recipe above doesn’t work, would be to edit your /usr/share/X11/xkb/symbols/pc file like so:

...
// key <CAPS> {    [ Caps_Lock     ]   };
key <CAPS> {    [ Hyper_L       ]   };
...
// modifier_map Lock   { Caps_Lock };
modifier_map Mod3   { Hyper_L, Hyper_R };
...
// modifier_map Mod4   { <HYPR> };
modifier_map Mod3   { <HYPR> };

Named command maps

There are seven named command map keys (three of them general leaders), each serving its unique purpose by prefixing (groups of) actions by common context or scope.

The general leader keys have so-called sinks for additional commands.

Ambassador

Leader key ambassador deals with the (buffer-, project-)local context.

If the respective buffer-local minor-mode is active, the following commands and command maps are bound:

  • 0 for dashboard-refresh-buffer
  • 8 for kubernetes
  • b for dap-mode
  • d for docker
  • f for flycheck
  • @ for mu4e
  • h for diff-hl
  • v for verb.

Major

Leader key major invokes a dispatch if the underlying major-mode has it defined.

Editor

Leader key editor provides a layer of useful editing actions.

They are:

  • c to copy a line
  • d to duplicate lines (in Emacs 29)
  • h to kill-save whole buffer
  • j to go to next spelling error with jinx
  • k for to start/stop kmacro recording
  • M-. to go to definition with dumb-jump
  • m to move a line
  • s to insert pair with surround
  • . to mark all “like this”
  • w to kill-save a line
  • x to kill a line,
  • <up>, <down>, <left> and <right> to drag stuff.

The sink for editor provides alternative version of these calls.

They are:

  • c to copy a region
  • j to fix spelling with jinx
  • m to move a region
  • s to kill between pair with surround
  • . to mark all ends in a region
  • w to kill a region
  • x to delete a region.

Adjunct

Binds various custom commands.

Seeker

Binds various custom commands that relate to finding things.

Administrator

A command map that binds various administrative Emacs commands.

Completionist

A command map that binds various completion commands.

Header

;;; wal-key-bindings.el --- Key bindings. -*- lexical-binding: t -*-

;;; Commentary:
;;
;; Key bindings package.

;;; Code:

(eval-when-compile
  (require 'wal-useful nil t)
  (require 'wal-package nil t))

(declare-function general-define-key "ext:general")
(declare-function transient-args "ext:transient.el")
(declare-function transient-arg-value "ext:transient.el")

(defvar transient-current-command)

(defgroup wal-key-bindings nil
  "Change key bindings settings."
  :group 'wal
  :tag "Key bindings")

;;;; Customization:

(defcustom wal-hyper-mock (kbd "C-c w")
  "The key sequence to use to mock hyper modifier."
  :type 'key-sequence
  :group 'wal-key-bindings)

(defcustom wal-leaders '(("6" . whaler)
                         ("7" . editor)
                         ("8" . ambassador)
                         ("9" . administrator)
                         ("0" . seeker)
                         ("-" . adjunct)
                         ("=" . major))
  "Alist mapping prefix keys to leaders."
  :type '(alist :key-type string :value-type symbol)
  :group 'wal-key-bindings)

Leaders

(defsubst wal-prefix-user-key (user-key)
  "Prefix USER-KEY."
  (let ((prefix "H-"))

    (concat prefix user-key)))

(defun wal-key-by-leader (leader)
  "Get the key for LEADER."
  (car-safe (rassoc leader wal-leaders)))

(cl-defun wal-key-combo-for-leader (leader &key key in-sink translate)
  "Get the key combination for LEADER.

If KEY is non-nil, append it. If IN-SINK is non-nil, infix leader
key. If TRANSLATE is non-nil, convert using `kbd'."
  (when-let* ((leader-key (wal-key-by-leader leader))
              (prefix (if (string-prefix-p "<" leader-key)
                          leader-key
                        (wal-prefix-user-key leader-key)))
              (combo (if key
                         (if in-sink
                             (concat prefix " " leader-key " " key)
                           (concat prefix " " key))
                       prefix)))
    (if translate
        (kbd combo)
      combo)))

Packages

general

Allows defining custom prefixes. This adds macros to create so-called sinks for leader keys, an additional layer using the same prefix key, as well as to mirror certain commands for the editor leader key.

(defvar wal-general-leaders '(editor seeker administrator adjunct ambassador)
  "Leaders that with a `general' definer.

The exceptions bind `transient' maps directly.")

(cl-defmacro wal-create-leader-sink (name &key definer prefix)
  "Macro to create a leader sink `NAME-sink'.

NAME is the name of the macro. DEFINER is the definer to create
the sink for and PREFIX is its prefix."
  (declare (indent defun))

  (let* ((defname (symbol-name definer))
         (suffix (substring prefix -1))
         (wk (upcase (concat defname "!"))))

    (progn
      (general-define-key :prefix prefix suffix `(:ignore t :wk ,wk))

      `(defmacro ,name (&rest args)
         `(, ',definer ,@,`(mapcar (lambda (it)
                                     (if (stringp it)
                                         (concat ,suffix it)
                                       it))
                                   args))))))

(cl-defmacro editors (key fun mfun &rest args)
  "Bind FUN to KEY, MFUN in the sink.

All ARGS are passed to both definers."
  (declare (indent defun))

  `(progn
    (editor ,@args ,key ,fun)
    (editor-sink ,@args ,key ,mfun)))

(defun wal-general-create-definer (leader)
  "Create a definer for LEADER with a sink."
  (let* ((key (wal-key-combo-for-leader leader))
         (sink (intern (format "%s-sink" leader)))
         (name (symbol-name leader)))

    ;; Queue up `which-key' replacements.
    (eval-after-load 'which-key `(which-key-add-key-based-replacements ,key ,name))

    ;; Create the normal definer.
    (eval `(general-create-definer ,leader :prefix ,key))

    ;; Also create the sink.
    (eval `(wal-create-leader-sink ,sink :definer ,leader :prefix ,key))))

(defun major? ()
  "Show message when major is not locally bound."
  (interactive)

  (let ((key (propertize (wal-key-combo-for-leader 'major) 'face 'success))
        (mode (propertize (symbol-name major-mode) 'face 'success)))

    (message "Major (%s) has no binding in %s" key mode)))

(use-package general
  :demand t
  :wal-ways t

  :config
  (seq-do #'wal-general-create-definer wal-general-leaders)

  :functions (general-define-key))

repeat

(use-package repeat
  :custom
  (repeat-exit-key (kbd "q"))
  (repeat-exit-timeout 5))

transient

Another nice way of grouping keys.

Some transients are bound directly, others are wal-univ variants (see above).

(defun wal-transient-grab (arg)
  "Grab argument ARG from current command."
  (transient-arg-value
   (format "--%s=" arg)
   (transient-args transient-current-command)))

(defun wal-transient-command-or-major ()
  "Show only major if command includes it."
  (if (string-match "major" mode-line-buffer-identification)
      "major"
    mode-line-buffer-identification))

(defun wal-with-delayed-transient-popup (fun &rest args)
  "Delay the transient FUN before calling it with ARGS."
  (defvar transient-show-popup)
  (let ((transient-show-popup 0.8))

    (apply fun args)))

(use-package transient
  :demand t

  :custom
  (transient-hide-during-minibuffer-read t)
  (transient-mode-line-format '("%e"
                                mode-line-front-space
                                (:eval (wal-transient-command-or-major)))))

which-key

Show the next possible key presses towards a command.

(cl-defmacro that-key (description &key key condition user-key leader)
  "Add DESCRIPTION for KEY after loading `which-key'.

If CONDITION is non-nil, surround the replacement with it.
USER-KEY and LEADER can be used to prefix the key."
  (let ((key (cond
              (user-key
               (wal-prefix-user-key user-key))
              (leader
               (apply 'wal-key-combo-for-leader leader))
              (key key)
              (t ""))))
    `(with-eval-after-load 'which-key
       (declare-function which-key-add-key-based-replacements "ext:which-key.el")

       ,(if condition
            `(when ,condition
               (which-key-add-key-based-replacements ,key ,description))
          `(which-key-add-key-based-replacements ,key ,description)))))

(use-package which-key
  :defer 2
  :wal-ways t

  :config
  (which-key-mode 1)

  :custom
  (which-key-lighter " wk?")

  (which-key-idle-delay 0.8)
  (which-key-idle-secondary-delay 0.2)

  (which-key-sort-uppercase-first nil)
  (which-key-sort-order #'which-key-prefix-then-key-order)

  (which-key-show-docstrings t)
  (which-key-preserve-window-configuration t)
  (which-key-show-early-on-C-h t)

  :functions (which-key-mode))

Key Bindings

(with-no-warnings
  (with-eval-after-load 'general
    ;; Additional `general' bindings.
    (administrator
      "f" '(:ignore t :wk "find")
      "fc" 'wal-find-custom-file
      "fi" 'wal-find-init
      "fl" 'find-library

      "l" '(:ignore t :wk "list")
      "lp" 'list-processes
      "lt" 'list-timers

      "s" '(:ignore t :wk "set")
      "st" 'wal-set-transparency
      "sc" 'wal-set-cursor-type

      "p" '(:ignore t :wk "package")
      "pf" 'package-refresh-contents
      "pi" 'package-install
      "pl" 'list-packages
      "pr" 'package-reinstall
      "pd" 'package-delete
      "pu" 'package-upgrade

      "t" '(:ignore t :wk "profiler")
      "ts" 'profiler-startd
      "to" 'profiler-stop
      "tr" 'profiler-report

      "h" '(:ignore t :wk "help")
      "hw" 'woman)

    (general-create-definer completionist :prefix (wal-prefix-user-key "C-/"))
    (eval-after-load 'which-key
      (which-key-add-key-based-replacements (wal-prefix-user-key "M-/") "completionist"))

    (global-set-key (kbd (wal-key-combo-for-leader 'major)) #'major?)
    (global-set-key (kbd (wal-key-combo-for-leader 'whaler)) #'whaler)

    (when (wal-modern-emacs-p 29)
      (editor "d" 'duplicate-dwim))

    (editor "h" 'wal-kill-ring-save-whole-buffer)

    (adjunct
      "b" 'wal-kill-some-file-buffers
      "d" 'wal-doppelganger
      "f" 'wal-fundamental-mode
      "1" 'wal-force-delete-other-windows)

    (seeker
      "f" 'wal-find-fish-config
      "h" 'wal-dired-from-home
      "s" 'find-sibling-file))

  (global-set-key [remap kill-line] #'wal-kwim)
  (global-set-key (kbd "C-c x") #'wal-scratch-buffer)
  (global-set-key (kbd "C-c `") #'eww)
  (global-set-key (kbd "C-M-i") #'completion-at-point)
  (global-set-key (kbd "C-M-s") #'wal-isearch-other-window)
  (global-set-key (kbd "C-M-q") #'wal-spill-paragraph)
  (global-set-key (kbd "C-z") nil)
  (global-set-key (kbd (wal-prefix-user-key "\\")) #'wal-supernova)
  (global-set-key [remap zap-to-char] #'zap-up-to-char)

  (unless (wal-modern-emacs-p 30)
    ;; Replaced by `toggle-window-dedicated' in Emacs 30.
    (define-key window-prefix-map (kbd "d") #'wal-l)
    (define-key window-prefix-map (kbd "q") #'quit-window))

  ;; Replace `buffer-list'.
  (global-set-key [remap list-buffers] #'ibuffer-other-window)

  ;; Alternate binding for C-c x @ h.
  (define-key function-key-map wal-hyper-mock #'event-apply-hyper-modifier)

  ;; One-handed events.
  (define-key function-key-map (kbd "<f5>") #'event-apply-control-modifier)
  (define-key function-key-map (kbd "<f6>") #'event-apply-meta-modifier)
  (define-key function-key-map (kbd "<f7>") #'event-apply-hyper-modifier)
  (define-key function-key-map (kbd "<f8>") #'event-apply-shift-modifier)

  ;; Add alternative bindings to repeat map.
  (define-key undo-repeat-map "/" #'undo)
  (define-key undo-repeat-map "?" #'undo-redo)

  ;; Bind additional `other-window' commands.
  (define-key ctl-x-4-map (kbd "M-4") 'wal-swipe-window-prefix)
  (global-set-key (kbd "M-o") 'wal-other-window)
  (global-set-key (kbd "C-M-o") 'wal-switch-to-other-buffer)

  (with-eval-after-load 'window
    (when (boundp 'other-window-repeat-map)
      (define-key other-window-repeat-map "0" 'delete-window)
      (define-key other-window-repeat-map "1" 'delete-other-windows)
      (define-key other-window-repeat-map (kbd "C-k") 'wal-force-delete-other-windows)
      (define-key other-window-repeat-map "5" 'other-frame))))

Footer

(provide 'wal-key-bindings)

;;; wal-key-bindings.el ends here

Footnotes

[fn:1] To get a full overview you’ll have to call describe-personal-keybindings and general-describe-keybindings.

[fn:2] Note that C-c w is bound to apply the hyper modifier as well; so if you don’t have access to the key, you can always use that instead.