summaryrefslogtreecommitdiff
path: root/modules/keybindings.el
blob: 1f8867efd80c56f883fa53358d36e9795e7f68fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
;;; keybindings --- General Keyboard Shortcuts -*- lexical-binding: t; coding: utf-8; -*-
;; author: Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; Global keybinding configuration and custom keymap framework.
;;
;; Main features include:
;; - custom keymap prefix  C-; for all custom commands,
;; - jump-to-file commands C-c j <key> for frequently used files
;; - which-key integration for keybinding discovery
;; - free-keys for finding available keybindings
;;
;; Key principles:
;; - avoid keybindings close to commonly-used keys that have painful results
;;
;;; Code:

;; Loaded earlier in init.el
(eval-when-compile (require 'user-constants))

;; ------------------------------- Custom Keymap -------------------------------

(defvar-keymap cj/custom-keymap
  :doc "User custom prefix keymap base for nested keymaps.")
(keymap-global-set "C-;" cj/custom-keymap)

;; ------------------------------ Jump To Commands -----------------------------

(defun cj/jump-open-var (var)
  "Open the file whose path is stored in VAR.
Errors if VAR is unbound, not a non-empty string, or the file does not exist."
  (unless (boundp var)
	(user-error "Variable %s is not bound" var))
  (let ((path (symbol-value var)))
	(unless (and (stringp path) (> (length path) 0))
	  (user-error "Variable %s does not contain a valid file path" var))
	(unless (file-exists-p path)
	  (user-error "File does not exist: %s" path))
	(find-file path)))

(defconst cj/jump--specs
  '(("r" reference      reference-file)
	("s" schedule       schedule-file)
	("i" inbox          inbox-file)
	("c" contacts       contacts-file)
	("m" macros         macros-file)
	("n" reading-notes  reading-notes-file)
	("w" webclipped     webclipped-file)
	("g" gcal           gcal-file)
	("I" emacs-init     emacs-init-file))
  "Specs for jump commands: each entry is (KEY NAME-SYM VAR-SYM).")

(defvar-keymap cj/jump-map
  :doc "Key map for quick jumps to commonly used files.")

;; Define commands and populate the keymap from the specs.
(dolist (spec cj/jump--specs)
  (pcase-let ((`(,key ,name ,var) spec))
	(let* ((fn (intern (format "cj/jump-to-%s" name)))
		   (doc (format "Open the file from variable `%s'." var)))
	  ;; Define a named command that opens the file from VAR.
	  (defalias fn
		`(lambda ()
		   ,doc
		   (interactive)
		   (cj/jump-open-var ',var)))
	  ;; Bind it under the prefix map.
	  (keymap-set cj/jump-map key fn))))

;; Bind the prefix globally (user-reserved prefix).
(keymap-global-set "C-c j" cj/jump-map)

;; nicer prefix label in which-key
(with-eval-after-load 'which-key
  (which-key-add-key-based-replacements "C-c j" "Jump to common files."))

;; ---------------------------- Keybinding Discovery ---------------------------

(use-package free-keys
  :commands (free-keys)
  :bind (:map help-map
		 ("C-k" . free-keys)))

(use-package which-key
  :commands (which-key-mode)
  :hook (emacs-startup . which-key-mode)
  :custom
  (which-key-idle-delay 1.0)
  (which-key-popup-type 'side-window)
  :config
  (which-key-setup-side-window-bottom)
  ;; never show keybindings that have been 'cj/disabled'
  (push '((nil . "cj/disabled") . t) which-key-replacement-alist))

;; ---------------------------- General Keybindings ----------------------------

;; Avoid hostile bindings
(keymap-global-unset  "C-x C-f")   ;; find-file-read-only
(keymap-global-set  "C-x C-f" #'find-file)
(keymap-global-unset  "C-z")       ;; suspend-frame is accidentally hit often
(keymap-global-unset  "M-o")       ;; facemenu-mode

;; Add commonly-used general keybindings
(keymap-global-set  "M-*"     #'calculator)
(keymap-global-set  "M-Y"     #'yank-media)

;; Normally bound to ESC ESC ESC, hit ESC once to get out of unpleasant situations.
(keymap-global-set  "<escape>" #'keyboard-escape-quit)

;; remap C-x \ to sort-lines (from remap activate-transient-input-method)
(keymap-global-unset  "C-x \\")
(keymap-global-set  "C-x \\" #'sort-lines)

;; training myself to use C-/ for undo (bound internally) as it's faster.
(keymap-global-unset  "C-x u")
(keymap-global-set "C-x u"
			#'(lambda () (interactive)
				(message (concat "Seriously, " user-name
								 "? Use 'C-/'. It's faster."))))

(provide 'keybindings)
;;; keybindings.el ends here