Merge pull request #23 from segv/master

Improve mc/cycle and mc/mark-next/prev
This commit is contained in:
Magnar Sveen 2012-10-12 10:26:46 -07:00
commit c3b2d8483b
5 changed files with 177 additions and 62 deletions

View File

@ -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
"""

View File

@ -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"

View File

@ -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))
(defun mc/cycle-forward ()
(interactive)
(let ((next-cursor (mc/next-cursor-after-point)))
(unless next-cursor
(error "We're already at the last cursor"))
(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)))
(recenter))
(defun mc/cycle-forward ()
(interactive)
(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)

View File

@ -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)

View File

@ -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."