226 lines
8.4 KiB
EmacsLisp
Executable File
226 lines
8.4 KiB
EmacsLisp
Executable File
;;; syd-keybinds.el -*- lexical-binding: t; -*-
|
|
;;; Universal keybindings, not /too/ tied to any particular packages.
|
|
|
|
(defvar syd-leader-key "SPC"
|
|
"A prefix key akin to Vim's <Leader>.")
|
|
|
|
(defvar syd-localleader-key "SPC m"
|
|
"A prefix key akin to Vim's <LocalLeader>.")
|
|
|
|
(defvar syd-alt-leader-key "M-SPC"
|
|
"`syd-leader', but for the states specified in `syd-alt-leader-key-states'.
|
|
|
|
Often, your \"usual\" leader key will be something unavailable in the Insert
|
|
state. This key exists as a fallback for when you need your Leader, but must
|
|
remain in the Insert state. Substitute \"Insert state\" for your states of
|
|
choice with `syd-alt-leader-key-states'.")
|
|
|
|
(defvar syd-alt-localleader-key "M-SPC m"
|
|
"`syd-localleader', but for the states specified in `syd-alt-leader-key-states'.
|
|
|
|
See `syd-alt-leader-key' for rationale.")
|
|
|
|
(defvar syd-leader-key-states '(normal visual motion)
|
|
"States for which the Leader keys (`syd-leader-key', `syd-localleader-key')
|
|
are active.")
|
|
|
|
(defvar syd-alt-leader-key-states '(emacs insert)
|
|
"States for which the alternative Leader keys are active. See
|
|
`syd-alt-leader-key' and `syd-alt-localleader-key'.")
|
|
|
|
(defvar-keymap syd-leader-map
|
|
:doc "Leader-prefixed commands")
|
|
|
|
(defun syd--initialise-leader ()
|
|
"Set up the (empty) keymap associated with `syd-leader-key',
|
|
`syd-localleader-key', `syd-alt-leader-key', and `syd-alt-localleader-key'."
|
|
(require 'evil)
|
|
;; Define `syd/leader' as a command corresponding to the prefix map
|
|
;; `syd-leader-map'.
|
|
(define-prefix-command 'syd/leader 'syd-leader-map)
|
|
;; This should help make the Leader key close to universally available.
|
|
;; Ideally, *nothing* takes precedence over Leader — it's an incredibly
|
|
;; important key!
|
|
;; https://github.com/noctuid/evil-guide?tab=readme-ov-file#undoprevent-overridingintercept-maps
|
|
;; See `evil-make-overriding-map'.
|
|
(define-key syd-leader-map [override-state] 'all)
|
|
;; Finally, we shall bind the damned keys. }:)
|
|
(let ((map general-override-mode-map))
|
|
(evil-define-key* syd-leader-key-states map (kbd syd-leader-key) 'syd/leader)
|
|
(evil-define-key* syd-alt-leader-key-states map (kbd syd-alt-leader-key) 'syd/leader))
|
|
(general-override-mode 1))
|
|
|
|
(defvar syd-escape-hook nil
|
|
"A hook run when C-g is pressed (or ESC in Evil's normal state).
|
|
|
|
More specifically, when `syd/escape' is pressed. If any hook returns non-nil,
|
|
all hooks after it are ignored.")
|
|
|
|
;;
|
|
;;; Universal, non-nuclear escape
|
|
|
|
;; `keyboard-quit' is too much of a nuclear option. I want ESC/C-g to
|
|
;; do-what-I-mean. It serves four purposes (in order):
|
|
;;
|
|
;; 1. Quit active states; e.g. highlights, searches, snippets, iedit,
|
|
;; multiple-cursors, recording macros, etc.
|
|
;; 2. Close popup windows remotely (if it is allowed to)
|
|
;; 3. Refresh buffer indicators, like diff-hl and flycheck
|
|
;; 4. Or fall back to `keyboard-quit'
|
|
;;
|
|
;; And it should do these things incrementally, rather than all at once. And it
|
|
;; shouldn't interfere with recording macros or the minibuffer. This may
|
|
;; require you press ESC/C-g two or three times on some occasions to reach
|
|
;; `keyboard-quit', but this is much more intuitive.
|
|
|
|
(defun syd/escape (&optional interactive)
|
|
"Run `syd-escape-hook'."
|
|
(interactive (list 'interactive))
|
|
(let ((inhibit-quit t))
|
|
(cond ((minibuffer-window-active-p (minibuffer-window))
|
|
;; quit the minibuffer if open.
|
|
(when interactive
|
|
(setq this-command 'abort-recursive-edit))
|
|
(abort-recursive-edit))
|
|
;; Run all escape hooks. If any returns non-nil, then stop there.
|
|
((run-hook-with-args-until-success 'syd-escape-hook))
|
|
;; don't abort macros
|
|
((or defining-kbd-macro executing-kbd-macro) nil)
|
|
;; Back to the default
|
|
((unwind-protect (keyboard-quit)
|
|
(when interactive
|
|
(setq this-command 'keyboard-quit)))))))
|
|
|
|
(with-eval-after-load 'eldoc
|
|
(eldoc-add-command 'syd/escape))
|
|
|
|
;; In normal state, pressing escape should run `syd-escape-hook'.
|
|
(with-eval-after-load 'evil
|
|
(defun evil-syd/escape-a (&rest _)
|
|
"Call `syd/escape' if `evil-force-normal-state' is called interactively."
|
|
(when (called-interactively-p 'any)
|
|
(call-interactively #'syd/escape)))
|
|
(advice-add #'evil-force-normal-state
|
|
:after #'evil-syd/escape-a))
|
|
|
|
(defun syd-keybinds-initialise ()
|
|
(syd--initialise-leader)
|
|
|
|
(global-set-key [remap keyboard-quit] #'syd/escape)
|
|
|
|
(general-def
|
|
:states 'motion
|
|
"/" #'evil-ex-search-forward
|
|
"?" #'evil-ex-search-backward
|
|
"n" #'evil-ex-search-next
|
|
"N" #'evil-ex-search-previous)
|
|
|
|
;; Buffer
|
|
(require 'syd-buffers)
|
|
(general-def
|
|
:prefix-map 'syd-leader-buffer-map
|
|
"k" `("Kill buffer" . ,#'kill-current-buffer)
|
|
"K" `("Kill all buffers" . ,#'syd/kill-all-buffers)
|
|
"O" `("Kill other buffers" . ,#'syd/kill-other-buffers)
|
|
"i" `("IBuffer" . ,#'ibuffer)
|
|
"Z" `("Kill burried buffers" . ,#'syd/kill-burried-buffers)
|
|
"C" `("Clone (indirect) buffer O/W" . ,#'clone-indirect-buffer-other-window)
|
|
"c" `("Clone (indirect) buffer" . ,#'clone-indirect-buffer)
|
|
"u" `("Save buffer as root" . ,#'syd/save-buffer-as-root)
|
|
"r" `("Revert buffer" . ,#'revert-buffer))
|
|
|
|
;; Search
|
|
(general-def
|
|
:prefix-map 'syd-leader-search-map
|
|
"i" `("IMenu" . ,#'consult-imenu)
|
|
"b" `("Search buffer" . ,#'syd/search-buffer))
|
|
|
|
;; File
|
|
(require 'syd-file)
|
|
(general-def
|
|
:prefix-map 'syd-leader-file-map
|
|
"D" `("Delete file" . ,#'syd/delete-this-file)
|
|
"R" `("Move file" . ,#'syd/move-this-file)
|
|
"C" `("Copy file" . ,#'syd/copy-this-file)
|
|
;; "F" `("Find file under here" . ,#'syd/find-file-under-here)
|
|
;; "p" `("Find under Emacs config" . ,#'syd/find-file-under-emacs-user-directory)
|
|
"P" `("Browse Emacs config" . ,#'syd/find-file-in-emacs-user-directory)
|
|
"u" `("Find file as root" . ,#'syd/find-file-as-root)
|
|
"U" `("Open this file as root" . ,#'syd/open-this-file-as-root)
|
|
"y" `("Yank buffer path" . ,#'syd/yank-buffer-path))
|
|
|
|
;; Window
|
|
(require 'syd-window)
|
|
(general-def
|
|
:prefix-map 'syd-leader-window-maximise-map
|
|
"m" `("Maximise" . ,#'syd/window-maximise))
|
|
(general-def
|
|
:prefix-map 'syd-leader-window-map
|
|
"h" `("Select window left" . ,#'evil-window-left)
|
|
"j" `("Select window below" . ,#'evil-window-down)
|
|
"k" `("Select window above" . ,#'evil-window-up)
|
|
"l" `("Select window right" . ,#'evil-window-right)
|
|
"H" `("Swap window left" . ,#'syd/window-swap-left)
|
|
"J" `("Swap window below" . ,#'syd/window-swap-down)
|
|
"K" `("Swap window above" . ,#'syd/window-swap-up)
|
|
"L" `("Swap window right" . ,#'syd/window-swap-right)
|
|
"T" `("Tear off window" . ,#'tear-off-window)
|
|
"=" `("Balance windows" . ,#'balance-windows)
|
|
"v" `("Vertical split" . ,#'evil-window-vsplit)
|
|
"s" `("Horizontal split" . ,#'evil-window-split)
|
|
"F" `("Fit window to contents" . ,#'fit-window-to-buffer)
|
|
"r" `("Rotate window downwards" . ,#'evil-window-rotate-downwards)
|
|
"R" `("Rotate window upwards" . ,#'evil-window-rotate-upwards)
|
|
"u" `("Undo window change" . ,#'winner-undo)
|
|
"C-r" `("Redo window change" . ,#'winner-redo)
|
|
"m" `("Maximise" . ,syd-leader-window-maximise-map))
|
|
|
|
;; Open
|
|
(require 'syd-handle-repl)
|
|
(general-def
|
|
:prefix-map 'syd-leader-open-map
|
|
"r" `("Repl o/w" . ,#'+syd/open-repl-other-window))
|
|
|
|
;; Project
|
|
(general-def
|
|
:prefix-map 'syd-leader-project-map
|
|
"C" `("Compile project" . ,#'project-compile))
|
|
|
|
;; Help
|
|
(general-def
|
|
:prefix-map 'help-map
|
|
"F" #'describe-face
|
|
"'" #'describe-char)
|
|
|
|
(general-def
|
|
:keymaps 'evil-ex-completion-map
|
|
"C-k" #'previous-history-element
|
|
"C-j" #'next-history-element)
|
|
|
|
;; Leader
|
|
(general-def
|
|
:keymaps 'syd-leader-map
|
|
"." #'find-file
|
|
"SPC" `("Find file in project" . ,#'project-find-file)
|
|
"x" `("Open scratch buffer" . ,#'scratch-buffer)
|
|
"u" `("Universal argument" . ,#'universal-argument)
|
|
"b" `("Buffer" . ,syd-leader-buffer-map)
|
|
"o" `("Open" . ,syd-leader-open-map)
|
|
"p" `("Project" . ,syd-leader-project-map)
|
|
"w" `("Window" . ,syd-leader-window-map)
|
|
"f" `("File" . ,syd-leader-file-map)
|
|
"s" `("Search" . ,syd-leader-search-map)
|
|
"h" `("Help" . ,help-map)))
|
|
|
|
(syd-keybinds-initialise)
|
|
|
|
;; Show possible completions for a partially-entered key sequence.
|
|
(use-package which-key
|
|
;; BUG: (#4) If the first input is a prefix key, `which-key-mode' won't be
|
|
;; activated in time.
|
|
:hook (on-first-input . which-key-mode)
|
|
:custom ((which-key-allow-evil-operators t)
|
|
(which-key-show-operator-state-maps t)))
|
|
|
|
(provide 'syd-keybinds)
|