diff options
Diffstat (limited to 'lisp/mouse.el')
-rw-r--r-- | lisp/mouse.el | 440 |
1 files changed, 375 insertions, 65 deletions
diff --git a/lisp/mouse.el b/lisp/mouse.el index 89e5d7c48a3..4f9c49ce463 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el @@ -46,7 +46,7 @@ :type 'boolean) (defcustom mouse-drag-copy-region nil - "If non-nil, copy to kill-ring upon mouse adjustments of the region. + "If non-nil, copy to kill ring upon mouse adjustments of the region. This affects `mouse-save-then-kill' (\\[mouse-save-then-kill]) in addition to mouse drags. @@ -180,7 +180,7 @@ items `Turn Off' and `Help'." `(keymap ,(format "%s - %s" indicator (capitalize - (replace-regexp-in-string + (string-replace "-" " " (format "%S" minor-mode)))) (turn-off menu-item "Turn off minor mode" ,mm-fun) (help menu-item "Help for minor mode" @@ -277,11 +277,302 @@ not it is actually displayed." minor-mode-menus))) +;; Context menus. + +(defcustom context-menu-functions '(context-menu-undo + context-menu-region + context-menu-middle-separator + context-menu-local + context-menu-minor) + "List of functions that produce the contents of the context menu. +Each function receives the menu and the mouse click event as its arguments +and should return the same menu with changes such as added new menu items." + :type '(repeat + (choice (function-item context-menu-undo) + (function-item context-menu-region) + (function-item context-menu-middle-separator) + (function-item context-menu-toolbar) + (function-item context-menu-global) + (function-item context-menu-local) + (function-item context-menu-minor) + (function-item context-menu-buffers) + (function-item context-menu-vc) + (function-item context-menu-ffap) + (function :tag "Custom function"))) + :version "28.1") + +(defcustom context-menu-filter-function nil + "Function that can filter the list produced by `context-menu-functions'." + :type '(choice (const nil) function) + :version "28.1") + +(defun context-menu-map (&optional click) + "Return menu map constructed for context near mouse CLICK. +The menu is populated by calling functions from `context-menu-functions'. +Each function receives the menu and the mouse click event +and returns the same menu after adding own menu items to the composite menu. +When there is a text property `context-menu-function' at CLICK, +it overrides all functions from `context-menu-functions'. +At the end, it's possible to modify the final menu by specifying +the function `context-menu-filter-function'." + (let* ((menu (make-sparse-keymap (propertize "Context Menu" 'hide t))) + (click (or click last-input-event)) + (fun (mouse-posn-property (event-start click) + 'context-menu-function))) + + (if (functionp fun) + (setq menu (funcall fun menu click)) + (run-hook-wrapped 'context-menu-functions + (lambda (fun) + (setq menu (funcall fun menu click)) + nil))) + + ;; Remove duplicate separators + (let ((l menu)) + (while (consp l) + (when (and (equal (cdr-safe (car l)) menu-bar-separator) + (equal (cdr-safe (cadr l)) menu-bar-separator)) + (setcdr l (cddr l))) + (setq l (cdr l)))) + + (when (functionp context-menu-filter-function) + (setq menu (funcall context-menu-filter-function menu click))) + menu)) + +(defun context-menu-middle-separator (menu _click) + "Add separator to the middle of the context menu. +Some context functions add menu items below the separator." + (define-key-after menu [middle-separator] menu-bar-separator) + menu) + +(defun context-menu-toolbar (menu _click) + "Populate MENU with submenus from the tool bar." + (run-hooks 'activate-menubar-hook 'menu-bar-update-hook) + (define-key-after menu [separator-toolbar] menu-bar-separator) + (map-keymap (lambda (key binding) + (when (consp binding) + (define-key-after menu (vector key) + (copy-sequence binding)))) + (lookup-key global-map [tool-bar])) + menu) + +(defun context-menu-global (menu _click) + "Populate MENU with submenus from the global menu." + (run-hooks 'activate-menubar-hook 'menu-bar-update-hook) + (define-key-after menu [separator-global] menu-bar-separator) + (map-keymap (lambda (key binding) + (when (consp binding) + (define-key-after menu (vector key) + (copy-sequence binding)))) + (menu-bar-keymap global-map)) + menu) + +(defun context-menu-local (menu _click) + "Populate MENU with submenus provided by major mode." + (run-hooks 'activate-menubar-hook 'menu-bar-update-hook) + (define-key-after menu [separator-local] menu-bar-separator) + (let ((keymap (local-key-binding [menu-bar]))) + (when keymap + (map-keymap (lambda (key binding) + (when (consp binding) + (define-key-after menu (vector key) + (copy-sequence binding)))) + keymap))) + menu) + +(defun context-menu-minor (menu _click) + "Populate MENU with submenus provided by minor modes." + (run-hooks 'activate-menubar-hook 'menu-bar-update-hook) + (define-key-after menu [separator-minor] menu-bar-separator) + (dolist (mode (reverse (minor-mode-key-binding [menu-bar]))) + (when (and (consp mode) (symbol-value (car mode))) + (map-keymap (lambda (key binding) + (when (consp binding) + (define-key-after menu (vector key) + (copy-sequence binding)))) + (cdr mode)))) + menu) + +(defun context-menu-buffers (menu _click) + "Populate MENU with the buffer submenus to buffer switching." + (run-hooks 'activate-menubar-hook 'menu-bar-update-hook) + (define-key-after menu [separator-buffers] menu-bar-separator) + (map-keymap (lambda (key binding) + (when (consp binding) + (define-key-after menu (vector key) + (copy-sequence binding)))) + (mouse-buffer-menu-keymap)) + menu) + +(defun context-menu-vc (menu _click) + "Populate MENU with Version Control commands." + (define-key-after menu [separator-vc] menu-bar-separator) + (define-key-after menu [vc-menu] vc-menu-entry) + menu) + +(defun context-menu-undo (menu _click) + "Populate MENU with undo commands." + (define-key-after menu [separator-undo] menu-bar-separator) + (when (and (not buffer-read-only) + (not (eq t buffer-undo-list)) + (if (eq last-command 'undo) + (listp pending-undo-list) + (consp buffer-undo-list))) + (define-key-after menu [undo] + `(menu-item ,(if (region-active-p) "Undo in Region" "Undo") undo + :help "Undo last edits"))) + (when (and (not buffer-read-only) + (undo--last-change-was-undo-p buffer-undo-list)) + (define-key-after menu [undo-redo] + `(menu-item (if undo-in-region "Redo in Region" "Redo") undo-redo + :help "Redo last undone edits"))) + menu) + +(defun context-menu-region (menu click) + "Populate MENU with region commands." + (define-key-after menu [separator-region] menu-bar-separator) + (when (and mark-active (not buffer-read-only)) + (define-key-after menu [cut] + '(menu-item "Cut" kill-region + :help + "Cut (kill) text in region between mark and current position"))) + (when mark-active + (define-key-after menu [copy] + ;; ns-win.el said: Substitute a Copy function that works better + ;; under X (for GNUstep). + `(menu-item "Copy" ,(if (featurep 'ns) + 'ns-copy-including-secondary + 'kill-ring-save) + :help "Copy text in region between mark and current position" + :keys ,(if (featurep 'ns) + "\\[ns-copy-including-secondary]" + "\\[kill-ring-save]")))) + (when (and (or (gui-backend-selection-exists-p 'CLIPBOARD) + (if (featurep 'ns) ; like paste-from-menu + (cdr yank-menu) + kill-ring)) + (not buffer-read-only)) + (define-key-after menu [paste] + `(menu-item "Paste" mouse-yank-at-click + :help "Paste (yank) text most recently cut/copied"))) + (when (and (cdr yank-menu) (not buffer-read-only)) + (let ((submenu (make-sparse-keymap (propertize "Paste from Kill Menu"))) + (i 0)) + (dolist (item (reverse yank-menu)) + (when (consp item) + (define-key submenu (vector (setq i (1+ i))) + `(menu-item ,(cadr item) + ,(lambda () (interactive) + (mouse-yank-from-menu click (car item))))))) + (define-key-after menu (if (featurep 'ns) [select-paste] [paste-from-menu]) + `(menu-item ,(if (featurep 'ns) "Select and Paste" "Paste from Kill Menu") + ,submenu + :help "Choose a string from the kill ring and paste it")))) + (when (and mark-active (not buffer-read-only)) + (define-key-after menu [clear] + '(menu-item "Clear" delete-active-region + :help + "Delete text in region between mark and current position"))) + + (let ((submenu (make-sparse-keymap (propertize "Select")))) + (define-key-after submenu [mark-whole-buffer] + `(menu-item "All" + ,(lambda (e) (interactive "e") (mark-thing-at-mouse e 'buffer)) + :help "Mark the whole buffer for a subsequent cut/copy")) + (when (let* ((pos (posn-point (event-end click))) + (char (when pos (char-after pos)))) + (or (and char (eq (char-syntax char) ?\")) + (nth 3 (save-excursion (syntax-ppss pos))))) + (define-key-after submenu [mark-string] + `(menu-item "String" + ,(lambda (e) (interactive "e") (mark-thing-at-mouse e 'string)) + :help "Mark the string at click for a subsequent cut/copy"))) + (define-key-after submenu [mark-line] + `(menu-item "Line" + ,(lambda (e) (interactive "e") (mark-thing-at-mouse e 'line)) + :help "Mark the line at click for a subsequent cut/copy")) + (when (region-active-p) + (define-key-after submenu [mark-none] + `(menu-item "None" + ,(lambda (_e) (interactive "e") (deactivate-mark)) + :help "Deactivate the region"))) + + (define-key-after menu [select-region] + `(menu-item "Select" ,submenu))) + menu) + +(defun context-menu-ffap (menu click) + "Populate MENU with commands that find file at point." + (save-excursion + (mouse-set-point click) + (when (ffap-guess-file-name-at-point) + (define-key menu [ffap-separator] menu-bar-separator) + (define-key menu [ffap-at-mouse] + '(menu-item "Find File or URL" ffap-at-mouse + :help "Find file or URL from text around mouse click")))) + menu) + +(defvar context-menu-entry + `(menu-item ,(purecopy "Context Menu") ignore + :filter (lambda (_) (context-menu-map))) + "Menu item that creates the context menu and can be bound to a mouse key.") + +(defvar context-menu-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [mouse-3] nil) + (define-key map [down-mouse-3] context-menu-entry) + (define-key map [menu] #'context-menu-open) + (if (featurep 'w32) + (define-key map [apps] #'context-menu-open)) + (when (featurep 'ns) + (define-key map [C-mouse-1] nil) + (define-key map [C-down-mouse-1] context-menu-entry)) + map) + "Context Menu mode map.") + +(define-minor-mode context-menu-mode + "Toggle Context Menu mode. + +When Context Menu mode is enabled, clicking the mouse button down-mouse-3 +activates the menu whose contents depends on its surrounding context." + :global t :group 'mouse) + +(defun context-menu-open () + "Start key navigation of the context menu. +This is the keyboard interface to \\[context-menu-map]." + (interactive) + (let ((inhibit-mouse-event-check t)) + (popup-menu (context-menu-map) (point)))) + +(global-set-key [S-f10] 'context-menu-open) + +(defun mark-thing-at-mouse (click thing) + "Activate the region around THING found near the mouse CLICK." + (let ((bounds (bounds-of-thing-at-mouse click thing))) + (when bounds + (goto-char (if mouse-select-region-move-to-beginning + (car bounds) (cdr bounds))) + (push-mark (if mouse-select-region-move-to-beginning + (cdr bounds) (car bounds)) + t 'activate)))) + +(defun mouse-yank-from-menu (click string) + "Insert STRING at mouse CLICK." + ;; Give temporary modes such as isearch a chance to turn off. + (run-hooks 'mouse-leave-buffer-hook) + (when select-active-regions + (deactivate-mark)) + (or mouse-yank-at-point (mouse-set-point click)) + (push-mark) + (insert string)) + + ;; Commands that operate on windows. (defun mouse-minibuffer-check (event) (let ((w (posn-window (event-start event)))) - (and (window-minibuffer-p w) + (and (windowp w) + (window-minibuffer-p w) (not (minibuffer-window-active-p w)) (user-error "Minibuffer window is not active"))) ;; Give temporary modes such as isearch a chance to turn off. @@ -1186,9 +1477,10 @@ its value is returned." ;; Mouse clicks in the fringe come with a position in ;; (nth 5). This is useful but is not exactly where we clicked, so ;; don't look up that position's properties! - (and pt (not (memq (posn-area pos) '(left-fringe right-fringe - left-margin right-margin))) - (get-char-property pt property w)))) + (and pt (not (memq (posn-area pos) + '(left-fringe right-fringe + left-margin right-margin tab-bar))) + (get-char-property pt property w)))) (get-char-property pos property))) (defun mouse-on-link-p (pos) @@ -1277,8 +1569,7 @@ The region will be defined with mark and point." (mouse-minibuffer-check start-event) (setq mouse-selection-click-count-buffer (current-buffer)) (deactivate-mark) - (let* ((scroll-margin 0) ; Avoid margin scrolling (Bug#9541). - (start-posn (event-start start-event)) + (let* ((start-posn (event-start start-event)) (start-point (posn-point start-posn)) (start-window (posn-window start-posn)) (_ (with-current-buffer (window-buffer start-window) @@ -1300,65 +1591,84 @@ The region will be defined with mark and point." ;; Don't count the mode line. (1- (nth 3 bounds)))) (click-count (1- (event-click-count start-event))) - ;; Suppress automatic hscrolling, because that is a nuisance - ;; when setting point near the right fringe (but see below). + ;; Save original automatic scrolling behavior (see below). (auto-hscroll-mode-saved auto-hscroll-mode) - (old-track-mouse track-mouse)) + (scroll-margin-saved scroll-margin) + (old-track-mouse track-mouse) + (cleanup (lambda () + (setq track-mouse old-track-mouse) + (setq auto-hscroll-mode auto-hscroll-mode-saved) + (setq scroll-margin scroll-margin-saved)))) + (condition-case err + (progn + (setq mouse-selection-click-count click-count) + + ;; Suppress automatic scrolling near the edges while tracking + ;; movement, as it interferes with the natural dragging behavior + ;; (point will unexpectedly be moved beneath the pointer, making + ;; selections in auto-scrolling margins impossible). + (setq auto-hscroll-mode nil) + (setq scroll-margin 0) + + ;; In case the down click is in the middle of some intangible text, + ;; use the end of that text, and put it in START-POINT. + (if (< (point) start-point) + (goto-char start-point)) + (setq start-point (point)) + + ;; Activate the region, using `mouse-start-end' to determine where + ;; to put point and mark (e.g., double-click will select a word). + (setq-local transient-mark-mode + (if (eq transient-mark-mode 'lambda) + '(only) + (cons 'only transient-mark-mode))) + (let ((range (mouse-start-end start-point start-point click-count))) + (push-mark (nth 0 range) t t) + (goto-char (nth 1 range))) - (setq mouse-selection-click-count click-count) - ;; In case the down click is in the middle of some intangible text, - ;; use the end of that text, and put it in START-POINT. - (if (< (point) start-point) - (goto-char start-point)) - (setq start-point (point)) + (setf (terminal-parameter nil 'mouse-drag-start) start-event) + (setq track-mouse t) - ;; Activate the region, using `mouse-start-end' to determine where - ;; to put point and mark (e.g., double-click will select a word). - (setq-local transient-mark-mode - (if (eq transient-mark-mode 'lambda) - '(only) - (cons 'only transient-mark-mode))) - (let ((range (mouse-start-end start-point start-point click-count))) - (push-mark (nth 0 range) t t) - (goto-char (nth 1 range))) - - (setf (terminal-parameter nil 'mouse-drag-start) start-event) - (setq track-mouse t) - (setq auto-hscroll-mode nil) - - (set-transient-map - (let ((map (make-sparse-keymap))) - (define-key map [switch-frame] #'ignore) - (define-key map [select-window] #'ignore) - (define-key map [mouse-movement] - (lambda (event) (interactive "e") - (let* ((end (event-end event)) - (end-point (posn-point end))) - (unless (eq end-point start-point) - ;; As soon as the user moves, we can re-enable auto-hscroll. - (setq auto-hscroll-mode auto-hscroll-mode-saved) - ;; And remember that we have moved, so mouse-set-region can know - ;; its event is really a drag event. - (setcar start-event 'mouse-movement)) - (if (and (eq (posn-window end) start-window) - (integer-or-marker-p end-point)) - (mouse--drag-set-mark-and-point start-point - end-point click-count) - (let ((mouse-row (cdr (cdr (mouse-position))))) - (cond - ((null mouse-row)) - ((< mouse-row top) - (mouse-scroll-subr start-window (- mouse-row top) - nil start-point)) - ((>= mouse-row bottom) - (mouse-scroll-subr start-window (1+ (- mouse-row bottom)) - nil start-point)))))))) - map) - t (lambda () - (setq track-mouse old-track-mouse) - (setq auto-hscroll-mode auto-hscroll-mode-saved) - (deactivate-mark) - (pop-mark))))) + (set-transient-map + (let ((map (make-sparse-keymap))) + (define-key map [switch-frame] #'ignore) + (define-key map [select-window] #'ignore) + (define-key map [mouse-movement] + (lambda (event) (interactive "e") + (let* ((end (event-end event)) + (end-point (posn-point end))) + (unless (eq end-point start-point) + ;; And remember that we have moved, so mouse-set-region can know + ;; its event is really a drag event. + (setcar start-event 'mouse-movement)) + (if (and (eq (posn-window end) start-window) + (integer-or-marker-p end-point)) + (mouse--drag-set-mark-and-point start-point + end-point click-count) + (let ((mouse-row (cdr (cdr (mouse-position))))) + (cond + ((null mouse-row)) + ((< mouse-row top) + (mouse-scroll-subr start-window (- mouse-row top) + nil start-point)) + ((>= mouse-row bottom) + (mouse-scroll-subr start-window (1+ (- mouse-row bottom)) + nil start-point)))))))) + map) + t (lambda () + (funcall cleanup) + ;; Don't deactivate the mark when the context menu was invoked + ;; by down-mouse-3 immediately after down-mouse-1 and without + ;; releasing the mouse button with mouse-1. This allows to use + ;; region-related context menu to operate on the selected region. + (unless (and context-menu-mode + (eq (car-safe (aref (this-command-keys-vector) 0)) + 'down-mouse-3)) + (deactivate-mark) + (pop-mark))))) + ;; Cleanup on errors + (error (funcall cleanup) + (signal (car err) (cdr err)))))) (defun mouse--drag-set-mark-and-point (start click click-count) (let* ((range (mouse-start-end start click click-count)) @@ -2167,7 +2477,7 @@ a large number if you prefer a mixed multitude. The default is 4." ("Text" . "Text") ("Outline" . "Text") ("\\(HT\\|SG\\|X\\|XHT\\)ML" . "SGML") - ("log\\|diff\\|vc\\|cvs\\|Git\\|Annotate" . "Version Control") + ("\\blog\\b\\|diff\\|\\bvc\\b\\|cvs\\|Git\\|Annotate" . "Version Control") ("Threads\\|Memory\\|Disassembly\\|Breakpoints\\|Frames\\|Locals\\|Registers\\|Inferior I/O\\|Debugger" . "GDB") ("Lisp" . "Lisp"))) |