├── .gitignore ├── Cask ├── Makefile ├── README.md ├── evil-textobj-line.el ├── image └── evil-textobj-line.gif └── test ├── evil-textobj-line-tests.el └── test.el /.gitignore: -------------------------------------------------------------------------------- 1 | /.cask/ 2 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "evil-textobj-line.el") 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | 3 | EMACS ?= emacs 4 | CASK ?= cask 5 | 6 | LOADPATH = -L . -L test 7 | 8 | ELPA_DIR = \ 9 | .cask/$(shell $(EMACS) -Q --batch --eval '(princ emacs-version)')/elpa 10 | 11 | test: elpa 12 | $(CASK) exec $(EMACS) -Q -batch $(LOADPATH) \ 13 | -l test/test.el \ 14 | -f ert-run-tests-batch-and-exit 15 | 16 | elpa: $(ELPA_DIR) 17 | $(ELPA_DIR): Cask 18 | $(CASK) install 19 | touch $@ 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # evil-textobj-line 2 | 3 | This package is Emacs port of [vim-textobj-line](https://github.com/kana/vim-textobj-line). 4 | 5 | 6 | ## Screencast 7 | 8 | Demo of `vil` and `val` keys. 9 | 10 | ![evil-textobj-line](image/evil-textobj-line.gif) 11 | 12 | 13 | ## Customize variable 14 | 15 | #### `evil-textobj-line-i-key`(Default: `"l"`) 16 | 17 | Keys for `evil-inner-line`. 18 | 19 | #### `evil-textobj-line-a-key`(Default: `"l"`) 20 | 21 | Keys for `evil-a-line` 22 | -------------------------------------------------------------------------------- /evil-textobj-line.el: -------------------------------------------------------------------------------- 1 | ;;; evil-textobj-line.el --- Line text object for Evil 2 | 3 | ;; Copyright (C) 2015 Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | ;; Package-Requires: ((evil "1.0.0")) 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; This package implements a line text object for Evil. 24 | 25 | ;;; Code: 26 | 27 | (require 'evil) 28 | 29 | (defgroup evil-textobj-line nil 30 | "Line text object for Evil." 31 | :group 'evil) 32 | 33 | (defcustom evil-textobj-line-i-key "l" 34 | "Keys for evil-inner-line." 35 | :type 'string 36 | :group 'evil-textobj-line) 37 | 38 | (defcustom evil-textobj-line-a-key "l" 39 | "Keys for evil-a-line." 40 | :type 'string 41 | :group 'evil-textobj-line) 42 | 43 | (defun evil-line-range (count beg end type &optional inclusive) 44 | (if inclusive 45 | (evil-range (line-beginning-position) (line-end-position)) 46 | (let ((start (save-excursion 47 | (back-to-indentation) 48 | (point))) 49 | (end (save-excursion 50 | (goto-char (line-end-position)) 51 | (skip-syntax-backward " " (line-beginning-position)) 52 | (point)))) 53 | (evil-range start end)))) 54 | 55 | (evil-define-text-object evil-a-line (count &optional beg end type) 56 | "Select range between a character by which the command is followed." 57 | (evil-line-range count beg end type t)) 58 | (evil-define-text-object evil-inner-line (count &optional beg end type) 59 | "Select inner range between a character by which the command is followed." 60 | (evil-line-range count beg end type)) 61 | 62 | (define-key evil-outer-text-objects-map evil-textobj-line-a-key 'evil-a-line) 63 | (define-key evil-inner-text-objects-map evil-textobj-line-i-key 'evil-inner-line) 64 | 65 | (provide 'evil-textobj-line) 66 | 67 | ;;; evil-textobj-line.el ends here 68 | -------------------------------------------------------------------------------- /image/evil-textobj-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacsorphanage/evil-textobj-line/9eaf9a5485c2b5c05e16552b34632ca520cd681d/image/evil-textobj-line.gif -------------------------------------------------------------------------------- /test/evil-textobj-line-tests.el: -------------------------------------------------------------------------------- 1 | ;;; This file is taken from from evil-tests.el 2 | ;;; License: 3 | 4 | ;; This file is part of Evil. 5 | ;; 6 | ;; Evil is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or 9 | ;; (at your option) any later version. 10 | ;; 11 | ;; Evil is distributed in the hope that it will be useful, 12 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ;; GNU General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with Evil. If not, see . 18 | 19 | (defvar evil-test-point nil 20 | "Marker for point.") 21 | (make-variable-buffer-local 'evil-test-point) 22 | (defvar evil-test-visual-start nil 23 | "Marker for Visual beginning.") 24 | (make-variable-buffer-local 'evil-test-visual-start) 25 | (defvar evil-test-visual-end nil 26 | "Marker for Visual end.") 27 | (make-variable-buffer-local 'evil-test-visual-end) 28 | 29 | (defmacro evil-test-buffer (&rest body) 30 | "Execute FORMS in a temporary buffer. 31 | The following optional keywords specify the buffer's properties: 32 | 33 | :state STATE The initial state, defaults to `normal'. 34 | :visual SELECTION The Visual selection, defaults to `char'. 35 | :point-start STRING String for matching beginning of point, 36 | defaults to \"[\". 37 | :point-end STRING String for matching end of point, 38 | defaults to \"]\". 39 | :visual-start STRING String for matching beginning of 40 | Visual selection, defaults to \"<\". 41 | :visual-end STRING String for matching end of 42 | Visual selection, defaults to \">\". 43 | 44 | Then follows one or more forms. If the first form is a string, 45 | it is taken to be a buffer description as passed to 46 | `evil-test-buffer-from-string', and initializes the buffer. 47 | Subsequent string forms validate the buffer. 48 | 49 | If a form is a list of strings or vectors, it is taken to be a 50 | key sequence and is passed to `execute-kbd-macro'. If the form 51 | is \(file FILENAME CONTENTS), then the test fails unless the 52 | contents of FILENAME equal CONTENTS. If the form is \(error 53 | SYMBOL ...) then the test fails unless an error of type SYMBOL is 54 | raised. Remaining forms are evaluated as-is. 55 | 56 | \(fn [[KEY VALUE]...] FORMS...)" 57 | (declare (indent defun)) 58 | (let ((state 'normal) 59 | arg key point-start point-end string 60 | visual visual-start visual-end) 61 | ;; collect keywords 62 | (while (keywordp (car-safe body)) 63 | (setq key (pop body) 64 | arg (pop body)) 65 | (cond 66 | ((eq key :point-start) 67 | (setq point-start (or arg ""))) 68 | ((eq key :point-end) 69 | (setq point-end (or arg ""))) 70 | ((eq key :state) 71 | (setq state arg)) 72 | ((eq key :visual) 73 | (setq visual arg)) 74 | ((eq key :visual-start) 75 | (setq visual-start (or arg ""))) 76 | ((eq key :visual-end) 77 | (setq visual-end (or arg ""))))) 78 | ;; collect buffer initialization 79 | (when (stringp (car-safe body)) 80 | (setq string (pop body))) 81 | ;; macro expansion 82 | `(let ((buffer (evil-test-buffer-from-string 83 | ,string ',state 84 | ,point-start ,point-end 85 | ',visual ,visual-start ,visual-end)) 86 | (kill-ring kill-ring) 87 | (kill-ring-yank-pointer kill-ring-yank-pointer) 88 | x-select-enable-clipboard 89 | message-log-max) 90 | (unwind-protect 91 | (save-window-excursion 92 | (with-current-buffer buffer 93 | ;; necessary for keyboard macros to work 94 | (switch-to-buffer-other-window (current-buffer)) 95 | (buffer-enable-undo) 96 | (undo-tree-mode 1) 97 | ;; parse remaining forms 98 | ,@(mapcar 99 | #'(lambda (form) 100 | (let (error-symbol) 101 | (when (and (listp form) 102 | (eq (car-safe form) 'error)) 103 | (setq error-symbol (car-safe (cdr-safe form)) 104 | form (cdr-safe (cdr-safe form)))) 105 | (let ((result 106 | (cond 107 | ((stringp form) 108 | `(evil-test-buffer-string 109 | ,form 110 | ',point-start ',point-end 111 | ',visual-start ',visual-end)) 112 | ((eq (car-safe form) 'file) 113 | `(evil-test-file-contents ,(cadr form) 114 | ,(caddr form))) 115 | ((or (stringp (car-safe form)) 116 | (vectorp (car-safe form)) 117 | (memq (car-safe (car-safe form)) 118 | '(kbd vconcat))) 119 | ;; we need to execute everything as a single 120 | ;; sequence for command loop hooks to work 121 | `(execute-kbd-macro 122 | (apply #'vconcat 123 | (mapcar #'listify-key-sequence 124 | (mapcar #'eval ',form))))) 125 | ((memq (car-safe form) '(kbd vconcat)) 126 | `(execute-kbd-macro ,form)) 127 | (t 128 | form)))) 129 | (if error-symbol 130 | `(should-error ,result :type ',error-symbol) 131 | result)))) 132 | body))) 133 | (and (buffer-name buffer) 134 | (kill-buffer buffer)))))) 135 | 136 | (when (fboundp 'font-lock-add-keywords) 137 | (font-lock-add-keywords 'emacs-lisp-mode 138 | '(("(\\(evil-test-buffer\\)\\>" 139 | 1 font-lock-keyword-face)))) 140 | 141 | (defun evil-test-buffer-string (string &optional 142 | point-start point-end 143 | visual-start visual-end) 144 | "Validate the current buffer according to STRING. 145 | If STRING contains an occurrence of POINT-START immediately 146 | followed by POINT-END, that position is compared against point. 147 | If STRING contains an occurrence of VISUAL-START followed by 148 | VISUAL-END, those positions are compared against the Visual selection. 149 | POINT-START and POINT-END default to [ and ]. 150 | VISUAL-START and VISUAL-END default to < and >." 151 | (let ((actual-buffer (current-buffer)) 152 | (marker-buffer (evil-test-marker-buffer-from-string 153 | string 154 | point-start point-end 155 | visual-start visual-end)) 156 | before-point after-point string selection) 157 | (unwind-protect 158 | (with-current-buffer marker-buffer 159 | (setq string (buffer-string)) 160 | (when evil-test-point 161 | (setq before-point (buffer-substring (point-min) evil-test-point) 162 | after-point (buffer-substring evil-test-point (point-max)))) 163 | (when (and evil-test-visual-start evil-test-visual-end) 164 | (setq selection (buffer-substring 165 | evil-test-visual-start evil-test-visual-end))) 166 | (with-current-buffer actual-buffer 167 | (if (or before-point after-point) 168 | (evil-test-text before-point after-point) 169 | ;; if the cursor isn't specified, just test the whole buffer 170 | (save-excursion 171 | (goto-char (point-min)) 172 | (evil-test-text nil string #'bobp #'eobp))) 173 | (when selection 174 | (evil-test-selection selection)))) 175 | (kill-buffer marker-buffer)))) 176 | 177 | (defun evil-test-buffer-from-string (string &optional 178 | state 179 | point-start point-end 180 | visual visual-start visual-end) 181 | "Create a new buffer according to STRING. 182 | If STRING contains an occurrence of POINT-START immediately 183 | followed by POINT-END, then point is moved to that position. 184 | If STRING contains an occurrence of VISUAL-START followed by 185 | VISUAL-END, then a Visual selection is created with those boundaries. 186 | POINT-START and POINT-END default to [ and ]. 187 | VISUAL-START and VISUAL-END default to < and >. 188 | STATE is the initial state; it defaults to `normal'. 189 | VISUAL is the Visual selection: it defaults to `char'." 190 | (let ((type (evil-visual-type (or visual 'char))) 191 | (buffer (evil-test-marker-buffer-from-string 192 | string point-start point-end 193 | visual-start visual-end))) 194 | (with-current-buffer buffer 195 | (prog1 buffer 196 | (evil-change-state state) 197 | ;; let the buffer change its major mode without disabling Evil 198 | (add-hook 'after-change-major-mode-hook #'evil-initialize) 199 | (when (and (markerp evil-test-visual-start) 200 | (markerp evil-test-visual-end)) 201 | (evil-visual-select 202 | evil-test-visual-start evil-test-visual-end type) 203 | (when evil-test-point 204 | (goto-char evil-test-point) 205 | (evil-visual-refresh) 206 | (unless (and (= evil-visual-beginning 207 | evil-test-visual-start) 208 | (= evil-visual-end 209 | evil-test-visual-end)) 210 | (evil-visual-select 211 | evil-test-visual-start evil-test-visual-end type -1) 212 | (goto-char evil-test-point) 213 | (evil-visual-refresh)))) 214 | (when (markerp evil-test-point) 215 | (goto-char evil-test-point)))))) 216 | 217 | (defun evil-test-marker-buffer-from-string (string &optional 218 | point-start point-end 219 | visual-start visual-end) 220 | "Create a new marker buffer according to STRING. 221 | If STRING contains an occurrence of POINT-START immediately 222 | followed by POINT-END, that position is stored in the 223 | buffer-local variable `evil-test-point'. Similarly, 224 | if STRING contains an occurrence of VISUAL-START followed by 225 | VISUAL-END, those positions are stored in the variables 226 | `evil-test-visual-beginning' and `evil-test-visual-end'. 227 | POINT-START and POINT-END default to [ and ]. 228 | VISUAL-START and VISUAL-END default to < and >." 229 | (let ((string (or string "")) 230 | (point-start (regexp-quote 231 | (if (characterp point-start) 232 | (string point-start) 233 | (or point-start "[")))) 234 | (point-end (regexp-quote 235 | (if (characterp point-end) 236 | (string point-end) 237 | (or point-end "]")))) 238 | (visual-start (regexp-quote 239 | (if (characterp visual-start) 240 | (string visual-start) 241 | (or visual-start "<")))) 242 | (visual-end (regexp-quote 243 | (if (characterp visual-end) 244 | (string visual-end) 245 | (or visual-end ">"))))) 246 | (with-current-buffer (generate-new-buffer " *test*") 247 | (prog1 (current-buffer) 248 | (save-excursion 249 | (insert string)) 250 | (save-excursion 251 | (when (> (length point-start) 0) 252 | (if (> (length point-end) 0) 253 | (when (re-search-forward 254 | (format "\\(%s\\)[^%s]?\\(%s\\)" 255 | point-start point-end point-end) nil t) 256 | (goto-char (match-beginning 0)) 257 | (delete-region (match-beginning 2) (match-end 2)) 258 | (delete-region (match-beginning 1) (match-end 1)) 259 | (setq evil-test-point 260 | (move-marker (make-marker) (point)))) 261 | (when (re-search-forward point-start nil t) 262 | (goto-char (match-beginning 0)) 263 | (delete-region (match-beginning 0) (match-end 0)) 264 | (setq evil-test-point 265 | (move-marker (make-marker) (point))))))) 266 | (save-excursion 267 | (when (and (> (length visual-start) 0) 268 | (> (length visual-end) 0)) 269 | (when (re-search-forward visual-start nil t) 270 | (goto-char (match-beginning 0)) 271 | (delete-region (match-beginning 0) (match-end 0)) 272 | (setq evil-test-visual-start 273 | (move-marker (make-marker) (point)))) 274 | (when (re-search-forward visual-end nil t) 275 | (goto-char (match-beginning 0)) 276 | (delete-region (match-beginning 0) (match-end 0)) 277 | (setq evil-test-visual-end 278 | (move-marker (make-marker) (point)))))))))) 279 | 280 | (defun evil-test-text (before after &optional before-predicate after-predicate) 281 | "Verify the text around point. 282 | BEFORE is the expected text before point, and AFTER is 283 | the text after point. BEFORE-PREDICATE is a predicate function 284 | to execute at the beginning of the text, and AFTER-PREDICATE 285 | is executed at the end." 286 | (when before 287 | (if (functionp before) 288 | (setq before-predicate before 289 | before nil) 290 | (should (string= (buffer-substring 291 | (max (point-min) (- (point) (length before))) 292 | (point)) 293 | before)))) 294 | (when after 295 | (if (functionp after) 296 | (setq after-predicate after 297 | after nil) 298 | (should (string= (buffer-substring 299 | (point) 300 | (min (point-max) (+ (point) (length after)))) 301 | after)))) 302 | (when before-predicate 303 | (ert-info ((format "Expect `%s' at the beginning" before-predicate)) 304 | (save-excursion 305 | (backward-char (length before)) 306 | (should (funcall before-predicate))))) 307 | (when after-predicate 308 | (ert-info ((format "Expect `%s' at the end" after-predicate)) 309 | (save-excursion 310 | (forward-char (length after)) 311 | (should (funcall after-predicate)))))) 312 | 313 | (defmacro evil-test-selection (string &optional end-string 314 | before-predicate after-predicate) 315 | "Verify that the Visual selection contains STRING." 316 | (declare (indent defun)) 317 | `(progn 318 | (save-excursion 319 | (goto-char (or evil-visual-beginning (region-beginning))) 320 | (evil-test-text nil (or ,string ,end-string) ,before-predicate)) 321 | (save-excursion 322 | (goto-char (or evil-visual-end (region-end))) 323 | (evil-test-text (or ,end-string ,string) nil nil ,after-predicate)))) 324 | 325 | (defmacro evil-test-region (string &optional end-string 326 | before-predicate after-predicate) 327 | "Verify that the region contains STRING." 328 | (declare (indent defun)) 329 | `(progn 330 | (save-excursion 331 | (goto-char (region-beginning)) 332 | (evil-test-text nil (or ,string ,end-string) ,before-predicate)) 333 | (save-excursion 334 | (goto-char (region-end)) 335 | (evil-test-text (or ,end-string ,string) nil nil ,after-predicate)))) 336 | 337 | (defmacro evil-test-overlay (overlay string &optional end-string 338 | before-predicate after-predicate) 339 | "Verify that OVERLAY contains STRING." 340 | (declare (indent defun)) 341 | `(progn 342 | (save-excursion 343 | (goto-char (overlay-start ,overlay)) 344 | (evil-test-text nil (or ,string ,end-string) ,before-predicate)) 345 | (save-excursion 346 | (goto-char (overlay-end ,overlay)) 347 | (evil-test-text (or ,end-string ,string) nil nil ,after-predicate)))) 348 | 349 | (defun evil-temp-filename () 350 | "Return an appropriate temporary filename." 351 | (make-temp-name (expand-file-name "evil-test" 352 | temporary-file-directory))) 353 | 354 | (defmacro evil-with-temp-file (file-var content &rest body) 355 | "Create a temp file with CONTENT and bind its name to FILE-VAR within BODY. 356 | FILE-VAR must be a symbol which contains the name of the 357 | temporary file within the macro body. CONTENT is either a string 358 | to be used as the content of the temporary file or a form to be 359 | executed with the temporary file's buffer as \(current-buffer), 360 | see `with-temp-file'. BODY contains the forms to be executed 361 | while the temporary file exists. The temporary file is deleted at 362 | the end of the execution of BODY." 363 | (declare (indent 2) 364 | (debug (symbolp form body))) 365 | `(let ((,file-var (evil-temp-filename))) 366 | (with-temp-file ,file-var 367 | ,(if (stringp content) 368 | `(insert ,content) 369 | content)) 370 | ,@body 371 | (delete-file ,file-var))) 372 | 373 | (defun evil-test-file-contents (name contents) 374 | "Ensure that the contents of file with NAME equal CONTENTS." 375 | (with-temp-buffer 376 | (insert-file-contents name) 377 | (should (string= (buffer-string) 378 | contents)))) 379 | 380 | (provide 'evil-textobj-line-tests) 381 | -------------------------------------------------------------------------------- /test/test.el: -------------------------------------------------------------------------------- 1 | ;;; test.el --- test for evil-textobj-line 2 | 3 | ;; Copyright (C) 2015 by Syohei YOSHIDA 4 | 5 | ;; Author: Syohei YOSHIDA 6 | 7 | ;; This program is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;;; Code: 23 | 24 | (require 'ert) 25 | (require 'evil) 26 | (require 'evil-textobj-line-tests) 27 | 28 | (require 'evil-textobj-line) 29 | 30 | (ert-deftest evil-line-i () 31 | "Test `evil-inner-line'" 32 | (ert-info ("Simple case") 33 | (evil-test-buffer 34 | "[T]his is line" 35 | ("vil") 36 | "") 37 | 38 | (evil-test-buffer 39 | "This is first line\nThis is [s]econd line\n" 40 | ("vil") 41 | "")) 42 | 43 | (ert-info ("Has leading spaces") 44 | (evil-test-buffer 45 | "This is first line\n This is [s]econd line\n" 46 | ("vil") 47 | "")) 48 | 49 | (ert-info ("Has trailing spaces") 50 | (evil-test-buffer 51 | "This is first line\nThis is [s]econd line \nThis is third line" 52 | ("vil") 53 | "")) 54 | 55 | (ert-info ("Both leading and trailing spaces") 56 | (evil-test-buffer 57 | "This is first line\n This is [s]econd line \nThis is third line" 58 | ("vil") 59 | ""))) 60 | 61 | (ert-deftest evil-line-a () 62 | "Test `evil-a-line'" 63 | (ert-info ("Simple case") 64 | (evil-test-buffer 65 | "[T]his is line" 66 | ("val") 67 | "") 68 | 69 | (evil-test-buffer 70 | "This is first line\nThis is [s]econd line\n" 71 | ("val") 72 | "")) 73 | 74 | (ert-info ("Has leading space") 75 | (evil-test-buffer 76 | "This is first line\n This is [s]econd line\n" 77 | ("val") 78 | "< This is second lin[e]>")) 79 | 80 | (ert-info ("Has trailing space") 81 | (evil-test-buffer 82 | "This is first line\nThis is [s]econd line \nThis is third line" 83 | ("val") 84 | "")) 85 | 86 | (ert-info ("Both leading and trailing spaces") 87 | (evil-test-buffer 88 | "This is first line\n This is [s]econd line \nThis is third line" 89 | ("val") 90 | "< This is second line [ ]>"))) 91 | 92 | ;;; test.el ends here 93 | --------------------------------------------------------------------------------