doomemacs/modules/feature/evil/config.el

497 lines
18 KiB
EmacsLisp
Raw Normal View History

;;; feature/evil/config.el
2017-01-31 19:50:02 -05:00
;; I'm a vimmer at heart. Its modal philosophy suits me better, and this module
;; strives to make Emacs a much better vim than vim was.
(def-setting! :evil-state (&rest mode-state-list)
2017-02-04 21:09:33 -05:00
"Set the initialize STATE of MODE using `evil-set-initial-state'."
(if (cl-every #'listp mode-state-list)
`(progn
,@(let (forms)
(dolist (it mode-state-list (nreverse forms))
(unless (consp it)
(error ":evil-state expected cons cells, got %s" it))
(push `(evil-set-initial-state ',(car it) ',(cdr it)) forms))))
(let ((argc (length mode-state-list)))
(unless (= argc 2)
(error ":evil-state expected 2 arguments, got %s" argc)))
`(evil-set-initial-state ',(car mode-state-list) ',(cadr mode-state-list))))
2017-02-04 21:09:33 -05:00
2017-01-31 19:50:02 -05:00
;;
;; evil-mode
;;
(def-package! evil :demand t
2017-01-31 19:50:02 -05:00
:init
(setq evil-want-C-u-scroll t
evil-want-visual-char-semi-exclusive t
evil-want-fine-undo nil
evil-want-Y-yank-to-eol t
evil-ex-interactive-search-highlight 'selected-window
2017-01-31 19:50:02 -05:00
evil-magic t
evil-echo-state t
evil-indent-convert-tabs t
2017-01-31 19:50:02 -05:00
evil-ex-search-vim-style-regexp t
evil-ex-substitute-global t
evil-ex-visual-char-range t ; column range for ex commands
2017-02-21 00:47:34 -05:00
evil-insert-skip-empty-lines t
;; don't activate mark on shift-click
shift-select-mode nil)
2017-01-31 19:50:02 -05:00
:config
2017-02-19 18:14:46 -05:00
(evil-mode +1)
(evil-select-search-module 'evil-search-module 'evil-search)
(set! :popup
2017-02-21 16:04:35 -05:00
'("*evil-registers*" :size 0.3)
'("*Command Line*" :size 8))
;; Don't interfere with localleader key
(define-key evil-motion-state-map "\\" nil)
2017-02-19 18:14:46 -05:00
;; Set cursor colors later, presumably once theme is loaded
2017-04-22 01:49:15 -04:00
(defun +evil*init-cursors (&rest _)
2017-05-10 05:28:50 +02:00
(setq evil-default-cursor (face-background 'cursor nil t)
2017-02-19 18:14:46 -05:00
evil-normal-state-cursor 'box
2017-05-10 05:28:50 +02:00
evil-emacs-state-cursor `(,(face-foreground 'warning) box)
2017-02-19 18:14:46 -05:00
evil-insert-state-cursor 'bar
evil-visual-state-cursor 'hollow))
2017-04-22 01:49:15 -04:00
(advice-add #'load-theme :after #'+evil*init-cursors)
2017-02-19 18:14:46 -05:00
;; highlight matching delimiters where it's important
(defun +evil|show-paren-mode-off () (show-paren-mode -1))
(defun +evil|show-paren-mode-on ()
(unless (bound-and-true-p org-indent-mode) ; interferes with org-indent
(show-paren-mode +1)))
(add-hook 'evil-insert-state-entry-hook #'+evil|show-paren-mode-on)
(add-hook 'evil-insert-state-exit-hook #'+evil|show-paren-mode-off)
(add-hook 'evil-visual-state-entry-hook #'+evil|show-paren-mode-on)
(add-hook 'evil-visual-state-exit-hook #'+evil|show-paren-mode-off)
(add-hook 'evil-operator-state-entry-hook #'+evil|show-paren-mode-on)
(add-hook 'evil-operator-state-exit-hook #'+evil|show-paren-mode-off)
(add-hook 'evil-normal-state-entry-hook #'+evil|show-paren-mode-off)
2017-02-19 18:14:46 -05:00
(dolist (mode '(tabulated-list-mode view-mode comint-mode term-mode calendar-mode Man-mode grep-mode))
(evil-set-initial-state mode 'emacs))
(dolist (mode '(help-mode))
(evil-set-initial-state mode 'normal))
;; make `try-expand-dabbrev' from `hippie-expand' work in mini-buffer
;; @see `he-dabbrev-beg', so we need re-define syntax for '/'
(defun minibuffer-inactive-mode-hook-setup ()
(set-syntax-table (let* ((table (make-syntax-table)))
(modify-syntax-entry ?/ "." table)
table)))
(add-hook 'minibuffer-inactive-mode-hook #'minibuffer-inactive-mode-hook-setup))
2017-01-31 19:50:02 -05:00
(defsubst +evil--textobj (key inner-fn &optional outer-fn)
2017-01-31 19:50:02 -05:00
"Define a text object."
(define-key evil-inner-text-objects-map key inner-fn)
(define-key evil-outer-text-objects-map key (or outer-fn inner-fn)))
;;
;; evil hacks
;;
(defvar +evil-esc-hook nil
"A hook run after ESC is pressed in normal mode (invoked by
`evil-force-normal-state').")
(defun +evil*attach-escape-hook ()
"Run the `+evil-esc-hook'."
(run-hooks '+evil-esc-hook))
(advice-add #'evil-force-normal-state :after #'+evil*attach-escape-hook)
(defun +evil|escape-minibuffer ()
"Quit the minibuffer if open."
2017-01-31 19:50:02 -05:00
(when (minibuffer-window-active-p (minibuffer-window))
(abort-recursive-edit)))
(defun +evil|escape-highlights ()
"Disable ex search buffer highlights."
2017-01-31 19:50:02 -05:00
(when (evil-ex-hl-active-p 'evil-ex-search)
(evil-ex-nohighlight)))
(add-hook! '+evil-esc-hook '(+evil|escape-minibuffer +evil|escape-highlights))
2017-01-31 19:50:02 -05:00
(defun +evil*restore-normal-state-on-windmove (orig-fn &rest args)
"If in anything but normal or motion mode when moving to another window,
restore normal mode. This prevents insert state from bleeding into other modes
across windows."
(unless (memq evil-state '(normal motion))
(evil-normal-state +1))
(apply orig-fn args))
(advice-add #'windmove-do-window-select :around #'+evil*restore-normal-state-on-windmove)
2017-02-19 18:14:46 -05:00
(defun +evil*static-reindent (orig-fn &rest args)
2017-03-19 22:50:52 -04:00
"Don't move cursor on indent."
2017-02-19 18:14:46 -05:00
(save-excursion (apply orig-fn args)))
(advice-add #'evil-indent :around #'+evil*static-reindent)
2017-02-19 18:14:46 -05:00
2017-01-31 19:50:02 -05:00
;; Move to new split
(defun +evil*window-follow (&rest _) (evil-window-down 1))
(defun +evil*window-vfollow (&rest _) (evil-window-right 1))
(advice-add #'evil-window-split :after #'+evil*window-follow)
(advice-add #'evil-window-vsplit :after #'+evil*window-vfollow)
2017-01-31 19:50:02 -05:00
;; monkey patch `evil-ex-replace-special-filenames' to add more ex
;; substitution flags to evil-mode
(advice-add #'evil-ex-replace-special-filenames
:override #'+evil*ex-replace-special-filenames)
2017-01-31 19:50:02 -05:00
;; Add extra argument types that highlight matches in the current buffer.
2017-02-02 04:35:54 -05:00
;; TODO Must be simpler way to do this
(evil-ex-define-argument-type buffer-match :runner +evil-ex-buffer-match)
(evil-ex-define-argument-type global-match :runner +evil-ex-global-match)
2017-01-31 19:50:02 -05:00
(evil-define-interactive-code "<//>"
:ex-arg buffer-match (list (when (evil-ex-p) evil-ex-argument)))
(evil-define-interactive-code "<g//>"
:ex-arg global-match (when (evil-ex-p) (evil-ex-parse-global evil-ex-argument)))
(evil-define-operator +evil:global (beg end pattern command &optional invert)
"Rewritten :g[lobal] that will highlight buffer matches. Takes the same arguments."
:motion mark-whole-buffer :move-point nil
(interactive "<r><g//><!>")
(evil-ex-global beg end pattern command invert))
(evil-define-operator +evil:align (&optional beg end bang pattern)
"Ex interface to `align-regexp'. Accepts vim-style regexps."
(interactive "<r><!><//>")
(align-regexp
beg end
(concat "\\(\\s-*\\)"
(if bang
(regexp-quote pattern)
(evil-transform-vim-style-regexp pattern)))
1 1))
(evil-ex-define-cmd "g[lobal]" #'+evil:global)
(evil-ex-define-cmd "al[ign]" #'+evil:align)
2017-01-31 19:50:02 -05:00
;;
;; Plugins
;;
(def-package! evil-args
2017-02-02 04:35:54 -05:00
:commands (evil-inner-arg evil-outer-arg
evil-forward-arg evil-backward-arg
2017-01-31 19:50:02 -05:00
evil-jump-out-args)
:init (+evil--textobj "a" #'evil-inner-arg #'evil-outer-arg))
2017-01-31 19:50:02 -05:00
(def-package! evil-commentary
2017-01-31 19:50:02 -05:00
:commands (evil-commentary evil-commentary-yank evil-commentary-line)
2017-02-02 04:35:54 -05:00
:config (evil-commentary-mode 1))
2017-01-31 19:50:02 -05:00
(def-package! evil-easymotion
2017-01-31 19:50:02 -05:00
:defer 1
2017-02-19 18:14:46 -05:00
:commands evilem-define
2017-01-31 19:50:02 -05:00
:config
(let ((prefix "g SPC"))
(evilem-default-keybindings prefix)
(evilem-define (kbd (concat prefix " n")) #'evil-ex-search-next)
(evilem-define (kbd (concat prefix " N")) #'evil-ex-search-previous)
(evilem-define (kbd (concat prefix " s")) 'evil-snipe-repeat
:pre-hook (save-excursion (call-interactively #'evil-snipe-s))
:bind ((evil-snipe-scope 'buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight)))
(evilem-define (kbd (concat prefix " S")) #'evil-snipe-repeat-reverse
:pre-hook (save-excursion (call-interactively #'evil-snipe-s))
:bind ((evil-snipe-scope 'buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight))))
(defvar +evil--snipe-repeat-fn
(evilem-create #'evil-snipe-repeat
:bind ((evil-snipe-scope 'whole-buffer)
(evil-snipe-enable-highlight)
(evil-snipe-enable-incremental-highlight)))))
2017-01-31 19:50:02 -05:00
(def-package! evil-embrace
2017-01-31 19:50:02 -05:00
:after evil-surround
:config
(setq evil-embrace-show-help-p nil)
(evil-embrace-enable-evil-surround-integration)
;; Defuns
(defun +evil--embrace-get-pair (char)
2017-02-19 18:14:46 -05:00
(if-let (pair (cdr-safe (assoc (string-to-char char) evil-surround-pairs-alist)))
pair
(if-let (pair (assoc-default char embrace--pairs-list))
(if-let (real-pair (and (functionp (embrace-pair-struct-read-function pair))
(funcall (embrace-pair-struct-read-function pair))))
real-pair
(cons (embrace-pair-struct-left pair) (embrace-pair-struct-right pair)))
(cons char char))))
2017-01-31 19:50:02 -05:00
(defun +evil--embrace-escaped ()
"Backslash-escaped surround character support for embrace."
(let ((char (read-char "\\")))
(if (eq char 27)
(cons "" "")
(let ((pair (+evil--embrace-get-pair (string char)))
(text (if (sp-point-in-string) "\\\\%s" "\\%s")))
(cons (format text (car pair))
(format text (cdr pair)))))))
(defun +evil--embrace-latex ()
"LaTeX command support for embrace."
(cons (format "\\%s{" (read-string "\\")) "}"))
(defun +evil--embrace-elisp-fn ()
"Elisp function support for embrace."
(cons (format "(%s " (or (read-string "(") "")) ")"))
;; Add escaped-sequence support to embrace
(push (cons ?\\ (make-embrace-pair-struct
:key ?\\
:read-function #'+evil--embrace-escaped
2017-01-31 19:50:02 -05:00
:left-regexp "\\[[{(]"
:right-regexp "\\[]})]"))
(default-value 'embrace--pairs-list))
;; Add extra pairs
(add-hook 'LaTeX-mode-hook #'embrace-LaTeX-mode-hook)
(add-hook 'org-mode-hook #'embrace-org-mode-hook)
(add-hook! emacs-lisp-mode
2017-01-31 19:50:02 -05:00
(embrace-add-pair ?\` "`" "'"))
(add-hook! (emacs-lisp-mode lisp-mode)
(embrace-add-pair-regexp ?f "([^ ]+ " ")" #'+evil--embrace-elisp-fn))
(add-hook! (org-mode LaTeX-mode)
(embrace-add-pair-regexp ?l "\\[a-z]+{" "}" #'+evil--embrace-latex)))
2017-01-31 19:50:02 -05:00
(def-package! evil-escape
2017-01-31 19:50:02 -05:00
:commands evil-escape-mode
:init
(defun +evil|escape-disable () (evil-escape-mode -1))
(defun +evil|escape-enable () (evil-escape-mode +1))
;; I only need evil-escape in insert and replace modes.
(add-hook 'evil-insert-state-entry-hook #'+evil|escape-enable)
(add-hook 'evil-insert-state-exit-hook #'+evil|escape-disable)
(add-hook 'evil-replace-state-entry-hook #'+evil|escape-enable)
(add-hook 'evil-replace-state-exit-hook #'+evil|escape-disable)
2017-01-31 19:50:02 -05:00
:config
(setq evil-escape-key-sequence "jk"
evil-escape-delay 0.25))
(def-package! evil-exchange
2017-01-31 19:50:02 -05:00
:commands evil-exchange
:config
(defun +evil|escape-exchange ()
2017-01-31 19:50:02 -05:00
(if evil-exchange--overlays (evil-exchange-cancel)))
(add-hook '+evil-esc-hook #'+evil|escape-exchange))
2017-01-31 19:50:02 -05:00
(def-package! evil-indent-plus
2017-01-31 19:50:02 -05:00
:commands (evil-indent-plus-i-indent
evil-indent-plus-a-indent
evil-indent-plus-i-indent-up
evil-indent-plus-a-indent-up
evil-indent-plus-i-indent-up-down
evil-indent-plus-a-indent-up-down)
:init
(+evil--textobj "i" #'evil-indent-plus-i-indent #'evil-indent-plus-a-indent)
(+evil--textobj "I" #'evil-indent-plus-i-indent-up #'evil-indent-plus-a-indent-up)
(+evil--textobj "J" #'evil-indent-plus-i-indent-up-down #'evil-indent-plus-a-indent-up-down))
2017-01-31 19:50:02 -05:00
(def-package! evil-matchit
2017-01-31 19:50:02 -05:00
:commands (evilmi-jump-items evilmi-text-object global-evil-matchit-mode)
:config (global-evil-matchit-mode 1)
:init
(+evil--textobj "%" #'evilmi-text-object)
(defun +evil|simple-matchit ()
"Force evil-matchit to favor simple bracket jumping. Helpful where the new
algorithm is just confusing, like in python or ruby."
(setq-local evilmi-always-simple-jump t)))
2017-01-31 19:50:02 -05:00
(def-package! evil-mc
:commands evil-mc-make-cursor-here
:init
(defvar evil-mc-key-map (make-sparse-keymap))
(map! :n "M-d" #'evil-mc-make-cursor-here)
:config
;; Start evil-mc in paused mode.
(add-hook 'evil-mc-mode-hook #'evil-mc-pause-cursors)
(add-hook 'evil-mc-before-cursors-created #'evil-mc-pause-cursors)
(global-evil-mc-mode 1)
(setq evil-mc-custom-known-commands
'((doom/deflate-space-maybe . ((:default . evil-mc-execute-default-call)))))
(defun +evil/mc-toggle-cursors ()
"Toggle frozen state of evil-mc cursors."
(interactive)
(setq evil-mc-frozen (not (and (evil-mc-has-cursors-p)
evil-mc-frozen))))
;; My workflow is to place the cursors, get into position, then enable evil-mc
;; (either by going into insert mode, or pressing M-d).
(map! :map evil-mc-key-map
:n "M-D" #'+evil/mc-toggle-cursors)
;; If I switch to insert mode, chances are I want to begin editing.
(add-hook 'evil-insert-state-entry-hook #'evil-mc-resume-cursors)
;; Undo cursors on ESC (from normal mode)
(defun +evil|escape-multiple-cursors ()
"Undo cursors and freeze them again (for next time)."
(when (evil-mc-has-cursors-p)
(evil-mc-undo-all-cursors)))
(add-hook '+evil-esc-hook #'+evil|escape-multiple-cursors))
2017-01-31 19:50:02 -05:00
(def-package! evil-multiedit
2017-01-31 19:50:02 -05:00
:commands (evil-multiedit-match-all
evil-multiedit-match-and-next
evil-multiedit-match-and-prev
evil-multiedit-match-symbol-and-next
evil-multiedit-match-symbol-and-prev
evil-multiedit-toggle-or-restrict-region
evil-multiedit-next
evil-multiedit-prev
evil-multiedit-abort
evil-multiedit-ex-match)
:init
(map! :v "M-d" #'evil-multiedit-match-and-next
:v "M-D" #'evil-multiedit-match-and-prev
:v "C-M-d" #'evil-multiedit-restore
:v "R" #'evil-multiedit-match-all)
:config
(evil-ex-define-cmd "ie[dit]" 'evil-multiedit-ex-match)
(map! (:map evil-multiedit-state-map
"M-d" #'evil-multiedit-match-and-next
"M-D" #'evil-multiedit-match-and-prev
"RET" #'evil-multiedit-toggle-or-restrict-region)
(:map (evil-multiedit-state-map evil-multiedit-insert-state-map)
"C-n" #'evil-multiedit-next
"C-p" #'evil-multiedit-prev)))
2017-01-31 19:50:02 -05:00
(def-package! evil-textobj-anyblock
2017-01-31 19:50:02 -05:00
:commands (evil-numbers/inc-at-pt evil-numbers/dec-at-pt)
:init
(+evil--textobj "B"
#'evil-textobj-anyblock-inner-block
#'evil-textobj-anyblock-a-block))
2017-01-31 19:50:02 -05:00
(def-package! evil-snipe :demand t
2017-01-31 19:50:02 -05:00
:init
(setq evil-snipe-smart-case t
evil-snipe-scope 'line
evil-snipe-repeat-scope 'visible
2017-02-19 18:14:46 -05:00
evil-snipe-override-evil-repeat-keys nil
2017-01-31 19:50:02 -05:00
evil-snipe-char-fold t
evil-snipe-aliases '((?\[ "[[{(]")
(?\] "[]})]")
(?\; "[;:]")))
:config
;; (evil-snipe-mode +1)
(evil-snipe-override-mode +1)
;; Switch to evil-easymotion/avy after a snipe
(map! :map evil-snipe-parent-transient-map
"C-;" (λ! (require 'evil-easymotion)
2017-02-04 02:53:57 -05:00
(call-interactively +evil--snipe-repeat-fn))))
2017-01-31 19:50:02 -05:00
(def-package! evil-surround
2017-01-31 19:50:02 -05:00
:commands (global-evil-surround-mode
evil-surround-edit
evil-Surround-edit
evil-surround-region)
:config (global-evil-surround-mode 1))
(def-package! evil-visualstar
2017-01-31 19:50:02 -05:00
:commands (global-evil-visualstar-mode
evil-visualstar/begin-search
evil-visualstar/begin-search-forward
evil-visualstar/begin-search-backward)
:config (global-evil-visualstar-mode 1))
;; A side-panel for browsing my project files. Inspired by vim's NERDTree. Sure,
;; there's dired and projectile, but sometimes I'd like a bird's eye view of a
;; project.
(def-package! neotree
2017-01-31 19:50:02 -05:00
:commands (neotree-show
neotree-hide
neotree-toggle
neotree-dir
neotree-find
neo-global--with-buffer
neo-global--window-exists-p)
2017-02-21 16:04:35 -05:00
:config
(setq neo-create-file-auto-open nil
2017-01-31 19:50:02 -05:00
neo-auto-indent-point nil
neo-autorefresh nil
2017-01-31 19:50:02 -05:00
neo-mode-line-type 'none
neo-window-width 25
neo-show-updir-line nil
neo-theme 'nerd ; fallback
neo-banner-message nil
neo-show-hidden-files nil
neo-hidden-regexp-list
'(;; vcs folders
"^\\.\\(git\\|hg\\|svn\\)$"
;; compiled files
"\\.\\(pyc\\|o\\|elc\\|lock\\|css.map\\)$"
;; generated files, caches or local pkgs
"^\\(node_modules\\|vendor\\|.\\(project\\|cask\\|yardoc\\|sass-cache\\)\\)$"
;; org-mode folders
"^\\.\\(sync\\|export\\|attach\\)$"
"~$"
"^#.*#$"))
(set! :evil-state 'neotree-mode 'motion)
2017-01-31 19:50:02 -05:00
2017-02-19 18:14:46 -05:00
(push neo-buffer-name winner-boring-buffers)
(defun +evil*neotree-create-node (orig-fun &rest args)
"Don't ask for confirmation when creating files"
(cl-letf (((symbol-function 'yes-or-no-p) (lambda (&rest _) t)))
(apply orig-fun args)))
(advice-add #'neotree-create-node :around #'+evil*neotree-create-node)
2017-05-08 10:32:57 +02:00
;; `neotree-mode-map' are overridden when the neotree buffer is created. So we
;; bind them in a hook.
(add-hook 'neo-after-create-hook #'+evil|neotree-init-keymap)
2017-02-02 04:35:54 -05:00
(defun +evil|neotree-init-keymap (&rest _)
(map! :Lm "\\\\" 'evil-window-prev
2017-02-04 02:53:57 -05:00
:Lm "RET" 'neotree-enter
:Lm "<return>" 'neotree-enter
:Lm "ESC ESC" 'neotree-hide
:Lm [return] 'neotree-enter
:Lm "q" 'neotree-hide
:Lm "J" 'neotree-select-next-sibling-node
:Lm "K" 'neotree-select-previous-sibling-node
:Lm "H" 'neotree-select-up-node
:Lm "L" 'neotree-select-down-node
:Lm "h" '+evil/neotree-collapse-or-up
:Lm "j" 'neotree-next-line
:Lm "k" 'neotree-previous-line
:Lm "l" '+evil/neotree-expand-or-open
2017-02-04 02:53:57 -05:00
:Lm "v" 'neotree-enter-vertical-split
:Lm "s" 'neotree-enter-horizontal-split
:Lm "c" 'neotree-create-node
:Lm "d" 'neotree-delete-node
:Lm "\C-r" 'neotree-refresh
:Lm "r" 'neotree-rename-node
:Lm "R" 'neotree-change-root)))