diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4b25436 --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/README.md b/README.md index 636afa0..c28bab8 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/features/mark-multiple-integration.feature b/features/mark-multiple-integration.feature new file mode 100644 index 0000000..cd3eca8 --- /dev/null +++ b/features/mark-multiple-integration.feature @@ -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" diff --git a/features/step-definitions/multiple-cursors-steps.el b/features/step-definitions/multiple-cursors-steps.el new file mode 100644 index 0000000..2836291 --- /dev/null +++ b/features/step-definitions/multiple-cursors-steps.el @@ -0,0 +1,3 @@ +(And "^delete-selection-mode is active$" + (lambda () + (delete-selection-mode 1))) diff --git a/features/support/env.el b/features/support/env.el new file mode 100644 index 0000000..6c5fe8e --- /dev/null +++ b/features/support/env.el @@ -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) diff --git a/multiple-cursors.el b/multiple-cursors.el index 7158e17..5367701 100644 --- a/multiple-cursors.el +++ b/multiple-cursors.el @@ -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 diff --git a/util/ecukes b/util/ecukes new file mode 160000 index 0000000..4b30d7d --- /dev/null +++ b/util/ecukes @@ -0,0 +1 @@ +Subproject commit 4b30d7dd4ccf070a5efc73d5615e815ece484882 diff --git a/util/espuds b/util/espuds new file mode 160000 index 0000000..62ef75c --- /dev/null +++ b/util/espuds @@ -0,0 +1 @@ +Subproject commit 62ef75cb51eef76c547d33a81cb2b6b6e384aefc diff --git a/util/vendor/mark-more-like-this.el b/util/vendor/mark-more-like-this.el new file mode 100644 index 0000000..7b690ba --- /dev/null +++ b/util/vendor/mark-more-like-this.el @@ -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 +;; 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 . +;; +;;; 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: + + Mark previous like this + Mark next like this + If last was previous, skip it + If last was next, remove it + 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 diff --git a/util/vendor/mark-multiple.el b/util/vendor/mark-multiple.el new file mode 100644 index 0000000..55be086 --- /dev/null +++ b/util/vendor/mark-multiple.el @@ -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 +;; 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 . + +;;; 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 "") '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