Subject: bug#22873: Can we support multiple Cursors?

From: Keith David Bershatsky <[email-masked]>
Date: Mon, 28 Mar 2016 20:45:07 -0700

The attached multiple_cursors_007.diff applies to the master branch as of today, March 28, 2016, bearing commit a30e7e12ed8465e2565dd318d921bc87f52ce18e.

mc_calc now runs from the applicable nsterm.m/w32term.c/xterm.c, instead of xdisp.c.  mc-calc has been broken down into 3 distinct sections, rather than if / if else / if else.  A bool argument has been added to mc_calc -- true for remove/cleanup, false for draw fake cursors.  The real cursor is redrawn if mc-list is set to Qnil (just in case it got erased during cleanup).  I do not believe mc_calc needs to run inside xdisp.c where draw_window_cursor is called at either location.

I've lightly tested this patch on Windows (XP SP-3), X11 (on an OSX box), and OSX (10.6.8).  I've switched over my own in-house setup to use this latest patch and I will be making notes of any problems as they arise -- e.g., I see there are a few rare instances where some of the fake cursors are not getting erased on OSX, and I see that Windows is a little slow drawing fake cursors when dealing with about 50 on the screen [I'm testing in a virtual machine called Parallels).  My primary usage of fake cursors is a vertical line that tracks the cursor position (compatible with word-wrap), and my secondary usage is with Magnar's library.

The usage of this patch is as follows:

    (defun mc-test (&optional list)
    "Draw fake cursors at all POS defined in the `mc-list'.  Multiple fake cursors
    are supported by GUI versions of Emacs built for X, Windows and OSX.
    Color vector is LSL (The Linden Scripting Language), rather than standard RGB.
    `nsterm.m' uses `NSColor', which works well with LSL.  `w32term.c' uses
    `PALETTERGB' or `RGB', and the conversion from LSL is done internally by
    multiplying each element of the LSL color vector by 255.  `xterm.c' uses
    `x_make_truecolor_pixel', which uses 16-bit RGB -- the conversion from LSL
    happens internally by multiplying each element of the LSL color vector by 65535."
    (interactive)
      (setq mc-list (if list list '(
        (3 "hbar" [1.0 0.0 0.0])
        (4 "bar" [0.0 1.0 0.0])
        (5 "box" [0.0 0.0 1.0])
        (6 "hollow" [0.8 0.4 0.2])
        (7 ("hbar" 3) [1.0 0.0 1.0])
        (8 ("bar" 3) [0.0 1.0 1.0])))))

    (global-set-key [f1] 'mc-test)

For anyone interested in trying out this latest patch with Magnar's library, the following are the only modifications that I am are of that would be needed (as of today, 03/28/2016) -- blue colored bar cursor for even numbered columns, red colored bar cursor for odd numbered columns.  One of the nifty things I see is that these fake cursors work well when placed at the very end of a line.

    (require 'multiple-cursors)

    (defun mc/clear-mc-list ()
      (setq mc-list nil))

    (add-hook 'multiple-cursors-mode-disabled-hook 'mc/clear-mc-list)

    (defun mc/create-fake-cursor-at-point (&optional id)
      "Add a fake cursor and possibly a fake active region overlay based on point and mark.
    Saves the current state in the overlay to be restored later."
      (unless mc--max-cursors-original
        (setq mc--max-cursors-original mc/max-cursors))
      (when mc/max-cursors
        (unless (< (mc/num-cursors) mc/max-cursors)
          (if (yes-or-no-p (format "%d active cursors. Continue? " (mc/num-cursors)))
              (setq mc/max-cursors (read-number "Enter a new, temporary maximum: "))
            (mc/remove-fake-cursors)
            (error "Aborted: too many cursors"))))
      (let ((overlay (make-overlay (point) (point))))
        (overlay-put overlay 'mc-id (or id (mc/create-cursor-id)))
        (overlay-put overlay 'type 'fake-cursor)
        (overlay-put overlay 'priority 100)
        (mc/store-current-state-in-overlay overlay)
        (when (use-region-p)
          (overlay-put overlay 'region-overlay
                       (mc/make-region-overlay-between-point-and-mark)))
        (let* (
            (overlays (mc/all-fake-cursors))
            (lst
              (mapcar
                (lambda (x)
                  (save-excursion
                    (let* (
                        current-column
                        (pos (overlay-start x)))
                      (goto-char pos)
                      (setq current-column (current-column))
                      (cond
                        ((oddp current-column)
                          (list pos "bar" [1.0 0.0 0.0])) ;; red
                        ((evenp current-column)
                          (list pos "bar" [0.0 0.0 1.0])))))) ;; blue
                overlays)) )
          (setq mc-list lst))
        overlay))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;