branch: elpa/multiple-cursors commit c3b2d8483b5ba6e8253a068504b722fd30cd810d Merge: a0f771f 2818d9e Author: Magnar Sveen <magn...@gmail.com> Commit: Magnar Sveen <magn...@gmail.com>
Merge pull request #23 from segv/master Improve mc/cycle and mc/mark-next/prev --- features/mark-more.feature | 38 +++++++++++ features/multiple-cursors-core.feature | 14 ++++ mc-cycle-cursors.el | 63 ++++++++++++++---- mc-mark-more.el | 113 ++++++++++++++++++++------------- multiple-cursors-core.el | 11 ++-- 5 files changed, 177 insertions(+), 62 deletions(-) diff --git a/features/mark-more.feature b/features/mark-more.feature index 189b22c..8ca86f7 100644 --- a/features/mark-more.feature +++ b/features/mark-more.feature @@ -94,3 +94,41 @@ Feature: Marking multiple parts of the buffer And I type "more" Then I should have 2 cursors And I should see "Here's more, more and text" + + Scenario: Marking without an active region + When I insert: + """ + aaa + bbb + ccc + """ + And I go to the front of the word "bbb" + And I press "C->" + And I type "_" + Then I should have 2 cursors + And I should see: + """ + aaa + _bbb + _ccc + """ + + Scenario: Increasing number of cursors without an active region + When I insert: + """ + aaa + bbb + ccc + """ + And I go to the front of the word "bbb" + And I press "C->" + And I press "C-<" + And i press "C-f" + And I type "_" + Then I should have 3 cursors + And I should see: + """ + a_aa + b_bb + c_cc + """ diff --git a/features/multiple-cursors-core.feature b/features/multiple-cursors-core.feature index 46d14bf..8d8fb65 100644 --- a/features/multiple-cursors-core.feature +++ b/features/multiple-cursors-core.feature @@ -158,3 +158,17 @@ Feature: Multiple cursors core contains twice """ + + Scenario: Looping forwards around cursors + Given I have cursors at "_" in "1_34567_9" + And I press "C-v" + And I press "C-v" + And I press "C-v" + Then the cursor should be at point "8" + + Scenario: Looping backwards around cursors + Given I have cursors at "_" in "1_34567_9" + And I press "M-v" + And I press "M-v" + Then the cursor should be at point "2" + diff --git a/mc-cycle-cursors.el b/mc-cycle-cursors.el index f70a96a..46d7426 100644 --- a/mc-cycle-cursors.el +++ b/mc-cycle-cursors.el @@ -30,7 +30,7 @@ (eval-when-compile (require 'cl)) -(defun mc/next-cursor-after-point () +(defun mc/next-fake-cursor-after-point () (let ((pos (point)) (next-pos (point-max)) next) @@ -42,7 +42,7 @@ (setq next cursor)))) next)) -(defun mc/prev-cursor-before-point () +(defun mc/prev-fake-cursor-before-point () (let ((pos (point)) (prev-pos (point-min)) prev) @@ -54,23 +54,58 @@ (setq prev cursor)))) prev)) +(defcustom mc/cycle-looping-behaviour 'continue + "What to do if asked to cycle beyond the last cursor or before the first cursor." + :type '(radio (const :tag "Loop around to beginning/end of document." continue) + (const :tag "Warn and then loop around." warn) + (const :tag "Signal an error." error) + (const :tag "Don't loop." stop))) + +(defun mc/handle-loop-condition (error-message) + (ecase mc/cycle-looping-behaviour + (error (error error-message)) + (warn (message error-message)) + (continue 'continue) + (stop 'stop))) + +(defun mc/first-fake-cursor-after (point) + "Very similar to mc/furthest-cursor-before-point, but ignores (mark) and (point)." + (let* ((cursors (mc/all-fake-cursors)) + (cursors-after-point (remove-if (lambda (cursor) + (< (mc/cursor-beg cursor) point)) + cursors)) + (cursors-in-order (sort* cursors-after-point '< :key 'mc/cursor-beg))) + (first cursors-in-order))) + +(defun mc/last-fake-cursor-before (point) + "Very similar to mc/furthest-cursor-before-point, but ignores (mark) and (point)." + (let* ((cursors (mc/all-fake-cursors)) + (cursors-before-point (remove-if (lambda (cursor) + (> (mc/cursor-end cursor) point)) + cursors)) + (cursors-in-order (sort* cursors-before-point '> :key 'mc/cursor-end))) + (first cursors-in-order))) + +(defun* mc/cycle (next-cursor fallback-cursor loop-message) + (when (null next-cursor) + (when (eql 'stop (mc/handle-loop-condition loop-message)) + (return-from mc/cycle nil)) + (setf next-cursor fallback-cursor)) + (mc/create-fake-cursor-at-point) + (mc/pop-state-from-overlay next-cursor) + (recenter)) + (defun mc/cycle-forward () (interactive) - (let ((next-cursor (mc/next-cursor-after-point))) - (unless next-cursor - (error "We're already at the last cursor")) - (mc/create-fake-cursor-at-point) - (mc/pop-state-from-overlay next-cursor) - (recenter))) + (mc/cycle (mc/next-fake-cursor-after-point) + (mc/first-fake-cursor-after (point-min)) + "We're already at the last cursor.")) (defun mc/cycle-backward () (interactive) - (let ((prev-cursor (mc/prev-cursor-before-point))) - (unless prev-cursor - (error "We're already at the first cursor")) - (mc/create-fake-cursor-at-point) - (mc/pop-state-from-overlay prev-cursor) - (recenter))) + (mc/cycle (mc/prev-fake-cursor-before-point) + (mc/last-fake-cursor-before (point-max)) + "We're already at the last cursor")) (define-key mc/keymap (kbd "C-v") 'mc/cycle-forward) (define-key mc/keymap (kbd "M-v") 'mc/cycle-backward) diff --git a/mc-mark-more.el b/mc-mark-more.el index 883fb1e..7d722b0 100644 --- a/mc-mark-more.el +++ b/mc-mark-more.el @@ -79,34 +79,54 @@ (mc/cursor-end cursor)))) strings)) +(defun mc/maybe-multiple-cursors-mode () + "Enable multiple-cursors-mode if there is more than one currently active cursor." + (if (> (mc/num-cursors) 1) + (multiple-cursors-mode 1) + (multiple-cursors-mode 0))) + +(defun mc/mark-more-like-this (skip-last direction) + (let ((case-fold-search nil) + (re (regexp-opt (mc/region-strings))) + (point-out-of-order (ecase direction + (forwards (< (point) (mark))) + (backwards (not (< (point) (mark)))))) + (furthest-cursor (ecase direction + (forwards (mc/furthest-cursor-after-point)) + (backwards (mc/furthest-cursor-before-point)))) + (start-char (ecase direction + (forwards (mc/furthest-region-end)) + (backwards (mc/first-region-start)))) + (search-function (ecase direction + (forwards 'search-forward-regexp) + (backwards 'search-backward-regexp))) + (match-point-getter (ecase direction + (forwards 'match-beginning) + (backwards 'match-end)))) + (mc/save-excursion + (goto-char start-char) + (when skip-last + (mc/remove-fake-cursor furthest-cursor)) + (if (funcall search-function re nil t) + (progn + (push-mark (funcall match-point-getter 0)) + (when point-out-of-order + (exchange-point-and-mark)) + (mc/create-fake-cursor-at-point)) + (error "no more matches found."))))) + ;;;###autoload (defun mc/mark-next-like-this (arg) "Find and mark the next part of the buffer matching the currently active region With negative ARG, delete the last one instead. With zero ARG, skip the last one and mark next." (interactive "p") - (unless (region-active-p) - (error "Mark a region to match first.")) - (when (< arg 0) - (mc/remove-fake-cursor (mc/furthest-cursor-after-point))) - (when (>= arg 0) - (let ((case-fold-search nil) - (point-first (< (point) (mark))) - (re (regexp-opt (mc/region-strings))) - (furthest-cursor (mc/furthest-cursor-after-point))) - (mc/save-excursion - (goto-char (mc/furthest-region-end)) - (when (= arg 0) - (mc/remove-fake-cursor furthest-cursor)) - (if (search-forward-regexp re nil t) - (progn - (push-mark (match-beginning 0)) - (when point-first (exchange-point-and-mark)) - (mc/create-fake-cursor-at-point)) - (error "no more found forward"))))) - (if (> (mc/num-cursors) 1) - (multiple-cursors-mode 1) - (multiple-cursors-mode 0))) + (if (region-active-p) + (if (< arg 0) + (mc/remove-fake-cursor (mc/furthest-cursor-after-point)) + (mc/mark-more-like-this (= arg 0) 'forwards)) + (mc/mark-lines arg 'forwards)) + (mc/maybe-multiple-cursors-mode)) ;;;###autoload (defun mc/mark-previous-like-this (arg) @@ -114,28 +134,33 @@ With zero ARG, skip the last one and mark next." With negative ARG, delete the last one instead. With zero ARG, skip the last one and mark next." (interactive "p") - (unless (region-active-p) - (error "Mark a region to match first.")) - (when (< arg 0) - (mc/remove-fake-cursor (mc/furthest-cursor-before-point))) - (when (>= arg 0) - (let ((case-fold-search nil) - (point-first (< (point) (mark))) - (re (regexp-opt (mc/region-strings))) - (furthest-cursor (mc/furthest-cursor-before-point))) - (mc/save-excursion - (goto-char (mc/first-region-start)) - (when (= arg 0) - (mc/remove-fake-cursor furthest-cursor)) - (if (search-backward-regexp re nil t) - (progn - (push-mark (match-end 0)) - (unless point-first (exchange-point-and-mark)) - (mc/create-fake-cursor-at-point)) - (error "no more found backward"))))) - (if (> (mc/num-cursors) 1) - (multiple-cursors-mode 1) - (multiple-cursors-mode 0))) + (if (region-active-p) + (if (< arg 0) + (mc/remove-fake-cursor (mc/furthest-cursor-before-point)) + (mc/mark-more-like-this (= arg 0) 'backwards)) + (mc/mark-lines arg 'backwards)) + (mc/maybe-multiple-cursors-mode)) + +(defun mc/mark-lines (num-lines direction) + (dotimes (i num-lines) + (mc/create-fake-cursor-at-point) + (ecase direction + (forwards (loop do (next-line 1 nil) + while (mc/all-fake-cursors (point) (1+ (point))))) + (backwards (loop do (previous-line 1 nil) + while (mc/all-fake-cursors (point) (1+ (point)))))))) + +;;;###autoload +(defun mc/mark-next-lines (arg) + (interactive "p") + (mc/mark-lines arg 'forwards) + (mc/maybe-multiple-cursors-mode)) + +;;;###autoload +(defun mc/mark-previous-lines (arg) + (interactive "p") + (mc/mark-lines arg 'backwards) + (mc/maybe-multiple-cursors-mode)) ;;;###autoload (defun mc/unmark-next-like-this (arg) diff --git a/multiple-cursors-core.el b/multiple-cursors-core.el index 387f7ea..3874231 100644 --- a/multiple-cursors-core.el +++ b/multiple-cursors-core.el @@ -49,12 +49,15 @@ (setq buffer-undo-list ;; otherwise add a function to activate this cursor (cons (cons 'apply (cons 'activate-cursor-for-undo (list id))) buffer-undo-list))))) +(defun mc/all-fake-cursors (&optional start end) + (remove-if-not 'mc/fake-cursor-p + (overlays-in (or start (point-min)) + (or end (point-max))))) + (defmacro mc/for-each-fake-cursor (&rest forms) "Runs the body for each fake cursor, bound to the name cursor" - `(mapc #'(lambda (cursor) - (when (mc/fake-cursor-p cursor) - ,@forms)) - (overlays-in (point-min) (point-max)))) + `(mapc #'(lambda (cursor) ,@forms) + (mc/all-fake-cursors))) (defmacro mc/save-excursion (&rest forms) "Saves and restores all the state that multiple-cursors cares about."