commit 3d1274f3ba0b2977a36afdbffdf389c873f90cc8 (HEAD, refs/remotes/origin/master) Author: Juri Linkov Date: Thu Sep 9 10:54:14 2021 +0300 ; Add NEWS about tab bar mouse commands diff --git a/etc/NEWS b/etc/NEWS index fb99827bcc..7a924a860f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -403,6 +403,13 @@ both modes are on). ** Tab Bars and Tab Lines +*** The tab bar now supports more mouse commands. +Clicking 'mouse-2' closes the tab, 'mouse-3' displays the context menu +with items that operate on the clicked tab. Dragging the tab with +'mouse-1' moves it to another position on the tab bar. Mouse wheel +scrolling switches to the previous/next tab, and holding the Shift key +during scrolling moves the tab to the left/right. + *** The prefix key 'C-x t t' can be used to display a buffer in a new tab. Typing 'C-x t t' before a command will cause the buffer shown by that command to be displayed in a new tab. 'C-x t t" is bound to the commit 14d5145441559bdbefd3a4188144217e01c033de Merge: 8ac5510a0e 3a8b8df478 Author: Juri Linkov Date: Thu Sep 9 10:52:41 2021 +0300 Merge branch 'feature/tab-bar-events' commit 3a8b8df478f794560d83270d0f688e7bc00081c6 (refs/remotes/origin/feature/tab-bar-events) Author: Juri Linkov Date: Thu Sep 9 10:49:43 2021 +0300 ; Remove NEWS about tab bar mouse commands to avoid merge conflicts diff --git a/etc/NEWS b/etc/NEWS index c48e110f9a..1b4712828c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -748,13 +748,6 @@ of the next command to be displayed in a new frame. ** Tab Bars -*** The tab bar now supports more mouse commands. -Clicking 'mouse-2' closes the tab, 'mouse-3' displays the context menu -with items that operate on the clicked tab. Dragging the tab with -'mouse-1' moves it to another position on the tab bar. Mouse wheel -scrolling switches to the previous/next tab, and holding the Shift key -during scrolling moves the tab to the left/right. - *** The key prefix 'C-x t t' displays next command buffer in a new tab. It's bound to the command 'other-tab-prefix' that requests the buffer of the next command to be displayed in a new tab. commit 8ac5510a0e0e545384d51366f45dedbe1edef656 Author: Michael Albinus Date: Wed Sep 8 16:57:12 2021 +0200 Document restriction of completion styles with remote file names * doc/misc/tramp.texi (File name completion): Mention restriction of completion styles. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index b2dcddc793..7109ca67dc 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -3231,7 +3231,10 @@ directory @file{/sbin} on your local host. Type @kbd{s h @value{postfixhop}} for the minibuffer completion to @samp{@value{prefix}ssh@value{postfixhop}}. Typing @kbd{@key{TAB}} shows host names @value{tramp} extracts from @file{~/.ssh/config} -file, for example. +@c bug#50387 +file, for example@footnote{Some completion styles, like +@code{substring} or @code{flex}, require to type at least one +character after the trailing @samp{@value{postfixhop}}.}. @example @group commit 439ca062c830a7f3288a2dac48457edf5b1bfc59 Author: Lars Ingebrigtsen Date: Wed Sep 8 12:21:12 2021 +0200 Don't ding at the user in pop-mark * lisp/simple.el (pop-mark): Don't ding at the user if there's no mark to pop (bug#44375). This function is used (in some circumstances) when the user mouse-1-clicks links (in *Help* buffer, for instance), which will then ding at the user before following the link. diff --git a/lisp/simple.el b/lisp/simple.el index 9e29241cc7..14e5abc87d 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -6622,7 +6622,6 @@ Does not set point. Does nothing if mark ring is empty." (setq mark-ring (nconc mark-ring (list (copy-marker (mark-marker))))) (set-marker (mark-marker) (car mark-ring)) (set-marker (car mark-ring) nil) - (unless (mark t) (ding)) (pop mark-ring)) (deactivate-mark)) commit 794fdce55d097f2b58ce37818edffb2deef7b9de Author: Juri Linkov Date: Sun Sep 5 20:16:33 2021 +0300 Improve tab-bar event handling (bug#41343) * lisp/tab-bar.el (tab-bar--key-to-number): Rename from tab--key-to-number. (tab-bar--event-to-item): New function from tab-bar-handle-mouse. (tab-bar-mouse-select-tab, tab-bar-mouse-close-tab) (tab-bar-mouse-context-menu, tab-bar-mouse-move-tab): Use tab-bar--event-to-item. * src/menu.c (x_popup_menu_1): Handle Qtab_bar in the second list element. * src/xdisp.c (tty_get_tab_bar_item): Change arg 'end' to bool 'close_p'. (tty_get_tab_bar_item): Detect if the close button was clicked. (tty_handle_tab_bar_click): Return a list with caption that has text properties. diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index ef7babb87b..aac7bb5d10 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el @@ -221,61 +221,53 @@ a list of frames to update." (tab-bar--define-keys) (tab-bar--undefine-keys))) -(defun tab--key-to-number (key) - (unless (or (null key) (eq key 'current-tab)) - (string-to-number - (string-replace "tab-" "" (format "%S" key))))) - -(defun tab-bar-handle-mouse (event) - "Text-mode emulation of switching tabs on the tab bar. -This command is used when you click the mouse in the tab bar -on a console which has no window system but does have a mouse." - (interactive "e") - (let* ((x-position (car (posn-x-y (event-start event)))) - (keymap (lookup-key (cons 'keymap (nreverse (current-active-maps))) [tab-bar])) - (column 0)) - (when x-position - (unless (catch 'done - (map-keymap - (lambda (key binding) - (when (eq (car-safe binding) 'menu-item) - (when (> (+ column (length (nth 1 binding))) x-position) - (if (get-text-property - (- x-position column) 'close-tab (nth 1 binding)) - (tab-bar-close-tab (tab--key-to-number key)) - (if (nth 2 binding) - (call-interactively (nth 2 binding)) - (tab-bar-select-tab (tab--key-to-number key)))) - (throw 'done t)) - (setq column (+ column (length (nth 1 binding)))))) - keymap)) - ;; Clicking anywhere outside existing tabs will add a new tab - (tab-bar-new-tab))))) +(defun tab-bar--key-to-number (key) + (let ((key-name (format "%S" key))) + (when (string-prefix-p "tab-" key-name) + (string-to-number (string-replace "tab-" "" key-name))))) + +(defun tab-bar--event-to-item (posn) + (if (posn-window posn) + (let ((caption (car (posn-string posn)))) + (when caption + (get-text-property 0 'menu-item caption))) + ;; Text-mode emulation of switching tabs on the tab bar. + ;; This code is used when you click the mouse in the tab bar + ;; on a console which has no window system but does have a mouse. + (let* ((x-position (car (posn-x-y posn))) + (keymap (lookup-key (cons 'keymap (nreverse (current-active-maps))) [tab-bar])) + (column 0)) + (when x-position + (catch 'done + (map-keymap + (lambda (key binding) + (when (eq (car-safe binding) 'menu-item) + (when (> (+ column (length (nth 1 binding))) x-position) + (throw 'done (list + key (nth 2 binding) + (get-text-property + (- x-position column) 'close-tab (nth 1 binding))))) + (setq column (+ column (length (nth 1 binding)))))) + keymap)))))) (defun tab-bar-mouse-select-tab (event) (interactive "e") - (if (posn-window (event-start event)) - (let* ((caption (car (posn-string (event-start event)))) - (item (and caption (get-text-property 0 'menu-item caption)))) - (if (nth 2 item) - (tab-bar-close-tab (tab--key-to-number (nth 0 item))) - (if (functionp (nth 1 item)) - (call-interactively (nth 1 item)) - (tab-bar-select-tab (tab--key-to-number (nth 0 item)))))) - ;; TTY - (tab-bar-handle-mouse event))) + (let ((item (tab-bar--event-to-item (event-start event)))) + (if (nth 2 item) + (tab-bar-close-tab (tab-bar--key-to-number (nth 0 item))) + (if (functionp (nth 1 item)) + (call-interactively (nth 1 item)) + (tab-bar-select-tab (tab-bar--key-to-number (nth 0 item))))))) (defun tab-bar-mouse-close-tab (event) (interactive "e") - (let* ((caption (car (posn-string (event-start event)))) - (item (and caption (get-text-property 0 'menu-item caption)))) - (tab-bar-close-tab (tab--key-to-number (nth 0 item))))) + (let ((item (tab-bar--event-to-item (event-start event)))) + (tab-bar-close-tab (tab-bar--key-to-number (nth 0 item))))) (defun tab-bar-mouse-context-menu (event) (interactive "e") - (let* ((caption (car (posn-string (event-start event)))) - (item (and caption (get-text-property 0 'menu-item caption))) - (tab-number (tab--key-to-number (nth 0 item))) + (let* ((item (tab-bar--event-to-item (event-start event))) + (tab-number (tab-bar--key-to-number (nth 0 item))) (menu (make-sparse-keymap "Context Menu"))) (define-key-after menu [close] @@ -287,12 +279,12 @@ on a console which has no window system but does have a mouse." (defun tab-bar-mouse-move-tab (event) (interactive "e") - (let* ((caption (car (posn-string (event-start event)))) - (item (and caption (get-text-property 0 'menu-item caption))) - (from (tab--key-to-number (nth 0 item))) - (caption (car (posn-string (event-end event)))) - (item (and caption (get-text-property 0 'menu-item caption))) - (to (tab--key-to-number (nth 0 item)))) + (let ((from (tab-bar--key-to-number + (nth 0 (tab-bar--event-to-item + (event-start event))))) + (to (tab-bar--key-to-number + (nth 0 (tab-bar--event-to-item + (event-end event)))))) (tab-bar-move-tab-to to from))) (defun toggle-tab-bar-mode-from-frame (&optional arg) diff --git a/src/menu.c b/src/menu.c index 3b1d740257..990a74b92e 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1127,9 +1127,12 @@ x_popup_menu_1 (Lisp_Object position, Lisp_Object menu) /* Decode the first argument: find the window and the coordinates. */ if (EQ (position, Qt) - || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar) - || EQ (XCAR (position), Qtab_bar) - || EQ (XCAR (position), Qtool_bar)))) + || (CONSP (position) + && (EQ (XCAR (position), Qmenu_bar) + || EQ (XCAR (position), Qtab_bar) + || (CONSP (XCDR (position)) + && EQ (XCAR (XCDR (position)), Qtab_bar)) + || EQ (XCAR (position), Qtool_bar)))) { get_current_pos_p = 1; } diff --git a/src/w32term.c b/src/w32term.c index c9570b0c67..aca4739a2e 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5186,7 +5186,7 @@ w32_read_socket (struct terminal *terminal, { /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ - Lisp_Object tab_bar_key = Qnil; + Lisp_Object tab_bar_arg = Qnil; bool tab_bar_p = 0; bool tool_bar_p = 0; int button = 0; @@ -5209,12 +5209,12 @@ w32_read_socket (struct terminal *terminal, if (EQ (window, f->tab_bar_window)) { - tab_bar_key = w32_handle_tab_bar_click (f, &inev); + tab_bar_arg = w32_handle_tab_bar_click (f, &inev); tab_bar_p = 1; } } - if ((tab_bar_p && NILP (tab_bar_key)) + if ((tab_bar_p && NILP (tab_bar_arg)) || (dpyinfo->w32_focus_frame && f != dpyinfo->w32_focus_frame /* This does not help when the click happens in @@ -5222,8 +5222,8 @@ w32_read_socket (struct terminal *terminal, && !frame_ancestor_p (f, dpyinfo->w32_focus_frame))) inev.kind = NO_EVENT; - if (!NILP (tab_bar_key)) - inev.arg = tab_bar_key; + if (!NILP (tab_bar_arg)) + inev.arg = tab_bar_arg; /* Is this in the tool-bar? */ if (WINDOWP (f->tool_bar_window) diff --git a/src/xdisp.c b/src/xdisp.c index 4a5ab172cc..91f9bb98e6 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -13891,7 +13891,7 @@ note_tab_bar_highlight (struct frame *f, int x, int y) /* Find the tab-bar item at X coordinate and return its information. */ static Lisp_Object -tty_get_tab_bar_item (struct frame *f, int x, int *idx, ptrdiff_t *end) +tty_get_tab_bar_item (struct frame *f, int x, int *prop_idx, bool *close_p) { ptrdiff_t clen = 0; @@ -13904,8 +13904,11 @@ tty_get_tab_bar_item (struct frame *f, int x, int *idx, ptrdiff_t *end) clen += SCHARS (caption); if (x < clen) { - *idx = i; - *end = clen; + *prop_idx = i; + *close_p = !NILP (Fget_text_property (make_fixnum (SCHARS (caption) + - (clen - x)), + Qclose_tab, + caption)); return caption; } } @@ -13928,8 +13931,8 @@ tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, /* Find the tab-bar item where the X,Y coordinates belong. */ int prop_idx; - ptrdiff_t clen; - Lisp_Object caption = tty_get_tab_bar_item (f, x, &prop_idx, &clen); + bool close_p; + Lisp_Object caption = tty_get_tab_bar_item (f, x, &prop_idx, &close_p); if (NILP (caption)) return Qnil; @@ -13941,24 +13944,21 @@ tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, if (down_p) f->last_tab_bar_item = prop_idx; else - { - f->last_tab_bar_item = -1; - } + f->last_tab_bar_item = -1; - /* Generate a TAB_BAR_EVENT event. */ - Lisp_Object key = AREF (f->tab_bar_items, - prop_idx * TAB_BAR_ITEM_NSLOTS - + TAB_BAR_ITEM_KEY); - /* Kludge alert: we assume the last two characters of a tab - label are " x", and treat clicks on those 2 characters as a - Close Tab command. */ - eassert (STRINGP (caption)); - int lastc = SSDATA (caption)[SCHARS (caption) - 1]; - bool close_p = false; - if ((x == clen - 1 || (clen > 1 && x == clen - 2)) && lastc == 'x') - close_p = true; + caption = Fcopy_sequence (caption); - return list3 (Qtab_bar, key, close_p ? Qt : Qnil); + AUTO_LIST2 (props, Qmenu_item, + list3 (AREF (f->tab_bar_items, prop_idx * TAB_BAR_ITEM_NSLOTS + + TAB_BAR_ITEM_KEY), + AREF (f->tab_bar_items, prop_idx * TAB_BAR_ITEM_NSLOTS + + TAB_BAR_ITEM_BINDING), + close_p ? Qt : Qnil)); + + Fadd_text_properties (make_fixnum (0), make_fixnum (SCHARS (caption)), + props, caption); + + return Fcons (Qtab_bar, Fcons (caption, make_fixnum (0))); } @@ -33524,7 +33524,7 @@ note_mouse_highlight (struct frame *f, int x, int y) && y < FRAME_MENU_BAR_LINES (f) + FRAME_TAB_BAR_LINES (f))) { int prop_idx; - ptrdiff_t ignore; + bool ignore; Lisp_Object caption = tty_get_tab_bar_item (f, x, &prop_idx, &ignore); if (!NILP (caption)) diff --git a/src/xterm.c b/src/xterm.c index 57229d3d61..846b67b069 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -9166,7 +9166,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, { /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ - Lisp_Object tab_bar_key = Qnil; + Lisp_Object tab_bar_arg = Qnil; bool tab_bar_p = false; bool tool_bar_p = false; @@ -9216,7 +9216,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, tab_bar_p = EQ (window, f->tab_bar_window); if (tab_bar_p) - tab_bar_key = handle_tab_bar_click + tab_bar_arg = handle_tab_bar_click (f, x, y, event->xbutton.type == ButtonPress, x_x_to_emacs_modifiers (dpyinfo, event->xbutton.state)); } @@ -9240,7 +9240,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif /* !USE_GTK */ - if (!(tab_bar_p && NILP (tab_bar_key)) && !tool_bar_p) + if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p) #if defined (USE_X_TOOLKIT) || defined (USE_GTK) if (! popup_activated ()) #endif @@ -9259,8 +9259,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, else x_construct_mouse_click (&inev.ie, &event->xbutton, f); - if (!NILP (tab_bar_key)) - inev.ie.arg = tab_bar_key; + if (!NILP (tab_bar_arg)) + inev.ie.arg = tab_bar_arg; } if (FRAME_X_EMBEDDED_P (f)) xembed_send_message (f, event->xbutton.time, commit ad9c57f54ae3eea9e5b2fe9264e9edb8b2ed1857 Author: Juri Linkov Date: Wed Aug 18 20:52:32 2021 +0300 Mouse wheel scrolling on the tab bar * lisp/tab-bar.el (tab-bar-map): Bind mouse-4/wheel-up/wheel-left to tab-previous and mouse-5/wheel-down/wheel-right to tab-next. Bind S-mouse-4/wheel-up/wheel-left to tab-bar-move-tab-backward and S-mouse-5/wheel-down/wheel-right to tab-bar-move-tab. (tab-bar-move-tab-backward): New command. (tab-bar-move-repeat-map): Use tab-bar-move-tab-backward instead of lambda. * src/xterm.c (handle_one_xevent): Remove restriction to allow clicking mouse-4 and mouse-5. diff --git a/etc/NEWS b/etc/NEWS index fb553e82ff..c48e110f9a 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -751,7 +751,9 @@ of the next command to be displayed in a new frame. *** The tab bar now supports more mouse commands. Clicking 'mouse-2' closes the tab, 'mouse-3' displays the context menu with items that operate on the clicked tab. Dragging the tab with -'mouse-1' moves it to another position on the tab bar. +'mouse-1' moves it to another position on the tab bar. Mouse wheel +scrolling switches to the previous/next tab, and holding the Shift key +during scrolling moves the tab to the left/right. *** The key prefix 'C-x t t' displays next command buffer in a new tab. It's bound to the command 'other-tab-prefix' that requests the buffer diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index 68de770e06..ef7babb87b 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el @@ -327,6 +327,20 @@ new frame when the global `tab-bar-mode' is enabled, by using (define-key map [mouse-2] 'ignore) (define-key map [down-mouse-3] 'tab-bar-mouse-context-menu) + (define-key map [mouse-4] 'tab-previous) + (define-key map [mouse-5] 'tab-next) + (define-key map [wheel-up] 'tab-previous) + (define-key map [wheel-down] 'tab-next) + (define-key map [wheel-left] 'tab-previous) + (define-key map [wheel-right] 'tab-next) + + (define-key map [S-mouse-4] 'tab-bar-move-tab-backward) + (define-key map [S-mouse-5] 'tab-bar-move-tab) + (define-key map [S-wheel-up] 'tab-bar-move-tab-backward) + (define-key map [S-wheel-down] 'tab-bar-move-tab) + (define-key map [S-wheel-left] 'tab-bar-move-tab-backward) + (define-key map [S-wheel-right] 'tab-bar-move-tab) + map) "Keymap for the commands used on the tab bar.") @@ -1055,6 +1069,12 @@ where argument addressing is absolute." (to-index (mod (+ from-index arg) (length tabs)))) (tab-bar-move-tab-to (1+ to-index) (1+ from-index)))) +(defun tab-bar-move-tab-backward (&optional arg) + "Move the current tab ARG positions to the left. +Like `tab-bar-move-tab', but moves in the opposite direction." + (interactive "p") + (tab-bar-move-tab (- (or arg 1)))) + (defun tab-bar-move-tab-to-frame (arg &optional from-frame from-index to-frame to-index) "Move tab from FROM-INDEX position to new position at TO-INDEX. FROM-INDEX defaults to the current tab index. @@ -2118,11 +2138,8 @@ Used in `repeat-mode'.") (defvar tab-bar-move-repeat-map (let ((map (make-sparse-keymap))) - (define-key map "m" 'tab-move) - (define-key map "M" (lambda () - (interactive) - (setq repeat-map 'tab-bar-move-repeat-map) - (tab-move -1))) + (define-key map "m" 'tab-bar-move-tab) + (define-key map "M" 'tab-bar-move-tab-backward) map) "Keymap to repeat tab move key sequences `C-x t m m M'. Used in `repeat-mode'.") diff --git a/src/xterm.c b/src/xterm.c index 80fa747b97..57229d3d61 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -9215,7 +9215,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, window = window_from_coordinates (f, x, y, 0, true, true); tab_bar_p = EQ (window, f->tab_bar_window); - if (tab_bar_p && event->xbutton.button < 4) + if (tab_bar_p) tab_bar_key = handle_tab_bar_click (f, x, y, event->xbutton.type == ButtonPress, x_x_to_emacs_modifiers (dpyinfo, event->xbutton.state)); commit 56d567acb6eae72352e39acf4f206f7cb3195900 Author: Juri Linkov Date: Wed Aug 18 20:44:14 2021 +0300 Bind [drag-mouse-1] to tab-bar-mouse-move-tab on tab-bar-map * lisp/tab-bar.el (tab-bar-mouse-move-tab): New command. (tab-bar-map): Bind [drag-mouse-1] to tab-bar-mouse-move-tab. (tab-bar-select-tab): Zero or nil arg means the current tab. * src/xdisp.c (handle_tab_bar_click): Remove restriction to allow dragging the tab to another tab. diff --git a/etc/NEWS b/etc/NEWS index cb500dd697..fb553e82ff 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -750,7 +750,8 @@ of the next command to be displayed in a new frame. *** The tab bar now supports more mouse commands. Clicking 'mouse-2' closes the tab, 'mouse-3' displays the context menu -with items that operate on the clicked tab. +with items that operate on the clicked tab. Dragging the tab with +'mouse-1' moves it to another position on the tab bar. *** The key prefix 'C-x t t' displays next command buffer in a new tab. It's bound to the command 'other-tab-prefix' that requests the buffer diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index 91d22b4b7b..68de770e06 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el @@ -285,6 +285,16 @@ on a console which has no window system but does have a mouse." (popup-menu menu event))) +(defun tab-bar-mouse-move-tab (event) + (interactive "e") + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption))) + (from (tab--key-to-number (nth 0 item))) + (caption (car (posn-string (event-end event)))) + (item (and caption (get-text-property 0 'menu-item caption))) + (to (tab--key-to-number (nth 0 item)))) + (tab-bar-move-tab-to to from))) + (defun toggle-tab-bar-mode-from-frame (&optional arg) "Toggle tab bar on or off, based on the status of the current frame. Used in the Show/Hide menu, to have the toggle reflect the current frame. @@ -311,6 +321,7 @@ new frame when the global `tab-bar-mode' is enabled, by using (defvar tab-bar-map (let ((map (make-sparse-keymap))) (define-key map [down-mouse-1] 'tab-bar-mouse-select-tab) + (define-key map [drag-mouse-1] 'tab-bar-mouse-move-tab) (define-key map [mouse-1] 'ignore) (define-key map [down-mouse-2] 'tab-bar-mouse-close-tab) (define-key map [mouse-2] 'ignore) @@ -894,11 +905,13 @@ ARG counts from 1. Negative ARG counts tabs from the end of the tab bar." (let ((key (event-basic-type last-command-event))) (setq arg (if (and (characterp key) (>= key ?1) (<= key ?9)) (- key ?0) - 1)))) + 0)))) (let* ((tabs (funcall tab-bar-tabs-function)) (from-index (tab-bar--current-tab-index tabs)) - (to-index (if (< arg 0) (+ (length tabs) (1+ arg)) arg)) + (to-index (cond ((< arg 0) (+ (length tabs) (1+ arg))) + ((zerop arg) (1+ from-index)) + (t arg))) (to-index (1- (max 1 (min to-index (length tabs)))))) (unless (eq from-index to-index) diff --git a/src/xdisp.c b/src/xdisp.c index 1a4efba4b8..4a5ab172cc 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -13759,10 +13759,7 @@ handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, frame_to_window_pixel_xy (w, &x, &y); ts = get_tab_bar_item (f, x, y, &glyph, &hpos, &vpos, &prop_idx, &close_p); - if (ts == -1 - /* If the button is released on a tab other than the one where - it was pressed, don't generate the tab-bar button click event. */ - || (ts != 0 && !down_p)) + if (ts == -1) return Qnil; /* If item is disabled, do nothing. */ commit e6bea0cbc76bc47f435be9c7f6f3c4b770994924 Author: Juri Linkov Date: Wed Aug 18 20:32:32 2021 +0300 Redesign tab-bar event processing (bug#41342, bug#41343) Instead of emitting menu-item keys like [tab-1], emit normal mouse events like [mouse-1] and [down-mouse-3] for all mouse clicks issued on the tab-bar. * lisp/mouse.el (mouse-posn-property): Handle 'tab-bar' posn-area. * lisp/tab-bar.el (tab--key-to-number): New internal function. (tab-bar-handle-mouse): Use tab key to select/close tab. (tab-bar-mouse-select-tab, tab-bar-mouse-close-tab) (tab-bar-mouse-context-menu): New commands. (tab-bar-map): Bind [down-mouse-1] to tab-bar-mouse-select-tab, [down-mouse-2] to tab-bar-mouse-close-tab, [down-mouse-3] to tab-bar-mouse-context-menu. (tab-bar-keymap-cache): Remove. (tab-bar-make-keymap): Don't use cache. (tab-bar--format-tab): Remove default bindings from menu items. (tab-bar-make-keymap-1): Prepend tab-bar-map. * src/keyboard.c (make_lispy_event): Append event->arg to position for Qtab_bar. * src/term.c (handle_one_term_event): Simplify to set event arg. * src/w32inevt.c (do_mouse_event): Set emacs_ev->arg to the value returned from tty_handle_tab_bar_click. * src/w32term.c (w32_handle_tab_bar_click): Return value from handle_tab_bar_click. (w32_read_socket): Set tab_bar_key to value returned from w32_handle_tab_bar_click, and set event arg from it. * src/xdisp.c (handle_tab_bar_click): Instead of emitting event, return a list with Qtab_bar and tab caption with text properties that contain Qmenu_item with key and binding. (tty_handle_tab_bar_click): Simplify to return a list of Qtab_bar, key and close_p, instead of emitting event. * src/xterm.c (handle_one_xevent): Set tab_bar_key to value returned from handle_tab_bar_click, and set event arg from it. diff --git a/etc/NEWS b/etc/NEWS index 1b4712828c..cb500dd697 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -748,6 +748,10 @@ of the next command to be displayed in a new frame. ** Tab Bars +*** The tab bar now supports more mouse commands. +Clicking 'mouse-2' closes the tab, 'mouse-3' displays the context menu +with items that operate on the clicked tab. + *** The key prefix 'C-x t t' displays next command buffer in a new tab. It's bound to the command 'other-tab-prefix' that requests the buffer of the next command to be displayed in a new tab. diff --git a/lisp/mouse.el b/lisp/mouse.el index 4c4a7d35a8..0047475a0d 100644 --- a/lisp/mouse.el +++ b/lisp/mouse.el @@ -1374,9 +1374,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) diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index 4ec1143128..91d22b4b7b 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el @@ -27,10 +27,7 @@ ;; bindings for the global tab bar. ;; The normal global binding for [tab-bar] (below) uses the value of -;; `tab-bar-map' as the actual keymap to define the tab bar. Modes -;; may either bind items under the [tab-bar] prefix key of the local -;; map to add to the global bar or may set `tab-bar-map' -;; buffer-locally to override it. +;; `tab-bar-map' as the actual keymap to define the tab bar. ;;; Code: @@ -224,6 +221,11 @@ a list of frames to update." (tab-bar--define-keys) (tab-bar--undefine-keys))) +(defun tab--key-to-number (key) + (unless (or (null key) (eq key 'current-tab)) + (string-to-number + (string-replace "tab-" "" (format "%S" key))))) + (defun tab-bar-handle-mouse (event) "Text-mode emulation of switching tabs on the tab bar. This command is used when you click the mouse in the tab bar @@ -238,18 +240,51 @@ on a console which has no window system but does have a mouse." (lambda (key binding) (when (eq (car-safe binding) 'menu-item) (when (> (+ column (length (nth 1 binding))) x-position) - (if (get-text-property (- x-position column) 'close-tab (nth 1 binding)) - (let* ((close-key (vector (intern (format "C-%s" key)))) - (close-def (lookup-key keymap close-key))) - (when close-def - (call-interactively close-def))) - (call-interactively (nth 2 binding))) + (if (get-text-property + (- x-position column) 'close-tab (nth 1 binding)) + (tab-bar-close-tab (tab--key-to-number key)) + (if (nth 2 binding) + (call-interactively (nth 2 binding)) + (tab-bar-select-tab (tab--key-to-number key)))) (throw 'done t)) (setq column (+ column (length (nth 1 binding)))))) keymap)) ;; Clicking anywhere outside existing tabs will add a new tab (tab-bar-new-tab))))) +(defun tab-bar-mouse-select-tab (event) + (interactive "e") + (if (posn-window (event-start event)) + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption)))) + (if (nth 2 item) + (tab-bar-close-tab (tab--key-to-number (nth 0 item))) + (if (functionp (nth 1 item)) + (call-interactively (nth 1 item)) + (tab-bar-select-tab (tab--key-to-number (nth 0 item)))))) + ;; TTY + (tab-bar-handle-mouse event))) + +(defun tab-bar-mouse-close-tab (event) + (interactive "e") + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption)))) + (tab-bar-close-tab (tab--key-to-number (nth 0 item))))) + +(defun tab-bar-mouse-context-menu (event) + (interactive "e") + (let* ((caption (car (posn-string (event-start event)))) + (item (and caption (get-text-property 0 'menu-item caption))) + (tab-number (tab--key-to-number (nth 0 item))) + (menu (make-sparse-keymap "Context Menu"))) + + (define-key-after menu [close] + `(menu-item "Close" (lambda () (interactive) + (tab-bar-close-tab ,tab-number)) + :help "Close the tab")) + + (popup-menu menu event))) + (defun toggle-tab-bar-mode-from-frame (&optional arg) "Toggle tab bar on or off, based on the status of the current frame. Used in the Show/Hide menu, to have the toggle reflect the current frame. @@ -273,24 +308,26 @@ new frame when the global `tab-bar-mode' is enabled, by using (set-frame-parameter frame 'tab-bar-lines-keep-state (not (frame-parameter frame 'tab-bar-lines-keep-state)))) -(defvar tab-bar-map (make-sparse-keymap) - "Keymap for the tab bar. -Define this locally to override the global tab bar.") +(defvar tab-bar-map + (let ((map (make-sparse-keymap))) + (define-key map [down-mouse-1] 'tab-bar-mouse-select-tab) + (define-key map [mouse-1] 'ignore) + (define-key map [down-mouse-2] 'tab-bar-mouse-close-tab) + (define-key map [mouse-2] 'ignore) + (define-key map [down-mouse-3] 'tab-bar-mouse-context-menu) + + map) + "Keymap for the commands used on the tab bar.") (global-set-key [tab-bar] `(menu-item ,(purecopy "tab bar") ignore :filter tab-bar-make-keymap)) -(defconst tab-bar-keymap-cache (make-hash-table :weakness t :test 'equal)) - (defun tab-bar-make-keymap (&optional _ignore) "Generate an actual keymap from `tab-bar-map'. -Its main job is to show tabs in the tab bar." - (if (= 1 (length tab-bar-map)) - (tab-bar-make-keymap-1) - (let ((key (cons (frame-terminal) tab-bar-map))) - (or (gethash key tab-bar-keymap-cache) - (puthash key tab-bar-map tab-bar-keymap-cache))))) +Its main job is to show tabs in the tab bar +and to bind mouse events to the commands." + (tab-bar-make-keymap-1)) (defcustom tab-bar-show t @@ -608,19 +645,12 @@ You can hide these buttons by customizing `tab-bar-format' and removing `((,(intern (format "tab-%i" i)) menu-item ,(funcall tab-bar-tab-name-format-function tab i) - ,(or - (alist-get 'binding tab) - `(lambda () - (interactive) - (tab-bar-select-tab ,i))) + ,(alist-get 'binding tab) :help "Click to visit tab")))) - `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) - menu-item "" - ,(or - (alist-get 'close-binding tab) - `(lambda () - (interactive) - (tab-bar-close-tab ,i))))))) + (when (alist-get 'close-binding tab) + `((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i))) + menu-item "" + ,(alist-get 'close-binding tab)))))) (defun tab-bar-format-tabs () (let ((i 0)) @@ -760,9 +790,7 @@ on the tab bar instead." (defun tab-bar-make-keymap-1 () "Generate an actual keymap from `tab-bar-map', without caching." - (append - '(keymap (mouse-1 . tab-bar-handle-mouse)) - (tab-bar-format-list tab-bar-format))) + (append tab-bar-map (tab-bar-format-list tab-bar-format))) ;; Some window-configuration parameters don't need to be persistent. diff --git a/src/dispextern.h b/src/dispextern.h index 33fcaa4c07..f4c7575b35 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -3415,8 +3415,8 @@ extern void get_glyph_string_clip_rect (struct glyph_string *, NativeRectangle *nr); extern Lisp_Object find_hot_spot (Lisp_Object, int, int); -extern void handle_tab_bar_click (struct frame *, - int, int, bool, int); +extern Lisp_Object handle_tab_bar_click (struct frame *, + int, int, bool, int); extern void handle_tool_bar_click (struct frame *, int, int, bool, int); diff --git a/src/keyboard.c b/src/keyboard.c index 2e4c4e6aab..e25833276c 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -5649,6 +5649,12 @@ make_lispy_event (struct input_event *event) position = make_lispy_position (f, event->x, event->y, event->timestamp); + + if (CONSP (event->arg) && EQ (XCAR (event->arg), Qtab_bar)) + { + XSETCAR (XCDR (position), Qtab_bar); + position = nconc2 (position, Fcons (XCDR (event->arg), Qnil)); + } } #ifndef USE_TOOLKIT_SCROLL_BARS else diff --git a/src/term.c b/src/term.c index c995a4499c..89b3568003 100644 --- a/src/term.c +++ b/src/term.c @@ -2568,21 +2568,8 @@ handle_one_term_event (struct tty_display_info *tty, Gpm_Event *event) { f->mouse_moved = 0; term_mouse_click (&ie, event, f); - /* eassert (ie.kind == MOUSE_CLICK_EVENT); */ - if (tty_handle_tab_bar_click (f, event->x, event->y, - (ie.modifiers & down_modifier) != 0, &ie)) - { - /* eassert (ie.kind == MOUSE_CLICK_EVENT - * || ie.kind == TAB_BAR_EVENT); */ - /* tty_handle_tab_bar_click stores 2 events in the event - queue, so we are done here. */ - /* FIXME: Actually, `tty_handle_tab_bar_click` returns true - without storing any events, when - (ie.modifiers & down_modifier) != 0 */ - count += 2; - return count; - } - /* eassert (ie.kind == MOUSE_CLICK_EVENT); */ + ie.arg = tty_handle_tab_bar_click (f, event->x, event->y, + (ie.modifiers & down_modifier) != 0, &ie); kbd_buffer_store_event (&ie); count++; } diff --git a/src/termchar.h b/src/termchar.h index f50c1bfb6e..7ab9337fbe 100644 --- a/src/termchar.h +++ b/src/termchar.h @@ -234,7 +234,7 @@ extern struct tty_display_info *tty_list; #define CURTTY() FRAME_TTY (SELECTED_FRAME()) struct input_event; -extern bool tty_handle_tab_bar_click (struct frame *, int, int, bool, - struct input_event *); +extern Lisp_Object tty_handle_tab_bar_click (struct frame *, int, int, bool, + struct input_event *); #endif /* EMACS_TERMCHAR_H */ diff --git a/src/w32inevt.c b/src/w32inevt.c index 1255072b7f..9a69b32bcb 100644 --- a/src/w32inevt.c +++ b/src/w32inevt.c @@ -586,9 +586,8 @@ do_mouse_event (MOUSE_EVENT_RECORD *event, int x = event->dwMousePosition.X; int y = event->dwMousePosition.Y; struct frame *f = get_frame (); - if (tty_handle_tab_bar_click (f, x, y, (button_state & mask) != 0, - emacs_ev)) - return 0; /* tty_handle_tab_bar_click adds the event to queue */ + emacs_ev->arg = tty_handle_tab_bar_click (f, x, y, (button_state & mask) != 0, + emacs_ev); emacs_ev->modifiers |= ((button_state & mask) ? down_modifier : up_modifier); @@ -597,7 +596,6 @@ do_mouse_event (MOUSE_EVENT_RECORD *event, XSETFASTINT (emacs_ev->x, x); XSETFASTINT (emacs_ev->y, y); XSETFRAME (emacs_ev->frame_or_window, f); - emacs_ev->arg = Qnil; return 1; } diff --git a/src/w32term.c b/src/w32term.c index ad4d1a3282..c9570b0c67 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -168,8 +168,8 @@ int w32_keyboard_codepage; int w32_message_fd = -1; #endif /* CYGWIN */ -static void w32_handle_tab_bar_click (struct frame *, - struct input_event *); +static Lisp_Object w32_handle_tab_bar_click (struct frame *, + struct input_event *); static void w32_handle_tool_bar_click (struct frame *, struct input_event *); static void w32_define_cursor (Window, Emacs_Cursor); @@ -3684,17 +3684,17 @@ w32_mouse_position (struct frame **fp, int insist, Lisp_Object *bar_window, frame-relative coordinates X/Y. EVENT_TYPE is either ButtonPress or ButtonRelease. */ -static void +static Lisp_Object w32_handle_tab_bar_click (struct frame *f, struct input_event *button_event) { int x = XFIXNAT (button_event->x); int y = XFIXNAT (button_event->y); if (button_event->modifiers & down_modifier) - handle_tab_bar_click (f, x, y, 1, 0); + return handle_tab_bar_click (f, x, y, 1, 0); else - handle_tab_bar_click (f, x, y, 0, - button_event->modifiers & ~up_modifier); + return handle_tab_bar_click (f, x, y, 0, + button_event->modifiers & ~up_modifier); } @@ -5186,6 +5186,7 @@ w32_read_socket (struct terminal *terminal, { /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ + Lisp_Object tab_bar_key = Qnil; bool tab_bar_p = 0; bool tool_bar_p = 0; int button = 0; @@ -5208,12 +5209,12 @@ w32_read_socket (struct terminal *terminal, if (EQ (window, f->tab_bar_window)) { - w32_handle_tab_bar_click (f, &inev); + tab_bar_key = w32_handle_tab_bar_click (f, &inev); tab_bar_p = 1; } } - if (tab_bar_p + if ((tab_bar_p && NILP (tab_bar_key)) || (dpyinfo->w32_focus_frame && f != dpyinfo->w32_focus_frame /* This does not help when the click happens in @@ -5221,6 +5222,9 @@ w32_read_socket (struct terminal *terminal, && !frame_ancestor_p (f, dpyinfo->w32_focus_frame))) inev.kind = NO_EVENT; + if (!NILP (tab_bar_key)) + inev.arg = tab_bar_key; + /* Is this in the tool-bar? */ if (WINDOWP (f->tool_bar_window) && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window))) diff --git a/src/xdisp.c b/src/xdisp.c index 972b90177c..1a4efba4b8 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -13745,7 +13745,7 @@ get_tab_bar_item (struct frame *f, int x, int y, struct glyph **glyph, false for button release. MODIFIERS is event modifiers for button release. */ -void +Lisp_Object handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, int modifiers) { @@ -13763,12 +13763,12 @@ handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, /* If the button is released on a tab other than the one where it was pressed, don't generate the tab-bar button click event. */ || (ts != 0 && !down_p)) - return; + return Qnil; /* If item is disabled, do nothing. */ enabled_p = AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_ENABLED_P); if (NILP (enabled_p)) - return; + return Qnil; if (down_p) { @@ -13779,24 +13779,24 @@ handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, } else { - Lisp_Object key, frame; - struct input_event event; - EVENT_INIT (event); - /* Show item in released state. */ if (!NILP (Vmouse_highlight)) show_mouse_face (hlinfo, DRAW_IMAGE_RAISED); - - key = AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_KEY); - - XSETFRAME (frame, f); - event.kind = TAB_BAR_EVENT; - event.frame_or_window = frame; - event.arg = key; - event.modifiers = close_p ? ctrl_modifier | modifiers : modifiers; - kbd_buffer_store_event (&event); f->last_tab_bar_item = -1; } + + Lisp_Object caption = + Fcopy_sequence (AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_CAPTION)); + + AUTO_LIST2 (props, Qmenu_item, + list3 (AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_KEY), + AREF (f->tab_bar_items, prop_idx + TAB_BAR_ITEM_BINDING), + close_p ? Qt : Qnil)); + + Fadd_text_properties (make_fixnum (0), make_fixnum (SCHARS (caption)), + props, caption); + + return Fcons (Qtab_bar, Fcons (caption, make_fixnum (0))); } @@ -13920,14 +13920,14 @@ tty_get_tab_bar_item (struct frame *f, int x, int *idx, ptrdiff_t *end) structure, store it in keyboard queue, and return true; otherwise return false. MODIFIERS are event modifiers for generating the tab release event. */ -bool +Lisp_Object tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, struct input_event *event) { /* Did they click on the tab bar? */ if (y < FRAME_MENU_BAR_LINES (f) || y >= FRAME_MENU_BAR_LINES (f) + FRAME_TAB_BAR_LINES (f)) - return false; + return Qnil; /* Find the tab-bar item where the X,Y coordinates belong. */ int prop_idx; @@ -13935,46 +13935,33 @@ tty_handle_tab_bar_click (struct frame *f, int x, int y, bool down_p, Lisp_Object caption = tty_get_tab_bar_item (f, x, &prop_idx, &clen); if (NILP (caption)) - return false; + return Qnil; if (NILP (AREF (f->tab_bar_items, prop_idx * TAB_BAR_ITEM_NSLOTS + TAB_BAR_ITEM_ENABLED_P))) - return false; + return Qnil; if (down_p) f->last_tab_bar_item = prop_idx; else { - /* Force reset of up_modifier bit from the event modifiers. */ - if (event->modifiers & up_modifier) - event->modifiers &= ~up_modifier; - - /* Generate a TAB_BAR_EVENT event. */ - Lisp_Object frame; - Lisp_Object key = AREF (f->tab_bar_items, - prop_idx * TAB_BAR_ITEM_NSLOTS - + TAB_BAR_ITEM_KEY); - /* Kludge alert: we assume the last two characters of a tab - label are " x", and treat clicks on those 2 characters as a - Close Tab command. */ - eassert (STRINGP (caption)); - int lastc = SSDATA (caption)[SCHARS (caption) - 1]; - bool close_p = false; - if ((x == clen - 1 || (clen > 1 && x == clen - 2)) && lastc == 'x') - close_p = true; - - event->code = 0; - XSETFRAME (frame, f); - event->kind = TAB_BAR_EVENT; - event->frame_or_window = frame; - event->arg = key; - if (close_p) - event->modifiers |= ctrl_modifier; - kbd_buffer_store_event (event); f->last_tab_bar_item = -1; } - return true; + /* Generate a TAB_BAR_EVENT event. */ + Lisp_Object key = AREF (f->tab_bar_items, + prop_idx * TAB_BAR_ITEM_NSLOTS + + TAB_BAR_ITEM_KEY); + /* Kludge alert: we assume the last two characters of a tab + label are " x", and treat clicks on those 2 characters as a + Close Tab command. */ + eassert (STRINGP (caption)); + int lastc = SSDATA (caption)[SCHARS (caption) - 1]; + bool close_p = false; + if ((x == clen - 1 || (clen > 1 && x == clen - 2)) && lastc == 'x') + close_p = true; + + return list3 (Qtab_bar, key, close_p ? Qt : Qnil); } diff --git a/src/xterm.c b/src/xterm.c index 1887c3255d..80fa747b97 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -9166,6 +9166,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, { /* If we decide we want to generate an event to be seen by the rest of Emacs, we put it here. */ + Lisp_Object tab_bar_key = Qnil; bool tab_bar_p = false; bool tool_bar_p = false; @@ -9215,7 +9216,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, tab_bar_p = EQ (window, f->tab_bar_window); if (tab_bar_p && event->xbutton.button < 4) - handle_tab_bar_click + tab_bar_key = handle_tab_bar_click (f, x, y, event->xbutton.type == ButtonPress, x_x_to_emacs_modifiers (dpyinfo, event->xbutton.state)); } @@ -9239,7 +9240,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif /* !USE_GTK */ - if (!tab_bar_p && !tool_bar_p) + if (!(tab_bar_p && NILP (tab_bar_key)) && !tool_bar_p) #if defined (USE_X_TOOLKIT) || defined (USE_GTK) if (! popup_activated ()) #endif @@ -9257,6 +9258,9 @@ handle_one_xevent (struct x_display_info *dpyinfo, } else x_construct_mouse_click (&inev.ie, &event->xbutton, f); + + if (!NILP (tab_bar_key)) + inev.ie.arg = tab_bar_key; } if (FRAME_X_EMBEDDED_P (f)) xembed_send_message (f, event->xbutton.time,