summaryrefslogtreecommitdiff
path: root/modules/keybindings.el
blob: 491c102cc7277b8125592d7fbc120caf41b30f37 (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
;;; 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
;; - hostile keybinding protection that disables accidental suspend-frame and other dangerous operations
;; - training  to encourage faster keybindings
;;
;; Key principles:
;; - avoid keybindings close to commonly-used keys that have painful results
;; - use global-map for truly global bindings
;; - use custom-keymap (~C-;~) for custom functionality
;; - keep ~C-c <letter>~ reserved for user bindings per Emacs conventions
;;
;;; 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)
  ;; :init
  ;; ;; Load + enable after a short idle so it doesn't count toward startup.
  ;; (run-with-idle-timer 0.5 nil
  ;; 	(lambda ()
  ;; 	  (require 'which-key nil t)
  ;; 	  ;; Ensure config has applied, then enable the mode.
  ;; 	  (with-eval-after-load 'which-key
  ;; 		(unless (bound-and-true-p which-key-mode)
  ;; 		  (which-key-mode 1)))))
  :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