Make 'mouse-drag-and-drop-region' more robust and customizable
* lisp/mouse.el (mouse-drag-and-drop-region-cut-when-buffers-differ): New option to permit 'mouse-drag-and-drop-region' to cut text also when source and destination buffers differ. (mouse-drag-and-drop-region-show-tooltip): New option to toggle display of tooltip during mouse dragging on graphic displays. (mouse-drag-and-drop-region-show-cursor): New option to toggle moving point with mouse cursor during mouse dragging of region. (mouse-drag-and-drop-region): New face to highlight original text while dragging. (mouse-drag-and-drop-region): Make use of new options and face. Ignore errors during tracking.
This commit is contained in:
parent
2e9eba2013
commit
c62ced5b4d
1 changed files with 278 additions and 59 deletions
337
lisp/mouse.el
337
lisp/mouse.el
|
@ -2345,10 +2345,10 @@ choose a font."
|
||||||
|
|
||||||
;; Drag and drop support.
|
;; Drag and drop support.
|
||||||
(defcustom mouse-drag-and-drop-region nil
|
(defcustom mouse-drag-and-drop-region nil
|
||||||
"If non-nil, dragging the mouse drags the region, if that exists.
|
"If non-nil, dragging the mouse drags the region, if it exists.
|
||||||
If the value is a modifier, such as `control' or `shift' or `meta',
|
If the value is a modifier, such as `control' or `shift' or
|
||||||
then if that modifier key is pressed when dropping the region, region
|
`meta', then if that modifier key is pressed when dropping the
|
||||||
text is copied instead of being cut."
|
region, text is copied instead of being cut."
|
||||||
:type `(choice
|
:type `(choice
|
||||||
(const :tag "Disable dragging the region" nil)
|
(const :tag "Disable dragging the region" nil)
|
||||||
,@(mapcar
|
,@(mapcar
|
||||||
|
@ -2361,6 +2361,45 @@ text is copied instead of being cut."
|
||||||
:version "26.1"
|
:version "26.1"
|
||||||
:group 'mouse)
|
:group 'mouse)
|
||||||
|
|
||||||
|
(defcustom mouse-drag-and-drop-region-cut-when-buffers-differ nil
|
||||||
|
"If non-nil, cut text also when source and destination buffers differ.
|
||||||
|
If this option is nil, `mouse-drag-and-drop-region' will leave
|
||||||
|
the text in the source buffer alone when dropping it in a
|
||||||
|
different buffer. If this is non-nil, it will cut the text just
|
||||||
|
as it does when dropping text in the source buffer."
|
||||||
|
:type 'boolean
|
||||||
|
:version "26.1"
|
||||||
|
:group 'mouse)
|
||||||
|
|
||||||
|
(defcustom mouse-drag-and-drop-region-show-tooltip 256
|
||||||
|
"If non-nil, text is shown by a tooltip in a graphic display.
|
||||||
|
If this option is nil, `mouse-drag-and-drop-region' does not show
|
||||||
|
tooltips. If this is t, it shows the entire text dragged in a
|
||||||
|
tooltip. If this is an integer (as with the default value of
|
||||||
|
256), it will show that many characters of the dragged text in
|
||||||
|
a tooltip."
|
||||||
|
:type 'integer
|
||||||
|
:version "26.1"
|
||||||
|
:group 'mouse)
|
||||||
|
|
||||||
|
(defcustom mouse-drag-and-drop-region-show-cursor t
|
||||||
|
"If non-nil, move point with mouse cursor during dragging.
|
||||||
|
If this is nil, `mouse-drag-and-drop-region' leaves point alone.
|
||||||
|
Otherwise, it will move point together with the mouse cursor and,
|
||||||
|
in addition, temporarily highlight the original region with the
|
||||||
|
`mouse-drag-and-drop-region' face."
|
||||||
|
:type 'boolean
|
||||||
|
:version "26.1"
|
||||||
|
:group 'mouse)
|
||||||
|
|
||||||
|
(defface mouse-drag-and-drop-region '((t :inherit region))
|
||||||
|
"Face to highlight original text during dragging.
|
||||||
|
This face is used by `mouse-drag-and-drop-region' to temporarily
|
||||||
|
highlight the original region when
|
||||||
|
`mouse-drag-and-drop-region-show-cursor' is non-nil."
|
||||||
|
:version "26.1"
|
||||||
|
:group 'mouse)
|
||||||
|
|
||||||
(defun mouse-drag-and-drop-region (event)
|
(defun mouse-drag-and-drop-region (event)
|
||||||
"Move text in the region to point where mouse is dragged to.
|
"Move text in the region to point where mouse is dragged to.
|
||||||
The transportation of text is also referred as `drag and drop'.
|
The transportation of text is also referred as `drag and drop'.
|
||||||
|
@ -2369,66 +2408,246 @@ modifier key was pressed when dropping, and the value of the
|
||||||
variable `mouse-drag-and-drop-region' is that modifier, the text
|
variable `mouse-drag-and-drop-region' is that modifier, the text
|
||||||
is copied instead of being cut."
|
is copied instead of being cut."
|
||||||
(interactive "e")
|
(interactive "e")
|
||||||
(require 'tooltip)
|
(let* ((mouse-button (event-basic-type last-input-event))
|
||||||
(let ((start (region-beginning))
|
(mouse-drag-and-drop-region-show-tooltip
|
||||||
(end (region-end))
|
(when (and mouse-drag-and-drop-region-show-tooltip
|
||||||
(point (point))
|
(display-multi-frame-p)
|
||||||
(buffer (current-buffer))
|
(require 'tooltip))
|
||||||
(window (selected-window))
|
mouse-drag-and-drop-region-show-tooltip))
|
||||||
value-selection)
|
(start (region-beginning))
|
||||||
(track-mouse
|
(end (region-end))
|
||||||
;; When event was click instead of drag, skip loop
|
(point (point))
|
||||||
(while (progn
|
(buffer (current-buffer))
|
||||||
(setq event (read-event))
|
(window (selected-window))
|
||||||
(or (mouse-movement-p event)
|
(text-from-read-only buffer-read-only)
|
||||||
;; Handle `mouse-autoselect-window'.
|
(mouse-drag-and-drop-overlay (make-overlay start end))
|
||||||
(eq (car-safe event) 'select-window)))
|
point-to-paste
|
||||||
(unless value-selection ; initialization
|
point-to-paste-read-only
|
||||||
(delete-overlay mouse-secondary-overlay)
|
window-to-paste
|
||||||
(setq value-selection (buffer-substring start end))
|
buffer-to-paste
|
||||||
(move-overlay mouse-secondary-overlay start end)) ; (deactivate-mark)
|
cursor-in-text-area
|
||||||
(ignore-errors (deactivate-mark) ; care existing region in other window
|
no-modifier-on-drop
|
||||||
(mouse-set-point event)
|
drag-but-negligible
|
||||||
(tooltip-show value-selection)))
|
clicked
|
||||||
(tooltip-hide))
|
value-selection ; This remains nil when event was "click".
|
||||||
;; Do not modify buffer under mouse when "event was click",
|
text-tooltip
|
||||||
;; "drag negligible", or
|
states
|
||||||
;; "drag to read-only".
|
window-exempt)
|
||||||
(if (or (equal (mouse-posn-property (event-end event) 'face) 'region) ; "event was click"
|
|
||||||
(member 'secondary-selection ; "drag negligible"
|
;; STATES stores for each window on this frame its start and point
|
||||||
(mapcar (lambda (xxx) (overlay-get xxx 'face))
|
;; positions so we can restore them on all windows but for the one
|
||||||
(overlays-at (posn-point (event-end event)))))
|
;; where the drop occurs. For inter-frame drags we'll have to do
|
||||||
buffer-read-only)
|
;; this for all windows on all visible frames. In addition we save
|
||||||
;; Do not modify buffer under mouse.
|
;; also the cursor type for the window's buffer so we can restore it
|
||||||
|
;; in case we modified it.
|
||||||
|
;; https://lists.gnu.org/archive/html/emacs-devel/2017-12/msg00090.html
|
||||||
|
(walk-window-tree
|
||||||
|
(lambda (window)
|
||||||
|
(setq states
|
||||||
|
(cons
|
||||||
|
(list
|
||||||
|
window
|
||||||
|
(copy-marker (window-start window))
|
||||||
|
(copy-marker (window-point window))
|
||||||
|
(with-current-buffer (window-buffer window)
|
||||||
|
cursor-type))
|
||||||
|
states))))
|
||||||
|
|
||||||
|
(ignore-errors
|
||||||
|
(track-mouse
|
||||||
|
;; When event was "click" instead of "drag", skip loop.
|
||||||
|
(while (progn
|
||||||
|
(setq event (read-key)) ; read-event or read-key
|
||||||
|
(or (mouse-movement-p event)
|
||||||
|
;; Handle `mouse-autoselect-window'.
|
||||||
|
(eq (car-safe event) 'select-window)))
|
||||||
|
;; Obtain the dragged text in region. When the loop was
|
||||||
|
;; skipped, value-selection remains nil.
|
||||||
|
(unless value-selection
|
||||||
|
(setq value-selection (buffer-substring start end))
|
||||||
|
(when mouse-drag-and-drop-region-show-tooltip
|
||||||
|
(let ((text-size mouse-drag-and-drop-region-show-tooltip))
|
||||||
|
(setq text-tooltip
|
||||||
|
(if (and (integerp text-size)
|
||||||
|
(> (length value-selection) text-size))
|
||||||
|
(concat
|
||||||
|
(substring value-selection 0 (/ text-size 2))
|
||||||
|
"\n...\n"
|
||||||
|
(substring value-selection (- (/ text-size 2)) -1))
|
||||||
|
value-selection))))
|
||||||
|
|
||||||
|
;; Check if selected text is read-only.
|
||||||
|
(setq text-from-read-only (or text-from-read-only
|
||||||
|
(get-text-property start 'read-only)
|
||||||
|
(not (equal
|
||||||
|
(next-single-char-property-change
|
||||||
|
start 'read-only nil end)
|
||||||
|
end)))))
|
||||||
|
(setq window-to-paste (posn-window (event-end event)))
|
||||||
|
(setq point-to-paste (posn-point (event-end event)))
|
||||||
|
;; Set nil when target buffer is minibuffer.
|
||||||
|
(setq buffer-to-paste (let (buf)
|
||||||
|
(when (windowp window-to-paste)
|
||||||
|
(setq buf (window-buffer window-to-paste))
|
||||||
|
(when (not (minibufferp buf))
|
||||||
|
buf))))
|
||||||
|
(setq cursor-in-text-area (and window-to-paste
|
||||||
|
point-to-paste
|
||||||
|
buffer-to-paste))
|
||||||
|
|
||||||
|
(when cursor-in-text-area
|
||||||
|
;; Check if point under mouse is read-only.
|
||||||
|
(save-window-excursion
|
||||||
|
(select-window window-to-paste)
|
||||||
|
(setq point-to-paste-read-only
|
||||||
|
(or buffer-read-only
|
||||||
|
(get-text-property point-to-paste 'read-only))))
|
||||||
|
|
||||||
|
;; Check if "drag but negligible". Operation "drag but
|
||||||
|
;; negligible" is defined as drag-and-drop the text to
|
||||||
|
;; the original region. When modifier is pressed, the
|
||||||
|
;; text will be inserted to inside of the original
|
||||||
|
;; region.
|
||||||
|
(setq drag-but-negligible
|
||||||
|
(and (eq (overlay-buffer mouse-drag-and-drop-overlay)
|
||||||
|
buffer-to-paste)
|
||||||
|
(< (overlay-start mouse-drag-and-drop-overlay)
|
||||||
|
point-to-paste)
|
||||||
|
(< point-to-paste
|
||||||
|
(overlay-end mouse-drag-and-drop-overlay)))))
|
||||||
|
|
||||||
|
;; Show a tooltip.
|
||||||
|
(if mouse-drag-and-drop-region-show-tooltip
|
||||||
|
(tooltip-show text-tooltip)
|
||||||
|
(tooltip-hide))
|
||||||
|
|
||||||
|
;; Show cursor and highlight the original region.
|
||||||
|
(when mouse-drag-and-drop-region-show-cursor
|
||||||
|
;; Modify cursor even when point is out of frame.
|
||||||
|
(setq cursor-type (cond
|
||||||
|
((not cursor-in-text-area)
|
||||||
|
nil)
|
||||||
|
((or point-to-paste-read-only
|
||||||
|
drag-but-negligible)
|
||||||
|
'hollow)
|
||||||
|
(t
|
||||||
|
'bar)))
|
||||||
|
(when cursor-in-text-area
|
||||||
|
(overlay-put mouse-drag-and-drop-overlay
|
||||||
|
'face 'mouse-drag-and-drop-region)
|
||||||
|
(deactivate-mark) ; Maintain region in other window.
|
||||||
|
(mouse-set-point event)))))
|
||||||
|
|
||||||
|
;; Hide a tooltip.
|
||||||
|
(when mouse-drag-and-drop-region-show-tooltip (tooltip-hide))
|
||||||
|
|
||||||
|
;; Check if modifier was pressed on drop.
|
||||||
|
(setq no-modifier-on-drop
|
||||||
|
(not (member mouse-drag-and-drop-region (event-modifiers event))))
|
||||||
|
|
||||||
|
;; Check if event was "click".
|
||||||
|
(setq clicked (not value-selection))
|
||||||
|
|
||||||
|
;; Restore status on drag to outside of text-area or non-mouse input.
|
||||||
|
(when (or (not cursor-in-text-area)
|
||||||
|
(not (equal (event-basic-type event) mouse-button)))
|
||||||
|
(setq drag-but-negligible t
|
||||||
|
no-modifier-on-drop t))
|
||||||
|
|
||||||
|
;; Do not modify any buffers when event is "click",
|
||||||
|
;; "drag but negligible", or "drag to read-only".
|
||||||
|
(let* ((mouse-drag-and-drop-region-cut-when-buffers-differ
|
||||||
|
(if no-modifier-on-drop
|
||||||
|
mouse-drag-and-drop-region-cut-when-buffers-differ
|
||||||
|
(not mouse-drag-and-drop-region-cut-when-buffers-differ)))
|
||||||
|
(wanna-paste-to-same-buffer (equal buffer-to-paste buffer))
|
||||||
|
(wanna-cut-on-same-buffer (and wanna-paste-to-same-buffer
|
||||||
|
no-modifier-on-drop))
|
||||||
|
(wanna-cut-on-other-buffer
|
||||||
|
(and (not wanna-paste-to-same-buffer)
|
||||||
|
mouse-drag-and-drop-region-cut-when-buffers-differ))
|
||||||
|
(cannot-paste (or point-to-paste-read-only
|
||||||
|
(when (or wanna-cut-on-same-buffer
|
||||||
|
wanna-cut-on-other-buffer)
|
||||||
|
text-from-read-only))))
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
;; "drag negligible" or "drag to read-only", restore region.
|
;; Move point within region.
|
||||||
(value-selection
|
(clicked
|
||||||
(select-window window) ; In case miss drag to other window
|
(deactivate-mark)
|
||||||
|
(mouse-set-point event))
|
||||||
|
;; Undo operation. Set back the original text as region.
|
||||||
|
((or (and drag-but-negligible
|
||||||
|
no-modifier-on-drop)
|
||||||
|
cannot-paste)
|
||||||
|
;; Inform user either source or destination buffer cannot be modified.
|
||||||
|
(when (and (not drag-but-negligible)
|
||||||
|
cannot-paste)
|
||||||
|
(message "Buffer is read-only"))
|
||||||
|
|
||||||
|
;; Select source window back and restore region.
|
||||||
|
;; (set-window-point window point)
|
||||||
|
(select-window window)
|
||||||
(goto-char point)
|
(goto-char point)
|
||||||
(setq deactivate-mark nil)
|
(setq deactivate-mark nil)
|
||||||
(activate-mark))
|
(activate-mark))
|
||||||
;; "event was click"
|
;; Modify buffers.
|
||||||
(t
|
(t
|
||||||
(deactivate-mark)
|
;; * DESTINATION BUFFER::
|
||||||
(mouse-set-point event)))
|
;; Insert the text to destination buffer under mouse.
|
||||||
;; Modify buffer under mouse by inserting text.
|
(select-window window-to-paste)
|
||||||
(push-mark)
|
(setq window-exempt window-to-paste)
|
||||||
(insert value-selection)
|
(goto-char point-to-paste)
|
||||||
(when (not (equal (mark) (point))) ; on success insert
|
(push-mark)
|
||||||
(setq deactivate-mark nil)
|
(insert value-selection)
|
||||||
(activate-mark)) ; have region on destination
|
;; On success, set the text as region on destination buffer.
|
||||||
;; Take care of initial region on source.
|
(when (not (equal (mark) (point)))
|
||||||
(if (equal (current-buffer) buffer) ; when same buffer
|
(setq deactivate-mark nil)
|
||||||
(let (deactivate-mark) ; remove text
|
(activate-mark))
|
||||||
(unless (member mouse-drag-and-drop-region (event-modifiers event))
|
|
||||||
(kill-region (overlay-start mouse-secondary-overlay)
|
;; * SOURCE BUFFER::
|
||||||
(overlay-end mouse-secondary-overlay))))
|
;; Set back the original text as region or delete the original
|
||||||
(let ((window1 (selected-window))) ; when beyond buffer
|
;; text, on source buffer.
|
||||||
(select-window window)
|
(if wanna-paste-to-same-buffer
|
||||||
(goto-char point) ; restore point on source window
|
;; When source buffer and destination buffer are the same,
|
||||||
(activate-mark) ; restore region
|
;; remove the original text.
|
||||||
(select-window window1))))
|
(when no-modifier-on-drop
|
||||||
(delete-overlay mouse-secondary-overlay)))
|
(let (deactivate-mark)
|
||||||
|
(delete-region (overlay-start mouse-drag-and-drop-overlay)
|
||||||
|
(overlay-end mouse-drag-and-drop-overlay))))
|
||||||
|
;; When source buffer and destination buffer are different,
|
||||||
|
;; keep (set back the original text as region) or remove the
|
||||||
|
;; original text.
|
||||||
|
(select-window window) ; Select window with source buffer.
|
||||||
|
(goto-char point) ; Move point to the original text on source buffer.
|
||||||
|
|
||||||
|
(if mouse-drag-and-drop-region-cut-when-buffers-differ
|
||||||
|
;; Remove the dragged text from source buffer like
|
||||||
|
;; operation `cut'.
|
||||||
|
(delete-region (overlay-start mouse-drag-and-drop-overlay)
|
||||||
|
(overlay-end mouse-drag-and-drop-overlay))
|
||||||
|
;; Set back the dragged text as region on source buffer
|
||||||
|
;; like operation `copy'.
|
||||||
|
(activate-mark))
|
||||||
|
(select-window window-to-paste))))))
|
||||||
|
|
||||||
|
;; Clean up.
|
||||||
|
(delete-overlay mouse-drag-and-drop-overlay)
|
||||||
|
|
||||||
|
;; Restore old states but for the window where the drop
|
||||||
|
;; occurred. Restore cursor types for all windows.
|
||||||
|
(dolist (state states)
|
||||||
|
(let ((window (car state)))
|
||||||
|
(when (and window-exempt
|
||||||
|
(not (eq window window-exempt)))
|
||||||
|
(set-window-start window (nth 1 state) 'noforce)
|
||||||
|
(set-marker (nth 1 state) nil)
|
||||||
|
;; If window is selected, the following automatically sets
|
||||||
|
;; point for that window's buffer.
|
||||||
|
(set-window-point window (nth 2 state))
|
||||||
|
(set-marker (nth 2 state) nil))
|
||||||
|
(with-current-buffer (window-buffer window)
|
||||||
|
(setq cursor-type (nth 3 state)))))))
|
||||||
|
|
||||||
|
|
||||||
;;; Bindings for mouse commands.
|
;;; Bindings for mouse commands.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue