Started adding feature tests.

This commit is contained in:
Magnar Sveen 2012-06-06 08:45:20 +02:00
parent 382616ad9a
commit ef7eddf1c5
10 changed files with 539 additions and 14 deletions

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "util/ecukes"]
path = util/ecukes
url = git://github.com/rejeep/ecukes.git
[submodule "util/espuds"]
path = util/espuds
url = git://github.com/rejeep/espuds.git

View File

@ -23,20 +23,6 @@ You can also switch to multiple-cursors-mode by pressing C-g when in
mark-multiple-mode. This is symmetrical to how pressing C-g with an active
region deactivates it. Press C-g again to remove extra cursors.
## Contribute
There's plenty wrong with this implementation still. I'm actively trying things
out, and also working on combining it with
[mark-multiple.el](https://github.com/magnars/mark-multiple.el) to get a more
comprehensive tool.
Still, if you've got something to contribute, please do not hesitate to open
an issue, and we can take a look together before you dive into the elisp. :-)
You'll find the repo at:
https://github.com/magnars/multiple-cursors.el
## Combining with mark-multiple
Right now you can go from multiple marks to multiple cursors with C-g.
@ -55,6 +41,31 @@ needs to be rewritten, and possibly integrated into multiple-cursors.
For now, mark-multiple is an excellent tool to place your cursors where you need
them to be.
## Contribute
There's plenty wrong with this implementation still. I'm actively trying things
out, and also working on combining it with
[mark-multiple.el](https://github.com/magnars/mark-multiple.el) to get a more
comprehensive tool.
Still, if you've got something to contribute, please do not hesitate to open
an issue, and we can take a look together before you dive into the elisp. :-)
You'll find the repo at:
https://github.com/magnars/multiple-cursors.el
To fetch the test dependencies:
$ cd /path/to/multiple-cursors
$ git submodule init
$ git submodule update
Run the tests with:
$ ./util/ecukes/ecukes features
## License
Copyright (C) 2012 Magnar Sveen

View File

@ -0,0 +1,13 @@
Feature: Mark multiple integration
In order to quickly and precisely get multiple cursors
As an Emacs user with mark-multiple
I want to mark multiple regions and then go to multiple-cursors-mode
Scenario: Mark two words and change them
Given there is no region selected
And delete-selection-mode is active
When I insert "This text contains the word text twice"
And I select "text"
And I press "C->"
And I type "sentence"
Then I should see "This sentence contains the word sentence twice"

View File

@ -0,0 +1,3 @@
(And "^delete-selection-mode is active$"
(lambda ()
(delete-selection-mode 1)))

27
features/support/env.el Normal file
View File

@ -0,0 +1,27 @@
(let* ((current-directory (file-name-directory load-file-name))
(features-directory (expand-file-name ".." current-directory))
(project-directory (expand-file-name ".." features-directory)))
(setq multiple-cursors-root-path project-directory)
(setq multiple-cursors-util-path (expand-file-name "util" project-directory)))
(add-to-list 'load-path multiple-cursors-root-path)
(add-to-list 'load-path (expand-file-name "espuds" multiple-cursors-util-path))
(add-to-list 'load-path (expand-file-name "vendor" multiple-cursors-util-path))
(require 'mark-more-like-this)
(require 'multiple-cursors)
(require 'espuds)
(require 'ert)
(Before
(global-set-key (kbd "C->") 'mark-next-like-this)
(switch-to-buffer
(get-buffer-create "*multiple-cursors*"))
(erase-buffer)
(transient-mark-mode 1)
(cua-mode 0)
(delete-selection-mode 0)
(setq set-mark-default-inactive nil)
(deactivate-mark))
(After)

View File

@ -163,6 +163,8 @@ from being executed if in multiple-cursors-mode."
end-of-line
js2-beginning-of-line
js2-end-of-line
js2r-inline-var
change-number-at-point
move-end-of-line
move-end-of-line-or-next-line
beginning-of-line

1
util/ecukes Submodule

@ -0,0 +1 @@
Subproject commit 4b30d7dd4ccf070a5efc73d5615e815ece484882

1
util/espuds Submodule

@ -0,0 +1 @@
Subproject commit 62ef75cb51eef76c547d33a81cb2b6b6e384aefc

192
util/vendor/mark-more-like-this.el vendored Normal file
View File

@ -0,0 +1,192 @@
;;; mark-more-like-this.el --- Mark additional regions in buffer matching current region.
;;
;; Copyright (C) 2011 Magnar Sveen
;;
;; Author: Magnar Sveen <magnars@gmail.com>
;; Keywords: marking replace
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Commentary:
;;
;; These commands will look for strings in the buffer that matches your currently
;; active region and make them mirrors. The mirrors are updated inline as you type.
;;
;; (require 'mark-more-like-this)
;; (global-set-key (kbd "C-<") 'mark-previous-like-this)
;; (global-set-key (kbd "C->") 'mark-next-like-this)
;; (global-set-key (kbd "C-M-m") 'mark-more-like-this)
;;
;; You should feel free to make your own keybindings.
;;
;; 'mark-more-like-this marks the ARG next matches (previous if negative)
;;
;; 'mark-next-like-this marks the next occurance.
;; - with a negative ARG, removes the last occurance.
;; - with a zero ARG, skips the last occurance and marks the next.
;;
;; 'mark-previous-like-this works like -next- but in the other direction.
;;
;; This extension is dependent on the mark-multiple library.
;; https://github.com/magnars/mark-multiple.el
;;; Code:
(require 'mark-multiple)
(defun 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 (or (region-active-p)
mm/master)
(error "Mark a region to match first."))
(if (< arg 0)
(mm/remove-mirror (mm/furthest-mirror-after-master)))
(if (>= arg 0)
(progn
(when (null mm/master)
(mm/create-master (region-beginning) (region-end)))
(save-excursion
(goto-char (mm/last-overlay-end))
(if (= arg 0)
(mm/remove-mirror (mm/furthest-mirror-after-master)))
(let ((case-fold-search nil)
(master-str (mm/master-substring)))
(if (search-forward master-str nil t)
(mm/add-mirror (- (point) (length master-str)) (point))
(error "no more found \"%s\" forward"
(substring-no-properties master-str))))))))
(defun mark-previous-like-this (arg)
"Find and mark the previous 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 previous."
(interactive "p")
(unless (or (region-active-p)
mm/master)
(error "Mark a region to match first."))
(if (< arg 0)
(mm/remove-mirror (mm/furthest-mirror-before-master)))
(if (>= arg 0)
(progn
(when (null mm/master)
(mm/create-master (region-beginning) (region-end)))
(save-excursion
(goto-char (mm/first-overlay-start))
(if (= arg 0)
(mm/remove-mirror (mm/furthest-mirror-before-master)))
(let ((case-fold-search nil)
(master-str (mm/master-substring)))
(if (search-backward master-str nil t)
(mm/add-mirror (point) (+ (point) (length master-str)))
(error "no more found \"%s\" backward"
(substring-no-properties master-str))))))))
(defun mark-all-like-this ()
"Find and mark all the parts of the buffer matching the currently active region"
(interactive)
(unless (or (region-active-p) mm/master) (error "Mark a region to match first."))
(if (not mm/master)
(mm/create-master (region-beginning) (region-end)))
(dolist (mirror mm/mirrors)
(delete-overlay mirror))
(setq mm/mirrors ())
(save-excursion
(goto-char 0)
(let ((case-fold-search nil)
(master-str (mm/master-substring)))
(while (search-forward master-str nil t)
(let ((start (- (point) (length master-str)))
(end (point)))
(if (/= (overlay-start mm/master) start)
(mm/add-mirror start end)))))))
(defun mark-all-like-this-in-region (reg-start reg-end)
"Find and mark all the parts in the region matching the given search"
(interactive "r")
(let ((search (read-from-minibuffer "Mark all in region: "))
(case-fold-search nil))
(deactivate-mark)
(save-excursion
(goto-char reg-start)
(while (search-forward search reg-end t)
(mm/create-master-or-mirror (- (point) (length search)) (point))))
(unless mm/master
(error "Search failed for %S" search))
(goto-char (mm/master-start))))
(defun mark-more-like-this (arg)
"Marks next part of buffer that matches the currently active region ARG times.
Given a negative ARG it searches backwards instead."
(interactive "p")
(unless (or (region-active-p)
mm/master)
(error "Mark a region to match first."))
(if (> arg 0)
(dotimes (i arg) (mark-next-like-this 1))
(dotimes (i (- arg)) (mark-previous-like-this 1))))
(defun mark-more-like-this-extended ()
"Like mark-more-like-this, but then lets you adjust with arrows key.
The actual adjustment made depends on the final component of the
key-binding used to invoke the command, with all modifiers removed:
<up> Mark previous like this
<down> Mark next like this
<left> If last was previous, skip it
If last was next, remove it
<right> If last was next, skip it
If last was previous, remove it
Then, continue to read input events and further add or move marks
as long as the input event read (with all modifiers removed)
is one of the above."
(interactive)
(let ((first t)
(ev last-command-event)
(cmd 'mark-next-like-this)
(arg 1)
last echo-keystrokes)
(while cmd
(let ((base (event-basic-type ev)))
(cond ((eq base 'left)
(if (eq last 'mark-previous-like-this)
(setq cmd last arg 0)
(setq cmd 'mark-next-like-this arg -1)))
((eq base 'up)
(setq cmd 'mark-previous-like-this arg 1))
((eq base 'right)
(if (eq last 'mark-next-like-this)
(setq cmd last arg 0)
(setq cmd 'mark-previous-like-this arg -1)))
((eq base 'down)
(setq cmd 'mark-next-like-this arg 1))
(first
(setq cmd 'mark-next-like-this arg 1))
(t
(setq cmd nil))))
(when cmd
(ignore-errors
(funcall cmd arg))
(setq first nil last cmd)
(setq ev (read-event "Use arrow keys for more marks: "))))
(push ev unread-command-events)))
(provide 'mark-more-like-this)
;;; mark-more-like-this.el ends here

269
util/vendor/mark-multiple.el vendored Normal file
View File

@ -0,0 +1,269 @@
;;; mark-multiple.el --- A library that sorta lets you mark several regions at once
;; Copyright (C) 2011 Magnar Sveen
;; Author: Magnar Sveen <magnars@gmail.com>
;; Keywords: marking library
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; An emacs extension that sorta lets you mark several regions at once.
;;
;; More precisely, it allows for one master region, with several mirror
;; regions. The mirrors are updated inline while you type. This allows for some
;; awesome functionality. Or at least, some more visually pleasing insert and
;; replace operations.
;;
;; Video
;; -----
;; You can [watch an intro to mark-multiple at Emacs Rocks](http://emacsrocks.com/e08.html).
;;
;; Done
;; ----
;; * A general library for managing master and mirrors
;; * `mark-more-like-this` which selects next/previous substring in the buffer that
;; matches the current region.
;; * `inline-string-rectangle` which works like `string-rectangle` but lets you
;; write inline - making it less error prone.
;; * `rename-sgml-tag` which updates the matching tag while typing.
;; * `js2-rename-var` which renames the variable on point and all occurrences
;; in its lexical scope.
;;
;; Installation
;; ------------
;;
;; git submodule add https://github.com/magnars/mark-multiple.el.git site-lisp/mark-multiple
;;
;; Then add the modules you want to your init-file:
;;
;; (require 'inline-string-rectangle)
;; (global-set-key (kbd "C-x r t") 'inline-string-rectangle)
;;
;; (require 'mark-more-like-this)
;; (global-set-key (kbd "C-<") 'mark-previous-like-this)
;; (global-set-key (kbd "C->") 'mark-next-like-this)
;; (global-set-key (kbd "C-M-m") 'mark-more-like-this) ; like the other two, but takes an argument (negative is previous)
;;
;; (require 'rename-sgml-tag)
;; (define-key sgml-mode-map (kbd "C-c C-r") 'rename-sgml-tag)
;;
;; Feel free to come up with your own keybindings.
;;
;; Ideas for more
;; --------------
;; * `js-rename-local-var` which renames the variable at point in the local file.
;;
;; Bugs and gotchas
;; ----------------
;; * Adding a master and mirrors does not remove the active region. This might feel
;; strange, but turns out to be practical.
;;
;; * The current mark-multiple general library lets you do stupid shit, like adding
;; overlapping mirrors. That's only a problem for people who want to write their
;; own functions using `mm/create-master` and `mm/add-mirror`.
;;
;; * Seems like there is some conflict with undo-tree.el, which sometimes clobbers
;; the undo history. I might be doing something particularly stupid. Looking into it.
;;
;; * Reverting the buffer with active marks makes them unremovable.
;;
;; A wild idea
;; -----------
;;
;; Is this a subset of a crazy multiple-point module? How would that even work?
;;
;; There is one use for it I can see, which is editing the end of lines. Set up one
;; cursor at the end of each line, then just edit normally. The command is repeated
;; for each position.
;;
;; Might be too far out there. I still want to do edit-end-of-lines tho.
;;
;;; Code:
(defvar mm/master nil
"The master overlay has the point. Moving point out of master clears all.")
(defvar mm/mirrors nil
"A list of overlays that mirrors master after each change.")
(make-variable-buffer-local 'mm/master)
(make-variable-buffer-local 'mm/mirrors)
(defvar mm/keymap (make-sparse-keymap))
(define-key mm/keymap (kbd "C-g") 'mm/deactivate-region-or-clear-all)
(define-key mm/keymap (kbd "C-m") 'mm/deactivate-region-and-clear-all)
(define-key mm/keymap (kbd "<return>") 'mm/deactivate-region-and-clear-all)
(defface mm/master-face
'((((class color) (background light)) (:background "DarkSeaGreen1"))
(t (:background "DimGrey")))
"The face used to highlight master"
:group 'mark-multiple)
(defface mm/mirror-face
'((((class color) (background light)) (:background "DarkSeaGreen1"))
(t (:background "DimGrey")))
"The face used to highlight mirror"
:group 'mark-multiple)
(defun mm/create-master (start end)
"Start a new multiple mark selection by defining the master region from START to END.
Point must be within the region."
(if (or (< (point) start)
(> (point) end))
(error "Point must be inside master region"))
(mm/clear-all)
(setq mm/master
(make-overlay start end nil nil t))
(overlay-put mm/master 'priority 100)
(overlay-put mm/master 'face 'mm/master-face)
(overlay-put mm/master 'keymap mm/keymap)
(overlay-put mm/master 'modification-hooks '(mm/on-master-modification))
(overlay-put mm/master 'insert-in-front-hooks '(mm/on-master-modification))
(overlay-put mm/master 'insert-behind-hooks '(mm/on-master-modification))
(setq mm/mirrors ())
(add-hook 'post-command-hook 'mm/post-command-handler nil t))
(defun mm/add-mirror (start end)
"Add a region START to END that will mirror the current master."
(if (null mm/master)
(error "No master defined to mirror. Start with mm/create-master."))
(let ((mirror (make-overlay start end nil nil t)))
(setq mm/mirrors (cons mirror mm/mirrors))
(overlay-put mirror 'priority 100)
(overlay-put mirror 'face 'mm/mirror-face)))
(defun mm/deactivate-region-or-clear-all ()
"Deactivate mark if active, otherwise clear all."
(interactive)
(if (use-region-p)
(deactivate-mark)
(mm/clear-all)))
(defun mm/deactivate-region-and-clear-all ()
"Deactivate mark and clear all."
(interactive)
(deactivate-mark)
(mm/clear-all))
(defun mm/clear-all ()
"Remove all marks"
(interactive)
(when (overlayp mm/master)
(delete-overlay mm/master)
(dolist (mirror mm/mirrors)
(delete-overlay mirror))
(setq mm/master nil)
(setq mm/mirrors ())
(remove-hook 'post-command-hook 'mm/post-command-handler)))
(defun mm/master-start ()
(overlay-start mm/master))
(defun mm/master-end ()
(overlay-end mm/master))
(defun mm/point-is-outside-of-master ()
"Is point outside of master?"
(or (null mm/master)
(< (point) (mm/master-start))
(> (point) (mm/master-end))))
(defun mm/active-region-is-outside-of-master ()
"Is region active and mark outside master?"
(and (region-active-p)
(or (< (mark) (mm/master-start))
(> (mark) (mm/master-end)))))
(defun mm/post-command-handler ()
"Clear all marks if point or region is outside of master"
(if (or (mm/point-is-outside-of-master)
(mm/active-region-is-outside-of-master))
(mm/clear-all)))
(defun mm/master-substring ()
"Get the buffer substring that is in master"
(buffer-substring (mm/master-start) (mm/master-end)))
(defun mm/on-master-modification (overlay after? beg end &optional length)
"Update all mirrors after a change"
(save-excursion
(dolist (mirror mm/mirrors)
(mm/replace-mirror-substring mirror (mm/master-substring)))))
(defun mm/replace-mirror-substring (mirror substring)
"Replace the contents of MIRROR with SUBSTRING"
(goto-char (overlay-start mirror))
(delete-char (- (overlay-end mirror) (overlay-start mirror)))
(insert substring))
;; Define some utility functions for users of mark-multiple:
(defun mm/create-master-or-mirror (start end)
"Create master from START to END if there is none, otherwise add mirror."
(if (null mm/master)
(mm/create-master start end)
(mm/add-mirror start end)))
(defun mm/remove-mirror (mirror)
"Removes all traces of MIRROR"
(setq mm/mirrors (remove mirror mm/mirrors))
(delete-overlay mirror))
(defun mm/furthest-mirror-before-master ()
"Find the mirror with the lowest start position before master"
(if (null mm/mirrors)
(error "No mirrors to be found, sir."))
(let ((first nil)
(start (mm/master-start)))
(dolist (mirror mm/mirrors)
(when (< (overlay-start mirror) start)
(setq first mirror)
(setq start (overlay-start mirror))))
first))
(defun mm/furthest-mirror-after-master ()
"Find the mirror with the highest end position after master"
(if (null mm/mirrors)
(error "No mirrors to be found, sir."))
(let ((last nil)
(end (mm/master-end)))
(dolist (mirror mm/mirrors)
(when (> (overlay-end mirror) end)
(setq last mirror)
(setq end (overlay-end mirror))))
last))
(defun mm/first-overlay-start ()
"Find first buffer position covered by master and mirrors"
(let ((start (mm/master-start)))
(dolist (mirror mm/mirrors)
(if (< (overlay-start mirror) start)
(setq start (overlay-start mirror))))
start))
(defun mm/last-overlay-end ()
"Find last buffer position covered by master and mirrors"
(let ((end (mm/master-end)))
(dolist (mirror mm/mirrors)
(if (> (overlay-end mirror) end)
(setq end (overlay-end mirror))))
end))
(provide 'mark-multiple)
;;; mark-multiple.el ends here