├── README.md └── evil-jumper.el /README.md: -------------------------------------------------------------------------------- 1 | # evil-jumper [![MELPA](https://melpa.org/packages/evil-jumper-badge.svg)](https://melpa.org/#/evil-jumper) 2 | 3 | evil-jumper is an add-on for **older versions** of [evil-mode][1] (prior to Feb 2016) which replaces the implementation of the jump list such that it mimics more closely with Vim's behavior. Specifically, it will jump across buffer boundaries and revive dead buffers if necessary. The jump list can also be persisted to history file using `savehist` and restored between sessions. 4 | 5 | # package status 6 | 7 | evil-jumper is **OBSOLETE**. evil-jumper has been merged into upstream evil-mode; therefore this functionality is already available by default. 8 | 9 | # installation 10 | 11 | Install `evil-jumper` from [MELPA][3]. 12 | 13 | # usage 14 | 15 | After installation, a global minor mode `evil-jumper-mode` will be made available. 16 | 17 | # license 18 | 19 | [GPL3][2] 20 | 21 | [1]: https://bitbucket.org/lyro/evil/wiki/Home 22 | [2]: http://www.gnu.org/copyleft/gpl.html 23 | [3]: http://melpa.org 24 | -------------------------------------------------------------------------------- /evil-jumper.el: -------------------------------------------------------------------------------- 1 | ;;; evil-jumper.el --- Jump like vimmers do! 2 | 3 | ;; Copyright (C) 2014-2016 by Bailey Ling 4 | ;; Author: Bailey Ling 5 | ;; URL: https://github.com/bling/evil-jumper 6 | ;; Filename: evil-jumper.el 7 | ;; Description: Jump like vimmers do! (for older versions of evil-mode) 8 | ;; Created: 2014-07-01 9 | ;; Version: 0.3.1 10 | ;; Keywords: evil vim jumplist jump list 11 | ;; Package-Requires: ((evil "0") (cl-lib "0.5")) 12 | ;; 13 | ;; This file is not part of GNU Emacs. 14 | ;; 15 | ;; This program is free software; you can redistribute it and/or 16 | ;; modify it under the terms of the GNU General Public License as 17 | ;; published by the Free Software Foundation; either version 3, or 18 | ;; (at your option) any later version. 19 | ;; 20 | ;; This program is distributed in the hope that it will be useful, 21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 | ;; General Public License for more details. 24 | ;; 25 | ;; You should have received a copy of the GNU General Public License 26 | ;; along with this program; see the file COPYING. If not, write to 27 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 28 | ;; Floor, Boston, MA 02110-1301, USA. 29 | ;; 30 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 31 | 32 | ;;; Commentary: 33 | ;; 34 | ;; evil-jumper is an add-on for older versions of evil-mode (prior 35 | ;; to Feb 2016) which replaces the implementation the jump list such 36 | ;; that it mimics more closely with Vim's behavior. Specifically, it 37 | ;; will jump across buffer boundaries and revive dead buffers if 38 | ;; necessary. The jump list can also be persisted to history file 39 | ;; using `savehist' and restored 40 | ;; between sessions. 41 | ;; 42 | ;; Install: 43 | ;; 44 | ;; (require 'evil-jumper) 45 | ;; 46 | ;; Usage: 47 | ;; 48 | ;; (evil-jumper-mode t) 49 | 50 | ;;; Code: 51 | 52 | (require 'cl-lib) 53 | (require 'evil) 54 | 55 | (defgroup evil-jumper nil 56 | "evil-jumper configuration options." 57 | :prefix "evil-jumper" 58 | :group 'evil) 59 | 60 | (defcustom evil-jumper-max-length 100 61 | "The maximum number of jumps to keep track of." 62 | :type 'integer 63 | :group 'evil-jumper) 64 | 65 | (defcustom evil-jumper-pre-jump-hook nil 66 | "Hooks to run just before jumping to a location in the jump list." 67 | :type 'hook 68 | :group 'evil-jumper) 69 | 70 | (defcustom evil-jumper-post-jump-hook nil 71 | "Hooks to run just after jumping to a location in the jump list." 72 | :type 'hook 73 | :group 'evil-jumper) 74 | 75 | (defcustom evil-jumper-ignored-file-patterns '("COMMIT_EDITMSG$" "TAGS$") 76 | "A list of pattern regexps to match on the file path to exclude from being included in the jump list." 77 | :type '(repeat string) 78 | :group 'evil-jumper) 79 | 80 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 81 | 82 | (defvar evil-jumper--jumping nil) 83 | (defvar evil-jumper--debug nil) 84 | (defvar evil-jumper--wired nil) 85 | 86 | (defvar evil-jumper--buffer-targets "\\*\\(new\\|scratch\\)\\*" 87 | "Regexp to match against `buffer-name' to determine whether it's a valid jump target.") 88 | 89 | (defvar evil-jumper--window-jumps (make-hash-table) 90 | "Hashtable which stores all jumps on a per window basis.") 91 | 92 | (defvar evil-jumper--jump-list nil 93 | "Printable version of `evil-jumper--window-jumps'.") 94 | 95 | (cl-defstruct evil-jumper-struct 96 | jumps 97 | (idx -1)) 98 | 99 | (defun evil-jumper--message (format &rest args) 100 | (when evil-jumper--debug 101 | (setq format (concat "evil-jumper: " format)) 102 | (apply 'message format args))) 103 | 104 | (defun evil-jumper--get-current (&optional window) 105 | (unless window 106 | (setq window (frame-selected-window))) 107 | (let* ((jump-struct (gethash window evil-jumper--window-jumps))) 108 | (unless jump-struct 109 | (setq jump-struct (make-evil-jumper-struct)) 110 | (puthash window jump-struct evil-jumper--window-jumps)) 111 | jump-struct)) 112 | 113 | (defun evil-jumper--get-window-jump-list () 114 | (let ((struct (evil-jumper--get-current))) 115 | (evil-jumper-struct-jumps struct))) 116 | 117 | (defun evil-jumper--set-window-jump-list (list) 118 | (let ((struct (evil-jumper--get-current))) 119 | (setf (evil-jumper-struct-jumps struct) list))) 120 | 121 | (defun evil-jumper--savehist-sync () 122 | "Updates the printable value of window jumps for `savehist'." 123 | (setq evil-jumper--jump-list 124 | (cl-remove-if-not #'identity 125 | (mapcar #'(lambda (jump) 126 | (let* ((mark (car jump)) 127 | (pos (if (markerp mark) 128 | (marker-position mark) 129 | mark)) 130 | (file-name (cadr jump))) 131 | (if (and (not (file-remote-p file-name)) 132 | (file-exists-p file-name) 133 | pos) 134 | (list pos file-name) 135 | nil))) 136 | (evil-jumper--get-window-jump-list))))) 137 | 138 | (defun evil-jumper--jump-to-index (idx) 139 | (let ((target-list (evil-jumper--get-window-jump-list))) 140 | (when (and (< idx (length target-list)) 141 | (>= idx 0)) 142 | (run-hooks 'evil-jumper-pre-jump-hook) 143 | (setf (evil-jumper-struct-idx (evil-jumper--get-current)) idx) 144 | (let* ((place (nth idx target-list)) 145 | (pos (car place)) 146 | (file-name (cadr place))) 147 | (setq evil-jumper--jumping t) 148 | (if (string-match-p evil-jumper--buffer-targets file-name) 149 | (switch-to-buffer file-name) 150 | (find-file file-name)) 151 | (setq evil-jumper--jumping nil) 152 | (goto-char pos) 153 | (run-hooks 'evil-jumper-post-jump-hook))))) 154 | 155 | (defun evil-jumper--push () 156 | "Pushes the current cursor/file position to the jump list." 157 | (let ((target-list (evil-jumper--get-window-jump-list))) 158 | (while (> (length target-list) evil-jumper-max-length) 159 | (nbutlast target-list 1)) 160 | (let ((file-name (buffer-file-name)) 161 | (buffer-name (buffer-name)) 162 | (current-pos (point-marker)) 163 | (first-pos nil) 164 | (first-file-name nil) 165 | (excluded nil)) 166 | (when (and (not file-name) 167 | (string-match-p evil-jumper--buffer-targets buffer-name)) 168 | (setq file-name buffer-name)) 169 | (when file-name 170 | (dolist (pattern evil-jumper-ignored-file-patterns) 171 | (when (string-match-p pattern file-name) 172 | (setq excluded t))) 173 | (unless excluded 174 | (when target-list 175 | (setq first-pos (caar target-list)) 176 | (setq first-file-name (car (cdar target-list)))) 177 | (unless (and (equal first-pos current-pos) 178 | (equal first-file-name file-name)) 179 | (push `(,current-pos ,file-name) target-list))))) 180 | (evil-jumper--message "%s %s" (selected-window) (car target-list)) 181 | (evil-jumper--set-window-jump-list target-list))) 182 | 183 | (defun evil-jumper--set-jump () 184 | (unless evil-jumper--jumping 185 | ;; clear out intermediary jumps when a new one is set 186 | (let* ((struct (evil-jumper--get-current)) 187 | (target-list (evil-jumper-struct-jumps struct)) 188 | (idx (evil-jumper-struct-idx struct))) 189 | (nbutlast target-list idx) 190 | (setf (evil-jumper-struct-jumps struct) target-list) 191 | (setf (evil-jumper-struct-idx struct) -1)) 192 | (evil-jumper--push))) 193 | 194 | (evil-define-motion evil-jumper/backward (count) 195 | (let ((count (or count 1))) 196 | (evil-motion-loop (nil count) 197 | (let* ((struct (evil-jumper--get-current)) 198 | (idx (evil-jumper-struct-idx struct))) 199 | (when (= idx -1) 200 | (setq idx (+ idx 1)) 201 | (setf (evil-jumper-struct-idx struct) idx) 202 | (evil-jumper--push)) 203 | (evil-jumper--jump-to-index (+ idx 1)))))) 204 | 205 | (evil-define-motion evil-jumper/forward (count) 206 | (let ((count (or count 1))) 207 | (evil-motion-loop (nil count) 208 | (let* ((struct (evil-jumper--get-current)) 209 | (idx (evil-jumper-struct-idx struct))) 210 | (evil-jumper--jump-to-index (- idx 1)))))) 211 | 212 | (defun evil-jumper--window-configuration-hook (&rest args) 213 | (let* ((window-list (window-list-1 nil nil t)) 214 | (existing-window (selected-window)) 215 | (new-window (previous-window))) 216 | (when (and (not (eq existing-window new-window)) 217 | (> (length window-list) 1)) 218 | (let* ((target-jump-struct (evil-jumper--get-current new-window)) 219 | (target-jump-count (length (evil-jumper-struct-jumps target-jump-struct)))) 220 | (if (evil-jumper-struct-jumps target-jump-struct) 221 | (evil-jumper--message "target window %s already has %s jumps" new-window target-jump-count) 222 | (evil-jumper--message "new target window detected; copying %s to %s" existing-window new-window) 223 | (let* ((source-jump-struct (evil-jumper--get-current existing-window)) 224 | (source-list (evil-jumper-struct-jumps source-jump-struct))) 225 | (when (= (length (evil-jumper-struct-jumps target-jump-struct)) 0) 226 | (setf (evil-jumper-struct-idx target-jump-struct) (evil-jumper-struct-idx source-jump-struct)) 227 | (setf (evil-jumper-struct-jumps target-jump-struct) (copy-sequence source-list))))))) 228 | ;; delete obsolete windows 229 | (maphash (lambda (key val) 230 | (unless (member key window-list) 231 | (evil-jumper--message "removing %s" key) 232 | (remhash key evil-jumper--window-jumps))) 233 | evil-jumper--window-jumps))) 234 | 235 | (defun evil-jumper--savehist-init () 236 | (unless evil-jumper--wired 237 | (evil-jumper--set-window-jump-list evil-jumper--jump-list) 238 | (eval-after-load 'savehist 239 | '(progn 240 | (push 'evil-jumper--jump-list savehist-additional-variables) 241 | (add-hook 'savehist-save-hook #'evil-jumper--savehist-sync))) 242 | (setq evil-jumper--wired t))) 243 | 244 | ;;;###autoload 245 | (define-minor-mode evil-jumper-mode 246 | "Global minor mode for vim jumplist emulation." 247 | :global t 248 | :keymap (let ((map (make-sparse-keymap))) 249 | (evil-define-key 'normal map [remap evil-jump-backward] #'evil-jumper/backward) 250 | (evil-define-key 'normal map [remap evil-jump-forward] #'evil-jumper/forward) 251 | map) 252 | (when (fboundp 'evil-jumps-struct-p) 253 | (message "evil-jumper has been integrated into evil-mode and is obsolete.") 254 | (setq evil-jumper-mode nil)) 255 | 256 | (if evil-jumper-mode 257 | (progn 258 | (if (boundp 'evil-jumper-file) 259 | (message "The variable `evil-jumper-file' is obsolete. Persistence is done with `savehist' now.")) 260 | (if (boundp 'evil-jumper-auto-center) 261 | (message "The variable `evil-jumper-auto-center' is obsolete. It has been replaced with `evil-jumper-post-jump-hook'.")) 262 | (evil-jumper--savehist-init) 263 | (add-hook 'next-error-hook #'evil-jumper--set-jump) 264 | (add-hook 'window-configuration-change-hook #'evil-jumper--window-configuration-hook) 265 | (defadvice evil-set-jump (after evil-jumper activate) 266 | (evil-jumper--set-jump)) 267 | (defadvice switch-to-buffer (before evil-jumper activate) 268 | (evil-jumper--set-jump)) 269 | (defadvice split-window-internal (before evil-jumper activate) 270 | (evil-jumper--set-jump)) 271 | (defadvice find-tag-noselect (before evil-jumper activate) 272 | (evil-jumper--set-jump))) 273 | (when evil-jumper--wired 274 | (remove-hook 'next-error-hook #'evil-jumper--set-jump) 275 | (remove-hook 'window-configuration-change-hook #'evil-jumper--window-configuration-hook) 276 | (ad-remove-advice 'evil-set-jump 'after 'evil-jumper) 277 | (ad-remove-advice 'switch-to-buffer 'before 'evil-jumper) 278 | (ad-remove-advice 'split-window-internal 'before 'evil-jumper) 279 | (ad-remove-advice 'find-tag-noselect 'before 'evil-jumper))) 280 | (evil-normalize-keymaps)) 281 | 282 | ;;;###autoload 283 | (defun turn-on-evil-jumper-mode () 284 | "Turns on vim jumplist emulation." 285 | (interactive) 286 | (evil-jumper-mode t)) 287 | 288 | ;;;###autoload 289 | (defun turn-off-evil-jumper-mode () 290 | "Turns off vim jumplist emulation." 291 | (interactive) 292 | (evil-jumper-mode -1)) 293 | 294 | ;;;###autoload 295 | (defalias 'global-evil-jumper-mode 'evil-jumper-mode) 296 | 297 | (provide 'evil-jumper) 298 | 299 | ;;; evil-jumper.el ends here 300 | --------------------------------------------------------------------------------