From 5cd29479f449c2918e808bd4891e81f833467605 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Sat, 6 Jan 2018 18:50:49 -0500 Subject: [PATCH] Refactor feature/evil hacks & advice; fix tests --- core/autoload/menu.el | 1 - core/core-lib.el | 75 --------------------- core/test/core-lib.el | 42 ------------ modules/feature/evil/autoload/evil.el | 96 +++++++++++++++++++++++++++ modules/feature/evil/config.el | 72 ++++++++------------ modules/feature/evil/test/evil.el | 43 +++++++++++- 6 files changed, 166 insertions(+), 163 deletions(-) diff --git a/core/autoload/menu.el b/core/autoload/menu.el index bd19bbc43..325595187 100644 --- a/core/autoload/menu.el +++ b/core/autoload/menu.el @@ -25,7 +25,6 @@ PROMPT (a string) and COMMAND (a list of command plists; see `def-menu!').") (t default-directory)))) (cond ((stringp command) (with-current-buffer (get-buffer-create "*compilation*") - (setq command (doom-resolve-vim-path command)) (save-window-excursion (compile command)) (setq header-line-format diff --git a/core/core-lib.el b/core/core-lib.el index 6ea99c594..75eb54efc 100644 --- a/core/core-lib.el +++ b/core/core-lib.el @@ -48,81 +48,6 @@ "Return EXP wrapped in a list, or as-is if already a list." (if (listp exp) exp (list exp))) -(defun doom-resolve-vim-path (file-name) - "Take a path and resolve any vim-like filename modifiers in it. On top of the -classical vim modifiers, this adds support for: - - %:P Resolves to `doom-project-root'. - -See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers." - (let* (case-fold-search - (regexp (concat "\\(?:^\\|[^\\\\]\\)" - "\\([#%]\\)" - "\\(\\(?::\\(?:[PphtreS~.]\\|g?s[^:\t\n ]+\\)\\)*\\)")) - (matches - (cl-loop with i = 0 - while (and (< i (length file-name)) - (string-match regexp file-name i)) - do (setq i (1+ (match-beginning 0))) - and collect - (cl-loop for j to (/ (length (match-data)) 2) - collect (match-string j file-name))))) - (dolist (match matches) - (let ((flags (split-string (car (cdr (cdr match))) ":" t)) - (path (and buffer-file-name - (pcase (car (cdr match)) - ("%" (file-relative-name buffer-file-name)) - ("#" (save-excursion (other-window 1) (file-relative-name buffer-file-name)))))) - flag global) - (if (not path) - (setq path "") - (while flags - (setq flag (pop flags)) - (when (string-suffix-p "\\" flag) - (setq flag (concat flag (pop flags)))) - (when (string-prefix-p "gs" flag) - (setq global t - flag (substring flag 1))) - (setq path - (or (pcase (substring flag 0 1) - ("p" (expand-file-name path)) - ("~" (concat "~/" (file-relative-name path "~"))) - ("." (file-relative-name path default-directory)) - ("t" (file-name-nondirectory (directory-file-name path))) - ("r" (file-name-sans-extension path)) - ("e" (file-name-extension path)) - ("S" (shell-quote-argument path)) - ("h" - (let ((parent (file-name-directory (expand-file-name path)))) - (unless (equal (file-truename path) - (file-truename parent)) - (if (file-name-absolute-p path) - (directory-file-name parent) - (file-relative-name parent))))) - ("s" - (if (featurep 'evil) - (when-let* ((args (evil-delimited-arguments (substring flag 1) 2))) - (let ((pattern (evil-transform-vim-style-regexp (car args))) - (replace (cadr args))) - (replace-regexp-in-string - (if global pattern (concat "\\(" pattern "\\).*\\'")) - (evil-transform-vim-style-regexp replace) path t t - (unless global 1)))) - path)) - ("P" - (let ((default-directory (file-name-directory (expand-file-name path)))) - (abbreviate-file-name (doom-project-root)))) - (_ path)) - ""))) - ;; strip trailing slash, if applicable - (when (and (not (string= path "")) (equal (substring path -1) "/")) - (setq path (substring path 0 -1)))) - (setq file-name - (replace-regexp-in-string (format "\\(?:^\\|[^\\\\]\\)\\(%s\\)" - (regexp-quote (string-trim-left (car match)))) - path file-name t t 1)))) - (replace-regexp-in-string regexp "\\1" file-name t))) - ;; ;; Library diff --git a/core/test/core-lib.el b/core/test/core-lib.el index 30e59a690..d001f0aa9 100644 --- a/core/test/core-lib.el +++ b/core/test/core-lib.el @@ -32,48 +32,6 @@ (should (equal (doom-enlist 'a) '(a))) (should (equal (doom-enlist '(a)) '(a)))) -;; `doom-resolve-vim-path' -(def-test! resolve-vim-path - (cl-flet ((do-it #'doom-resolve-vim-path)) - ;; file modifiers - (let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el") - (default-directory "~/.emacs.d/test/modules/")) - (should (equal (do-it "%") "feature/test-evil.el")) - (should (equal (do-it "%:r") "feature/test-evil")) - (should (equal (do-it "%:r.elc") "feature/test-evil.elc")) - (should (equal (do-it "%:e") "el")) - (should (equal (do-it "%:p") (expand-file-name buffer-file-name))) - (should (equal (do-it "%:h") "feature")) - (should (equal (do-it "%:t") "test-evil.el")) - (should (equal (do-it "%:.") "feature/test-evil.el")) - (should (equal (do-it "%:~") "~/.emacs.d/test/modules/feature/test-evil.el")) - (should (equal (file-truename (do-it "%:p")) - (file-truename buffer-file-name)))) - ;; nested file modifiers - (let ((buffer-file-name "~/vim/src/version.c") - (default-directory "~/vim/")) - (should (equal (do-it "%:p") (expand-file-name "~/vim/src/version.c"))) - (should (equal (do-it "%:p:.") "src/version.c")) - (should (equal (do-it "%:p:~") "~/vim/src/version.c")) - (should (equal (do-it "%:h") "src")) - (should (equal (do-it "%:p:h") (expand-file-name "~/vim/src"))) - (should (equal (do-it "%:p:h:h") (expand-file-name "~/vim"))) - (should (equal (do-it "%:t") "version.c")) - (should (equal (do-it "%:p:t") "version.c")) - (should (equal (do-it "%:r") "src/version")) - (should (equal (do-it "%:p:r") (expand-file-name "~/vim/src/version"))) - (should (equal (do-it "%:t:r") "version"))) - ;; empty file modifiers - (let (buffer-file-name default-directory) - (should (equal (do-it "%") "")) - (should (equal (do-it "%:r") "")) - (should (equal (do-it "%:e") "")) - (should (equal (do-it "%:h") "")) - (should (equal (do-it "%:t") "")) - (should (equal (do-it "%:.") "")) - (should (equal (do-it "%:~") "")) - (should (equal (do-it "%:P") ""))))) - ;; --- Macros ----------------------------- diff --git a/modules/feature/evil/autoload/evil.el b/modules/feature/evil/autoload/evil.el index 893cf10bc..ea6c0a0c4 100644 --- a/modules/feature/evil/autoload/evil.el +++ b/modules/feature/evil/autoload/evil.el @@ -193,3 +193,99 @@ evil-window-move-* (e.g. `evil-window-move-far-left')" (goto-char beg) (call-interactively #'wgrep-mark-deletion)) beg (1- end) nil)))) + + +;; +;; Advice +;; + +;;;###autoload +(defun +evil*static-reindent (orig-fn &rest args) + "Don't move cursor on indent." + (save-excursion (apply orig-fn args))) + +;;;###autoload +(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 emacs)) + (evil-normal-state +1)) + (apply orig-fn args)) + +;;;###autoload +(defun +evil*resolve-vim-path (file-name) + "Take a path and resolve any vim-like filename modifiers in it. This adds +support for most vim file modifiers, as well as: + + %:P Resolves to `doom-project-root'. + +See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers for +more information on modifiers." + (let* (case-fold-search + (regexp (concat "\\(?:^\\|[^\\\\]\\)" + "\\([#%]\\)" + "\\(\\(?::\\(?:[PphtreS~.]\\|g?s[^:\t\n ]+\\)\\)*\\)")) + (matches + (cl-loop with i = 0 + while (and (< i (length file-name)) + (string-match regexp file-name i)) + do (setq i (1+ (match-beginning 0))) + and collect + (cl-loop for j to (/ (length (match-data)) 2) + collect (match-string j file-name))))) + (dolist (match matches) + (let ((flags (split-string (car (cdr (cdr match))) ":" t)) + (path (and buffer-file-name + (pcase (car (cdr match)) + ("%" (file-relative-name buffer-file-name)) + ("#" (save-excursion (other-window 1) (file-relative-name buffer-file-name)))))) + flag global) + (if (not path) + (setq path "") + (while flags + (setq flag (pop flags)) + (when (string-suffix-p "\\" flag) + (setq flag (concat flag (pop flags)))) + (when (string-prefix-p "gs" flag) + (setq global t + flag (substring flag 1))) + (setq path + (or (pcase (substring flag 0 1) + ("p" (expand-file-name path)) + ("~" (concat "~/" (file-relative-name path "~"))) + ("." (file-relative-name path default-directory)) + ("t" (file-name-nondirectory (directory-file-name path))) + ("r" (file-name-sans-extension path)) + ("e" (file-name-extension path)) + ("S" (shell-quote-argument path)) + ("h" + (let ((parent (file-name-directory (expand-file-name path)))) + (unless (equal (file-truename path) + (file-truename parent)) + (if (file-name-absolute-p path) + (directory-file-name parent) + (file-relative-name parent))))) + ("s" + (if (featurep 'evil) + (when-let* ((args (evil-delimited-arguments (substring flag 1) 2))) + (let ((pattern (evil-transform-vim-style-regexp (car args))) + (replace (cadr args))) + (replace-regexp-in-string + (if global pattern (concat "\\(" pattern "\\).*\\'")) + (evil-transform-vim-style-regexp replace) path t t + (unless global 1)))) + path)) + ("P" + (let ((default-directory (file-name-directory (expand-file-name path)))) + (abbreviate-file-name (doom-project-root)))) + (_ path)) + ""))) + ;; strip trailing slash, if applicable + (when (and (not (string= path "")) (equal (substring path -1) "/")) + (setq path (substring path 0 -1)))) + (setq file-name + (replace-regexp-in-string (format "\\(?:^\\|[^\\\\]\\)\\(%s\\)" + (regexp-quote (string-trim-left (car match)))) + path file-name t t 1)))) + (replace-regexp-in-string regexp "\\1" file-name t))) diff --git a/modules/feature/evil/config.el b/modules/feature/evil/config.el index 1b6f086ad..e2116ad26 100644 --- a/modules/feature/evil/config.el +++ b/modules/feature/evil/config.el @@ -45,9 +45,6 @@ (set! :popup "^\\*evil-registers" '((size . 0.3))) (set! :popup "^\\*Command Line" '((size . 8))) - ;; Don't interfere with localleader key - (define-key evil-motion-state-map "\\" nil) - ;; Set cursor colors later, once theme is loaded (defun +evil*init-cursors (&rest _) (setq evil-default-cursor (face-background 'cursor nil t) @@ -63,19 +60,11 @@ (dolist (mode '(help-mode debugger-mode)) (evil-set-initial-state mode 'normal)) - ;; make `try-expand-dabbrev' from `hippie-expand' work in minibuffer - ;; @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) - ;; --- keybind fixes ---------------------- (map! (:after wgrep - ;; a wrapper that invokes `wgrep-mark-deletion' across lines - ;; you use `evil-delete' on. + ;; A wrapper that invokes `wgrep-mark-deletion' across lines you use + ;; `evil-delete' in wgrep buffers. :map wgrep-mode-map [remap evil-delete] #'+evil-delete) ;; replace native folding commands @@ -86,10 +75,6 @@ [remap evil-close-folds] #'+evil:fold-close-all [remap evil-open-folds] #'+evil:fold-open-all) - - ;; --- evil hacks ------------------------- - (advice-add #'evil-force-normal-state :after #'doom/escape) - (defun +evil|disable-highlights () "Disable ex search buffer highlights." (when (evil-ex-hl-active-p 'evil-ex-search) @@ -97,28 +82,40 @@ t)) (add-hook 'doom-escape-hook #'+evil|disable-highlights) - (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 emacs)) - (evil-normal-state +1)) - (apply orig-fn args)) + + ;; --- evil hacks ------------------------- + ;; Make ESC (from normal mode) the universal escaper. See `doom-escape-hook'. + (advice-add #'evil-force-normal-state :after #'doom/escape) + ;; Ensure buffer is in normal mode when we leave it and return to it. (advice-add #'windmove-do-window-select :around #'+evil*restore-normal-state-on-windmove) - - (defun +evil*static-reindent (orig-fn &rest args) - "Don't move cursor on indent." - (save-excursion (apply orig-fn args))) + ;; Don't move cursor when indenting (advice-add #'evil-indent :around #'+evil*static-reindent) - ;; monkey patch `evil-ex-replace-special-filenames' to add more ex ;; substitution flags to evil-mode - (advice-add #'evil-ex-replace-special-filenames :override #'doom-resolve-vim-path) + (advice-add #'evil-ex-replace-special-filenames :override #'+evil*resolve-vim-path) + + ;; make `try-expand-dabbrev' from `hippie-expand' work in minibuffer + ;; @see `he-dabbrev-beg', so we need re-define syntax for '/' + (defun +evil*fix-dabbrev-in-minibuffer () + (set-syntax-table (let* ((table (make-syntax-table))) + (modify-syntax-entry ?/ "." table) + table))) + (add-hook 'minibuffer-inactive-mode-hook #'+evil*fix-dabbrev-in-minibuffer) + + ;; Move to new split -- setting `evil-split-window-below' & + ;; `evil-vsplit-window-right' to non-nil mimics this, but that doesn't update + ;; window history. That means when you delete a new split, Emacs leaves you on + ;; the 2nd to last window on the history stack, which is jarring. + ;; + ;; Also recenters window on cursor in new split + (defun +evil*window-follow (&rest _) (evil-window-down 1) (recenter)) + (advice-add #'evil-window-split :after #'+evil*window-follow) + (defun +evil*window-vfollow (&rest _) (evil-window-right 1) (recenter)) + (advice-add #'evil-window-vsplit :after #'+evil*window-vfollow) ;; These arg types will highlight matches in the current buffer (evil-ex-define-argument-type buffer-match :runner +evil-ex-buffer-match) (evil-ex-define-argument-type global-match :runner +evil-ex-global-match) - ;; By default :g[lobal] doesn't highlight matches in the current buffer. I've ;; got to write my own argument type and interactive code to get it to do so. (evil-ex-define-argument-type global-delim-match :runner +evil-ex-global-delim-match) @@ -137,18 +134,7 @@ across windows." (evil-set-command-properties '+evil:align :move-point t :ex-arg 'buffer-match :ex-bang t :evil-mc t :keep-visual t :suppress-operator t) (evil-set-command-properties - '+evil:mc :move-point nil :ex-arg 'global-match :ex-bang t :evil-mc t) - - ;; Move to new split -- setting `evil-split-window-below' & - ;; `evil-vsplit-window-right' to non-nil mimics this, but that doesn't update - ;; window history. That means when you delete a new split, Emacs leaves you on - ;; the 2nd to last window on the history stack, which is jarring. - ;; - ;; Also recenters window on cursor in new split - (defun +evil*window-follow (&rest _) (evil-window-down 1) (recenter)) - (defun +evil*window-vfollow (&rest _) (evil-window-right 1) (recenter)) - (advice-add #'evil-window-split :after #'+evil*window-follow) - (advice-add #'evil-window-vsplit :after #'+evil*window-vfollow)) + '+evil:mc :move-point nil :ex-arg 'global-match :ex-bang t :evil-mc t)) ;; diff --git a/modules/feature/evil/test/evil.el b/modules/feature/evil/test/evil.el index ffe6f92a1..9cad36c70 100644 --- a/modules/feature/evil/test/evil.el +++ b/modules/feature/evil/test/evil.el @@ -5,8 +5,47 @@ ;; ;; `evil-ex-replace-special-filenames' -;; NOTE The majority of this function is tested in core/test/core-lib.el, this -;; only tests the evil-mode-specific functionality. +(def-test! resolve-vim-path + (cl-flet ((do-it #'evil-ex-replace-special-filenames)) + ;; file modifiers + (let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el") + (default-directory "~/.emacs.d/test/modules/")) + (should (equal (do-it "%") "feature/test-evil.el")) + (should (equal (do-it "%:r") "feature/test-evil")) + (should (equal (do-it "%:r.elc") "feature/test-evil.elc")) + (should (equal (do-it "%:e") "el")) + (should (equal (do-it "%:p") (expand-file-name buffer-file-name))) + (should (equal (do-it "%:h") "feature")) + (should (equal (do-it "%:t") "test-evil.el")) + (should (equal (do-it "%:.") "feature/test-evil.el")) + (should (equal (do-it "%:~") "~/.emacs.d/test/modules/feature/test-evil.el")) + (should (equal (file-truename (do-it "%:p")) + (file-truename buffer-file-name)))) + ;; nested file modifiers + (let ((buffer-file-name "~/vim/src/version.c") + (default-directory "~/vim/")) + (should (equal (do-it "%:p") (expand-file-name "~/vim/src/version.c"))) + (should (equal (do-it "%:p:.") "src/version.c")) + (should (equal (do-it "%:p:~") "~/vim/src/version.c")) + (should (equal (do-it "%:h") "src")) + (should (equal (do-it "%:p:h") (expand-file-name "~/vim/src"))) + (should (equal (do-it "%:p:h:h") (expand-file-name "~/vim"))) + (should (equal (do-it "%:t") "version.c")) + (should (equal (do-it "%:p:t") "version.c")) + (should (equal (do-it "%:r") "src/version")) + (should (equal (do-it "%:p:r") (expand-file-name "~/vim/src/version"))) + (should (equal (do-it "%:t:r") "version"))) + ;; empty file modifiers + (let (buffer-file-name default-directory) + (should (equal (do-it "%") "")) + (should (equal (do-it "%:r") "")) + (should (equal (do-it "%:e") "")) + (should (equal (do-it "%:h") "")) + (should (equal (do-it "%:t") "")) + (should (equal (do-it "%:.") "")) + (should (equal (do-it "%:~") "")) + (should (equal (do-it "%:P") ""))))) + (def-test! file-modifiers (cl-flet ((do-it #'evil-ex-replace-special-filenames)) (let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el")