editor/evil: port vim-unimpaired keybinds

Adds these keys, inspired by tpope/vim-unimpaired:

[ SPC, ] SPC (or [o, ]o)
  Add COUNT blank lines above/below the cursor
[f, ]f
  Visit previous/next file in current folder, alphabetically
[u, ]u
  Url encode/decode operators
[y, ]y
  C-string-style escaping/unescaping operators (escapes quotes,
  backslashes and control characters)
[x, ]x
  XML entity encoding/decoding operators (only if :lang web is enabled)
[F, ]F
  Focus previous/next frame (decided this is better than ]t/[t which is
  being used by hl-todo-{next,previous})

We already had gp and ]b/[b (buffers).

We're not going to port ]e/[e because it is redundant with ddp/ddP or gx
(evil-exchange). I also think these keybinds are better suited to
{next,previous}-error.
This commit is contained in:
Henrik Lissner 2019-06-15 16:17:44 +02:00
parent be3f6fb38f
commit 0b1ecb8105
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
3 changed files with 166 additions and 23 deletions

View file

@ -54,27 +54,49 @@
;; misc
:n "C-S-f" #'toggle-frame-fullscreen
;; Global evil keybinds
:m "]a" #'evil-forward-arg
:m "[a" #'evil-backward-arg
:m "]o" #'outline-next-visible-heading
:m "[o" #'outline-previous-visible-heading
;; ported vim keys
:nv "z=" #'flyspell-correct-word-generic
:v "@" #'+evil:apply-macro
;; ported from vim-unimpaired
:n "] SPC" #'+evil/insert-newline-below
:n "[ SPC" #'+evil/insert-newline-above
:n "]b" #'next-buffer
:n "[b" #'previous-buffer
:n "zx" #'kill-current-buffer
:n "ZX" #'bury-buffer
:n "]f" #'+evil/next-file
:n "[f" #'+evil/previous-file
:m "]u" #'+evil:url-encode
:m "[u" #'+evil:url-decode
:m "]y" #'+evil:c-string-encode
:m "[y" #'+evil:c-string-decode
;; NOTE hl-todo-{next,previous} have ]t/[t, use ]F/[F instead
;; NOTE {next,previous}-error have ]e/[e, use ddp/ddP or gx instead
(:when (featurep! :lang web)
:m "]x" #'+web:encode-html-entities
:m "[x" #'+web:decode-html-entities)
;; custom vim-unmpaired-esque keys
:m "]a" #'evil-forward-arg
:m "[a" #'evil-backward-arg
:n "]F" #'+evil/next-frame
:n "[F" #'+evil/previous-frame
:m "]h" #'outline-next-visible-heading
:m "[h" #'outline-previous-visible-heading
:n "[o" #'+evil/insert-newline-above
:n "]o" #'+evil/insert-newline-below
:n "gp" #'+evil/reselect-paste
:v "gp" #'+evil/paste-preserve-register
:nv "g@" #'+evil:apply-macro
:nv "gc" #'evil-commentary
:nv "gx" #'evil-exchange
:n "g=" #'evil-numbers/inc-at-pt
:n "g-" #'evil-numbers/dec-at-pt
:v "g=" #'evil-numbers/inc-at-pt-incremental
:v "g-" #'evil-numbers/dec-at-pt-incremental
:v "g+" #'evil-numbers/inc-at-pt
:nv "z=" #'flyspell-correct-word-generic
:nv "g@" #'+evil:apply-macro
:nv "gc" #'evil-commentary
:nv "gx" #'evil-exchange
:v "gp" #'+evil/paste-preserve-register
:v "@" #'+evil:apply-macro
;; custom evil keybinds
:n "zx" #'kill-current-buffer
:n "ZX" #'bury-buffer
;; repeat in visual mode (FIXME buggy)
:v "." #'+evil:apply-macro
;; don't leave visual mode after shifting

View file

@ -31,16 +31,6 @@
(evil-normal-state)
(evil-visual-restore))
;;;###autoload
(defun +evil/reselect-paste ()
"Return to visual mode and reselect the last pasted region."
(interactive)
(cl-destructuring-bind (_ _ _ beg end &optional _)
evil-last-paste
(evil-visual-make-selection
(save-excursion (goto-char beg) (point-marker))
end)))
;;;###autoload
(defun +evil/paste-preserve-register ()
"Call `evil-paste-after' without overwriting the clipboard (by writing to the

View file

@ -0,0 +1,131 @@
;;; editor/evil/autoload/unimpaired.el -*- lexical-binding: t; -*-
;; These are ported from vim-unimpaired https://github.com/tpope/vim-unimpaired
;; and bound in the :config default module (in +evil-bindings.el).
;;
;;; Next/Previous commands
;;; ] SPC / [ SPC
;;;###autoload
(defun +evil/insert-newline-below (count)
"Insert COUNT blank line(s) below current line. Does not change modes."
(interactive "p")
(dotimes (_ count)
(save-excursion (evil-insert-newline-below))))
;;;###autoload
(defun +evil/insert-newline-above (count)
"Insert COUNT blank line(s) above current line. Does not change modes."
(interactive "p")
(dotimes (_ count)
(save-excursion (evil-insert-newline-above))))
;;; ]t / [t
;;;###autoload
(defun +evil/next-frame (count)
"Focus next frame."
(interactive "p")
(dotimes (_ (abs count))
(let ((frame (if (> count 0) (next-frame) (previous-frame))))
(if (eq frame (selected-frame))
(user-error "No other frame")
(select-frame-set-input-focus frame)))))
;;;###autoload
(defun +evil/previous-frame (count)
"Focus previous frame."
(interactive "p")
(+evil/next-frame (- count)))
;;; ]f / [f
(defun +evil--next-file (n)
(unless buffer-file-name
(user-error "Must be called from a file-visiting buffer"))
(let* ((directory (file-name-directory buffer-file-name))
(filename (file-name-nondirectory buffer-file-name))
(files (doom-files-in directory :depth 0 :sort t :match "/[^._][^/]*$"))
(index (cl-position filename files :test #'string=)))
(when (null index)
(user-error "Couldn't find this file in current directory"))
(let ((index (+ index n)))
(cond ((>= index (length files))
(user-error "No files after this one"))
((< index 0)
(user-error "No files before this one"))
((expand-file-name (nth index files) directory))))))
;;;###autoload
(defun +evil/next-file (count)
"Open file following this one, alphabetically, in the same directory."
(interactive "p")
(find-file (+evil--next-file count)))
;;;###autoload
(defun +evil/previous-file (count)
"Open file preceding this one, alphabetically, in the same directory."
(interactive "p")
(find-file (+evil--next-file (- count))))
;;
;;; Encoding/Decoding
;; NOTE For ]x / [x see :lang web
;; - `+web:encode-html-entities'
;; - `+web:decode-html-entities'
(defun +evil--encode (beg end fn)
(save-excursion
(goto-char beg)
(let* ((end (if (eq evil-this-type 'line) (1- end) end))
(text (buffer-substring-no-properties beg end)))
(delete-region beg end)
(insert (funcall fn text)))))
;;; ]u / [u
;;;###autoload (autoload '+evil:url-encode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:url-encode (count &optional beg end type)
"TODO"
(interactive "<c><R>")
(+evil--encode beg end #'url-encode-url))
;;;###autoload (autoload '+evil:url-decode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:url-decode (count &optional beg end type)
"TODO"
(interactive "<c><R>")
(+evil--encode beg end #'url-unhex-string))
;;; ]y / [y
;;;###autoload (autoload '+evil:c-string-encode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:c-string-encode (count &optional beg end type)
"TODO"
(interactive "<c><R>")
(+evil--encode
beg end
(lambda (text)
(replace-regexp-in-string "[\"\\]" (lambda (ch) (concat "\\" ch)) text))))
;;;###autoload (autoload '+evil:c-string-decode "editor/evil/autoload/unimpaired" nil t)
(evil-define-operator +evil:c-string-decode (count &optional beg end type)
"TODO"
(interactive "<c><R>")
(+evil--encode
beg end
(lambda (text)
(replace-regexp-in-string "\\\\[\"\\]" (lambda (str) (substring str 1)) text))))
;;
;;; Standalone
;;; gp
;;;###autoload
(defun +evil/reselect-paste ()
"Return to visual mode and reselect the last pasted region."
(interactive)
(cl-destructuring-bind (_ _ _ beg end &optional _)
evil-last-paste
(evil-visual-make-selection
(save-excursion (goto-char beg) (point-marker))
end)))