Skip to content

Latest commit

 

History

History
576 lines (444 loc) · 16.6 KB

wal-key-bindings.org

File metadata and controls

576 lines (444 loc) · 16.6 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 eww
  • C-c c for org-capture (C-c v captures project tasks)
  • C-c e for eshell
  • C-c f for flymake
  • C-c g for smerge
  • C-c i for compile
  • C-c j is bound to outline-minor-mode-map (if mode is enabled)
  • C-c l to toggle display-line-numbers-mode
  • C-c m for bookmark
  • C-c n to take notes for an org-agenda item
  • C-c o quits windows showing usually hidden buffers
  • C-c p for a heavy pulse
  • C-c q to do a quick-calc (inserted if called with C-u)
  • C-c r to recompile
  • C-c s for flyspell
  • C-c t for transpose-frame
  • C-c x opens a scratch buffer (can be called with numeric argument).

Meta

  • M-o to switch to the most recently used window (C-M-o switch 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:

  • H-] calls dap-next commands (H-M-] binds custom command map)
  • H-[ calls wal-lsp-dwim in LSP buffers (H-M-. binds lsp-command-map)
  • =H-‘= switches projects (=H-M-‘= switches to parent project)
  • H-\ captures note with org-roam (H-M-\ opens custom transient)
  • H-h finds file in project (H-M-h finds file in current directory)
  • H-i calls consult to find a place using imenu or outline
  • H-j
    • jumps to word in line (H-M-j jumps to char with timer)
    • during vertico and corfu completion, it triggers the respective quick completion
    • when used with M it goes to char with a timer
  • H-; jumps to register, H-M-; stores point in register
  • H-k completes at point; when corfu is active, it quits the candidate selection (H-M-k will insert a separator while corfu is active, otherwise activate completionist keymap)
  • H-. acts on the current point with embark (immediately with H-M-.)
  • H-l jumps to line with avy (beginning of line if called with C-u, H-M-l goes to line using consult)
  • H-m for magit-status (H-M-m runs ma-magit)
  • H-<mouse3> adds another multiple-cursor at point
  • H-n searches project with rg (C-0 searches using regex, H-M-n does rg-menu)
  • H-o switches tabs (M-H-o renames the current tab)
  • H-p to rerun the last ship-mate command (H-M-p binds ship-mate-command-map)
  • H-<SPC> clocks in (or out if called with prefix; prefix 0 clocks in without continuation)
  • H-,= switches to buffer using =consult (=H-M-,= opens custom transient)
  • H-{up,down,left,right} moves with windmove (swap with shift, delete with meta, and prep display with control)
  • H-u switches buffers in partial-recall memory (H-M-u calls the command map)
  • H-y dispatches ace-window.

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
  • q to “spill” a paragraph
  • s to insert pair with surround
  • . to mark all “like this”
  • w to kill-save a line
  • x to kill a line.

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

Function key <f6> is bound to administrator, a command map that binds various administrative Emacs 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))

(defvar transient-current-command)

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

(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))

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-start
      "to" 'profiler-stop
      "tr" 'profiler-report

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

    (general-create-definer completionist :prefix (wal-prefix-user-key "M-k"))
    (eval-after-load 'which-key
      (which-key-add-key-based-replacements "C-c k" "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)

    (editor "q" 'wal-spill-paragraph)

    (adjunct
      "b" 'wal-kill-some-file-buffers
      "d" 'wal-doppelganger
      "w" 'wal-l
      "f" 'wal-fundamental-mode
      "1" 'wal-force-delete-other-windows
      "4" 'wal-swipe-window-prefix)

    (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 [remap move-beginning-of-line] #'wal-mwim-beginning)
  (global-set-key (kbd "C-c x") #'wal-scratch-buffer)
  (global-set-key (kbd "C-c b") #'eww)
  (global-set-key (kbd "C-c l") #'display-line-numbers-mode)
  (global-set-key (kbd "C-c o") #'wal-supernova)
  (global-set-key (kbd "C-M-i") #'completion-at-point)
  (global-set-key (kbd "C-M-s") #'wal-isearch-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.
  (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.