├── .gitignore ├── run-travis-ci.sh ├── Cask ├── Makefile ├── .travis.yml ├── features ├── evil-mc-visual.feature ├── support │ └── env.el ├── evil-mc-replace.feature ├── step-definitions │ └── evil-mc-steps.el ├── evil-mc-text-objects.feature ├── evil-mc-command-info.feature ├── evil-mc-copy-paste.feature ├── evil-mc-delete.feature ├── evil-mc-find.feature └── evil-mc-insert.feature ├── evil-mc-scratch.el ├── LICENSE ├── evil-mc-setup.el ├── evil-mc-undo.el ├── evil-mc-common.el ├── todo.org ├── evil-mc-cursor-state.el ├── evil-mc-region.el ├── evil-mc.el ├── README.org ├── evil-mc-vars.el ├── evil-mc-command-record.el ├── evil-mc-cursor-make.el ├── evil-mc-known-commands.el └── evil-mc-command-execute.el /.gitignore: -------------------------------------------------------------------------------- 1 | .cask 2 | .DS_Store 3 | .ecukes-failing-scenarios 4 | .#* 5 | -------------------------------------------------------------------------------- /run-travis-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cd "$(dirname "$0")" 4 | 5 | ECUKES_EMACS=$(which emacs) 6 | export ECUKES_EMACS 7 | 8 | echo "*** Emacs version ***" 9 | echo "ECUKES_EMACS = $ECUKES_EMACS" 10 | "$ECUKES_EMACS" --version 11 | echo 12 | 13 | cask exec ecukes --no-win -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "evil-mc.el") 5 | 6 | (development 7 | (depends-on "evil") 8 | (depends-on "evil-surround") 9 | (depends-on "evil-numbers") 10 | (depends-on "f") 11 | (depends-on "ecukes") 12 | (depends-on "espuds") 13 | (depends-on "ert-runner") 14 | (depends-on "el-mock")) 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEMP := $(shell find . -name ".\#*") 2 | 3 | all: test 4 | 5 | test: test-no-win 6 | 7 | test-no-win: 8 | @cask exec ecukes --no-win 9 | 10 | test-win: 11 | @cask exec ecukes --win 12 | 13 | test-debug: 14 | @cask exec ecukes --debug 15 | 16 | test-install: 17 | @$(RM) -r -f ./.cask/* 18 | @cask install 19 | 20 | clean-temp: 21 | $(RM) $(TEMP) 22 | 23 | .PHONY: ecukes test test-no-win test-install clean-temp all 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | 3 | branches: 4 | - master 5 | 6 | env: 7 | - EVM_EMACS=emacs-24.4-bin 8 | 9 | before_install: 10 | - sudo mkdir /usr/local/evm 11 | - sudo chown travis:travis /usr/local/evm 12 | - curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash 13 | - export PATH="/home/travis/.evm/bin:$PATH" 14 | - evm install $EVM_EMACS --skip --use 15 | - curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go | python 16 | - export PATH="/home/travis/.cask/bin:$PATH" 17 | - cask info 18 | - cask install 19 | - which emacs 20 | - emacs --version 21 | - cask --version 22 | 23 | script: 24 | ./run-travis-ci.sh -------------------------------------------------------------------------------- /features/evil-mc-visual.feature: -------------------------------------------------------------------------------- 1 | Feature: Visual region 2 | 3 | Scenario: Change selected text with the cursor at the end of the region 4 | When I replace the buffer text with: 5 | """ 6 | This is a line. 7 | This is a line. 8 | This is a line. 9 | """ 10 | And I press "grm" 11 | And I press "wve" 12 | And I press "cisn't" 13 | Then I should see: 14 | """ 15 | This isn't a line. 16 | This isn't a line. 17 | This isn't a line. 18 | """ 19 | 20 | Scenario: Change selected text with the cursor at the beginning of the region 21 | When I replace the buffer text with: 22 | """ 23 | This is a line. 24 | This is a line. 25 | This is a line. 26 | """ 27 | And I press "grm" 28 | And I press "wveo" 29 | And I press "cisn't" 30 | Then I should see: 31 | """ 32 | This isn't a line. 33 | This isn't a line. 34 | This isn't a line. 35 | """ -------------------------------------------------------------------------------- /features/support/env.el: -------------------------------------------------------------------------------- 1 | (require 'f) 2 | 3 | (defvar evil-multiple-cursors-support-path 4 | (f-dirname load-file-name)) 5 | 6 | (defvar evil-multiple-cursors-features-path 7 | (f-parent evil-multiple-cursors-support-path)) 8 | 9 | (defvar evil-multiple-cursors-root-path 10 | (f-parent evil-multiple-cursors-features-path)) 11 | 12 | (add-to-list 'load-path evil-multiple-cursors-root-path) 13 | 14 | (require 'cl) 15 | (require 'evil) 16 | (require 'evil-surround) 17 | (require 'evil-numbers) 18 | (require 'evil-mc) 19 | (require 'evil-mc-scratch) 20 | (require 'espuds) 21 | (require 'ert) 22 | 23 | (Setup 24 | ;; Before anything has run 25 | ) 26 | 27 | (Before 28 | (switch-to-buffer (get-buffer-create "*evil-mc*")) 29 | (evil-surround-mode 1) 30 | (evil-mode 1) 31 | (evil-mc-mode 1) 32 | (erase-buffer) 33 | (transient-mark-mode 1) 34 | (deactivate-mark)) 35 | 36 | (After 37 | (evil-mc-mode -1) 38 | ) 39 | 40 | (Teardown 41 | ;; After when everything has been run 42 | ) 43 | -------------------------------------------------------------------------------- /evil-mc-scratch.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-scratch.el --- Functions used during development 2 | 3 | ;;; Commentary: 4 | 5 | ;;; Code: 6 | 7 | ;; (global-evil-mc-mode 1) 8 | ;; (global-evil-mc-mode -1) 9 | 10 | ;; (evil-mc-mode 1) 11 | ;; (evil-mc-mode -1) 12 | 13 | (defun evil-mc-clear-buffer-undo-list () 14 | "Clear the `buffer-undo-list'." 15 | (interactive) 16 | (setq buffer-undo-list nil)) 17 | 18 | (defun evil-mc-clear-buffer-undo-tree () 19 | "Clear the `buffer-undo-tree'." 20 | (interactive) 21 | (setq buffer-undo-tree nil)) 22 | 23 | (defun evil-mc-remove-all-overlays () 24 | "Remove all overlays." 25 | (interactive) 26 | (remove-overlays) 27 | (setq evil-mc-cursor-list nil)) 28 | 29 | (defun evil-mc-insert-current-date-at-each-cursor () 30 | "Insert the current date at each cursor position." 31 | (interactive) 32 | (evil-mc-execute-for-all-cursors 33 | (lambda (cursor) 34 | (insert-string 35 | (format-time-string "%d/%m/%Y" (current-time)))))) 36 | 37 | (provide 'evil-mc-scratch) 38 | 39 | ;;; evil-mc-scratch.el ends here 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /features/evil-mc-replace.feature: -------------------------------------------------------------------------------- 1 | Feature: Replace text 2 | 3 | Scenario: Should replace a character 4 | When I replace the buffer text with: 5 | """ 6 | This is the start of text -1 -1 -1 t t t f f f k k k 7 | This is the second line -1 -1 -1 t t t f f f k k k 8 | This is the third line -1 -1 -1 t t t f f f k k k 9 | """ 10 | And I press "vgrm" 11 | And I type "rx" 12 | Then I should see: 13 | """ 14 | xhis is the start of text -1 -1 -1 t t t f f f k k k 15 | xhis is the second line -1 -1 -1 t t t f f f k k k 16 | xhis is the third line -1 -1 -1 t t t f f f k k k 17 | """ 18 | 19 | Scenario: Should replace a character with count 20 | When I replace the buffer text with: 21 | """ 22 | This is the start of text -1 -1 -1 t t t f f f k k k 23 | This is the second line -1 -1 -1 t t t f f f k k k 24 | This is the third line -1 -1 -1 t t t f f f k k k 25 | """ 26 | And I press "vgrm" 27 | And I type "3rx" 28 | Then I should see: 29 | """ 30 | xxxs is the start of text -1 -1 -1 t t t f f f k k k 31 | xxxs is the second line -1 -1 -1 t t t f f f k k k 32 | xxxs is the third line -1 -1 -1 t t t f f f k k k 33 | """ 34 | 35 | Scenario: Should replace multiple characters 36 | When I replace the buffer text with: 37 | """ 38 | This is the start of text -1 -1 -1 t t t f f f k k k 39 | This is the second line -1 -1 -1 t t t f f f k k k 40 | This is the third line -1 -1 -1 t t t f f f k k k 41 | """ 42 | And I press "vgrm" 43 | And I type "Rabc" 44 | Then I should see: 45 | """ 46 | abcs is the start of text -1 -1 -1 t t t f f f k k k 47 | abcs is the second line -1 -1 -1 t t t f f f k k k 48 | abcs is the third line -1 -1 -1 t t t f f f k k k 49 | """ -------------------------------------------------------------------------------- /evil-mc-setup.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-setup.el --- Sample setup for evil-mc 2 | 3 | ;;; Commentary: Example of setting up evil-mc 4 | 5 | ;;; Code: 6 | 7 | (when (fboundp 'add-to-load-path) 8 | (add-to-load-path (file-name-directory (buffer-file-name)))) 9 | 10 | (require 'evil-mc) 11 | 12 | (evil-define-local-var evil-mc-custom-paused nil 13 | "Paused functionality when there are multiple cursors active.") 14 | 15 | (defun evil-mc-pause-smartchr-for-mode (mode) 16 | "Temporarily disables the smartchr keys for MODE." 17 | (let ((m-mode (if (atom mode) mode (car mode))) 18 | (s-mode (if (atom mode) mode (cdr mode)))) 19 | (let ((init (intern (concat "smartchr/init-" (symbol-name s-mode)))) 20 | (undo (intern (concat "smartchr/undo-" (symbol-name s-mode))))) 21 | (when (eq major-mode m-mode) 22 | (funcall undo) 23 | (push `(lambda () (,init)) evil-mc-custom-paused))))) 24 | 25 | (defun evil-mc-before-cursors-setup-hook () 26 | "Hook to run before any cursor is created. 27 | Can be used to temporarily disable any functionality that doesn't 28 | play well with `evil-mc'." 29 | (mapc 'evil-mc-pause-smartchr-for-mode 30 | '(web-mode js2-mode java-mode (enh-ruby-mode . ruby-mode) css-mode)) 31 | (when (boundp whitespace-cleanup-disabled) 32 | (setq whitespace-cleanup-disabled t) 33 | (push (lambda () (setq whitespace-cleanup-disabled nil)) evil-mc-custom-paused))) 34 | 35 | (defun evil-mc-after-cursors-teardown-hook () 36 | "Hook to run after all cursors are deleted." 37 | (dolist (fn evil-mc-custom-paused) (funcall fn)) 38 | (setq evil-mc-custom-paused nil)) 39 | 40 | (add-hook 'evil-mc-before-cursors-created 'evil-mc-before-cursors-setup-hook) 41 | (add-hook 'evil-mc-after-cursors-deleted 'evil-mc-after-cursors-teardown-hook) 42 | 43 | (defvar evil-mc-mode-line-prefix "ⓜ" 44 | "Override of the default mode line string for `evil-mc-mode'.") 45 | 46 | (global-evil-mc-mode 1) 47 | -------------------------------------------------------------------------------- /evil-mc-undo.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-undo.el --- Undo related functions 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains functions related to undo/redo functionality when there are 6 | ;; multiple cursors active. 7 | 8 | ;;; Code: 9 | 10 | (require 'cl-lib) 11 | (require 'evil-mc-common) 12 | (require 'evil-mc-vars) 13 | 14 | (defmacro evil-mc-with-single-undo (&rest body) 15 | "Execute BODY as a single undo step combined with the current command step." 16 | (declare (indent defun) 17 | (debug t)) 18 | `(unwind-protect 19 | (let (buffer-undo-list) 20 | (prog1 21 | (evil-with-single-undo ,@body) 22 | (setq evil-mc-temporary-undo buffer-undo-list))) 23 | (unless (eq buffer-undo-list t) 24 | (let ((has-undo-boundary (evil-mc-has-undo-boundary-p))) 25 | (evil-mc-ensure-one-undo-step) 26 | (setq buffer-undo-list 27 | (if (cdr evil-mc-temporary-undo) 28 | (nconc evil-mc-temporary-undo buffer-undo-list) 29 | buffer-undo-list) 30 | evil-mc-temporary-undo nil) 31 | (evil-mc-remove-last-undo-boundary) 32 | (when has-undo-boundary 33 | (undo-boundary)))) 34 | (setq evil-undo-list-pointer nil))) 35 | 36 | (defun evil-mc-has-undo-boundary-p (&optional undo-list) 37 | "Return true if the UNDO-LIST ends with an undo boundary." 38 | (let ((undo-list (or undo-list buffer-undo-list))) 39 | (and undo-list (not (eq undo-list t)) (null (car-safe undo-list))))) 40 | 41 | (defun evil-mc-ensure-one-undo-step () 42 | "Combine `buffer-undo-list' entries for the current command to 43 | make up only one undo step." 44 | (let ((evil-undo-list-pointer (or (evil-mc-get-command-undo-list-pointer-pre) 45 | (last buffer-undo-list)))) 46 | (evil-refresh-undo-step))) 47 | 48 | (defun evil-mc-remove-last-undo-boundary () 49 | "Remove the last undo marker so that future commands 50 | are undone in the same step as the current command." 51 | (when (evil-mc-has-undo-boundary-p) 52 | (pop buffer-undo-list))) 53 | 54 | (provide 'evil-mc-undo) 55 | 56 | ;;; evil-mc-undo.el ends here -------------------------------------------------------------------------------- /evil-mc-common.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-common.el --- Common functions 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains common functionality 6 | 7 | ;;; Code: 8 | 9 | (defun evil-mc-get-object-property (obj prop) 10 | "Get the value of PROP from OBJ." 11 | (let ((item (assq prop obj))) 12 | (when item (cdr item)))) 13 | 14 | (defun evil-mc-put-object-property (obj prop val &rest properties) 15 | "Return a new OBJ that has PROP set to VAL and any other PROPERTIES specified." 16 | (let ((obj (assq-delete-all prop obj))) 17 | (setq obj (cons (cons prop val) obj)) 18 | (while properties 19 | (setq obj (evil-mc-put-object-property obj 20 | (pop properties) 21 | (pop properties)))) 22 | obj)) 23 | 24 | (defun evil-mc-put-object-properties (obj &rest properties) 25 | "Return a new OBJ that has all the PROPERTIES specified." 26 | (when properties 27 | (apply #'evil-mc-put-object-property 28 | (cons obj properties)))) 29 | 30 | 31 | (defun evil-mc-column-number (pos) 32 | "Return the column number at POS." 33 | (save-excursion 34 | (goto-char pos) 35 | (current-column))) 36 | 37 | (defun evil-mc-message (format-string &rest args) 38 | "Display a message given a FORMAT-STRING and ARGS." 39 | (apply 'message (concat (propertize "evil-mc " 40 | 'face 41 | 'font-lock-constant-face) 42 | format-string) args)) 43 | 44 | (defun evil-mc-ends-with-newline-p (text) 45 | "True if TEXT ends with a newline character." 46 | (string-match-p "\n$" text)) 47 | 48 | (defun evil-mc-goto-char (pos) 49 | "Goto to POS ensuring that point does not go beyond the end of line." 50 | (let ((point (max (min pos (point-max)) (point-min)))) 51 | (goto-char point) 52 | (when (eolp) (goto-char (max (point-at-bol) 53 | (1- (point-at-eol))))))) 54 | 55 | (defun evil-mc-starts-with-digit-p (vector) 56 | "Determine whether the first element of VECTOR is a number." 57 | (if (eq (length vector) 0) 58 | nil 59 | (let ((digit (elt vector 0))) 60 | (and (>= digit 49) (<= digit 57))))) 61 | 62 | (provide 'evil-mc-common) 63 | 64 | ;;; evil-mc-common.el ends here 65 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * Items left to do as of 09/06/2015 2 | ** TODO Make undo work properly 3 | *** Goals 4 | **** All cursors should return to their original positions after an undo/redo 5 | **** Cursors should not affect the overall undo steps 6 | - except all commands at cursor should be lumped in one 7 | undo step with the original command 8 | **** Undo/Redo should still work properly after the cursors are deactivated 9 | *** Progress 10 | **** Make each undo step contain the original command and the fake commands 11 | **** Keep track of cursor positions for each undo/redo step 12 | **** Move the cursors to their positions during an undo command 13 | - undo 14 | - redo 15 | - undo-tree-undo 16 | - undo-tree-redo 17 | **** Whitespace cleanup could interfere with the cursor positions if at eol 18 | **** Fix undo for evil-repeat 19 | - maybe this should be part of completing the evil-repeat implementation 20 | *** Implementation 21 | **** Track undo data per cursor 22 | ***** position : the cursor position when an undo step starts 23 | ***** undo-stack : previous positions per undo step 24 | **** Command recording should add the following info 25 | ***** new-step-p : this is command is a new undo step 26 | ***** undoable-p : this is an undo-able command 27 | ***** complete-undo-step-p : this command is a complete undo step 28 | ***** undo-p : this is an undo/redo command 29 | - one of undo, redo, undo-tree-undo, undo-tree-redo 30 | **** When running a command for all cursors 31 | ***** remove the nil marker if any 32 | ***** remove any nil markers in between the fake commands 33 | ***** re-add the nil marker after the fake commands have run if any was removed 34 | **** During an undo command 35 | ***** move cursors to their previous positions (saved in undo-stack) 36 | ***** what happens during a redo? 37 | ** TODO Make evil-repeat work for all commands 38 | ** TODO Fix move-text-up/down 39 | *** Currently it only works only for non-consecutive lines 40 | ** TODO Maybe have a default for unknown commands 41 | *** Default could be evil-mc-execute-macro but need to initialize the correct evil state 42 | *** This would allow supporting unnamed commands 43 | ** TODO Make change line (cc) work properly 44 | *** Currently it works if the cursors are on non-consecutive lines or if the real cursor is positioned before the fake cursors 45 | *** The work around is ^C but that doesn't work with a count 46 | ** DONE Add tests 47 | CLOSED: [2016-07-19 Tue 10:11] 48 | -------------------------------------------------------------------------------- /features/step-definitions/evil-mc-steps.el: -------------------------------------------------------------------------------- 1 | (And "^I press \"\\([^\"]+\\)\" followed by enter$" 2 | "Press ARG followed by a new line." 3 | (lambda (arg) 4 | (execute-kbd-macro 5 | (vconcat (edmacro-parse-keys arg) 6 | (edmacro-parse-keys ""))))) 7 | 8 | (When "^I replace the buffer text with\\(?: \"\\(.+\\)\"\\|:\\)$" 9 | "Replace the current buffer text with CONTENTS. 10 | Also enter normal state and go to the beginning of buffer." 11 | (lambda (contents) 12 | (erase-buffer) 13 | (evil-normal-state) 14 | (insert contents) 15 | (goto-char (point-min)))) 16 | 17 | (And "^I set the register to \"\\([^\"]+\\)\" then type \"\\([^\"]+\\)\"$" 18 | (lambda (register input) 19 | (execute-kbd-macro (vconcat [34] 20 | (string-to-vector register) 21 | (string-to-vector input))))) 22 | 23 | (Then "^The recorded command name should be \"\\([^\"]+\\)\"$" 24 | (lambda (cmd) 25 | (should (eq (evil-mc-get-command-name) (intern cmd))))) 26 | 27 | (And "^The recorded command keys should be \"\\([^\"]+\\)\"$" 28 | (lambda (keys) 29 | (should (equal (evil-mc-get-command-keys-string :keys) keys)))) 30 | 31 | (Given "^I have at least one cursor$" 32 | (lambda () 33 | (evil-mc-make-cursor-here))) 34 | 35 | (Given "^I have one cursor at \"\\([^\"]+\\)\" in \"\\([^\"]+\\)\"$" 36 | (lambda (pattern text) 37 | (insert text) 38 | (goto-char (point-min)) 39 | (search-forward pattern) 40 | (evil-mc-make-cursor-here))) 41 | 42 | (When "^These examples should pass:$" 43 | (lambda (table) (run-and-verify table))) 44 | 45 | (When "^These examples with undo should pass:$" 46 | (lambda (table) (run-and-verify table t))) 47 | 48 | (And "^I go to the beginning of buffer$" 49 | (lambda () (goto-char (point-min)))) 50 | 51 | (And "^The cursors are frozen$" 52 | (lambda () (evil-mc-pause-cursors))) 53 | 54 | (And "^I enable debugging$" 55 | (lambda() (evil-mc-recording-debug-on))) 56 | 57 | (defun run-and-verify (table &optional undo) 58 | "Runs all the key sequences in TABLE and verifies them, 59 | optionally running UNDO after each one." 60 | (let ((header (car table)) 61 | (rows (cdr table))) 62 | (dolist (row rows) 63 | (let* ((key (nth 0 row)) 64 | (cmd (nth 1 row)) 65 | (out (nth 2 row))) 66 | (when (or (null out) (eq 0 (length out))) (setq out key)) 67 | (evil-force-normal-state) 68 | (When "I press \"%s\"" key) 69 | (Then "The recorded command name should be \"%s\"" cmd) 70 | (Then "The recorded command keys should be \"%s\"" out) 71 | (when undo 72 | (evil-force-normal-state) 73 | (When "I press \"u\"")))))) 74 | -------------------------------------------------------------------------------- /features/evil-mc-text-objects.feature: -------------------------------------------------------------------------------- 1 | Feature: Text objects and surround 2 | Scenario: Change the bracket type 3 | When I replace the buffer text with: 4 | """ 5 | This is a (very) very (long) line with (lots of) words. 6 | """ 7 | And I press "f(v" 8 | And I press "grm" 9 | And I press "csbB" 10 | Then I should see: 11 | """ 12 | This is a {very} very {long} line with {lots of} words. 13 | """ 14 | 15 | Scenario: Delete brackets inner 16 | When I replace the buffer text with: 17 | """ 18 | This is a (very) very (long) line with (lots of) words. 19 | """ 20 | And I press "f(v" 21 | And I press "grm" 22 | And I press "dib" 23 | Then I should see: 24 | """ 25 | This is a () very () line with () words. 26 | """ 27 | 28 | Scenario: Delete brackets outer 29 | When I replace the buffer text with: 30 | """ 31 | This is a (very) very (long) line with (lots of) words. 32 | """ 33 | And I press "f(v" 34 | And I press "grm" 35 | And I press "dab" 36 | Then I should see: 37 | """ 38 | This is a very line with words. 39 | """ 40 | 41 | Scenario: Copy brackets inner 42 | When I replace the buffer text with: 43 | """ 44 | This is a (normal) line. 45 | This is a (normal) line. 46 | This is a (normal) line. 47 | """ 48 | And I press "fn" 49 | And I press "grm" 50 | And I press "yib" 51 | And I press "$p" 52 | Then I should see: 53 | """ 54 | This is a (normal) line.normal 55 | This is a (normal) line.normal 56 | This is a (normal) line.normal 57 | """ 58 | 59 | Scenario: Copy brackets outer 60 | When I replace the buffer text with: 61 | """ 62 | This is a (normal) line. 63 | This is a (normal) line. 64 | This is a (normal) line. 65 | """ 66 | And I press "fn" 67 | And I press "grm" 68 | And I press "yab" 69 | And I press "$p" 70 | Then I should see: 71 | """ 72 | This is a (normal) line.(normal) 73 | This is a (normal) line.(normal) 74 | This is a (normal) line.(normal) 75 | """ 76 | 77 | Scenario: Change a parenthesis expression inner 78 | When I replace the buffer text with: 79 | """ 80 | This is a (sentence) with brackets. 81 | This is a (sentence) with brackets. 82 | This is a (sentence) with brackets. 83 | """ 84 | And I press "grm" 85 | And I type "f(cibchanged" 86 | Then I should see: 87 | """ 88 | This is a (changed) with brackets. 89 | This is a (changed) with brackets. 90 | This is a (changed) with brackets. 91 | """ 92 | 93 | Scenario: Change a parenthesis expression outer 94 | When I replace the buffer text with: 95 | """ 96 | This is a (sentence) with brackets. 97 | This is a (sentence) with brackets. 98 | This is a (sentence) with brackets. 99 | """ 100 | And I press "grm" 101 | And I type "f(cabchanged" 102 | Then I should see: 103 | """ 104 | This is a changed with brackets. 105 | This is a changed with brackets. 106 | This is a changed with brackets. 107 | """ 108 | 109 | Scenario: Surround a word with quotes 110 | When I replace the buffer text with: 111 | """ 112 | This is a simple line. 113 | This is a simple line. 114 | That is a simple line. 115 | This is a simple line. 116 | """ 117 | And I press "grm" 118 | And I type "fmviwS'" 119 | Then I should see: 120 | """ 121 | This is a 'simple' line. 122 | This is a 'simple' line. 123 | That is a simple line. 124 | This is a 'simple' line. 125 | """ 126 | -------------------------------------------------------------------------------- /features/evil-mc-command-info.feature: -------------------------------------------------------------------------------- 1 | Feature: Record current command info 2 | Scenario: Record commands to replace text 3 | When I insert: 4 | """ 5 | This is the start of text -1 -1 -1 t t t f f f k k k 6 | This is the second line -1 -1 -1 t t t f f f k k k 7 | This is the third line -1 -1 -1 t t t f f f k k k 8 | """ 9 | And I go to the beginning of buffer 10 | Given I have at least one cursor 11 | And The cursors are frozen 12 | Then these examples with undo should pass: 13 | | keys | command | 14 | | rx | evil-replace | 15 | | 3rx | evil-replace | 16 | | 13rx | evil-replace | 17 | 18 | Scenario: Record commands to copy text 19 | When I insert: 20 | """ 21 | This is the start of text -1 -1 -1 t t t f f f k k k 22 | This is the second line -1 -1 -1 t t t f f f k k k 23 | This is the third line -1 -1 -1 t t t f f f k k k 24 | """ 25 | And I go to the beginning of buffer 26 | Given I have at least one cursor 27 | And The cursors are frozen 28 | Then these examples should pass: 29 | | keys | command | 30 | | yy | evil-yank | 31 | | 3yy | evil-yank | 32 | | yw | evil-yank | 33 | | ye | evil-yank | 34 | | 3yw | evil-yank | 35 | | yt- | evil-yank | 36 | | ytk | evil-yank | 37 | | 3yt- | evil-yank | 38 | | ytt | evil-yank | 39 | | yff | evil-yank | 40 | | 2ytt | evil-yank | 41 | | 2yff | evil-yank | 42 | # | y3w | evil-yank | - not supported 43 | # | y2tf | evil-yank | - not supported 44 | 45 | Scenario: Record commands to change text 46 | When I insert: 47 | """ 48 | This is the start of text -1 -1 -1 t t t f f f k k k k 49 | This is the second line -1 -1 -1 t t t f f f k k k k 50 | This is the third line -1 -1 -1 t t t f f f k k k k 51 | """ 52 | And I go to the beginning of buffer 53 | Given I have at least one cursor 54 | And The cursors are frozen 55 | Then These examples with undo should pass: 56 | | keys | command | 57 | | cc | evil-change | 58 | | ctk | evil-change | 59 | | cfk | evil-change | 60 | | ctt | evil-change | 61 | | cff | evil-change | 62 | | 3ctk | evil-change | 63 | | 3ctt | evil-change | 64 | | 3cfk | evil-change | 65 | | c3tk | evil-change | 66 | | c3tt | evil-change | 67 | | c3w | evil-change | 68 | | 3cff | evil-change | 69 | | 3cc | evil-change | 70 | 71 | Scenario: Record commands to delete text 72 | When I insert: 73 | """ 74 | This is the start of text -1 -1 -1 t t t f f f k k k 75 | This is the second line -1 -1 -1 t t t f f f k k k 76 | This is the third line -1 -1 -1 t t t f f f k k k 77 | """ 78 | And I go to the beginning of buffer 79 | Given I have at least one cursor 80 | And The cursors are frozen 81 | Then These examples with undo should pass: 82 | | keys | command | 83 | | dd | evil-delete | 84 | | dtk | evil-delete | 85 | | dfk | evil-delete | 86 | | dtt | evil-delete | 87 | | dff | evil-delete | 88 | | 3dtk | evil-delete | 89 | | 3dfk | evil-delete | 90 | | 3dtt | evil-delete | 91 | | 3dff | evil-delete | 92 | | 3dd | evil-delete | 93 | | d3w | evil-delete | 94 | | d3tk | evil-delete | 95 | 96 | Scenario: Record commands to work with surrounding delimiters 97 | Given I have one cursor at "inner" in "[external (outer (inner (center))]" 98 | And The cursors are frozen 99 | Then These examples with undo should pass: 100 | | keys | command | 101 | | csbB | evil-change | 102 | | cs[B | evil-change | 103 | | csb{ | evil-change | 104 | | dsb | evil-delete | 105 | | ds( | evil-delete | 106 | | cib | evil-change | 107 | | yib | evil-yank | 108 | 109 | Scenario: Record commands to select inside parentheses 110 | Given I have one cursor at "inner" in "[external (outer (inner (center))]" 111 | And The cursors are frozen 112 | When I press "vib" 113 | Then The recorded command name should be "evil-inner-paren" 114 | And The recorded command keys should be "ib" 115 | 116 | Scenario: Record the command to join two lines 117 | Given I have one cursor at "line" in "First line.\nSecond line." 118 | And The cursors are frozen 119 | When I press "J" 120 | Then The recorded command name should be "evil-join" 121 | And The recorded command keys should be "J" -------------------------------------------------------------------------------- /features/evil-mc-copy-paste.feature: -------------------------------------------------------------------------------- 1 | Feature: Copy paste 2 | 3 | Scenario: Copy paste a word (before) 4 | When I replace the buffer text with: 5 | """ 6 | Here is a list of words, some are small, some big and some huge. 7 | """ 8 | And I press "4fs" 9 | And I press "grm" 10 | And I press "bywbbP" 11 | Then I should see: 12 | """ 13 | Here is a list of some words, some are some small, some some big and some huge. 14 | """ 15 | 16 | Scenario: Copy paste a word (after) 17 | When I replace the buffer text with: 18 | """ 19 | Here is a list of words, some are small, some big and some huge. 20 | """ 21 | And I press "4fs" 22 | And I press "grm" 23 | And I press "bywbbp" 24 | Then I should see: 25 | """ 26 | Here is a list of wsome ords, some are ssome mall, some bsome ig and some huge. 27 | """ 28 | 29 | Scenario: Copy paste a word with count 30 | When I replace the buffer text with: 31 | """ 32 | blue and big and purple and big and pink and big and small 33 | """ 34 | And I press "fg" 35 | And I press "grm" 36 | And I press "bb3ywP" 37 | Then I should see: 38 | """ 39 | blue and big and and big and purple and big and and big and pink and big and and big and small 40 | """ 41 | 42 | Scenario: Copy paste up to a letter 43 | When I replace the buffer text with: 44 | """ 45 | Here are some words. 46 | Here are some words. 47 | Here are some words. 48 | """ 49 | And I press "grm" 50 | And I press "ytmbP" 51 | Then I should see: 52 | """ 53 | e are soHere are some words. 54 | e are soHere are some words. 55 | e are soHere are some words. 56 | """ 57 | 58 | Scenario: Copy paste till before a letter 59 | When I replace the buffer text with: 60 | """ 61 | Here are some words. 62 | Here are some words. 63 | Here are some words. 64 | """ 65 | And I press "grm" 66 | And I press "yfmbP" 67 | Then I should see: 68 | """ 69 | e are somHere are some words. 70 | e are somHere are some words. 71 | e are somHere are some words. 72 | """ 73 | 74 | Scenario: Copy paste until the end of the line 75 | When I replace the buffer text with: 76 | """ 77 | Here are some words. 78 | Here are some words. 79 | Here are some words. 80 | """ 81 | And I press "grm" 82 | And I type "fsy$$p" 83 | Then I should see: 84 | """ 85 | Here are some words.some words. 86 | Here are some words.some words. 87 | Here are some words.some words. 88 | """ 89 | 90 | Scenario: Copy paste a line 91 | When I replace the buffer text with: 92 | """ 93 | Here are some words. 94 | There are some words. 95 | Here are some words. 96 | There are some words. 97 | """ 98 | And I press "grm" 99 | And I press "yyp" 100 | Then I should see: 101 | """ 102 | Here are some words. 103 | Here are some words. 104 | There are some words. 105 | Here are some words. 106 | Here are some words. 107 | There are some words. 108 | """ 109 | 110 | Scenario: Copy paste a line with count 111 | When I replace the buffer text with: 112 | """ 113 | Here are some words. 114 | There are some words. 115 | Here are some words. 116 | There are some words. 117 | Here are some words. 118 | """ 119 | And I press "grm" 120 | And I press "2yyP" 121 | Then I should see: 122 | """ 123 | Here are some words. 124 | There are some words. 125 | Here are some words. 126 | There are some words. 127 | Here are some words. 128 | There are some words. 129 | Here are some words. 130 | There are some words. 131 | """ 132 | 133 | Scenario: Copy paste with registers 134 | When I replace the buffer text with: 135 | """ 136 | Here are some words. 137 | Here are some words. 138 | Here are some words. 139 | """ 140 | And I press "grm" 141 | And I press "b" 142 | And I set the register to "a" then type "yw" 143 | And I press "w" 144 | And I set the register to "b" then type "yw" 145 | And I press "w" 146 | And I set the register to "c" then type "yw" 147 | And I press "$" 148 | And I set the register to "a" then type "p" 149 | And I set the register to "b" then type "p" 150 | And I set the register to "c" then type "p" 151 | Then I should see: 152 | """ 153 | Here are some words.Here are some 154 | Here are some words.Here are some 155 | Here are some words.Here are some 156 | """ 157 | -------------------------------------------------------------------------------- /features/evil-mc-delete.feature: -------------------------------------------------------------------------------- 1 | Feature: Delete text 2 | 3 | Scenario: Delete a word 4 | When I replace the buffer text with "words words and more words" 5 | And I press "grm" 6 | And I type "bdw" 7 | Then I should see "and more " 8 | 9 | Scenario: Delete a letter 10 | When I replace the buffer text with "words words and more words" 11 | And I press "grm" 12 | And I type "bx" 13 | Then I should see "ords ords and more ords" 14 | 15 | Scenario: Delete a letter with count 16 | When I replace the buffer text with "words words and more words" 17 | And I press "grm" 18 | And I type "b2x" 19 | Then I should see "rds rds and more rds" 20 | 21 | Scenario: Delete a word with count 22 | When I replace the buffer text with: 23 | """ 24 | Lots of words words and more words 25 | Lots of words words and more words 26 | Lots of words words and more words 27 | Lots of words words and more words 28 | """ 29 | And I press "grm" 30 | And I type "fw3dw" 31 | Then I should see: 32 | """ 33 | Lots of more words 34 | Lots of more words 35 | Lots of more words 36 | Lots of more words 37 | """ 38 | 39 | Scenario: Delete a WORD 40 | When I replace the buffer text with: 41 | """ 42 | composite-words composite-words and more composite-words 43 | """ 44 | And I press "grm" 45 | And I type "bdaW" 46 | Then I should see "and more" 47 | 48 | Scenario: Delete a line 49 | When I replace the buffer text with: 50 | """ 51 | This is a line. 52 | That is a line. 53 | That is a line. 54 | This is a line. 55 | That is a line. 56 | """ 57 | And I press "j" 58 | And I press "grm" 59 | And I press "dd" 60 | Then I should see: 61 | """ 62 | This is a line. 63 | This is a line. 64 | """ 65 | 66 | Scenario: Delete a line with count 67 | When I replace the buffer text with: 68 | """ 69 | That is a line. 70 | This is a line. 71 | Another a line. 72 | That is a line. 73 | This is a line. 74 | This is a line. 75 | That is a line. 76 | Another a line. 77 | This is a line. 78 | Last line. 79 | """ 80 | And I press "grm" 81 | And I press "2dd" 82 | Then I should see: 83 | """ 84 | Another a line. 85 | This is a line. 86 | This is a line. 87 | Last line. 88 | """ 89 | 90 | Scenario: Delete to the end of line 91 | When I replace the buffer text with: 92 | """ 93 | This is a super duper long line. 94 | This is a super duper long line. 95 | This is a super duper long line. 96 | This is a super duper long line. 97 | """ 98 | And I press "grm" 99 | And I press "fdD" 100 | Then I should see: 101 | """ 102 | This is a super 103 | This is a super 104 | This is a super 105 | This is a super 106 | """ 107 | 108 | Scenario: Delete to the beginning of line 109 | When I replace the buffer text with: 110 | """ 111 | This is a super duper long line. 112 | This is a super duper long line. 113 | This is a super duper long line. 114 | This is a super duper long line. 115 | """ 116 | And I press "grm" 117 | And I type "fdd^" 118 | Then I should see: 119 | """ 120 | duper long line. 121 | duper long line. 122 | duper long line. 123 | duper long line. 124 | """ 125 | 126 | Scenario: Delete up to a letter 127 | When I replace the buffer text with: 128 | """ 129 | The road was dark brown with patches of green. 130 | The road was dark brown with patches of green. 131 | The road was dark brown with patches of green. 132 | The road was dark brown with patches of green. 133 | """ 134 | And I press "grm" 135 | And I press "fwdfh" 136 | Then I should see: 137 | """ 138 | The road patches of green. 139 | The road patches of green. 140 | The road patches of green. 141 | The road patches of green. 142 | """ 143 | 144 | Scenario: Delete till before a letter 145 | When I replace the buffer text with: 146 | """ 147 | The road was dark brown with patches of green. 148 | The road was dark brown with patches of green. 149 | The road was dark brown with patches of green. 150 | The road was dark brown with patches of green. 151 | """ 152 | And I press "grm" 153 | And I press "fwdth" 154 | Then I should see: 155 | """ 156 | The road h patches of green. 157 | The road h patches of green. 158 | The road h patches of green. 159 | The road h patches of green. 160 | """ 161 | 162 | Scenario: Delete till before a letter with count 163 | When I replace the buffer text with: 164 | """ 165 | The road was dark brown with patches of green. 166 | The road was dark brown with patches of green. 167 | The road was dark brown with patches of green. 168 | The road was dark brown with patches of green. 169 | """ 170 | And I press "grm" 171 | And I press "fw2dth" 172 | Then I should see: 173 | """ 174 | The road hes of green. 175 | The road hes of green. 176 | The road hes of green. 177 | The road hes of green. 178 | """ 179 | -------------------------------------------------------------------------------- /features/evil-mc-find.feature: -------------------------------------------------------------------------------- 1 | Feature: Find a character 2 | 3 | Scenario: Should find character to 4 | When I replace the buffer text with: 5 | """ 6 | This is the start of text -1 -1 -1 t t t f f f k k k 7 | This is the second line -1 -1 -1 t t t f f f k k k 8 | This is the third line -1 -1 -1 t t t f f f k k k 9 | """ 10 | And I press "vgrm" 11 | And I type "tkrx" 12 | Then I should see: 13 | """ 14 | This is the start of text -1 -1 -1 t t t f f fxk k k 15 | This is the second line -1 -1 -1 t t t f f fxk k k 16 | This is the third line -1 -1 -1 t t t f f fxk k k 17 | """ 18 | 19 | Scenario: Should find character 20 | When I replace the buffer text with: 21 | """ 22 | This is the start of text -1 -1 -1 t t t f f f k k k 23 | This is the second line -1 -1 -1 t t t f f f k k k 24 | This is the third line -1 -1 -1 t t t f f f k k k 25 | """ 26 | And I press "vgrm" 27 | And I type "fkrx" 28 | Then I should see: 29 | """ 30 | This is the start of text -1 -1 -1 t t t f f f x k k 31 | This is the second line -1 -1 -1 t t t f f f x k k 32 | This is the third line -1 -1 -1 t t t f f f x k k 33 | """ 34 | 35 | Scenario: Should find character to with count 36 | When I replace the buffer text with: 37 | """ 38 | This is the start of text -1 -1 -1 t t t f f f k k k 39 | This is the second line -1 -1 -1 t t t f f f k k k 40 | This is the third line -1 -1 -1 t t t f f f k k k 41 | """ 42 | And I press "vgrm" 43 | And I type "2tkrx" 44 | Then I should see: 45 | """ 46 | This is the start of text -1 -1 -1 t t t f f f kxk k 47 | This is the second line -1 -1 -1 t t t f f f kxk k 48 | This is the third line -1 -1 -1 t t t f f f kxk k 49 | """ 50 | 51 | Scenario: Should find character with count 52 | When I replace the buffer text with: 53 | """ 54 | This is the start of text -1 -1 -1 t t t f f f k k k 55 | This is the second line -1 -1 -1 t t t f f f k k k 56 | This is the third line -1 -1 -1 t t t f f f k k k 57 | """ 58 | And I press "vgrm" 59 | And I type "2fkrx" 60 | Then I should see: 61 | """ 62 | This is the start of text -1 -1 -1 t t t f f f k x k 63 | This is the second line -1 -1 -1 t t t f f f k x k 64 | This is the third line -1 -1 -1 t t t f f f k x k 65 | """ 66 | 67 | Scenario: Should find character to with repeat 68 | When I replace the buffer text with: 69 | """ 70 | This is the start of text -1 -1 -1 t t t f f f k k k 71 | This is the second line -1 -1 -1 t t t f f f k k k 72 | This is the third line -1 -1 -1 t t t f f f k k k 73 | """ 74 | And I press "vgrm" 75 | And I type "tk;;rx" 76 | Then I should see: 77 | """ 78 | This is the start of text -1 -1 -1 t t t f f f k kxk 79 | This is the second line -1 -1 -1 t t t f f f k kxk 80 | This is the third line -1 -1 -1 t t t f f f k kxk 81 | """ 82 | 83 | Scenario: Should find character with repeat 84 | When I replace the buffer text with: 85 | """ 86 | This is the start of text -1 -1 -1 t t t f f f k k k 87 | This is the second line -1 -1 -1 t t t f f f k k k 88 | This is the third line -1 -1 -1 t t t f f f k k k 89 | """ 90 | And I press "vgrm" 91 | And I type "fk;;rx" 92 | Then I should see: 93 | """ 94 | This is the start of text -1 -1 -1 t t t f f f k k x 95 | This is the second line -1 -1 -1 t t t f f f k k x 96 | This is the third line -1 -1 -1 t t t f f f k k x 97 | """ 98 | 99 | Scenario: Should find character to with repeat backwards 100 | When I replace the buffer text with: 101 | """ 102 | This is the start of text -1 -1 -1 t t t f f f k k k 103 | This is the second line -1 -1 -1 t t t f f f k k k 104 | This is the third line -1 -1 -1 t t t f f f k k k 105 | """ 106 | And I press "vgrm" 107 | And I type "tk;;,rx" 108 | Then I should see: 109 | """ 110 | This is the start of text -1 -1 -1 t t t f f f kxk k 111 | This is the second line -1 -1 -1 t t t f f f kxk k 112 | This is the third line -1 -1 -1 t t t f f f kxk k 113 | """ 114 | 115 | Scenario: Should find character with repeat backwards 116 | When I replace the buffer text with: 117 | """ 118 | This is the start of text -1 -1 -1 t t t f f f k k k 119 | This is the second line -1 -1 -1 t t t f f f k k k 120 | This is the third line -1 -1 -1 t t t f f f k k k 121 | """ 122 | And I press "vgrm" 123 | And I type "fk;;,rx" 124 | Then I should see: 125 | """ 126 | This is the start of text -1 -1 -1 t t t f f f k x k 127 | This is the second line -1 -1 -1 t t t f f f k x k 128 | This is the third line -1 -1 -1 t t t f f f k x k 129 | """ 130 | 131 | Scenario: Should find character to with repeat and count 132 | When I replace the buffer text with: 133 | """ 134 | This is the start of text -1 -1 -1 t t t f f f k k k k k k k k k 135 | This is the second line -1 -1 -1 t t t f f f k k k k k k k k k 136 | This is the third line -1 -1 -1 t t t f f f k k k k k k k k k 137 | """ 138 | And I press "vgrm" 139 | And I type "2tk;;rx" 140 | Then I should see: 141 | """ 142 | This is the start of text -1 -1 -1 t t t f f f k k kxk k k k k k 143 | This is the second line -1 -1 -1 t t t f f f k k kxk k k k k k 144 | This is the third line -1 -1 -1 t t t f f f k k kxk k k k k k 145 | """ 146 | 147 | Scenario: Should find character with repeat and count 148 | When I replace the buffer text with: 149 | """ 150 | This is the start of text -1 -1 -1 t t t f f f k k k k k k k k k 151 | This is the second line -1 -1 -1 t t t f f f k k k k k k k k k 152 | This is the third line -1 -1 -1 t t t f f f k k k k k k k k k 153 | """ 154 | And I press "vgrm" 155 | And I type "2fk;;rx" 156 | Then I should see: 157 | """ 158 | This is the start of text -1 -1 -1 t t t f f f k k k x k k k k k 159 | This is the second line -1 -1 -1 t t t f f f k k k x k k k k k 160 | This is the third line -1 -1 -1 t t t f f f k k k x k k k k k 161 | """ 162 | -------------------------------------------------------------------------------- /evil-mc-cursor-state.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-cursor-state.el --- State saved for each fake cursor 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains functions to interact with the state of a fake cursor 6 | 7 | (require 'evil-mc-common) 8 | 9 | ;;; Code: 10 | 11 | (defun evil-mc-get-cursor-variables (&optional categories) 12 | "Gets the cursor variable names associated with CATEGORIES. 13 | If CATEGORIES is nil return all cursor variables." 14 | (cond ((null categories) 15 | (apply 'append (mapcar 'cdr evil-mc-cursor-variables))) 16 | ((atom categories) 17 | (evil-mc-get-object-property evil-mc-cursor-variables categories)) 18 | (t (apply 'append (mapcar (lambda (category) 19 | (evil-mc-get-object-property 20 | evil-mc-cursor-variables 21 | category)) 22 | categories))))) 23 | 24 | (defun evil-mc-get-cursor-property (cursor name) 25 | "Return the value of the CURSOR property with NAME." 26 | (when cursor (evil-mc-get-object-property cursor name))) 27 | 28 | (defun evil-mc-put-cursor-property (cursor &rest properties) 29 | "Return a new CURSOR that has one or more PROPERTIES set to the specified values." 30 | (apply 'evil-mc-put-object-property (cons cursor properties))) 31 | 32 | (defun evil-mc-get-cursor-properties (cursor properties) 33 | "Return the values of all CURSOR PROPERTIES as a list." 34 | (when cursor 35 | (mapcar (lambda (prop) (evil-mc-get-object-property cursor prop)) 36 | properties))) 37 | 38 | (defun evil-mc-get-cursor-overlay (cursor) 39 | "Get the overlay for CURSOR." 40 | (evil-mc-get-cursor-property cursor 'overlay)) 41 | 42 | (defun evil-mc-put-cursor-overlay (cursor overlay) 43 | "Set the overlay for CURSOR to OVERLAY." 44 | (evil-mc-put-cursor-property cursor 'overlay overlay)) 45 | 46 | (defun evil-mc-get-cursor-last-position (cursor) 47 | "Get the last-position for CURSOR." 48 | (evil-mc-get-cursor-property cursor 'last-position)) 49 | 50 | (defun evil-mc-put-cursor-last-position (cursor last-position) 51 | "Set the last-position for CURSOR to LAST-POSITION." 52 | (evil-mc-put-cursor-property cursor 'last-position last-position)) 53 | 54 | (defun evil-mc-get-cursor-undo-stack (cursor) 55 | "Get the undo-stack for CURSOR." 56 | (evil-mc-get-cursor-property cursor 'undo-stack)) 57 | 58 | (defun evil-mc-put-cursor-undo-stack (cursor undo-stack) 59 | "Set the undo-stack for CURSOR to UNDO-STACK." 60 | (evil-mc-put-cursor-property cursor 'undo-stack undo-stack)) 61 | 62 | (defun evil-mc-get-cursor-undo-stack-pointer (cursor) 63 | "Get the undo-stack-pointer for CURSOR." 64 | (evil-mc-get-cursor-property cursor 'undo-stack-pointer)) 65 | 66 | (defun evil-mc-put-cursor-undo-stack-pointer (cursor undo-stack-pointer) 67 | "Set the undo-stack-pointer for CURSOR to UNDO-STACK-POINTER." 68 | (evil-mc-put-cursor-property cursor 'undo-stack-pointer undo-stack-pointer)) 69 | 70 | (defun evil-mc-get-cursor-region (cursor) 71 | "Get the region for CURSOR." 72 | (evil-mc-get-cursor-property cursor 'region)) 73 | 74 | (defun evil-mc-put-cursor-region (cursor region) 75 | "Set the region for CURSOR to REGION." 76 | (evil-mc-put-cursor-property cursor 'region region)) 77 | 78 | (defun evil-mc-get-cursor-kill-ring (cursor) 79 | "Get the `kill-ring' for CURSOR." 80 | (evil-mc-get-cursor-property cursor 'kill-ring)) 81 | 82 | (defun evil-mc-put-cursor-kill-ring (cursor kill-ring) 83 | "Set the `kill-ring' for CURSOR to KILL-RING." 84 | (evil-mc-put-cursor-property cursor 'kill-ring kill-ring)) 85 | 86 | (defun evil-mc-get-cursor-kill-ring-yank-pointer (cursor) 87 | "Get the `kill-ring-yank-pointer' for CURSOR." 88 | (evil-mc-get-cursor-property 89 | cursor 'kill-ring-yank-pointer)) 90 | 91 | (defun evil-mc-put-cursor-kill-ring-yank-pointer (cursor kill-ring-yank-pointer) 92 | "Set the `kill-ring-yank-pointer' for CURSOR to KILL-RING-YANK-POINTER." 93 | (evil-mc-put-cursor-property 94 | cursor 'kill-ring-yank-pointer kill-ring-yank-pointer)) 95 | 96 | (defun evil-mc-get-cursor-temporary-goal-column (cursor) 97 | "Get the temporary-goal-column for CURSOR." 98 | (evil-mc-get-cursor-property cursor 'temporary-goal-column)) 99 | 100 | (defun evil-mc-put-cursor-temporary-goal-column (cursor temporary-goal-column) 101 | "Set the temporary-goal-column for CURSOR to TEMPORARY-GOAL-COLUMN." 102 | (evil-mc-put-cursor-property cursor 'temporary-goal-column temporary-goal-column)) 103 | 104 | (defun evil-mc-get-cursor-evil-markers-alist (cursor) 105 | "Get the evil-markers-alist for CURSOR." 106 | (evil-mc-get-cursor-property cursor 'evil-markers-alist)) 107 | 108 | (defun evil-mc-put-cursor-evil-markers-alist (cursor evil-markers-alist) 109 | "Set the evil-markers-alist for CURSOR to EVIL-MARKERS-ALIST." 110 | (evil-mc-put-cursor-property cursor 'evil-markers-alist evil-markers-alist)) 111 | 112 | (defun evil-mc-get-cursor-evil-jump-list (cursor) 113 | "Get the evil-jump-list for CURSOR." 114 | (evil-mc-get-cursor-property cursor 'evil-jump-list)) 115 | 116 | (defun evil-mc-put-cursor-evil-jump-list (cursor evil-jump-list) 117 | "Set the evil-jump-list for CURSOR to EVIL-JUMP-LIST." 118 | (evil-mc-put-cursor-property cursor 'evil-jump-list evil-jump-list)) 119 | 120 | (defun evil-mc-get-cursor-mark-ring (cursor) 121 | "Get the `mark-ring' for CURSOR." 122 | (evil-mc-get-cursor-property cursor 'mark-ring)) 123 | 124 | (defun evil-mc-put-cursor-mark-ring (cursor mark-ring) 125 | "Set the `mark-ring' for CURSOR to MARK-RING." 126 | (evil-mc-put-cursor-property cursor 'mark-ring mark-ring)) 127 | 128 | (defun evil-mc-get-cursor-mark-active (cursor) 129 | "Get the `mark-active' for CURSOR." 130 | (evil-mc-get-cursor-property cursor 'mark-active)) 131 | 132 | (defun evil-mc-put-cursor-mark-active (cursor mark-active) 133 | "Set the `mark-active' for CURSOR to MARK-ACTIVE." 134 | (evil-mc-put-cursor-property cursor 'mark-active mark-active)) 135 | 136 | (defun evil-mc-get-cursor-start (cursor) 137 | "Get the CURSOR overlay start." 138 | (when cursor 139 | (overlay-start (evil-mc-get-cursor-overlay cursor)))) 140 | 141 | (defun evil-mc-get-cursor-end (cursor) 142 | "Get the CURSOR overlay end." 143 | (when cursor 144 | (overlay-end (evil-mc-get-cursor-overlay cursor)))) 145 | 146 | (defun evil-mc-delete-cursor-overlay (cursor) 147 | "Deletes the overlay associated with CURSOR." 148 | (when cursor 149 | (let ((overlay (evil-mc-get-cursor-overlay cursor))) 150 | (when overlay (delete-overlay overlay))))) 151 | 152 | (provide 'evil-mc-cursor-state) 153 | 154 | ;;; evil-mc-cursor-state.el ends here 155 | -------------------------------------------------------------------------------- /features/evil-mc-insert.feature: -------------------------------------------------------------------------------- 1 | Feature: Insert and change text 2 | 3 | Scenario: Text typed in insert state should be entered into the buffer 4 | When I replace the buffer text with "aaa" 5 | And I press "vgrm" 6 | And I type "clfirst text " 7 | Then I should see: 8 | """ 9 | first text first text first text 10 | """ 11 | 12 | Scenario: Enter new lines 13 | When I replace the buffer text with "bbb" 14 | And I press "vgrm" 15 | And I press "cl" 16 | And I press "word" followed by enter 17 | Then I should see: 18 | """ 19 | word 20 | word 21 | word 22 | """ 23 | 24 | Scenario: Open line below 25 | When I replace the buffer text with "bbb" 26 | And I press "vgrm" 27 | And I press "oabc" 28 | Then I should see: 29 | """ 30 | bbb 31 | abc 32 | abc 33 | abc 34 | """ 35 | 36 | Scenario: Open line above 37 | When I replace the buffer text with "bbb" 38 | And I press "vgrm" 39 | And I press "Oabc" 40 | Then I should see: 41 | """ 42 | abc 43 | abc 44 | abc 45 | bbb 46 | """ 47 | 48 | Scenario: Insert at cursor 49 | When I replace the buffer text with "a a a" 50 | And I press "vgrm" 51 | And I press "i-y-" 52 | Then I should see "-y-a -y-a -y-a" 53 | 54 | Scenario: Insert after cursor 55 | When I replace the buffer text with "a a a" 56 | And I press "vgrm" 57 | And I press "a-x-" 58 | Then I should see "a-x- a-x- a-x-" 59 | 60 | Scenario: Insert at the beginning of line 61 | When I replace the buffer text with: 62 | """ 63 | This is a line 64 | This is a line 65 | This is a line 66 | """ 67 | And I go to word "line" 68 | And I press "vgrm" 69 | And I type "Istart " 70 | Then I should see: 71 | """ 72 | start This is a line 73 | start This is a line 74 | start This is a line 75 | """ 76 | 77 | Scenario: Insert at the end of line 78 | When I replace the buffer text with: 79 | """ 80 | This is a line 81 | This is a line 82 | This is a line 83 | """ 84 | And I go to word "line" 85 | And I press "vgrm" 86 | And I type "A end" 87 | Then I should see: 88 | """ 89 | This is a line end 90 | This is a line end 91 | This is a line end 92 | """ 93 | 94 | Scenario: Change a letter 95 | When I replace the buffer text with "xyz xyz xyz" 96 | And I press "grm" 97 | And I type "clw" 98 | Then I should see "xyw xyw xyw" 99 | 100 | Scenario: Change a word 101 | When I replace the buffer text with "xyz xyz xyz" 102 | And I press "grm" 103 | And I type "bcwabc" 104 | Then I should see "abc abc abc" 105 | 106 | Scenario: Change a word at the beginning of line 107 | When I replace the buffer text with: 108 | """ 109 | This is a line 110 | This is a line 111 | This is a line 112 | """ 113 | And I press "grm" 114 | And I type "bcwabc" 115 | Then I should see: 116 | """ 117 | abc is a line 118 | abc is a line 119 | abc is a line 120 | """ 121 | 122 | Scenario: Change to the end of word 123 | When I replace the buffer text with "xyz xyz xyz" 124 | And I press "grm" 125 | And I type "bceabc" 126 | Then I should see "abc abc abc" 127 | 128 | Scenario: Change to the end of word with count 129 | When I replace the buffer text with: 130 | """ 131 | xyz yyz yyz xyz yyz yyz xyz yyz yyz xyz yyz yyz 132 | """ 133 | And I press "grm" 134 | And I type "b2ceabc" 135 | Then I should see "abc yyz abc yyz abc yyz abc yyz" 136 | 137 | Scenario: Change up to a letter (f) 138 | When I replace the buffer text with "another-test another-test another-test" 139 | And I press "grm" 140 | And I type "bbbcftxyz" 141 | Then I should see "xyzher-test xyzher-test xyzher-test" 142 | 143 | Scenario: Change up to a letter (f) with count 144 | When I replace the buffer text with "another-test another-test another-test" 145 | And I press "grm" 146 | And I type "bbb2cftxyz" 147 | Then I should see "xyzest xyzest xyzest" 148 | 149 | Scenario: Change up till before a letter (t) 150 | When I replace the buffer text with "another-test another-test another-test" 151 | And I press "grm" 152 | And I type "bbbcttxyz" 153 | Then I should see "xyzther-test xyzther-test xyzther-test" 154 | 155 | Scenario: Change a visual selection 156 | When I replace the buffer text with "another-test another-test another-test" 157 | And I press "grm" 158 | And I type "bbbv4lcxyz" 159 | Then I should see "xyzer-test xyzer-test xyzer-test" 160 | 161 | Scenario: Change a visual selection 2 162 | When I replace the buffer text with: 163 | """ 164 | This is a simple line. 165 | This is a simple line. 166 | This is a simple line. 167 | That is a simple line. 168 | """ 169 | And I press "grm" 170 | And I type "bvt.cChanged text" 171 | Then I should see: 172 | """ 173 | Changed text. 174 | Changed text. 175 | Changed text. 176 | That is a simple line. 177 | """ 178 | 179 | Scenario: Change until the end of line 180 | When I replace the buffer text with: 181 | """ 182 | This is a line. 183 | This is a line. 184 | This is a line. 185 | """ 186 | And I press "grm" 187 | And I press "wC" 188 | And I type "line has changed." 189 | Then I should see: 190 | """ 191 | This line has changed. 192 | This line has changed. 193 | This line has changed. 194 | """ 195 | 196 | # TODO ensure this works for consecutive lines and 197 | # passes for [ "cc" "^cc" "$cc" ] 198 | Scenario: Change a whole line 199 | When I replace the buffer text with: 200 | """ 201 | This is a line. 202 | That is a line. 203 | This is a line. 204 | That is a line. 205 | That is a line. 206 | """ 207 | And I press "grm" 208 | And I type "cc" 209 | And I type "The line has changed." 210 | Then I should see: 211 | """ 212 | The line has changed. 213 | That is a line. 214 | The line has changed. 215 | That is a line. 216 | """ 217 | 218 | Scenario: Change a whole visual line 219 | When I replace the buffer text with: 220 | """ 221 | This is a line. 222 | That is a line. 223 | This is a line. 224 | That is a line. 225 | """ 226 | And I press "grm" 227 | And I press "Vc" 228 | And I type "The line has changed." 229 | Then I should see: 230 | """ 231 | The line has changed. 232 | That is a line. 233 | The line has changed. 234 | That is a line. 235 | """ 236 | 237 | Scenario: Change a whole line with count 238 | When I replace the buffer text with: 239 | """ 240 | This is a line. 241 | The next line. 242 | The last line. 243 | This is a line. 244 | The next line. 245 | The last line. 246 | The last line. 247 | """ 248 | And I press "grm" 249 | And I press "2cc" 250 | And I type "The first two lines have changed." 251 | Then I should see: 252 | """ 253 | The first two lines have changed. 254 | The last line. 255 | The first two lines have changed. 256 | The last line. 257 | """ -------------------------------------------------------------------------------- /evil-mc-region.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-region.el --- Visual region 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains functions for creating a visual region for a fake cursor 6 | 7 | (require 'evil-mc-common) 8 | 9 | ;;; Code: 10 | 11 | (defun evil-mc-put-region-property (region &rest properties) 12 | "Return a new region that has one or more PROPERTIES 13 | set to the specified values." 14 | (apply 'evil-mc-put-object-property (cons region properties))) 15 | 16 | (defun evil-mc-get-region-property (region name) 17 | "Return the value of the property with NAME from REGION." 18 | (when region (evil-mc-get-object-property region name))) 19 | 20 | (defun evil-mc-get-region-overlay (region) 21 | "Return the overlay from REGION." 22 | (evil-mc-get-region-property region :overlay)) 23 | 24 | (defun evil-mc-get-region-mark (region) 25 | "Return the mark from REGION." 26 | (evil-mc-get-region-property region :mark)) 27 | 28 | (defun evil-mc-get-region-point (region) 29 | "Return the point from REGION." 30 | (evil-mc-get-region-property region :point)) 31 | 32 | (defun evil-mc-get-region-start (region) 33 | "Return REGION's overlay start position." 34 | (let ((overlay (evil-mc-get-region-overlay region))) 35 | (when overlay (overlay-start overlay)))) 36 | 37 | (defun evil-mc-get-region-end (region) 38 | "Return REGION's overlay end position." 39 | (let ((overlay (evil-mc-get-region-overlay region))) 40 | (when overlay (overlay-end overlay)))) 41 | 42 | (defun evil-mc-get-region-type (region) 43 | "Return the type from REGION." 44 | (evil-mc-get-region-property region :type)) 45 | 46 | (defun evil-mc-line-region-p (region) 47 | "True if REGION is of type line." 48 | (eq (evil-mc-get-region-type region) 'line)) 49 | 50 | (defun evil-mc-char-region-p (region) 51 | "True if REGION is of type char." 52 | (eq (evil-mc-get-region-type region) 'char)) 53 | 54 | (defun evil-mc-put-region-overlay (region overlay) 55 | "Return a new region with the overlay set to OVERLAY." 56 | (evil-mc-put-region-property region :overlay overlay)) 57 | 58 | (defun evil-mc-put-region-mark (region mark) 59 | "Return a new region with the mark set to MARK." 60 | (evil-mc-put-region-property region :mark mark)) 61 | 62 | (defun evil-mc-put-region-point (region point) 63 | "Return a new region with the point set to POINT." 64 | (evil-mc-put-region-property region :point point)) 65 | 66 | (defun evil-mc-put-region-type (region type) 67 | "Return a new region with the type set to TYPE." 68 | (evil-mc-put-region-property region :type type)) 69 | 70 | (defun evil-mc-get-pos-at-bol (pos) 71 | "Get the position at the beginning of the line with POS." 72 | (save-excursion (goto-char pos) (point-at-bol))) 73 | 74 | (defun evil-mc-get-pos-at-eol (pos) 75 | "Get the position at the end of the line with POS." 76 | (save-excursion (goto-char pos) (point-at-eol))) 77 | 78 | (defun evil-mc-calculate-region-bounds (prev-mark prev-point point) 79 | "Calculate new region bounds based on PREV-MARK PREV-POINT and current POINT." 80 | (let ((mark (or prev-mark prev-point))) 81 | (cond ((and (<= mark prev-point) (< point mark)) (setq mark (1+ mark))) 82 | ((and (< prev-point mark) (<= mark point)) (setq mark (1- mark)))) 83 | (cond ((< mark point) (cons mark (1+ point))) 84 | ((< point mark) (cons mark point)) 85 | (t (cons point (1+ (point))))))) 86 | 87 | (defun evil-mc-region-overlay (start end) 88 | "Make a visual region overlay from START to END." 89 | (let ((overlay (make-overlay start end nil nil nil))) 90 | (overlay-put overlay 'face 'evil-mc-region-face) 91 | (overlay-put overlay 'priority evil-mc-region-overlay-priority) 92 | overlay)) 93 | 94 | (defun evil-mc-char-region-overlay (mark point) 95 | "Make an overlay for a visual region of type char from MARK to POINT." 96 | (let* ((start (if (< mark point) mark point)) 97 | (end (if (< mark point) point mark)) 98 | (overlay (evil-mc-region-overlay start end))) 99 | (overlay-put overlay 'mark mark) 100 | (overlay-put overlay 'point point) 101 | overlay)) 102 | 103 | (defun evil-mc-line-region-overlay (mark point) 104 | "Make an overlay for a visual region of type line from MARK to POINT." 105 | (let* ((start-pos (if (< mark point) mark point)) 106 | (end-pos (if (< mark point) point mark)) 107 | (start-line (line-number-at-pos start-pos)) 108 | (end-line (line-number-at-pos end-pos)) 109 | (start (evil-mc-get-pos-at-bol start-pos)) 110 | (end (1+ (evil-mc-get-pos-at-eol end-pos))) 111 | (overlay (evil-mc-region-overlay start end))) 112 | (overlay-put overlay 'mark (if (< mark point) start end)) 113 | (overlay-put overlay 'point (if (< mark point) end start)) 114 | overlay)) 115 | 116 | (defun evil-mc-create-region-overlay (region) 117 | "Creates an overlay for REGION." 118 | (let ((mark (evil-mc-get-region-mark region)) 119 | (point (evil-mc-get-region-point region))) 120 | (cond ((evil-mc-char-region-p region) 121 | (evil-mc-char-region-overlay mark point)) 122 | ((evil-mc-line-region-p region) 123 | (evil-mc-line-region-overlay mark point))))) 124 | 125 | (defun evil-mc-update-region-overlay (region) 126 | "Return a new region based on REGION with the overlay updated." 127 | (evil-mc-put-region-overlay region (evil-mc-create-region-overlay region))) 128 | 129 | (defun evil-mc-create-region (mark point type) 130 | "Creates a region given MARK, POINT, and TYPE." 131 | (evil-mc-update-region (evil-mc-put-region-property nil 132 | :mark mark 133 | :point (or point (point)) 134 | :type type))) 135 | 136 | (defun evil-mc-update-region (region &optional point) 137 | "Makes a new region from REGION moved to according to POINT." 138 | (let* ((point (or point (point))) 139 | (prev-mark (evil-mc-get-region-mark region)) 140 | (prev-point (evil-mc-get-region-point region)) 141 | (type (evil-mc-get-region-type region)) 142 | (bounds (evil-mc-calculate-region-bounds prev-mark prev-point point)) 143 | (new-region (evil-mc-put-region-property nil 144 | :mark (car bounds) 145 | :point (cdr bounds) 146 | :type type))) 147 | (evil-mc-update-region-overlay new-region))) 148 | 149 | (defun evil-mc-change-region-type (region new-type) 150 | "Returns a new region with the type set to NEW-TYPE." 151 | (let ((new-region (evil-mc-put-region-type region new-type))) 152 | (evil-mc-update-region-overlay new-region))) 153 | 154 | (defun evil-mc-exchange-region-point-and-mark (region) 155 | "Return a new region identical to REGION but with point and mark exchanged." 156 | (let* ((mark (evil-mc-get-region-mark region)) 157 | (point (evil-mc-get-region-point region)) 158 | (new-region (evil-mc-put-region-property region 159 | :mark point 160 | :point mark))) 161 | (evil-mc-update-region-overlay new-region))) 162 | 163 | (defun evil-mc-delete-region-overlay (region) 164 | "Deletes the overlay associated with REGION." 165 | (when region 166 | (let ((overlay (evil-mc-get-region-overlay region))) 167 | (when overlay (delete-overlay overlay))))) 168 | 169 | (provide 'evil-mc-region) 170 | 171 | ;;; evil-mc-region.el ends here 172 | -------------------------------------------------------------------------------- /evil-mc.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc.el --- Multiple cursors for evil-mode 2 | 3 | ;; Copyright © 2015-2016 Gabriel Adomnicai 4 | 5 | ;; Author: Gabriel Adomnicai 6 | ;; Maintainer: Gabriel Adomnicai 7 | ;; Version: 0.0.2 8 | ;; Keywords: evil editing multiple-cursors vim evil-multiple-cursors evil-mc evil-mc 9 | ;; Homepage: https://github.com/gabesoft/evil-mc 10 | ;; Package-Requires: ((emacs "24.3") (evil "1.2.12") (cl-lib "0.5")) 11 | ;; 12 | ;; This file is NOT part of GNU Emacs. 13 | 14 | ;;; Commentary: 15 | 16 | ;; This library provides multiple cursors functionality for evil-mode 17 | ;; 18 | ;; Install: 19 | ;; 20 | ;; (require 'evil-mc) 21 | ;; 22 | ;; 23 | ;; Usage: 24 | ;; 25 | ;; (evil-mc-mode 1) ; enable for a single buffer 26 | ;; 27 | ;; (global-evil-mc-mode 1) ; enable for all buffers 28 | ;; 29 | ;; 30 | ;; See the README for more details 31 | 32 | ;;; Code: 33 | 34 | (require 'evil) 35 | 36 | (require 'evil-mc-common) 37 | (require 'evil-mc-vars) 38 | (require 'evil-mc-undo) 39 | (require 'evil-mc-cursor-state) 40 | (require 'evil-mc-cursor-make) 41 | (require 'evil-mc-command-record) 42 | (require 'evil-mc-command-execute) 43 | (require 'evil-mc-region) 44 | 45 | (defcustom evil-mc-mode-line 46 | `(:eval (if (> (evil-mc-get-cursor-count) 1) 47 | (format ,(propertize " %s:%d" 'face 'cursor) 48 | evil-mc-mode-line-prefix 49 | (evil-mc-get-cursor-count)) 50 | (format ,(propertize " %s") evil-mc-mode-line-prefix))) 51 | "Cursors indicator in the mode line." 52 | :group 'evil-mc) 53 | 54 | (put 'evil-mc-mode-line 'risky-local-variable t) 55 | 56 | (defvar evil-mc-key-map 57 | (let ((map (make-sparse-keymap)) 58 | (keys '(("grm" . evil-mc-make-all-cursors) 59 | ("gru" . evil-mc-undo-all-cursors) 60 | ("grs" . evil-mc-pause-cursors) 61 | ("grr" . evil-mc-resume-cursors) 62 | ("grf" . evil-mc-make-and-goto-first-cursor) 63 | ("grl" . evil-mc-make-and-goto-last-cursor) 64 | ("grh" . evil-mc-make-cursor-here) 65 | ("M-n" . evil-mc-make-and-goto-next-cursor) 66 | ("grN" . evil-mc-skip-and-goto-next-cursor) 67 | ("M-p" . evil-mc-make-and-goto-prev-cursor) 68 | ("grP" . evil-mc-skip-and-goto-prev-cursor) 69 | ("C-n" . evil-mc-make-and-goto-next-match) 70 | ("grn" . evil-mc-skip-and-goto-next-match) 71 | ("C-t" . evil-mc-skip-and-goto-next-match) 72 | ("C-p" . evil-mc-make-and-goto-prev-match) 73 | ("grp" . evil-mc-skip-and-goto-prev-match)))) 74 | (dolist (key-data keys) 75 | (evil-define-key 'normal map (kbd (car key-data)) (cdr key-data)) 76 | (evil-define-key 'visual map (kbd (car key-data)) (cdr key-data))) 77 | map)) 78 | 79 | ;;;###autoload 80 | (define-minor-mode evil-mc-mode 81 | "Toggle evil multiple cursors in a single buffer." 82 | :group 'evil-mc 83 | :init-value nil 84 | :keymap evil-mc-key-map 85 | :lighter evil-mc-mode-line 86 | (cond (evil-mc-mode 87 | (evil-mc-define-vars) 88 | (evil-mc-initialize-vars) 89 | (evil-mc-initialize-hooks)) 90 | (t 91 | (evil-mc-teardown-hooks))) 92 | (evil-normalize-keymaps)) 93 | 94 | (put 'evil-mc-mode 'permanent-local t) 95 | 96 | ;;;###autoload 97 | (define-globalized-minor-mode global-evil-mc-mode 98 | evil-mc-mode evil-mc-initialize) 99 | 100 | ;;;###autoload 101 | (defun evil-mc-initialize () 102 | "Enable `evil-mc-mode' in the current buffer." 103 | (evil-mc-mode 1)) 104 | 105 | ;;;###autoload 106 | (defun turn-on-evil-mc-mode () 107 | "Turn on evil-mc mode in the current buffer." 108 | (interactive) 109 | (evil-mc-mode 1)) 110 | 111 | ;;;###autoload 112 | (defun turn-off-evil-mc-mode () 113 | "Turn off evil-mc mode in the current buffer." 114 | (interactive) 115 | (evil-mc-mode -1)) 116 | 117 | (defun evil-mc-define-vars () 118 | "Define vars that can be overridden before enabling `evil-mc-mode'." 119 | 120 | (defvar evil-mc-mode-line-prefix "emc" 121 | "The string used in the mode line to identify `evil-mc-mode'.") 122 | 123 | (defvar evil-mc-incompatible-minor-modes 124 | '(flyspell-mode flycheck-mode aggressive-indent-mode yas-minor-mode) 125 | "Minor modes that are incompatible with `evil-mc-mode'. 126 | These modes will be paused while the cursors are active.") 127 | 128 | (defvar evil-mc-custom-known-commands nil 129 | "Custom command handlers. The entries here should have 130 | the same form as those in `evil-mc-known-commands'. 131 | This variable can be used to override default command handlers 132 | implementations.")) 133 | 134 | (defun evil-mc-initialize-vars () 135 | "Initialize all variables used by `evil-mc'." 136 | (evil-mc-clear-pattern) 137 | (evil-mc-clear-command) 138 | (evil-mc-clear-executing-command) 139 | (evil-mc-clear-recording-command) 140 | (evil-mc-clear-executing-debug) 141 | (evil-mc-clear-recording-debug) 142 | (evil-mc-clear-cursor-list) 143 | (evil-mc-resume-cursors)) 144 | 145 | (defun evil-mc-pause-incompatible-modes () 146 | "Temporarily disable incompatible minor modes." 147 | (dolist (mode evil-mc-incompatible-minor-modes) 148 | (when (and (boundp mode) (eval mode)) 149 | (push mode evil-mc-paused-modes) 150 | (funcall mode -1)))) 151 | 152 | (defun evil-mc-resume-incompatible-modes () 153 | "Re-enable incompatible minor modes." 154 | (dolist (mode evil-mc-paused-modes) (funcall mode 1)) 155 | (evil-mc-clear-paused-modes)) 156 | 157 | (defun evil-mc-initialize-hooks () 158 | "Initialize all hooks used by `evil-mc'." 159 | (add-hook 'evil-mc-before-cursors-created 'evil-mc-pause-incompatible-modes t t) 160 | (add-hook 'evil-mc-before-cursors-created 'evil-mc-initialize-active-state t t) 161 | (add-hook 'evil-mc-after-cursors-deleted 'evil-mc-teardown-active-state t t) 162 | (add-hook 'evil-mc-after-cursors-deleted 'evil-mc-resume-incompatible-modes t t)) 163 | 164 | (defun evil-mc-teardown-hooks () 165 | "Teardown all hooks used by `evil-mc'." 166 | (remove-hook 'evil-mc-before-cursors-created 'evil-mc-pause-incompatible-modes t) 167 | (remove-hook 'evil-mc-before-cursors-created 'evil-mc-initialize-active-state t) 168 | (remove-hook 'evil-mc-after-cursors-deleted 'evil-mc-teardown-active-state t) 169 | (remove-hook 'evil-mc-after-cursors-deleted 'evil-mc-resume-incompatible-modes t)) 170 | 171 | (defun evil-mc-initialize-active-state () 172 | "Initialize all variables and hooks used while there are active cursors." 173 | (evil-mc-clear-command) 174 | (evil-mc-clear-executing-command) 175 | (evil-mc-clear-recording-command) 176 | (add-hook 'pre-command-hook 'evil-mc-begin-command-save nil t) 177 | (add-hook 'post-command-hook 'evil-mc-finish-command-save t t) 178 | (add-hook 'post-command-hook 'evil-mc-execute-for-all t t) 179 | 180 | (defadvice evil-repeat-keystrokes (before evil-mc-repeat-keystrokes (flag) activate) 181 | (evil-mc-save-keys-motion flag)) 182 | (defadvice evil-repeat-motion (before evil-mc-repeat-motion (flag) activate) 183 | (evil-mc-save-keys-operator flag))) 184 | 185 | (defun evil-mc-teardown-active-state () 186 | "Teardown all variables and hooks used while there are active cursors." 187 | (remove-hook 'pre-command-hook 'evil-mc-begin-command-save t) 188 | (remove-hook 'post-command-hook 'evil-mc-finish-command-save t) 189 | (remove-hook 'post-command-hook 'evil-mc-execute-for-all t) 190 | 191 | (ad-remove-advice 'evil-repeat-keystrokes 'before 'evil-mc-repeat-keystrokes) 192 | (ad-remove-advice 'evil-repeat-motion 'before 'evil-mc-repeat-motion)) 193 | 194 | (provide 'evil-mc) 195 | 196 | ;;; evil-mc.el ends here 197 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | : ▓█████ ██▒ █▓ ██▓ ██▓ ███▄ ▄███▓ ▄████▄ 2 | : ▓█ ▀▓██░ █▒▓██▒▓██▒ ▓██▒▀█▀ ██▒▒██▀ ▀█ 3 | : ▒███ ▓██ █▒░▒██▒▒██░ ▓██ ▓██░▒▓█ ▄ 4 | : ▒▓█ ▄ ▒██ █░░░██░▒██░ ▒██ ▒██ ▒▓▓▄ ▄██▒ 5 | : ░▒████▒ ▒▀█░ ░██░░██████▒ ▒██▒ ░██▒▒ ▓███▀ ░ 6 | : ░░ ▒░ ░ ░ ▐░ ░▓ ░ ▒░▓ ░ ░ ▒░ ░ ░░ ░▒ ▒ ░ 7 | : ░ ░ ░ ░ ░░ ▒ ░░ ░ ▒ ░ ░ ░ ░ ░ ▒ 8 | : ░ ░░ ▒ ░ ░ ░ ░ ░ ░ 9 | : ░ ░ ░ ░ ░ ░ ░ ░ ░ 10 | : ░ ░ 11 | 12 | [[https://travis-ci.org/gabesoft/evil-mc][https://travis-ci.org/gabesoft/evil-mc.svg?branch=master]] [[http://melpa.org/#/evil-mc][file:http://melpa.org/packages/evil-mc-badge.svg?style=flat-square]] 13 | 14 | ** Multiple cursors implementation for evil-mode 15 | *** Synopsis 16 | *evil-mc* provides multiple cursors functionality for Emacs when used with *evil-mode* 17 | *** Usage 18 | Start with: 19 | #+BEGIN_SRC emacs-lisp 20 | (require 'evil-mc) 21 | #+END_SRC 22 | **** Local Setup 23 | To enable or disable *evil-mc* mode for a single buffer use:\\ 24 | #+BEGIN_SRC emacs-lisp 25 | (evil-mc-mode 1) ;; enable 26 | (evil-mc-mode -1) ;; disable 27 | #+END_SRC 28 | **** Global Setup 29 | To enable or disable *evil-mc* mode for all buffers use:\\ 30 | #+BEGIN_SRC emacs-lisp 31 | (global-evil-mc-mode 1) ;; enable 32 | (global-evil-mc-mode -1) ;; disable 33 | #+END_SRC 34 | **** Basic Usage 35 | The main commands used to create or delete cursors are: 36 | 37 | #+BEGIN_SRC emacs-lisp 38 | (evil-mc-make-all-cursors) 39 | ;; Create cursors for all strings that match the selected 40 | ;; region or the symbol under cursor. 41 | 42 | (evil-mc-undo-all-cursors) 43 | ;; Remove all cursors. 44 | 45 | (evil-mc-make-and-goto-next-match) 46 | ;; Make a cursor at point and go to the next match of the 47 | ;; selected region or the symbol under cursor. 48 | 49 | (evil-mc-skip-and-goto-next-match) 50 | ;; Go to the next match of the selected region or the symbol under 51 | ;; cursor without creating a cursor at point. 52 | #+END_SRC 53 | 54 | The above commands as well as others, detailed below, are setup with key bindings 55 | when the *evil-mc* mode is enabled. The keys are defined in ~evil-mc-key-map~. You can 56 | take a look at that variable declaration in [[https://github.com/gabesoft/evil-mc/blob/master/evil-mc.el][evil-mc.el]] to see all key bindings. But, 57 | in short, ~C-n~ / ~C-p~ are used for creating cursors, and ~M-n~ / ~M-p~ 58 | are used for cycling through cursors. The commands that create cursors wrap around; but, 59 | the ones that cycle them do not. 60 | To skip creating a cursor forward use ~C-t~ or ~grn~ and backward ~grp~. 61 | Finally use ~gru~ to remove all cursors. 62 | 63 | For an example of setting up *evil-mc* see this [[https://github.com/gabesoft/evil-mc/blob/master/evil-mc-setup.el][setup file]] 64 | **** Commands 65 | Here's a detailed list of all commands used to create, navigate through, or delete cursors:\\ 66 | /All the commands below assume that there is a real cursor and possibly some fake cursors./ 67 | 68 | #+BEGIN_SRC emacs-lisp 69 | (evil-mc-make-all-cursors) 70 | ;; Make a cursor for every match of the selected region or the symbol at point. 71 | 72 | (evil-mc-undo-all-cursors) 73 | ;; Remove all cursors. 74 | 75 | (evil-mc-make-and-goto-next-match) 76 | ;; Make a cursor at point, and go to the next match of the 77 | ;; selected region or the symbol at point. 78 | 79 | (evil-mc-make-and-goto-prev-match) 80 | ;; Make a cursor at point, and go to the previous match of the 81 | ;; selected region or the symbol at point. 82 | 83 | (evil-mc-skip-and-goto-next-match) 84 | ;; Go to the next match of the selected region or symbol at point 85 | ;; without making a cursor at point. This command can be used to 86 | ;; remove unwanted cursors. 87 | 88 | (evil-mc-skip-and-goto-prev-match) 89 | ;; Go to the previous match of the selected region or symbol at point 90 | ;; without making a cursor at point. This command can be used to 91 | ;; remove unwanted cursors. 92 | 93 | (evil-mc-make-and-goto-prev-cursor) 94 | ;; Make a cursor at point and move point to the cursor 95 | ;; closest to it when searching backwards. 96 | 97 | (evil-mc-make-and-goto-next-cursor) 98 | ;; Make a cursor at point and move point to the cursor 99 | ;; closest to it when searching forwards. 100 | 101 | (evil-mc-skip-and-goto-prev-cursor) 102 | ;; Move point to the cursor closest to it when searching backwards 103 | ;; without making a cursor at point. This command can be used to 104 | ;; remove unwanted cursors. 105 | 106 | (evil-mc-skip-and-goto-next-cursor) 107 | ;; Move point to the cursor closest to it when searching forwards 108 | ;; without making a cursor at point. This command can be used to 109 | ;; remove unwanted cursors. 110 | 111 | (evil-mc-make-and-goto-first-cursor) 112 | ;; Make a cursor at point and move point to the cursor at the first position. 113 | 114 | (evil-mc-make-and-goto-last-cursor) 115 | ;; Make a cursor at point and move point to the cursor at the last position. 116 | 117 | (evil-mc-make-cursor-here) 118 | ;; Create a cursor at point. This command should be used with `evil-mc-pause-cursors'. 119 | 120 | (evil-mc-pause-cursors) 121 | ;; Pause all fake cursors. This can be used with `evil-mc-make-cursor-here' 122 | 123 | (evil-mc-resume-cursors) 124 | ;; Call to resume paused cursors. 125 | #+END_SRC 126 | 127 | **** Customization 128 | *evil-mc* can be customized in several ways: 129 | 130 | - Every known command is executed using a command handler defined in a variable 131 | called ~evil-mc-known-commands~ in [[https://github.com/gabesoft/evil-mc/blob/master/evil-mc-known-commands.el][evil-mc-known-commands.el]]. Those can be overridden by 132 | defining the ~evil-mc-custom-known-commands~ variable. See the documentation of 133 | that variable in [[https://github.com/gabesoft/evil-mc/blob/master/evil-mc.el][evil-mc.el]] for more info. 134 | - Some minor modes are incompatible with *evil-mc*. Those modes are defined in 135 | ~evil-mc-incompatible-minor-modes~ and can be overridden by defining that variable. 136 | - In addition there are two hooks that can be used to temporarily disable or enable 137 | additional functionality while there are multiple cursors active 138 | #+BEGIN_SRC emacs-lisp 139 | evil-mc-before-cursors-created 140 | ;; this hook runs just before the first cursor is created 141 | 142 | evil-mc-after-cursors-created 143 | ;; this hook runs just after the last cursor is deleted 144 | #+END_SRC 145 | 146 | *** Notes 147 | - Currently *evil-mc* is in a very early beta stage and thus not fully stable. 148 | - Most evil motions and operators are supported but not every single command will work. 149 | - If the cursors don't seem to work during a command, either the command is 150 | not known (see ~evil-mc-known-commands~ in [[https://github.com/gabesoft/evil-mc/blob/master/evil-mc-known-commands.el][evil-mc-known-commands.el]]) or some minor modes 151 | could be interfering with the evil-mc operations. 152 | - Issues and pull requests are welcome. 153 | 154 | **** Debugging 155 | - When a command does not work, and you want to get more information, 156 | you can enable (or disable) debugging by running any of the commands 157 | below interactively. 158 | #+BEGIN_SRC emacs-lisp 159 | (evil-mc-executing-debug-on) 160 | ;; Turn debug on while executing a command. 161 | 162 | (evil-mc-executing-debug-off) 163 | ;; Turn debug off while executing a command. 164 | 165 | (evil-mc-recording-debug-on) 166 | ;; Turn debug on while recording a command. 167 | 168 | (evil-mc-recording-debug-off) 169 | ;; Turn debug off while recording a command. 170 | 171 | (evil-mc-all-debug-on) 172 | ;; Turn all debug on. 173 | 174 | (evil-mc-all-debug-off) 175 | ;; Turn all debug off. 176 | #+END_SRC 177 | 178 | **** Limitations 179 | - After an undo command the cursors will return to their original positions 180 | if [[http://www.emacswiki.org/emacs/UndoTree][undo-tree]] mode is enabled and ~evil-repeat~ has not been used. 181 | - Redo may cause the real cursor to get out of sync with the others. 182 | This can be worked around by setting a mark and returning to it after a redo. 183 | - Jumps work if [[https://github.com/bling/evil-jumper][evil-jumper]] mode is enabled 184 | 185 | **** Known issues 186 | - Only named commands can be executed by the fake cursors. 187 | - There could be a performance penalty when there are too many cursors (30+). 188 | - Paste will not work when [[https://github.com/syl20bnr/spacemacs][spacemacs]]' paste micro state is enabled. 189 | This is due to the fact that ~evil-paste-pop~ and ~evil-paste-pop-next~ 190 | commands are not supported. 191 | - ~cc~ does not work properly when the cursors are on consecutive lines 192 | use ~^C~ as a workaround 193 | - ~evil-repeat~ works only for some commands. 194 | In particular it doesn't work for delete. It will also interfere with the 195 | cursor positions during an undo or redo operation. 196 | 197 | 198 | 199 | * 200 | : .__ .___.__ __ .__ 201 | : | |__ _____ ______ ______ ___.__. ____ __| _/|__|/ |_|__| ____ ____ 202 | : | | \\__ \ \____ \\____ < | | _/ __ \ / __ | | \ __\ |/ \ / ___\ 203 | : | Y \/ __ \| |_> > |_> >___ | \ ___// /_/ | | || | | | | \/ /_/ > 204 | : |___| (____ / __/| __// ____| \___ >____ | |__||__| |__|___| /\___ / 205 | : \/ \/|__| |__| \/ \/ \/ \//_____/ 206 | -------------------------------------------------------------------------------- /evil-mc-vars.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-vars.el --- Variables for evil-mc 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains variables used by evil-mc 6 | 7 | (require 'evil-mc-known-commands) 8 | 9 | ;;; Code: 10 | 11 | (defgroup evil-mc nil 12 | "Multiple cursors implementation for evil mode." 13 | :prefix "evil-mc-" 14 | :group 'evil) 15 | 16 | (defface evil-mc-cursor-default-face 17 | '((t (:inherit cursor :inverse-video nil))) 18 | "The face used for fake cursors." 19 | :group 'evil-mc) 20 | 21 | (defface evil-mc-region-face 22 | '((t :inherit region)) 23 | "The face used for fake regions" 24 | :group 'evil-mc) 25 | 26 | (defcustom evil-mc-cursor-overlay-priority 201 27 | "The priority of the fake cursors overlay." 28 | :type 'integer 29 | :group 'evil-mc) 30 | 31 | (defcustom evil-mc-region-overlay-priority 99 32 | "The priority of the fake regions overlay." 33 | :type 'integer 34 | :group 'evil-mc) 35 | 36 | (defvar evil-mc-cursor-variables 37 | '((:default . (evil-exchange--overlays 38 | evil-exchange--position 39 | evil-jumper--window-jumps 40 | evil-jumper--jumping 41 | evil-jump-list 42 | evil-last-paste 43 | evil-last-register 44 | evil-last-repeat 45 | evil-markers-alist 46 | evil-recording-repeat 47 | evil-repeat-count 48 | evil-repeat-info 49 | evil-repeat-keys 50 | evil-repeat-move-cursor 51 | evil-repeat-pos 52 | evil-repeat-ring 53 | evil-this-register 54 | evil-was-yanked-without-register 55 | kill-ring 56 | kill-ring-yank-pointer 57 | mark-evil-active 58 | mark-ring 59 | last-position 60 | region 61 | register-alist 62 | undo-stack 63 | undo-stack-pointer 64 | temporary-goal-column)) 65 | (:dabbrev . (dabbrev--friend-buffer-list 66 | dabbrev--last-buffer 67 | dabbrev--last-buffer-found 68 | dabbrev--last-table 69 | dabbrev--last-abbrev-location 70 | dabbrev--last-abbreviation 71 | dabbrev--last-expansion 72 | dabbrev--last-expansion-location 73 | dabbrev--last-direction))) 74 | "Names of variables tracked per cursor during the execution of a command.") 75 | 76 | (evil-define-local-var evil-mc-cursor-state nil 77 | "The state of the real cursor saved while there are active cursors.") 78 | 79 | (evil-define-local-var evil-mc-executing-command nil 80 | "True when executing a command for all cursors.") 81 | 82 | (evil-define-local-var evil-mc-recording-command nil 83 | "True when recording `this-command' data.") 84 | 85 | (evil-define-local-var evil-mc-cursor-current-face nil 86 | "The face to use when making fake cursors.") 87 | 88 | (evil-define-local-var evil-mc-cursor-list nil 89 | "The list of current fake cursors.") 90 | 91 | (evil-define-local-var evil-mc-frozen nil 92 | "If true the fake cursors are frozen.") 93 | 94 | (evil-define-local-var evil-mc-pattern nil 95 | "The current pattern.") 96 | 97 | (evil-define-local-var evil-mc-command nil 98 | "The current command to be executed.") 99 | 100 | (evil-define-local-var evil-mc-command-count nil 101 | "The count for the current command") 102 | 103 | (evil-define-local-var evil-mc-temporary-undo nil 104 | "Variable for saving the `buffer-undo-list' temporarily.") 105 | 106 | (evil-define-local-var evil-mc-executing-debug nil 107 | "If true display debug messages during the execution of a command.") 108 | 109 | (evil-define-local-var evil-mc-recording-debug nil 110 | "If true display debug messages during the recording of a command.") 111 | 112 | (evil-define-local-var evil-mc-paused-modes nil 113 | "List of temporarily disabled minor modes.") 114 | 115 | (defun evil-mc-known-command-p (cmd) 116 | "True if CMD is a supported command." 117 | (or (not (null (assq cmd evil-mc-known-commands))) 118 | (not (null (assq cmd evil-mc-custom-known-commands))) 119 | (eq (evil-get-command-property cmd :repeat) 'motion))) 120 | 121 | (defun evil-mc-has-cursors-p () 122 | "True if there are any fake cursors." 123 | (not (null evil-mc-cursor-list))) 124 | 125 | (defun evil-mc-has-command-p () 126 | "True if there is data saved for the current command." 127 | (not (null evil-mc-command))) 128 | 129 | (defun evil-mc-has-pattern-p () 130 | "True if there is a saved pattern." 131 | (not (null evil-mc-pattern))) 132 | 133 | (defun evil-mc-executing-command-p () 134 | "True when executing a command for all fake cursors." 135 | (eq evil-mc-executing-command t)) 136 | 137 | (defun evil-mc-recording-command-p () 138 | "True when recording a command." 139 | (eq evil-mc-recording-command t)) 140 | 141 | (defun evil-mc-executing-debug-p () 142 | "True if debugging is enabled during the execution of a command." 143 | (eq evil-mc-executing-debug t)) 144 | 145 | (defun evil-mc-recording-debug-p () 146 | "True if debugging is enabled during the recording of a command." 147 | (eq evil-mc-recording-debug t)) 148 | 149 | (defun evil-mc-debug (state executing recording) 150 | "Enable debugging according to STATE for command EXECUTING or RECORDING or both." 151 | (when recording (setq evil-mc-recording-debug state)) 152 | (when executing (setq evil-mc-executing-debug state))) 153 | 154 | (defun evil-mc-executing-debug-on () 155 | "Turn debug on while executing a command." 156 | (interactive) 157 | (evil-mc-debug t t nil)) 158 | 159 | (defun evil-mc-executing-debug-off () 160 | "Turn debug off while executing a command." 161 | (interactive) 162 | (evil-mc-debug nil t nil)) 163 | 164 | (defun evil-mc-recording-debug-on () 165 | "Turn debug on while recording a command." 166 | (interactive) 167 | (evil-mc-debug t nil t)) 168 | 169 | (defun evil-mc-recording-debug-off () 170 | "Turn debug off while recording a command." 171 | (interactive) 172 | (evil-mc-debug nil nil t)) 173 | 174 | (defun evil-mc-all-debug-on () 175 | "Turn all debug on." 176 | (interactive) 177 | (evil-mc-debug t t t)) 178 | 179 | (defun evil-mc-all-debug-off () 180 | "Turn all debug off." 181 | (interactive) 182 | (evil-mc-debug nil t t)) 183 | 184 | (defun evil-mc-print-pattern () 185 | "Print the curent pattern." 186 | (interactive) 187 | (evil-mc-message "%s" evil-mc-pattern)) 188 | 189 | (defun evil-mc-print-cursor-list () 190 | "Return the cursor list." 191 | (interactive) 192 | (if evil-mc-cursor-list 193 | (evil-mc-message "%s: %s" (length evil-mc-cursor-list) evil-mc-cursor-list) 194 | (evil-mc-message "No cursors found"))) 195 | 196 | (defun evil-mc-print-command () 197 | "Print the information saved for the current command." 198 | (interactive) 199 | (evil-mc-message "%s" evil-mc-command)) 200 | 201 | (defun evil-mc-frozen-p () 202 | "True if the fake cursors are frozen." 203 | (eq evil-mc-frozen t)) 204 | 205 | (defun evil-mc-pause-cursors () 206 | "Freeze the fake cursors." 207 | (interactive) 208 | (setq evil-mc-frozen t)) 209 | 210 | (defun evil-mc-resume-cursors () 211 | "Unfreeze the fake cursors." 212 | (interactive) 213 | (setq evil-mc-frozen nil)) 214 | 215 | (defun evil-mc-clear-pattern () 216 | "Clear the currently saved pattern." 217 | (setq evil-mc-pattern nil)) 218 | 219 | (defun evil-mc-clear-command () 220 | "Clear the current command." 221 | (setq evil-mc-command nil)) 222 | 223 | (defun evil-mc-clear-command-count () 224 | "Clear the current command count." 225 | (setq evil-mc-command-count nil)) 226 | 227 | (defun evil-mc-clear-cursor-list () 228 | "Clear the cursor list." 229 | (setq evil-mc-cursor-list nil)) 230 | 231 | (defun evil-mc-update-cursor-list (cursors) 232 | "Updates the `evil-mc-cursor-list' to CURSORS." 233 | (setq evil-mc-cursor-list cursors)) 234 | 235 | (defun evil-mc-clear-executing-command () 236 | "Clear the `evil-mc-executing-command' variable." 237 | (setq evil-mc-executing-command nil)) 238 | 239 | (defun evil-mc-clear-recording-command () 240 | "Clear the `evil-mc-recording-command' variable." 241 | (setq evil-mc-recording-command nil)) 242 | 243 | (defun evil-mc-clear-executing-debug () 244 | "Clear the `evil-mc-executing-debug' variable." 245 | (setq evil-mc-executing-debug nil)) 246 | 247 | (defun evil-mc-clear-recording-debug () 248 | "Clear the `evil-mc-recording-debug' variable." 249 | (setq evil-mc-recording-debug nil)) 250 | 251 | (defun evil-mc-clear-paused-modes () 252 | "Clear the `evil-mc-paused-modes' variable." 253 | (setq evil-mc-paused-modes nil)) 254 | 255 | (defun evil-mc-clear-cursor-state () 256 | "Clear the `evil-mc-cursor-state' variable." 257 | (setq evil-mc-cursor-state nil)) 258 | 259 | (defun evil-mc-get-pattern () 260 | "Return the current pattern." 261 | (when evil-mc-pattern (car evil-mc-pattern))) 262 | 263 | (defun evil-mc-get-pattern-text () 264 | "Return the current pattern text." 265 | (when evil-mc-pattern (car (evil-mc-get-pattern)))) 266 | 267 | (defun evil-mc-get-pattern-start () 268 | "Return the current pattern start position." 269 | (when evil-mc-pattern (nth 1 evil-mc-pattern))) 270 | 271 | (defun evil-mc-get-pattern-end () 272 | "Return the current pattern end position." 273 | (when evil-mc-pattern (nth 2 evil-mc-pattern))) 274 | 275 | (defun evil-mc-get-pattern-length () 276 | "Return the current pattern length." 277 | (when evil-mc-pattern 278 | (- (evil-mc-get-pattern-end) (evil-mc-get-pattern-start)))) 279 | 280 | (defun evil-mc-get-cursor-count () 281 | "Return the count of active cursors." 282 | (1+ (length evil-mc-cursor-list))) 283 | 284 | (provide 'evil-mc-vars) 285 | 286 | ;;; evil-mc-vars.el ends here 287 | -------------------------------------------------------------------------------- /evil-mc-command-record.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-command-record.el --- Record info for the currently running command 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains functions for recording information about 6 | ;; the currently running command 7 | 8 | (require 'cl-lib) 9 | (require 'evil) 10 | (require 'evil-mc-common) 11 | 12 | ;;; Code: 13 | 14 | (defun evil-mc-command-p (cmd) 15 | "Return true if CMD is an `evil-mc' comand." 16 | (eq (evil-get-command-property cmd :evil-mc) t)) 17 | 18 | (defun evil-mc-command-reset () 19 | "Clear the currently saved command info." 20 | (evil-mc-clear-command) 21 | (evil-mc-clear-recording-command)) 22 | 23 | (defun evil-mc-get-command-property (name) 24 | "Return the current command property with NAME." 25 | (evil-mc-get-object-property evil-mc-command name)) 26 | 27 | (defun evil-mc-set-command-property (&rest properties) 28 | "Set one or more command PROPERTIES and their values into `evil-mc-command'." 29 | (setq evil-mc-command (apply 'evil-mc-put-object-property 30 | (cons evil-mc-command properties)))) 31 | 32 | (defun evil-mc-add-command-property (&rest properties) 33 | "Append to values of one or more PROPERTIES into `evil-mc-command'." 34 | (while properties 35 | (let* ((name (pop properties)) 36 | (new-value (pop properties)) 37 | (old-value (evil-mc-get-command-property name))) 38 | (cond ((null old-value) 39 | (evil-mc-set-command-property name new-value)) 40 | ((vectorp old-value) 41 | (evil-mc-set-command-property name (vconcat old-value new-value))) 42 | ((listp old-value) 43 | (evil-mc-set-command-property name (nconc old-value new-value))) 44 | (t 45 | (error "Current value is not a sequence %s" old-value)))))) 46 | 47 | (defun evil-mc-get-command-keys-vector (&optional name) 48 | "Get the command keys, stored at the property with NAME as a vector." 49 | (evil-mc-get-command-property (or name :keys))) 50 | 51 | (defun evil-mc-get-command-keys-count () 52 | "Get the current command numeric prefix or one." 53 | (or (evil-mc-get-command-property :keys-count) 1)) 54 | 55 | (defun evil-mc-get-command-keys-string (&optional name) 56 | "Get the command keys, stored at the property with NAME, as a string." 57 | (when evil-mc-command 58 | (let* ((keys (evil-mc-get-command-property (or name :keys))) 59 | (keys-string (mapcar (lambda (k) (if (characterp k) 60 | (char-to-string k) "")) 61 | keys))) 62 | (apply 'concat keys-string)))) 63 | 64 | (defun evil-mc-get-command-undo-list-pointer-pre () 65 | "Return a pointer to `buffer-undo-list' recorded before the current command executed." 66 | (when evil-mc-command 67 | (evil-mc-get-command-property :undo-list-pointer-pre))) 68 | 69 | (defun evil-mc-get-command-undo-list-pointer-post () 70 | "Return a pointer to `buffer-undo-list' recorded after the current command executed." 71 | (when evil-mc-command 72 | (evil-mc-get-command-property :undo-list-pointer-post))) 73 | 74 | (defun evil-mc-command-undoable-p () 75 | "Return true if the current command has stored undo information in `buffer-undo-list'." 76 | (not (eq (evil-mc-get-command-undo-list-pointer-pre) 77 | (evil-mc-get-command-undo-list-pointer-post)))) 78 | 79 | (defun evil-mc-undo-command-p () 80 | "True if the current command is an undo command." 81 | (memq (evil-mc-get-command-name) '(undo redo undo-tree-undo undo-tree-redo))) 82 | 83 | (defun evil-mc-get-command-name () 84 | "Return the current command name." 85 | (when evil-mc-command 86 | (evil-mc-get-command-property :name))) 87 | 88 | (defun evil-mc-get-command-state () 89 | "Return the current command end evil state." 90 | (when evil-mc-command 91 | (evil-mc-get-command-property :evil-state-end))) 92 | 93 | (defun evil-mc-get-command-last-input () 94 | "Return the last input for the current command." 95 | (when evil-mc-command 96 | (evil-mc-get-command-property :last-input))) 97 | 98 | (defun evil-mc-save-keys (flag pre-name post-name keys) 99 | "Save KEYS at PRE-NAME or POST-NAME according to FLAG." 100 | (cl-ecase flag 101 | (pre (evil-mc-add-command-property pre-name keys)) 102 | (post (evil-mc-add-command-property post-name keys)))) 103 | 104 | (defun evil-mc-update-command-count (keys-vector) 105 | "Update the current command count with the last digit of KEYS-VECTOR." 106 | (let* ((last-key (elt keys-vector (- (length keys-vector) 1))) 107 | (digit (string-to-number (char-to-string last-key))) 108 | (count (or evil-mc-command-count 0))) 109 | (setq evil-mc-command-count (+ (* 10 count) digit)))) 110 | 111 | (defun evil-mc-add-current-count (keys-vector) 112 | "Add the current count to KEYS-VECTOR if it does not contain it already." 113 | (if (or (not evil-mc-command-count) (eq (length keys-vector) 0)) 114 | keys-vector 115 | (if (evil-mc-starts-with-digit-p keys-vector) 116 | keys-vector 117 | (concat (string-to-vector (int-to-string evil-mc-command-count)) 118 | keys-vector)))) 119 | 120 | (defun evil-mc-begin-command-save () 121 | "Initialize all variables at the start of saving a command." 122 | (when (evil-mc-recording-debug-p) (evil-mc-message "Command %s %s" this-command (this-command-keys))) 123 | (when (and (eq this-command #'digit-argument) 124 | (evil-mc-has-cursors-p) 125 | (not (evil-mc-executing-command-p))) 126 | (evil-mc-update-command-count (this-command-keys-vector))) 127 | (when (and (not (evil-mc-executing-command-p)) 128 | (not (evil-mc-recording-command-p))) 129 | (evil-mc-clear-command) 130 | (when (and (evil-mc-has-cursors-p) 131 | (not (evil-emacs-state-p)) 132 | (not (evil-mc-command-p this-command)) 133 | (evil-mc-known-command-p this-command)) 134 | (setq evil-mc-recording-command t) 135 | (let ((keys-vector (evil-mc-add-current-count (this-command-keys-vector)))) 136 | (evil-mc-set-command-property :name this-command 137 | :keys-pre keys-vector 138 | :keys-pre-with-count (evil-extract-count keys-vector) 139 | :evil-state-begin evil-state 140 | :undo-list-pointer-pre buffer-undo-list) 141 | (when (evil-mc-recording-debug-p) (evil-mc-message "Record-begin %s" evil-mc-command)))))) 142 | (put 'evil-mc-begin-command-save 'permanent-local-hook t) 143 | 144 | (defun evil-mc-save-keys-motion (flag) 145 | "Save the current evil motion key sequence." 146 | (when (evil-mc-recording-command-p) 147 | (evil-mc-save-keys flag 148 | :keys-motion-pre 149 | :keys-motion-post 150 | (this-command-keys-vector)) 151 | (when (evil-mc-recording-debug-p) 152 | (evil-mc-message "Record-motion %s %s %s %s" 153 | flag (this-command-keys) (this-command-keys-vector) evil-state)))) 154 | 155 | (defun evil-mc-save-keys-operator (flag) 156 | "Save the current evil operator key sequence." 157 | (when (and (evil-mc-recording-command-p) 158 | (memq evil-state '(operator))) 159 | (evil-mc-save-keys flag 160 | :keys-operator-pre 161 | :keys-operator-post 162 | (this-command-keys-vector)) 163 | (when (evil-mc-recording-debug-p) 164 | (evil-mc-message "Record-operator %s %s %s %s" 165 | flag (this-command-keys) (this-command-keys-vector) evil-state)))) 166 | 167 | (defun evil-mc-finish-command-save () 168 | "Completes the save of a command." 169 | (when (evil-mc-recording-command-p) 170 | (evil-mc-set-command-property :evil-state-end evil-state 171 | :last-input last-input-event 172 | :keys-post (this-command-keys-vector) 173 | :keys-post-raw (this-single-command-raw-keys) 174 | :undo-list-pointer-post buffer-undo-list) 175 | (when (evil-mc-recording-debug-p) 176 | (evil-mc-message "Record-finish %s %s" evil-mc-command this-command)) 177 | (ignore-errors 178 | (condition-case error 179 | (evil-mc-finalize-command) 180 | (error (evil-mc-message "Saving command %s failed with %s" 181 | (evil-mc-get-command-name) 182 | (error-message-string error)) 183 | nil)))) 184 | (setq evil-mc-recording-command nil)) 185 | (put 'evil-mc-finish-command-save 'permanent-local-hook t) 186 | 187 | (defun evil-mc-finalize-command () 188 | "Make the command data ready for use, after a save." 189 | (let* ((keys-pre (evil-mc-get-command-property :keys-pre)) 190 | (keys-pre-with-count (evil-mc-get-command-property :keys-pre-with-count)) 191 | (keys-pre-count (nth 0 keys-pre-with-count)) 192 | (keys-pre-cmd (vconcat (nth 2 keys-pre-with-count))) 193 | (keys-post (evil-mc-get-command-property :keys-post)) 194 | (keys-motion-pre (evil-mc-add-current-count (evil-mc-get-command-property :keys-motion-pre))) 195 | (keys-motion-post (evil-mc-add-current-count (evil-mc-get-command-property :keys-motion-post))) 196 | (keys-operator-pre (evil-mc-get-command-property :keys-operator-pre)) 197 | (keys-operator-post (evil-mc-get-command-property :keys-operator-post))) 198 | (evil-mc-set-command-property :keys-count keys-pre-count) 199 | (evil-mc-set-command-property 200 | :keys (cond ((or keys-motion-post keys-motion-pre) 201 | (or keys-motion-post keys-motion-pre)) 202 | ((or keys-operator-pre keys-operator-post) 203 | (vconcat (if keys-pre-count keys-pre keys-pre-cmd) 204 | (if (or (equal keys-operator-pre keys-pre-cmd) 205 | (and (equal keys-operator-pre 206 | keys-operator-post) 207 | (not (or 208 | (equal keys-operator-pre [?t]) 209 | (equal keys-operator-pre [?f])))) 210 | (> (length keys-operator-pre) 1) 211 | (> (length keys-operator-post) 1)) 212 | keys-operator-post 213 | (vconcat keys-operator-pre 214 | keys-operator-post)))) 215 | (t (or keys-post keys-pre))) 216 | (evil-mc-clear-command-count))) 217 | (when (evil-mc-recording-debug-p) 218 | (evil-mc-message "Record-done %s pre %s post %s keys-motion %s keys-operator %s count %s keys %s" 219 | (evil-mc-get-command-name) 220 | (evil-mc-get-command-keys-string :keys-pre) 221 | (evil-mc-get-command-keys-string :keys-post) 222 | (evil-mc-get-command-keys-string :keys-motion-post) 223 | (evil-mc-get-command-keys-string :keys-operator-post) 224 | (evil-mc-get-command-property :keys-count) 225 | (evil-mc-get-command-keys-string :keys)))) 226 | 227 | (provide 'evil-mc-command-record) 228 | 229 | ;;; evil-mc-command-record.el ends here 230 | -------------------------------------------------------------------------------- /evil-mc-cursor-make.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-cursor-make.el --- Fake cursor creation and deletion 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains functions for creating and deleting fake cursors 6 | 7 | ;;; Code: 8 | 9 | (require 'cl-lib) 10 | (require 'evil-mc-common) 11 | (require 'evil-mc-vars) 12 | (require 'evil-mc-cursor-state) 13 | (require 'evil-mc-region) 14 | 15 | (defun evil-mc-get-cursor-face () 16 | "Get the current cursor face." 17 | (or evil-mc-cursor-current-face '(evil-mc-cursor-default-face))) 18 | 19 | (defun evil-mc-set-cursor-face (face) 20 | "Set the current cursor FACE." 21 | (setq evil-mc-cursor-current-face face)) 22 | 23 | (defun evil-mc-print-cursors-info (&optional msg) 24 | "Prints information about the current cursors preceded by MSG." 25 | (when (evil-mc-has-cursors-p) 26 | (evil-mc-message "%s %s cursors matching \"%s\"" 27 | (or msg "There are") 28 | (1+ (length evil-mc-cursor-list)) 29 | (evil-mc-get-pattern-text)))) 30 | 31 | (defun evil-mc-cursor-overlay (start end) 32 | "Make an overlay for a cursor from START to END." 33 | (let ((overlay (make-overlay start end nil nil nil))) 34 | (overlay-put overlay 'type 'evil-mc-cursor) 35 | (overlay-put overlay 'priority evil-mc-cursor-overlay-priority) 36 | overlay)) 37 | 38 | (defun evil-mc-cursor-overlay-at-eol (pos) 39 | "Make a cursor overlay at POS assuming pos is at the end of line." 40 | (let ((overlay (evil-mc-cursor-overlay pos pos)) 41 | (face (evil-mc-get-cursor-face))) 42 | (overlay-put overlay 'after-string (propertize " " 'face face)) 43 | overlay)) 44 | 45 | (defun evil-mc-cursor-overlay-inline (pos) 46 | "Make a cursor overlay at POS assuming pos is not at the end of line." 47 | (let ((overlay (evil-mc-cursor-overlay pos (1+ pos))) 48 | (face (evil-mc-get-cursor-face))) 49 | (overlay-put overlay 'face face) 50 | overlay)) 51 | 52 | (defun evil-mc-cursor-overlay-at-pos (&optional pos) 53 | "Make a cursor overlay at POS." 54 | (let ((pos (or pos (point)))) 55 | (save-excursion 56 | (goto-char pos) 57 | (if (eolp) 58 | (evil-mc-cursor-overlay-at-eol pos) 59 | (evil-mc-cursor-overlay-inline pos))))) 60 | 61 | (defun evil-mc-sort-cursors () 62 | "Sort the cursors list by position." 63 | (setq evil-mc-cursor-list 64 | (sort evil-mc-cursor-list 65 | (lambda (x y) 66 | (< (evil-mc-get-cursor-start x) 67 | (evil-mc-get-cursor-start y)))))) 68 | 69 | (defun evil-mc-copy-cursor-state (from &optional to) 70 | "Copy all state FROM cursor to TO cursor." 71 | (let ((names (evil-mc-get-cursor-variables))) 72 | (dolist (name names) 73 | (setq to (evil-mc-put-cursor-property 74 | to 75 | name 76 | (copy-tree (evil-mc-get-cursor-property from name))))) 77 | to)) 78 | 79 | (defun evil-mc-read-cursor-state (&optional state) 80 | "Read the state of the real cursor into STATE." 81 | (let ((names (evil-mc-get-cursor-variables))) 82 | (dolist (name names) 83 | (when (boundp name) 84 | (setq state (evil-mc-put-cursor-property state 85 | name 86 | (symbol-value name))))) 87 | state)) 88 | 89 | (defun evil-mc-write-cursor-state (state) 90 | "Write the state of the real cursor with values from STATE." 91 | (let ((names (evil-mc-get-cursor-variables))) 92 | (dolist (name names) 93 | (when (boundp name) 94 | (set name (evil-mc-get-cursor-property state name)))))) 95 | 96 | (defun evil-mc-insert-cursor-into-list (cursor cursor-list) 97 | "Insert CURSOR into CURSOR-LIST at the correct location so that 98 | the cursors are ordered by the cursor overlay start position." 99 | (cond ((null cursor-list) 100 | (setq cursor-list (list cursor))) 101 | ((> (evil-mc-get-cursor-start (car cursor-list)) 102 | (evil-mc-get-cursor-start cursor)) 103 | (setq cursor-list (cons cursor cursor-list))) 104 | (t (let ((start (evil-mc-get-cursor-start cursor)) 105 | (list cursor-list)) 106 | (while (and (cdr list) 107 | (> start (evil-mc-get-cursor-start (cadr list)))) 108 | (setq list (cdr list))) 109 | (setcdr list (cons cursor (cdr list)))))) 110 | cursor-list) 111 | 112 | (defun evil-mc-insert-cursor (cursor) 113 | "Insert CURSOR into `evil-mc-cursor-list' at the correct location so that 114 | the cursors are ordered by the cursor overlay start position." 115 | (setq evil-mc-cursor-list (evil-mc-insert-cursor-into-list 116 | cursor 117 | evil-mc-cursor-list))) 118 | 119 | (defun evil-mc-delete-cursor (cursor) 120 | "Delete all overlays associated with CURSOR." 121 | (evil-mc-delete-cursor-overlay cursor) 122 | (evil-mc-delete-region-overlay (evil-mc-get-cursor-region cursor))) 123 | 124 | (defun evil-mc-delete-all-regions () 125 | "Clear all visual regions and exit visual state." 126 | (when (evil-visual-state-p) 127 | (dolist (cursor evil-mc-cursor-list) 128 | (evil-mc-delete-region-overlay (evil-mc-get-cursor-region cursor))) 129 | (evil-exit-visual-state))) 130 | 131 | (defun evil-mc-undo-cursor (cursor) 132 | "Delete CURSOR and remove it from `evil-mc-cursor-list'." 133 | (when (and cursor (evil-mc-has-cursors-p)) 134 | (let ((start (evil-mc-get-cursor-start cursor))) 135 | (evil-mc-delete-cursor cursor) 136 | (setq evil-mc-cursor-list (delq cursor evil-mc-cursor-list)) 137 | cursor))) 138 | 139 | (defun evil-mc-get-default-cursor () 140 | "Return a new cursor with all default properties initialized." 141 | (evil-mc-put-cursor-property 142 | nil 143 | 'evil-markers-alist (default-value 'evil-markers-alist) 144 | 'evil-repeat-ring (make-ring 10) 145 | 'evil-jumper--window-jumps (make-hash-table) 146 | 'evil-jump-list nil 147 | 'kill-ring (copy-tree kill-ring) 148 | 'undo-stack nil 149 | 'undo-stack-pointer nil)) 150 | 151 | (defun evil-mc-make-cursor-at-pos (pos &optional source-cursor) 152 | "Make a cursor at POS and add it to `evil-mc-cursor-list'. 153 | If SOURCE-CURSOR is specified copy its state onto the new cursor" 154 | (let* ((source (evil-mc-copy-cursor-state 155 | (or source-cursor (evil-mc-get-default-cursor)))) 156 | (cursor (evil-mc-put-cursor-property 157 | source 158 | 'last-position pos 159 | 'temporary-goal-column (evil-mc-column-number pos) 160 | 'overlay (evil-mc-cursor-overlay-at-pos pos)))) 161 | (evil-mc-insert-cursor cursor) 162 | cursor)) 163 | 164 | (defun evil-mc-undo-cursor-at-pos (pos) 165 | "Delete the cursor at POS from `evil-mc-cursor-list' and remove its overlay. 166 | Return the deleted cursor." 167 | (let ((pos (or pos (point))) 168 | (found nil)) 169 | (when evil-mc-cursor-list 170 | (setq evil-mc-cursor-list 171 | (cl-remove-if (lambda (cursor) 172 | (when (eq pos (evil-mc-get-cursor-start cursor)) 173 | (evil-mc-delete-cursor cursor) 174 | (setq found cursor) 175 | t)) 176 | evil-mc-cursor-list))) 177 | found)) 178 | 179 | (defun evil-mc-find-prev-cursor (&optional pos) 180 | "Find the cursor closest to POS when searching backwards." 181 | (let ((prev nil) (pos (or pos (point)))) 182 | (catch 'evil-mc-undo-prev-cursor-done 183 | (dolist (cursor evil-mc-cursor-list) 184 | (if (> (evil-mc-get-cursor-start cursor) pos) 185 | (throw 'evil-mc-undo-prev-cursor-done t) 186 | (setq prev cursor)))) 187 | prev)) 188 | 189 | (defun evil-mc-find-next-cursor (&optional pos) 190 | "Find the cursor closest to POS when searching forwards." 191 | (let ((next nil) (pos (or pos (point)))) 192 | (catch 'evil-mc-undo-next-cursor-done 193 | (dolist (cursor evil-mc-cursor-list) 194 | (when (>= (evil-mc-get-cursor-start cursor) pos) 195 | (setq next cursor) 196 | (throw 'evil-mc-undo-next-cursor-done t)))) 197 | next)) 198 | 199 | (defun evil-mc-find-first-cursor () 200 | "Return the cursor with the lowest position." 201 | (car evil-mc-cursor-list)) 202 | 203 | (defun evil-mc-find-last-cursor () 204 | "Return the cursor with the highest position." 205 | (car (last evil-mc-cursor-list))) 206 | 207 | (defun evil-mc-make-pattern (text whole-word) 208 | "Make a search pattern for TEXT, that optionally matches only WHOLE-WORDs." 209 | (let ((literal (regexp-quote text))) 210 | (evil-ex-make-search-pattern 211 | (if whole-word (concat "\\_<" literal "\\_>") literal)))) 212 | 213 | (defun evil-mc-set-pattern-for-range (range whole-word) 214 | "Set `evil-mc-pattern' to the text given by RANGE, optionally matching only WHOLE-WORDs." 215 | (let ((start (car range)) (end (cadr range))) 216 | (if (and (<= (point-min) start) 217 | (>= (point-max) end) 218 | (< start end)) 219 | (setq evil-mc-pattern 220 | (cons (evil-mc-make-pattern (buffer-substring-no-properties start end) 221 | whole-word) 222 | range)) 223 | (error "Invalid range %s" range)))) 224 | 225 | (defun evil-mc-set-pattern () 226 | "Move the cursor to the end of the selected text or symbol at point and initialize `evil-mc-pattern'." 227 | (let ((whole-word (not (evil-visual-state-p)))) 228 | (if (evil-visual-state-p) 229 | (let ((end (cadr (evil-visual-range)))) 230 | (when (not (eq (point) end)) 231 | (goto-char (1- end)))) 232 | (let ((range (evil-inner-symbol))) 233 | (evil-visual-char (car range) (1- (cadr range))))) 234 | (setq evil-mc-pattern nil) 235 | (evil-mc-set-pattern-for-range (evil-visual-range) whole-word))) 236 | 237 | (defun evil-mc-make-cursors-for-all () 238 | "Make a cursor for all matches of `evil-mc-pattern'." 239 | (when (evil-mc-has-pattern-p) 240 | (let ((point (point))) 241 | (save-excursion 242 | (goto-char (point-min)) 243 | (while (eq (evil-ex-find-next (evil-mc-get-pattern) 'forward t) t) 244 | (goto-char (1- (point))) 245 | (when (/= point (point)) 246 | (evil-mc-run-cursors-before) 247 | (evil-mc-make-cursor-at-pos (point))) 248 | (goto-char (1+ (point)))))))) 249 | 250 | (defun evil-mc-goto-cursor (cursor create) 251 | "Move point to CURSOR and optionally CREATE a cursor at point." 252 | (when (evil-mc-has-cursors-p) 253 | (let ((start (evil-mc-get-cursor-start cursor)) 254 | (point (point))) 255 | (when (and cursor (/= start point)) 256 | (goto-char start) 257 | (when create 258 | (evil-mc-run-cursors-before) 259 | (evil-mc-make-cursor-at-pos point (evil-mc-read-cursor-state))) 260 | (evil-mc-write-cursor-state (evil-mc-undo-cursor cursor)) 261 | (evil-mc-run-cursors-after t))))) 262 | 263 | (defun evil-mc-goto-match (direction create) 264 | "Move point to the next match according to DIRECTION 265 | and optionally CREATE a cursor at point." 266 | (when (evil-mc-has-pattern-p) 267 | (let ((point (point)) 268 | (had-cursors (evil-mc-has-cursors-p)) 269 | (found (evil-ex-find-next (evil-mc-get-pattern) direction nil))) 270 | (cond ((eq (evil-mc-get-pattern-length) 1) 271 | (cl-ecase direction 272 | (forward 273 | (setq found (evil-ex-find-next (evil-mc-get-pattern) direction nil))) 274 | (backward 275 | (setq found (evil-ex-find-next (evil-mc-get-pattern) 'forward nil))))) 276 | (t 277 | (when (and found (eq direction 'backward)) 278 | (setq found (evil-ex-find-next (evil-mc-get-pattern) direction nil)) 279 | (setq found (evil-ex-find-next (evil-mc-get-pattern) 'forward nil))))) 280 | (if found 281 | (goto-char (1- (point))) 282 | (goto-char point) 283 | (evil-mc-message "No more matches found for %s" (evil-mc-get-pattern-text))) 284 | (when (and found create (/= point (point))) 285 | (evil-mc-run-cursors-before) 286 | (evil-mc-make-cursor-at-pos point (evil-mc-read-cursor-state))) 287 | (evil-mc-write-cursor-state (or (evil-mc-undo-cursor-at-pos (point)) 288 | (evil-mc-put-cursor-last-position 289 | (evil-mc-get-default-cursor) 290 | (point)))) 291 | (unless (evil-mc-has-cursors-p) (evil-mc-clear-pattern)) 292 | (evil-mc-run-cursors-after had-cursors)))) 293 | 294 | (defun evil-mc-find-and-goto-cursor (find create) 295 | "FIND a cursor, go to it, and optionally CREATE a cursor at point." 296 | (when (evil-mc-has-cursors-p) 297 | (evil-mc-delete-all-regions) 298 | (let* ((cursor (funcall find)) 299 | (start (evil-mc-get-cursor-start cursor))) 300 | (when cursor 301 | (cond ((eq find 'evil-mc-find-first-cursor) 302 | (when (> (point) start) (evil-mc-goto-cursor cursor create))) 303 | ((eq find 'evil-mc-find-last-cursor) 304 | (when (< (point) start) (evil-mc-goto-cursor cursor create))) 305 | (t 306 | (evil-mc-goto-cursor cursor create)))))) 307 | (evil-mc-print-cursors-info)) 308 | 309 | (defun evil-mc-find-and-goto-match (direction create) 310 | "Find the next match in DIRECTION and optionally CREATE a cursor at point." 311 | (unless (evil-mc-has-pattern-p) (evil-mc-set-pattern)) 312 | (evil-mc-delete-all-regions) 313 | (evil-mc-goto-match direction create) 314 | (evil-mc-print-cursors-info)) 315 | 316 | (defun evil-mc-run-cursors-before () 317 | "Runs `evil-mc-cursors-before' if there are no cursors created yet." 318 | (when (not (evil-mc-has-cursors-p)) 319 | (evil-mc-cursors-before))) 320 | 321 | (defun evil-mc-run-cursors-after (had-cursors) 322 | "Runs `evil-mc-cursors-after' if HAD-CURSORS and there are no more cursors." 323 | (when (and had-cursors (not (evil-mc-has-cursors-p))) 324 | (evil-mc-cursors-after))) 325 | 326 | (defun evil-mc-cursors-before () 327 | "Actions to be executed before any cursors are created." 328 | (setq evil-mc-cursor-state (evil-mc-read-cursor-state nil)) 329 | (evil-mc-write-cursor-state 330 | (evil-mc-put-cursor-last-position (evil-mc-get-default-cursor) (point))) 331 | (run-hooks 'evil-mc-before-cursors-created)) 332 | 333 | (defun evil-mc-cursors-after () 334 | "Actions to be executed after all cursors are deleted." 335 | (evil-mc-clear-pattern) 336 | (evil-mc-clear-cursor-list) 337 | (evil-mc-clear-cursor-state) 338 | (run-hooks 'evil-mc-after-cursors-deleted)) 339 | 340 | (evil-define-command evil-mc-make-cursor-here () 341 | "Create a cursor at point." 342 | :repeat ignore 343 | :evil-mc t 344 | (evil-mc-run-cursors-before) 345 | (evil-mc-make-cursor-at-pos (point))) 346 | 347 | (evil-define-command evil-mc-make-and-goto-first-cursor () 348 | "Make a cursor at point and move point to the cursor with the lowest position." 349 | :repeat ignore 350 | :evil-mc t 351 | (evil-mc-find-and-goto-cursor 'evil-mc-find-first-cursor t)) 352 | 353 | (evil-define-command evil-mc-make-and-goto-last-cursor () 354 | "Make a cursor at point and move point to the cursor with the last position." 355 | :repeat ignore 356 | :evil-mc t 357 | (evil-mc-find-and-goto-cursor 'evil-mc-find-last-cursor t)) 358 | 359 | (evil-define-command evil-mc-make-and-goto-prev-cursor () 360 | "Make a cursor at point and move point to the cursor 361 | closest to it when searching backwards." 362 | :repeat ignore 363 | :evil-mc t 364 | (evil-mc-find-and-goto-cursor 'evil-mc-find-prev-cursor t)) 365 | 366 | (evil-define-command evil-mc-make-and-goto-next-cursor () 367 | "Make a cursor at point and move point to the cursor 368 | closest to it when searching forwards." 369 | :repeat ignore 370 | :evil-mc t 371 | (evil-mc-find-and-goto-cursor 'evil-mc-find-next-cursor t)) 372 | 373 | (evil-define-command evil-mc-skip-and-goto-prev-cursor () 374 | "Move point to the cursor closest to it when searching backwards." 375 | :repeat ignore 376 | :evil-mc t 377 | (evil-mc-find-and-goto-cursor 'evil-mc-find-prev-cursor nil)) 378 | 379 | (evil-define-command evil-mc-skip-and-goto-next-cursor () 380 | "Move point to the cursor closest to it when searching forwards." 381 | :repeat ignore 382 | :evil-mc t 383 | (evil-mc-find-and-goto-cursor 'evil-mc-find-next-cursor nil)) 384 | 385 | (evil-define-command evil-mc-skip-and-goto-next-match () 386 | "Initialize `evil-mc-pattern' and go to the next match." 387 | :repeat ignore 388 | :evil-mc t 389 | (evil-mc-find-and-goto-match 'forward nil)) 390 | 391 | (evil-define-command evil-mc-skip-and-goto-prev-match () 392 | "Initialize `evil-mc-pattern' and go to the previous match." 393 | :repeat ignore 394 | :evil-mc t 395 | (evil-mc-find-and-goto-match 'backward nil)) 396 | 397 | (evil-define-command evil-mc-make-and-goto-next-match () 398 | "Initialize `evil-mc-pattern', make a cursor at point, and go to the next match." 399 | :repeat ignore 400 | :evil-mc t 401 | (evil-mc-find-and-goto-match 'forward t)) 402 | 403 | (evil-define-command evil-mc-make-and-goto-prev-match () 404 | "Initialize `evil-mc-pattern', make a cursor at point, and go to the previous match." 405 | :repeat ignore 406 | :evil-mc t 407 | (evil-mc-find-and-goto-match 'backward t)) 408 | 409 | (evil-define-command evil-mc-undo-all-cursors () 410 | "Delete all cursors and remove them from `evil-mc-cursor-list'." 411 | :repeat ignore 412 | :evil-mc t 413 | (when (evil-mc-has-cursors-p) 414 | (mapc 'evil-mc-delete-cursor evil-mc-cursor-list) 415 | (evil-exit-visual-state) 416 | (evil-mc-cursors-after))) 417 | 418 | (evil-define-command evil-mc-make-all-cursors () 419 | "Initialize `evil-mc-pattern' and make cursors for all matches." 420 | :repeat ignore 421 | :evil-mc t 422 | (if (evil-mc-has-cursors-p) (user-error "Cursors already exist.") 423 | (evil-mc-set-pattern) 424 | (evil-exit-visual-state) 425 | (evil-mc-make-cursors-for-all) 426 | (evil-mc-print-cursors-info "Created"))) 427 | 428 | (provide 'evil-mc-cursor-make) 429 | 430 | ;;; evil-mc-cursor-make.el ends here 431 | -------------------------------------------------------------------------------- /evil-mc-known-commands.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-known-commands.el --- A list of supported commands and their handlers 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains a list of supported commands and their handlers 6 | ;; specified by evil state 7 | 8 | ;;; Code: 9 | 10 | (defvar evil-mc-known-commands 11 | '((backward-delete-char-untabify . ((:default . evil-mc-execute-default-call-with-count))) 12 | (delete-forward-char . ((:default . evil-mc-execute-default-call-with-count))) 13 | (company-complete-selection . ((:default . evil-mc-execute-default-call))) 14 | (company-select-next . ((:default . evil-mc-execute-default-ignore))) 15 | (copy-to-the-end-of-line . ((:default . evil-mc-execute-default-call))) 16 | (delete-backward-char . ((:default . evil-mc-execute-default-call-with-count))) 17 | (electric-newline-and-maybe-indent . ((:default . evil-mc-execute-default-call))) 18 | (evil-a-WORD . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 19 | (evil-a-back-quote . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 20 | (evil-a-bracket . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 21 | (evil-a-curly . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 22 | (evil-a-double-quote . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 23 | (evil-a-paragraph . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 24 | (evil-a-paren . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 25 | (evil-a-sentence . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 26 | (evil-a-single-quote . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 27 | (evil-a-symbol . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 28 | (evil-a-tag . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 29 | (evil-a-word . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 30 | (evil-an-angle . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 31 | (evil-append . ((:default . evil-mc-execute-default-call-with-count))) 32 | (evil-append-line . ((:default . evil-mc-execute-default-call-with-count))) 33 | (evil-beginning-of-line . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 34 | (evil-beginning-of-line-or-digit-argument . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 35 | (evil-beginning-of-visual-line . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 36 | (evil-change . ((:default . evil-mc-execute-default-evil-change))) 37 | (evil-change-line . ((:default . evil-mc-execute-default-evil-change-line))) 38 | (evil-commentary . ((:default . evil-mc-execute-default-evil-commentary))) 39 | (evil-complete-next . ((:default . evil-mc-execute-default-complete))) 40 | (evil-complete-next-line . ((:default . evil-mc-execute-default-complete))) 41 | (evil-complete-previous . ((:default . evil-mc-execute-default-complete))) 42 | (evil-complete-previous-line . ((:default . evil-mc-execute-default-complete))) 43 | (evil-delete . ((:default . evil-mc-execute-default-evil-delete))) 44 | (evil-delete-backward-char-and-join . ((:default . evil-mc-execute-default-call-with-count))) 45 | (evil-delete-backward-word . ((:default . evil-mc-execute-default-call))) 46 | (evil-delete-char . ((:default . evil-mc-execute-default-evil-delete))) 47 | (evil-delete-line . ((:default . evil-mc-execute-default-evil-delete))) 48 | (evil-digit-argument-or-evil-beginning-of-line . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 49 | (evil-downcase . ((:default . evil-mc-execute-default-change-case))) 50 | (evil-exchange . ((:default . evil-mc-execute-default-evil-exchange))) 51 | (evil-exchange-cancel . ((:default . evil-mc-execute-default-call))) 52 | (evil-exchange-point-and-mark . ((visual . evil-mc-execute-visual-exchange-point-and-mark))) 53 | (evil-find-char . ((:default . evil-mc-execute-default-evil-find-char) (visual . evil-mc-execute-visual-evil-find-char))) 54 | (evil-find-char-backward . ((:default . evil-mc-execute-default-evil-find-char) (visual . evil-mc-execute-visual-evil-find-char))) 55 | (evil-find-char-to . ((:default . evil-mc-execute-default-evil-find-char) (visual . evil-mc-execute-visual-evil-find-char))) 56 | (evil-find-char-to-backward . ((:default . evil-mc-execute-default-evil-find-char) (visual . evil-mc-execute-visual-evil-find-char))) 57 | (evil-first-non-blank . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 58 | (evil-first-non-blank-of-visual-line . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 59 | (evil-goto-definition . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 60 | (evil-goto-line . ((:default . evil-mc-execute-default-evil-goto-line) (visual . evil-mc-execute-visual-evil-goto-line))) 61 | (evil-goto-mark . ((:default . evil-mc-execute-default-call-with-last-input) (visual . evil-mc-execute-visual-call-with-last-input))) 62 | (evil-goto-mark-line . ((:default . evil-mc-execute-default-call-with-last-input) (visual . evil-mc-execute-visual-call-with-last-input))) 63 | (evil-inner-WORD . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 64 | (evil-inner-angle . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 65 | (evil-inner-back-quote . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 66 | (evil-inner-bar . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 67 | (evil-inner-block-star . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 68 | (evil-inner-bracket . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 69 | (evil-inner-curly . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 70 | (evil-inner-dollar . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 71 | (evil-inner-double-quote . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 72 | (evil-inner-paragraph . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 73 | (evil-inner-paren . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 74 | (evil-inner-percent . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 75 | (evil-inner-sentence . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 76 | (evil-inner-single-quote . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 77 | (evil-inner-star . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 78 | (evil-inner-symbol . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 79 | (evil-inner-tag . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 80 | (evil-inner-word . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 81 | (evil-insert . ((:default . evil-mc-execute-default-call-with-count))) 82 | (evil-insert-line . ((:default . evil-mc-execute-default-call-with-count))) 83 | (evil-invert-case . ((:default . evil-mc-execute-default-change-case))) 84 | (evil-invert-char . ((:default . evil-mc-execute-default-change-case))) 85 | (evil-join . ((:default . evil-mc-execute-default-evil-join))) 86 | (evil-jump-item . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 87 | (evil-lookup . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 88 | (evil-middle-of-visual-line . ((:default . evil-mc-execute-default-call) (visual evil-mc-execute-visual-call))) 89 | (evil-next-line . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-call-with-count))) 90 | (evil-next-match . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 91 | (evil-normal-state . ((:default . evil-mc-execute-default-evil-normal-state))) 92 | (evil-open-above . ((:default . evil-mc-execute-default-call-with-count))) 93 | (evil-open-below . ((:default . evil-mc-execute-default-call-with-count))) 94 | (evil-outer-bar . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 95 | (evil-outer-block-star . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 96 | (evil-outer-dollar . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 97 | (evil-outer-percent . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 98 | (evil-outer-star . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 99 | (evil-paste-after . ((:default . evil-mc-execute-default-evil-paste))) 100 | (evil-paste-before . ((:default . evil-mc-execute-default-evil-paste))) 101 | (evil-paste-from-register . ((:default . evil-mc-execute-default-macro))) 102 | (evil-paste-pop . ((:default . evil-mc-execute-default-not-supported))) 103 | (evil-paste-pop-next . ((:default . evil-mc-execute-default-not-supported))) 104 | (evil-previous-line . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-call-with-count))) 105 | (evil-previous-match . ((:default . evil-mc-execute-default-call-with-count) (visual . evil-mc-execute-visual-text-object))) 106 | (evil-repeat . ((:default . evil-mc-execute-default-call-with-count))) 107 | (evil-repeat-pop . ((:default . evil-mc-execute-default-call-with-count))) 108 | (evil-repeat-pop-next . ((:default . evil-mc-execute-default-call-with-count))) 109 | (evil-replace . ((:default . evil-mc-execute-default-evil-replace))) 110 | (evil-search-backward . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 111 | (evil-search-forward . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 112 | (evil-set-marker . ((:default . evil-mc-execute-default-call-with-last-input) (visual . evil-mc-execute-visual-call-with-last-input))) 113 | (evil-shift-left . ((:default . evil-mc-execute-default-evil-shift-left) (visual . evil-mc-execute-visual-evil-shift-left))) 114 | (evil-shift-right . ((:default . evil-mc-execute-default-evil-shift-right) (visual . evil-mc-execute-visual-evil-shift-right))) 115 | (evil-snipe-F . ((:default . evil-mc-execute-default-evil-snipe) (visual . evil-mc-execute-visual-evil-snipe))) 116 | (evil-snipe-S . ((:default . evil-mc-execute-default-evil-snipe) (visual . evil-mc-execute-visual-evil-snipe))) 117 | (evil-snipe-T . ((:default . evil-mc-execute-default-evil-snipe) (visual . evil-mc-execute-visual-evil-snipe))) 118 | (evil-snipe-f . ((:default . evil-mc-execute-default-evil-snipe) (visual . evil-mc-execute-visual-evil-snipe))) 119 | (evil-snipe-s . ((:default . evil-mc-execute-default-evil-snipe) (visual . evil-mc-execute-visual-evil-snipe))) 120 | (evil-snipe-t . ((:default . evil-mc-execute-default-evil-snipe) (visual . evil-mc-execute-visual-evil-snipe))) 121 | (evil-snipe-repeat-reverse . ((:default . evil-mc-execute-default-evil-snipe-repeat-reverse) (visual . evil-mc-execute-visual-evil-snipe-repeat-reverse))) 122 | (evil-sp-change-line . ((:default . evil-mc-execute-default-evil-sp-change-line))) 123 | (evil-sp-delete . ((:default . evil-mc-execute-default-evil-sp-delete))) 124 | (evil-sp-delete-char . ((:default . evil-mc-execute-default-evil-sp-delete))) 125 | (evil-sp-delete-line . ((:default . evil-mc-execute-default-evil-sp-delete))) 126 | (evil-substitute . ((:default . evil-mc-execute-default-evil-substitute))) 127 | (evil-surround-region . ((:default . evil-mc-execute-default-evil-surround-region))) 128 | (evil-upcase . ((:default . evil-mc-execute-default-change-case))) 129 | (evil-use-register . ((:default . evil-mc-execute-default-call-with-last-input) (visual . evil-mc-execute-visual-call-with-last-input))) 130 | (evil-visual-block . ((visual . evil-mc-execute-default-not-supported))) 131 | (evil-visual-char . ((:default . evil-mc-execute-default-force-normal-state) (visual . evil-mc-execute-visual-char))) 132 | (evil-visual-exchange-corners . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 133 | (evil-visual-line . ((:default . evil-mc-execute-default-force-normal-state) (visual . evil-mc-execute-visual-line))) 134 | (evil-visual-restore . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 135 | (evil-window-middle . ((:default . evil-mc-execute-default-call) (visual . evil-mc-execute-visual-call))) 136 | (evil-yank . ((:default . evil-mc-execute-default-evil-yank))) 137 | (exchange-point-and-mark . ((visual . evil-mc-execute-visual-exchange-point-and-mark))) 138 | (hippie-expand . ((:default . evil-mc-execute-default-hippie-expand))) 139 | (indent-for-tab-command . ((:default . evil-mc-execute-default-call))) 140 | (indent-region-or-buffer . ((:default . evil-mc-execute-default-ignore))) 141 | (keyboard-quit . ((:default . evil-mc-execute-default-ignore))) 142 | (move-text-down . ((:default . evil-mc-execute-default-call-with-count))) 143 | (move-text-up . ((:default . evil-mc-execute-default-call-with-count))) 144 | (newline . ((:default . evil-mc-execute-default-call))) 145 | (newline-and-indent . ((:default . evil-mc-execute-default-call))) 146 | (paste-after-current-line . ((:default . evil-mc-execute-default-call-with-count))) 147 | (paste-before-current-line . ((:default . evil-mc-execute-default-call-with-count))) 148 | (redo . ((:default . evil-mc-execute-default-redo))) 149 | (self-insert-command . ((:default . evil-mc-execute-default-call-with-count))) 150 | (sp-backward-delete-char . ((:default . evil-mc-execute-default-call))) 151 | (transpose-chars-before-point . ((:default . evil-mc-execute-default-call-with-count))) 152 | (transpose-chars . ((:default . evil-mc-execute-default-call-with-count))) 153 | (undo . ((:default . evil-mc-execute-default-undo))) 154 | (undo-tree-undo . ((:default . evil-mc-execute-default-undo))) 155 | (undo-tree-redo . ((:default . evil-mc-execute-default-redo))) 156 | (yank . ((:default . evil-mc-execute-default-call))) 157 | 158 | ;; cc-mode 159 | (c-electric-backspace . ((:default . evil-mc-execute-default-call-with-count))) 160 | (c-electric-brace . ((:default . evil-mc-execute-default-call-with-count))) 161 | (c-electric-colon . ((:default . evil-mc-execute-default-call-with-count))) 162 | (c-electric-continued-statement . ((:default . evil-mc-execute-default-call))) 163 | (c-electric-delete . ((:default . evil-mc-execute-default-call-with-count))) 164 | (c-electric-delete-forward . ((:default . evil-mc-execute-default-call-with-count))) 165 | (c-electric-lt-gt . ((:default . evil-mc-execute-default-call-with-count))) 166 | (c-electric-paren . ((:default . evil-mc-execute-default-call-with-count))) 167 | (c-electric-pound . ((:default . evil-mc-execute-default-call-with-count))) 168 | (c-electric-semi&comma . ((:default . evil-mc-execute-default-call-with-count))) 169 | (c-electric-slash . ((:default . evil-mc-execute-default-call-with-count))) 170 | (c-electric-star . ((:default . evil-mc-execute-default-call-with-count))) 171 | (c-indent-new-comment-line . ((:default . evil-mc-execute-default-call))) 172 | 173 | ;; haskell 174 | (haskell-indentation-common-electric-command . ((:default . evil-mc-execute-default-macro))) 175 | (haskell-indentation-newline-and-indent . ((:default . evil-mc-execute-default-call))) 176 | (haskell-interactive-mode-space . ((:default . evil-mc-execute-default-call-with-count))) 177 | 178 | ;; ruby mode 179 | (ruby-tools-interpolate . ((:default . evil-mc-execute-default-call))) 180 | 181 | ;; java 182 | (spacemacs/java-completing-dot . ((:default . evil-mc-execute-default-call))) 183 | (spacemacs/java-completing-double-colon . ((:default . evil-mc-execute-default-call))) 184 | 185 | ;; c 186 | 187 | ;; python-mode 188 | (python-indent-dedent-line-backspace . ((:default . evil-mc-execute-default-call-with-count))) 189 | 190 | ;; scala 191 | (scala/newline-and-indent-with-asterisk . ((:default . evil-mc-execute-default-call))) 192 | (scala/completing-dot . ((:default . evil-mc-execute-default-call))) 193 | 194 | ;; org-mode 195 | (org-beginning-of-line . ((:default . evil-mc-execute-default-call-with-count))) 196 | (org-end-of-line . ((:default . evil-mc-execute-org-end-of-line))) 197 | (org-force-self-insert . ((:default . evil-mc-execute-default-call-with-count))) 198 | (org-return . ((:default . evil-mc-execute-default-call))) 199 | (org-self-insert-command . ((:default . evil-mc-execute-default-call-with-count))) 200 | (org-todo . ((:default . evil-mc-execute-default-call))) 201 | (orgtbl-self-insert-command . ((:default . evil-mc-execute-default-call-with-count))) 202 | (orgtbl-hijacker-command-100 . ((:default . evil-mc-execute-default-call-with-count))) 203 | 204 | ;; unimpaired 205 | (unimpaired/paste-above . ((:default . evil-mc-execute-default-call))) 206 | (unimpaired/paste-below . ((:default . evil-mc-execute-default-call))) 207 | 208 | ;; yaml 209 | (yaml-electric-backspace . ((:default . evil-mc-execute-default-call-with-count))) 210 | (yaml-electric-bar-and-angle . ((:default . evil-mc-execute-default-call-with-count))) 211 | (yaml-electric-dash-and-dot . ((:default . evil-mc-execute-default-call-with-count))) 212 | 213 | ;; evil-matchit 214 | (evilmi-jump-items . ((:default . evil-mc-execute-default-call))) 215 | 216 | ;; evil-numbers 217 | (evil-numbers/inc-at-pt . ((:default . evil-mc-execute-default-call-with-count))) 218 | (evil-numbers/dec-at-pt . ((:default . evil-mc-execute-default-call-with-count))) 219 | (spacemacs/evil-numbers-decrease . ((:default . evil-mc-execute-default-call-with-count))) 220 | (spacemacs/evil-numbers-increase . ((:default . evil-mc-execute-default-call-with-count))) 221 | 222 | ;; evil-cleverparens 223 | (evil-cp-append ; a 224 | (:default . evil-mc-execute-default-call-with-count)) 225 | (evil-cp-change ; c 226 | (:default . evil-mc-execute-default-evil-change)) 227 | (evil-cp-change-line ; C 228 | (:default . evil-mc-execute-default-evil-change-line)) 229 | (evil-cp-delete ; d 230 | (:default . evil-mc-execute-default-evil-delete)) 231 | (evil-cp-delete-line ; D 232 | (:default . evil-mc-execute-default-evil-delete)) 233 | (evil-cp-change-sexp ; M-c 234 | (:default . evil-mc-execute-default-evil-change)) 235 | (evil-cp-change-enclosing ; M-C 236 | (:default . evil-mc-execute-default-evil-change)) 237 | (evil-cp-delete-sexp ; M-d 238 | (:default . evil-mc-execute-default-evil-delete)) 239 | (evil-cp-delete-enclosing ; M-D 240 | (:default . evil-mc-execute-default-evil-delete)) 241 | (evil-cp-delete-char-or-splice ; x 242 | (:default . evil-mc-execute-default-evil-delete)) 243 | (evil-cp-insert ; i 244 | (:default . evil-mc-execute-default-call-with-count)) 245 | (evil-cp-substitute ; s 246 | (:default . evil-mc-execute-default-evil-substitute)) 247 | (evil-cp-yank ; y 248 | (:default . evil-mc-execute-default-evil-yank)) 249 | (evil-cp-first-non-blank-non-opening ; _ 250 | (:default . evil-mc-execute-default-call) 251 | (visual . evil-mc-execute-visual-call)) 252 | (evil-cp-< ; < 253 | (:default . evil-mc-execute-default-evil-shift-left) 254 | (visual . evil-mc-execute-visual-shift-left)) 255 | (evil-cp-> ; > 256 | (:default . evil-mc-execute-default-evil-shift-right) 257 | (visual . evil-mc-execute-visual-shift-right)) 258 | (evil-cp-wrap-next-round ; M-( 259 | (:default . evil-mc-execute-default-call-with-count)) 260 | (evil-cp-wrap-previous-round ; M-) 261 | (:default . evil-mc-execute-default-call-with-count)) 262 | (evil-cp-wrap-next-square ; M-[ 263 | (:default . evil-mc-execute-default-call-with-count)) 264 | (evil-cp-wrap-previous-square ; M-] 265 | (:default . evil-mc-execute-default-call-with-count)) 266 | (evil-cp-wrap-next-curly ; M-{ 267 | (:default . evil-mc-execute-default-call-with-count)) 268 | (evil-cp-wrap-previous-curly ; M-} 269 | (:default . evil-mc-execute-default-call-with-count)) 270 | (evil-cp-open-below-form ; M-o 271 | (:default . evil-mc-execute-default-call-with-count)) 272 | (evil-cp-open-above-form ; M-O 273 | (:default . evil-mc-execute-default-call-with-count)) 274 | (evil-cp-insert-at-end-of-form ; M-a 275 | (:default . evil-mc-execute-default-call-with-count)) 276 | (evil-cp-insert-at-beginning-of-form ; M-i 277 | (:default . evil-mc-execute-default-call-with-count)) 278 | (evil-cp-copy-paste-form ; M-w 279 | (:default . evil-mc-execute-default-call-with-count)) 280 | (evil-cp-drag-forward ; M-j 281 | (:default . evil-mc-execute-default-call-with-count)) 282 | (evil-cp-drag-backward ; M-k 283 | (:default . evil-mc-execute-default-call-with-count)) 284 | (evil-cp-raise-form ; M-R 285 | (:default . evil-mc-execute-default-call-with-count)) 286 | ;; note: couldn't actually get this one to work, so I set it the same as 287 | ;; `evil-delete-backward-word' 288 | (evil-cp-delete-backward-word ; C-w in insert state 289 | (:default . evil-mc-execute-default-call)) 290 | 291 | ;; not supported for now, because normal `evil-change-whole-line' also is 292 | ;; not supported 293 | ;; evil-cp-change-whole-line ; S 294 | 295 | ;; not supported for now, because normal `evil-yank-line' also is not 296 | ;; supported 297 | ;; evil-cp-yank-line ; Y 298 | 299 | ;; not supported: `evil-cp-override' needs to be called once for each cursor, 300 | ;; right before calling the next evil-cp command. For example, if the user 301 | ;; has 2 cursors and calls `evil-cp-override' and then 302 | ;; `evil-cp-delete-char-or-splice', evil-mc should call them in this order: 303 | ;; override, delete-or-splice, override, delete-or-splice 304 | ;; instead of: override, override, delete-or-splice, delete-or-splice 305 | ;; evil-cp-override ; M-z 306 | ) 307 | "A list of the supported commands and their handlers. 308 | Entries have the form (NAME . HANDLERS), where handlers is a list of entries of 309 | the form (STATE . HANDLER). The state can be any evil state name or `:default' 310 | which will be used if no entry matching the current state is found.") 311 | 312 | (provide 'evil-mc-known-commands) 313 | 314 | ;;; evil-mc-known-commands.el ends here 315 | -------------------------------------------------------------------------------- /evil-mc-command-execute.el: -------------------------------------------------------------------------------- 1 | ;;; evil-mc-command-execute.el --- Execute commands for every fake cursor 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains functions for executing a command for every fake cursor 6 | 7 | (require 'cl-lib) 8 | (require 'evil) 9 | (require 'evil-mc-common) 10 | (require 'evil-mc-vars) 11 | (require 'evil-mc-undo) 12 | (require 'evil-mc-cursor-state) 13 | (require 'evil-mc-cursor-make) 14 | (require 'evil-mc-command-record) 15 | (require 'evil-mc-region) 16 | 17 | ;;; Code: 18 | 19 | (defmacro evil-mc-define-handler (command &rest body) 20 | "Define a COMMAND handler with BODY. 21 | 22 | \(fn COMMAND BODY...)" 23 | (declare (indent defun) 24 | (debug (&define name 25 | [&optional lambda-list] 26 | [&optional stringp] 27 | [&rest keyword sexp] 28 | def-body))) 29 | (let (arg args doc doc-form key keys) 30 | (when (listp (car-safe body)) (setq args (pop body))) 31 | (when (> (length body) 1) 32 | (if (eq (car-safe (car-safe body)) 'format) 33 | (setq doc-form (pop body)) 34 | (when (stringp (car-safe body)) 35 | (setq doc (pop body))))) 36 | (while (keywordp (car-safe body)) 37 | (setq key (pop body) 38 | arg (pop body)) 39 | (unless nil (setq keys (plist-put keys key arg)))) 40 | `(progn 41 | ,(when (and command body) 42 | `(defun ,command ,args 43 | ,@(when doc `(,doc)) 44 | ,@body)) 45 | ,(when (and command doc-form) 46 | `(put ',command 'function-documentation ,doc-form)) 47 | (let ((func ',(if (and (null command) body) 48 | `(lambda ,args 49 | ,@body) 50 | command))) 51 | (apply #'evil-set-command-properties func ',keys) 52 | func)))) 53 | 54 | (defmacro evil-mc-save-window-scroll (&rest forms) 55 | "Saves and restores the window scroll position" 56 | (let ((p (make-symbol "p")) 57 | (s (make-symbol "start")) 58 | (h (make-symbol "hscroll"))) 59 | `(let ((,p (set-marker (make-marker) (point))) 60 | (,s (set-marker (make-marker) (window-start))) 61 | (,h (window-hscroll))) 62 | ,@forms 63 | (goto-char ,p) 64 | (set-window-start nil ,s t) 65 | (set-window-hscroll nil ,h) 66 | (set-marker ,p nil) 67 | (set-marker ,s nil)))) 68 | 69 | (defmacro evil-mc-define-visual-handler (command &rest body) 70 | "Define a visual COMMAND handler with BODY that updates the 71 | current region after executing BODY. 72 | 73 | \(fn COMMAND BODY ...)" 74 | (declare (indent 2) (debug t)) 75 | `(evil-mc-define-handler ,command () 76 | (ignore-errors ,@body) 77 | (evil-mc-update-current-region))) 78 | 79 | (defmacro evil-mc-with-region (region form &rest body) 80 | "Execute FORM if there is a REGION. Otherwise execute BODY." 81 | (declare (indent 2) (debug t)) 82 | `(if ,region 83 | (let ((region-start (evil-mc-get-region-start ,region)) 84 | (region-end (evil-mc-get-region-end ,region)) 85 | (region-type (evil-mc-get-region-type ,region))) 86 | ,form) 87 | ,@body)) 88 | 89 | (defmacro evil-mc-with-region-or-execute-macro (region add-register &rest body) 90 | "Execute BODY if there is a REGION. 91 | Otherwise, run `evil-mc-execute-macro' with ADD-REGISTER." 92 | (declare (indent 2) (debug t)) 93 | `(evil-mc-with-region ,region 94 | (progn ,@body) 95 | (evil-mc-execute-macro ,add-register))) 96 | 97 | (defun evil-mc-execute-hippie-expand () 98 | "Execute a completion command." 99 | (hippie-expand 1)) 100 | 101 | (defun evil-mc-execute-evil-find-char () 102 | "Execute an `evil-find-char' command." 103 | (evil-repeat-find-char (evil-mc-get-command-keys-count))) 104 | 105 | (defun evil-mc-execute-evil-snipe () 106 | "Execute an `evil-snipe' command." 107 | (funcall 'evil-snipe-repeat)) 108 | 109 | (defun evil-mc-execute-evil-snipe-reverse () 110 | "Execute an `evil-snipe-repeat-reverse' command." 111 | (funcall 'evil-snipe-repeat -1)) 112 | 113 | (defun evil-mc-execute-evil-commentary () 114 | "Execute an `evil-commentary' command." 115 | (evil-mc-with-region-or-execute-macro region nil 116 | (when (eq region-type 'char) (goto-char region-start)) 117 | (funcall 'evil-commentary region-start region-end))) 118 | 119 | (defun evil-mc-execute-evil-join () 120 | "Execute an `evil-join' command." 121 | (evil-mc-with-region-or-execute-macro region nil 122 | (goto-char region-start) 123 | (evil-join region-start region-end))) 124 | 125 | (defun evil-mc-execute-evil-shift (cmd) 126 | "Execute an `evil-shift-left' or `evil-shift-right'." 127 | (evil-mc-with-region-or-execute-macro region nil 128 | (funcall cmd 129 | region-start 130 | region-end 131 | (evil-mc-get-command-keys-count)))) 132 | 133 | (defun evil-mc-execute-evil-surround-region () 134 | "Execute an `evil-surround-region' command." 135 | (evil-mc-with-region-or-execute-macro region nil 136 | (goto-char region-start) 137 | (funcall 'evil-surround-region 138 | region-start 139 | region-end 140 | region-type 141 | (evil-mc-get-command-last-input)))) 142 | 143 | (defun evil-mc-execute-change-case (cmd) 144 | "Execute an `evil-invert-char', `evil-invert-case' `evil-upcase' 145 | or `evil-downcase' command." 146 | (evil-mc-with-region-or-execute-macro region nil 147 | (goto-char region-start) 148 | (funcall cmd region-start region-end region-type))) 149 | 150 | (defun evil-mc-execute-evil-replace () 151 | "Execute an `evil-replace' command." 152 | (evil-mc-with-region-or-execute-macro region nil 153 | (evil-replace region-start 154 | region-end 155 | region-type 156 | (evil-mc-get-command-last-input)))) 157 | 158 | (defun evil-mc-execute-evil-exchange () 159 | "Execute an `evil-exchange' command." 160 | (evil-mc-with-region-or-execute-macro region nil 161 | (goto-char region-start) 162 | (funcall 'evil-exchange region-start region-end region-type))) 163 | 164 | (defun evil-mc-execute-with-region-or-macro (cmd) 165 | "Execute CMD with the current register and region. 166 | If there is no region run an `evil-mc-execute-macro'." 167 | (evil-mc-with-region-or-execute-macro region t 168 | (funcall cmd region-start region-end region-type evil-this-register) 169 | (when (eolp) (evil-end-of-line)))) 170 | 171 | (defun evil-mc-execute-with-region-or-pos (cmd) 172 | "Execute a CMD with the current register and region. 173 | If there is no region call CMD with the point position." 174 | (evil-mc-with-region region 175 | (funcall cmd 176 | region-start 177 | region-end 178 | region-type 179 | evil-this-register) 180 | (funcall cmd (point) (1+ (point))))) 181 | 182 | (defun evil-mc-execute-evil-change-line () 183 | "Execute an `evil-change-line' comand." 184 | (evil-mc-execute-with-region-or-pos 'evil-delete-line)) 185 | 186 | (defun evil-mc-execute-evil-yank () 187 | "Execute an `evil-yank' comand." 188 | (evil-mc-with-region-or-execute-macro region t 189 | (evil-yank region-start region-end region-type evil-this-register) 190 | (goto-char (min (evil-mc-get-region-mark region) 191 | (evil-mc-get-region-point region))))) 192 | 193 | (defun evil-mc-execute-evil-substitute () 194 | "Execute an `evil-substitute' comand." 195 | (let ((point (point))) 196 | (evil-with-state normal 197 | (unless (or region (eq point (point-at-bol))) 198 | (evil-forward-char 1 nil t)) 199 | (evil-mc-execute-with-region-or-macro 'evil-substitute)))) 200 | 201 | (defun evil-mc-execute-evil-change () 202 | "Execute an `evil-change' comand." 203 | (let ((point (point))) 204 | (evil-with-state normal 205 | (unless (or (and region (< (evil-mc-get-region-mark region) 206 | (evil-mc-get-region-point region))) 207 | (eq point (point-at-bol))) 208 | (evil-forward-char 1 nil t)) 209 | (evil-mc-execute-with-region-or-macro 'evil-change)))) 210 | 211 | (defun evil-mc-execute-evil-paste () 212 | "Execute an `evil-paste-before' or `evil-paste-after' command." 213 | (cond ((null region) 214 | (funcall (evil-mc-get-command-name) 215 | (evil-mc-get-command-keys-count) 216 | evil-this-register)) 217 | ((evil-mc-char-region-p region) 218 | (let (new-kill-ring new-kill-ring-yank-pointer) 219 | (let ((kill-ring (copy-tree kill-ring)) 220 | (kill-ring-yank-pointer nil)) 221 | 222 | (evil-mc-execute-with-region-or-macro 'evil-delete) 223 | (setq new-kill-ring kill-ring) 224 | (setq new-kill-ring-yank-pointer kill-ring-yank-pointer)) 225 | 226 | ;; execute paste with the old key ring 227 | (evil-paste-before (evil-mc-get-command-keys-count) evil-this-register) 228 | 229 | ;; update the kill ring with the overwritten text 230 | (setq kill-ring new-kill-ring) 231 | (setq kill-ring-yank-pointer new-kill-ring-yank-pointer))) 232 | ((evil-mc-line-region-p region) 233 | (let ((text (substring-no-properties (current-kill 0 t))) 234 | (start (evil-mc-get-region-start region)) 235 | (end (evil-mc-get-region-end region))) 236 | (unless (evil-mc-ends-with-newline-p text) 237 | (evil-insert-newline-below)) 238 | (evil-paste-after (evil-mc-get-command-keys-count) evil-this-register) 239 | (save-excursion (evil-delete start end 'line)))))) 240 | 241 | (defun evil-mc-execute-macro (&optional add-register) 242 | "Execute a generic command as a keyboard macro. 243 | If ADD-REGISTER is not nil add the current `evil-this-register' 244 | to the keys vector" 245 | (execute-kbd-macro 246 | (if add-register 247 | (evil-mc-get-command-keys-vector-with-register) 248 | (evil-mc-get-command-keys-vector)))) 249 | 250 | (defun evil-mc-execute-evil-goto-line () 251 | "Execute an `evil-goto-line' command." 252 | (let ((count (evil-mc-get-command-property :keys-count))) 253 | (if count 254 | (evil-goto-line count) 255 | (evil-goto-line)))) 256 | 257 | (defun evil-mc-execute-call () 258 | "Execute a generic command as a function call without parameters." 259 | (funcall (evil-mc-get-command-name))) 260 | 261 | (defun evil-mc-execute-call-with-last-input () 262 | "Executed a generic command as a function call with the last input character." 263 | (funcall (evil-mc-get-command-name) (evil-mc-get-command-last-input))) 264 | 265 | (defun evil-mc-execute-call-with-count () 266 | "Execute a generic command as a function call with count." 267 | (funcall (evil-mc-get-command-name) (evil-mc-get-command-keys-count))) 268 | 269 | (defun evil-mc-execute-not-supported () 270 | "Throw an error for a not supported command." 271 | (evil-force-normal-state) 272 | (error (message "%s is not supported" (evil-mc-get-command-name)))) 273 | 274 | (defun evil-mc-clear-current-region () 275 | "Clears the current region." 276 | (setq region nil)) 277 | 278 | (defun evil-mc-update-current-region () 279 | "Update the current region." 280 | (setq region (evil-mc-update-region region))) 281 | 282 | (defun evil-mc-execute-visual-region (type) 283 | "Execute an `evil-visual-char' or `evil-visual-line' 284 | command according to TYPE." 285 | (cond ((or (null region) 286 | (eq (evil-mc-get-region-type region) type)) 287 | (setq region (evil-mc-create-region (point) (point) type))) 288 | (t 289 | (setq region (evil-mc-change-region-type region type))))) 290 | 291 | (defun evil-mc-get-command-keys-vector-with-register () 292 | "Return the keys-vector of current command prepended 293 | by the value of `evil-this-register'." 294 | (if evil-this-register 295 | (vconcat [?\"] 296 | (vector evil-this-register) 297 | (evil-mc-get-command-keys-vector)) 298 | (evil-mc-get-command-keys-vector))) 299 | 300 | 301 | ;; default handlers 302 | 303 | (evil-mc-define-handler evil-mc-execute-default-complete () 304 | :cursor-clear region 305 | :cursor-state :dabbrev 306 | (evil-mc-execute-call)) 307 | 308 | (evil-mc-define-handler evil-mc-execute-default-hippie-expand () 309 | :cursor-clear region 310 | :cursor-state :dabbrev 311 | (hippie-expand 1)) 312 | 313 | (evil-mc-define-handler evil-mc-execute-default-evil-find-char () 314 | :cursor-clear region 315 | (evil-mc-execute-evil-find-char)) 316 | 317 | (evil-mc-define-handler evil-mc-execute-default-evil-snipe () 318 | :cursor-clear region 319 | (evil-mc-execute-evil-snipe)) 320 | 321 | (evil-mc-define-handler evil-mc-execute-default-evil-snipe-repeat-reverse () 322 | :cursor-clear region 323 | (evil-mc-execute-evil-snipe-reverse)) 324 | 325 | (evil-mc-define-handler evil-mc-execute-default-evil-commentary () 326 | :cursor-clear region 327 | (evil-mc-execute-evil-commentary)) 328 | 329 | (evil-mc-define-handler evil-mc-execute-default-evil-join () 330 | :cursor-clear region 331 | (evil-mc-execute-evil-join)) 332 | 333 | (evil-mc-define-handler evil-mc-execute-default-evil-shift-left () 334 | :cursor-clear region 335 | (evil-mc-execute-evil-shift 'evil-shift-left)) 336 | 337 | (evil-mc-define-handler evil-mc-execute-default-evil-shift-right () 338 | :cursor-clear region 339 | (evil-mc-execute-evil-shift 'evil-shift-right)) 340 | 341 | (evil-mc-define-handler evil-mc-execute-default-evil-surround-region () 342 | :cursor-clear region 343 | (evil-mc-execute-evil-surround-region)) 344 | 345 | (evil-mc-define-handler evil-mc-execute-default-evil-replace () 346 | :cursor-clear region 347 | (evil-mc-execute-evil-replace)) 348 | 349 | (evil-mc-define-handler evil-mc-execute-default-evil-exchange () 350 | :cursor-clear region 351 | (evil-mc-execute-evil-exchange)) 352 | 353 | (evil-mc-define-handler evil-mc-execute-default-evil-substitute () 354 | :cursor-clear region 355 | (evil-mc-execute-evil-substitute)) 356 | 357 | (evil-mc-define-handler evil-mc-execute-default-evil-yank () 358 | :cursor-clear region 359 | (evil-mc-execute-evil-yank)) 360 | 361 | (evil-mc-define-handler evil-mc-execute-default-evil-change () 362 | :cursor-clear region 363 | (evil-mc-execute-evil-change)) 364 | 365 | (evil-mc-define-handler evil-mc-execute-default-evil-paste () 366 | :cursor-clear region 367 | (evil-mc-execute-evil-paste)) 368 | 369 | (evil-mc-define-handler evil-mc-execute-default-change-case 370 | :cursor-clear region 371 | (evil-mc-execute-change-case (evil-mc-get-command-name))) 372 | 373 | (evil-mc-define-handler evil-mc-execute-default-evil-delete () 374 | :cursor-clear region 375 | (evil-mc-execute-with-region-or-macro (evil-mc-get-command-name))) 376 | 377 | (evil-mc-define-handler evil-mc-execute-default-evil-change-line () 378 | :cursor-clear region 379 | (evil-mc-execute-with-region-or-pos 'evil-delete-line)) 380 | 381 | (evil-mc-define-handler evil-mc-execute-default-evil-sp-change-line () 382 | :cursor-clear region 383 | (evil-mc-execute-with-region-or-pos 'evil-sp-delete-line)) 384 | 385 | (evil-mc-define-handler evil-mc-execute-default-evil-sp-delete () 386 | :cursor-clear region 387 | (evil-mc-execute-with-region-or-pos (evil-mc-get-command-name))) 388 | 389 | (evil-mc-define-handler evil-mc-execute-default-evil-goto-line () 390 | :cursor-clear region 391 | (evil-mc-execute-evil-goto-line)) 392 | 393 | (evil-mc-define-handler evil-mc-execute-default-force-normal-state () 394 | :cursor-clear region 395 | (evil-force-normal-state)) 396 | 397 | (evil-mc-define-handler evil-mc-execute-default-evil-normal-state () 398 | :cursor-clear region 399 | (evil-insert 1) 400 | (evil-normal-state)) 401 | 402 | (evil-mc-define-handler evil-mc-execute-default-undo () 403 | :cursor-clear region 404 | (let ((point (car-safe undo-stack-pointer ))) 405 | (setq undo-stack-pointer (cdr-safe undo-stack-pointer )) 406 | (when point (evil-mc-goto-char point)))) 407 | 408 | (evil-mc-define-handler evil-mc-execute-default-redo () 409 | :cursor-clear region 410 | (when (and undo-stack (not (eq undo-stack 411 | undo-stack-pointer))) 412 | (let ((prev-1 undo-stack) 413 | (prev-2 nil)) 414 | (while (and prev-1 (not (eq (cdr prev-1) undo-stack-pointer))) 415 | (setq prev-2 prev-1) 416 | (pop prev-1)) 417 | (when (and prev-1 (eq (cdr prev-1) undo-stack-pointer)) 418 | (setq undo-stack-pointer prev-1) 419 | (when (and prev-2 (car prev-2)) 420 | (evil-mc-goto-char (car prev-2))))))) 421 | 422 | (evil-mc-define-handler evil-mc-execute-org-end-of-line () 423 | :cursor-clear region 424 | (funcall 'evil-end-of-line (evil-mc-get-command-keys-count))) 425 | 426 | (evil-mc-define-handler evil-mc-execute-default-macro () 427 | :cursor-clear region 428 | (evil-mc-execute-macro)) 429 | 430 | (evil-mc-define-handler evil-mc-execute-default-call () 431 | :cursor-clear region 432 | (evil-mc-execute-call)) 433 | 434 | (evil-mc-define-handler evil-mc-execute-default-call-with-last-input () 435 | :cursor-clear region 436 | (evil-mc-execute-call-with-last-input)) 437 | 438 | (evil-mc-define-handler evil-mc-execute-default-line-move () 439 | :cursor-clear region 440 | (evil-mc-execute-call-with-count)) 441 | 442 | (evil-mc-define-handler evil-mc-execute-default-call-with-count () 443 | :cursor-clear region 444 | (evil-mc-execute-call-with-count)) 445 | 446 | (evil-mc-define-handler evil-mc-execute-default-ignore () 447 | :cursor-clear region 448 | (ignore)) 449 | 450 | (evil-mc-define-handler evil-mc-execute-default-not-supported () 451 | :cursor-clear region 452 | (evil-mc-execute-not-supported)) 453 | 454 | ;; handlers for visual state 455 | 456 | (evil-mc-define-handler evil-mc-execute-visual-line () 457 | (evil-mc-execute-visual-region 'line)) 458 | 459 | (evil-mc-define-handler evil-mc-execute-visual-char () 460 | (evil-mc-execute-visual-region 'char)) 461 | 462 | (evil-mc-define-handler evil-mc-execute-visual-text-object () 463 | (let* ((limits (funcall (evil-mc-get-command-name))) 464 | (start (nth 0 limits)) 465 | (end (1- (nth 1 limits)))) 466 | (goto-char end) 467 | (setq region (evil-mc-create-region start end 'char)))) 468 | 469 | (evil-mc-define-handler evil-mc-execute-visual-exchange-point-and-mark () 470 | (let* ((next-region (evil-mc-exchange-region-point-and-mark region)) 471 | (mark (evil-mc-get-region-mark next-region)) 472 | (point (evil-mc-get-region-point next-region))) 473 | (goto-char (if (< mark point) (1- point) point)) 474 | (setq region next-region))) 475 | 476 | (evil-mc-define-visual-handler evil-mc-execute-visual-evil-find-char () 477 | (evil-mc-execute-evil-find-char)) 478 | 479 | (evil-mc-define-visual-handler evil-mc-execute-visual-evil-snipe () 480 | (evil-mc-execute-evil-snipe)) 481 | 482 | (evil-mc-define-visual-handler evil-mc-execute-visual-evil-snipe-repeat-reverse () 483 | (evil-mc-execute-evil-snipe-reverse)) 484 | 485 | (evil-mc-define-visual-handler evil-mc-execute-visual-shift-left () 486 | (evil-mc-execute-evil-shift 'evil-shift-left)) 487 | 488 | (evil-mc-define-visual-handler evil-mc-execute-visual-shift-right () 489 | (evil-mc-execute-evil-shift 'evil-shift-right)) 490 | 491 | (evil-mc-define-visual-handler evil-mc-execute-visual-macro () 492 | (evil-mc-execute-macro)) 493 | 494 | (evil-mc-define-visual-handler evil-mc-execute-visual-call-with-last-input () 495 | (evil-mc-execute-call-with-last-input)) 496 | 497 | (evil-mc-define-visual-handler evil-mc-execute-visual-call () 498 | (evil-mc-execute-call)) 499 | 500 | (evil-mc-define-visual-handler evil-mc-execute-visual-line-move () 501 | (evil-mc-execute-call-with-count)) 502 | 503 | (evil-mc-define-visual-handler evil-mc-execute-visual-evil-goto-line () 504 | (evil-mc-execute-evil-goto-line)) 505 | 506 | (evil-mc-define-visual-handler evil-mc-execute-visual-call-with-count () 507 | (evil-mc-execute-call-with-count)) 508 | 509 | ;; ---- 510 | 511 | (defun evil-mc-get-command-handler (cmd state) 512 | "Get the handler function for CMD and evil STATE." 513 | (when (symbolp cmd) (setq cmd (intern (symbol-name cmd)))) 514 | (let* ((handler-data 515 | (or (evil-mc-get-object-property evil-mc-custom-known-commands cmd) 516 | (evil-mc-get-object-property evil-mc-known-commands cmd))) 517 | (handler (evil-mc-get-object-property handler-data state))) 518 | (or handler 519 | (evil-mc-get-object-property handler-data :default) 520 | (cond ((eq (evil-get-command-property cmd :repeat) 'motion) 521 | (if (eq state 'visual) 522 | 'evil-mc-execute-visual-call-with-count 523 | 'evil-mc-execute-default-call-with-count)) 524 | (t 525 | (when (not (eq state 'visual)) 526 | 'evil-mc-execute-default-macro)))))) 527 | 528 | (defun evil-mc-get-state-variables (handler) 529 | "Get all cursor variables required to hold state for HANDLER." 530 | (let ((categories (evil-get-command-property handler :cursor-state))) 531 | (when (atom categories) (setq categories (list categories))) 532 | (when (not (memq :default categories)) (push :default categories)) 533 | (evil-mc-get-cursor-variables categories))) 534 | 535 | (defun evil-mc-get-clear-variables (handler) 536 | "Get all cursor variables that should be cleared after HANDLER." 537 | (let ((names (evil-get-command-property handler :cursor-clear))) 538 | (if (atom names) (list names) names))) 539 | 540 | (defun evil-mc-get-var-name-value (var) 541 | "Gets the current name and value pair of VAR or nil if it needs to be cleared." 542 | (list var (unless (memq var clear-variables) (symbol-value var)))) 543 | 544 | (defun evil-mc-execute-for (cursor state-variables clear-variables) 545 | "Execute the current command for CURSOR in the context of STATE-VARIABLES and 546 | ensure to set CLEAR-VARIABLES to nil after the execution is complete." 547 | (when (evil-mc-executing-debug-p) 548 | (evil-mc-message "Execute %s with %s" (evil-mc-get-command-name) handler)) 549 | (ignore-errors 550 | (cl-progv 551 | state-variables 552 | (evil-mc-get-cursor-properties cursor state-variables) 553 | 554 | (goto-char (evil-mc-get-cursor-start cursor)) 555 | 556 | (when (fboundp 'evil--jump-hook) 557 | (evil--jump-hook (evil-mc-get-command-name))) 558 | (evil-repeat-pre-hook) 559 | 560 | (ignore-errors 561 | (let ((prev-point (point))) 562 | (condition-case error 563 | (funcall handler) 564 | (error 565 | (evil-mc-message "Failed to execute %s with error: %s" 566 | (evil-mc-get-command-name) 567 | (error-message-string error)) 568 | (goto-char prev-point))))) 569 | 570 | (evil-repeat-post-hook) 571 | 572 | (when (and (evil-mc-command-undoable-p) 573 | (evil-mc-has-undo-boundary-p (evil-mc-get-command-undo-list-pointer-pre)) 574 | (not (evil-mc-undo-command-p))) 575 | (setq undo-stack (cons last-position undo-stack-pointer)) 576 | (setq undo-stack-pointer undo-stack)) 577 | 578 | (evil-mc-delete-cursor-overlay cursor) 579 | (evil-mc-delete-region-overlay (evil-mc-get-cursor-region cursor)) 580 | (setq last-position (point)) 581 | 582 | (let ((new-cursor (evil-mc-put-cursor-overlay 583 | cursor 584 | (evil-mc-cursor-overlay-at-pos))) 585 | (new-values (cl-mapcan 'evil-mc-get-var-name-value state-variables))) 586 | (apply 'evil-mc-put-cursor-property new-cursor new-values))))) 587 | 588 | (defun evil-mc-execute-for-all () 589 | "Execute the current command, stored at `evil-mc-command', for all fake cursors." 590 | (when (and (evil-mc-has-command-p) 591 | (not (evil-mc-executing-command-p)) 592 | (not (evil-mc-frozen-p))) 593 | (when (evil-mc-executing-debug-p) 594 | (evil-mc-message "Execute %s for all cursors" (evil-mc-get-command-name))) 595 | (let* ((evil-mc-executing-command t) 596 | (handler (evil-mc-get-command-handler 597 | (evil-mc-get-command-name) 598 | (evil-mc-get-command-state))) 599 | (state-variables (evil-mc-get-state-variables handler)) 600 | (clear-variables (evil-mc-get-clear-variables handler))) 601 | (unless handler 602 | (evil-mc-message "No handler found for command %s" (evil-mc-get-command-name))) 603 | (ignore-errors 604 | (when handler 605 | (evil-repeat-post-hook) 606 | (save-excursion 607 | (evil-mc-save-window-scroll 608 | (condition-case error 609 | (let ((next-cursor-list nil)) 610 | (evil-mc-with-single-undo 611 | (dolist (cursor evil-mc-cursor-list) 612 | (setq next-cursor-list (evil-mc-insert-cursor-into-list 613 | (evil-mc-execute-for cursor 614 | state-variables 615 | clear-variables) 616 | next-cursor-list)))) 617 | (evil-mc-update-cursor-list next-cursor-list)) 618 | (error (evil-mc-message "Failed to execute all %s with error: %s" 619 | (evil-mc-get-command-name) 620 | (error-message-string error)))))))) 621 | (evil-mc-clear-command)))) 622 | 623 | (defun evil-mc-execute-for-all-cursors (cmd) 624 | "Executes CMD for each active cursor fake and real." 625 | (funcall cmd (evil-mc-put-cursor-property nil :index 0 :real t)) 626 | (evil-mc-save-window-scroll 627 | (save-excursion 628 | (let ((next-cursor-list nil) 629 | (index 0)) 630 | (dolist (cursor evil-mc-cursor-list index) 631 | (incf index) 632 | (let* ((data (evil-mc-put-cursor-property cursor :index index)) 633 | (handler (lambda () (funcall cmd data))) 634 | (vars (evil-mc-get-cursor-variables))) 635 | (setq next-cursor-list (evil-mc-insert-cursor-into-list 636 | (evil-mc-execute-for cursor vars nil) 637 | next-cursor-list)))) 638 | (evil-mc-update-cursor-list next-cursor-list))))) 639 | 640 | (when (fboundp 'font-lock-add-keywords) 641 | (font-lock-add-keywords 642 | 'emacs-lisp-mode 643 | '(("(\\(evil-mc-define-handler\\|evil-mc-define-visual-handler\\)" . font-lock-keyword-face)))) 644 | 645 | (provide 'evil-mc-command-execute) 646 | 647 | ;;; evil-mc-command-execute.el ends here 648 | --------------------------------------------------------------------------------