diff options
| author | Pengji Zhang | 2025-04-02 20:52:30 +0800 |
|---|---|---|
| committer | Eli Zaretskii | 2025-04-05 12:30:46 +0300 |
| commit | c94f0d3dc82a72baa0ba8d69beda220aaf0b2d91 (patch) | |
| tree | a60e9a00236ab43fa7bf5d1aa92a0743a5bf50fa | |
| parent | 2d0b5f34a008979d34f337c872bcf93a296c6ec2 (diff) | |
| download | emacs-c94f0d3dc82a72baa0ba8d69beda220aaf0b2d91.tar.gz emacs-c94f0d3dc82a72baa0ba8d69beda220aaf0b2d91.zip | |
Fix mouse highlighting for compact mode lines (bug#77336)
When 'mode-line-compact' is non-nil, the mode line string is
displayed as a whole. That confuses the computation of ranges
of mouse highlighting on the mode line because all the glyphs
have the same Lisp object source. As such, in this commit we
instead split the mode line string by sources, and display those
elements one by one, so the boundaries of each element could be
correctly detected for the purpose of mouse highlighting.
* src/xdisp.c (display_mode_line): Display mode line elements
one by one when 'mode-line-compact' is non-nil.
(display_mode_element): Record source element number of the
stored string via a text property.
(Fformat_mode_line): Initialize 'mode_line_elt_no' to 0.
(syms_of_xdisp): New symbol for the text property.
| -rw-r--r-- | src/xdisp.c | 145 |
1 files changed, 106 insertions, 39 deletions
diff --git a/src/xdisp.c b/src/xdisp.c index 61c464d0f36..1340716e4a1 100644 --- a/src/xdisp.c +++ b/src/xdisp.c | |||
| @@ -13674,6 +13674,9 @@ static Lisp_Object mode_line_proptrans_alist; | |||
| 13674 | /* List of strings making up the mode-line. */ | 13674 | /* List of strings making up the mode-line. */ |
| 13675 | static Lisp_Object mode_line_string_list; | 13675 | static Lisp_Object mode_line_string_list; |
| 13676 | 13676 | ||
| 13677 | /* Element number for the stored mode line string. */ | ||
| 13678 | static ptrdiff_t mode_line_elt_no; | ||
| 13679 | |||
| 13677 | /* Base face property when building propertized mode line string. */ | 13680 | /* Base face property when building propertized mode line string. */ |
| 13678 | static Lisp_Object mode_line_string_face; | 13681 | static Lisp_Object mode_line_string_face; |
| 13679 | static Lisp_Object mode_line_string_face_prop; | 13682 | static Lisp_Object mode_line_string_face_prop; |
| @@ -27766,57 +27769,94 @@ display_mode_line (struct window *w, enum face_id face_id, Lisp_Object format) | |||
| 27766 | { | 27769 | { |
| 27767 | /* The window is wide enough; just display the mode line we | 27770 | /* The window is wide enough; just display the mode line we |
| 27768 | just computed. */ | 27771 | just computed. */ |
| 27769 | display_string (NULL, mode_string, Qnil, | 27772 | Lisp_Object start = make_fixnum (0), end; |
| 27770 | 0, 0, &it, 0, 0, 0, | 27773 | Lisp_Object elt; |
| 27771 | STRING_MULTIBYTE (mode_string)); | 27774 | |
| 27775 | /* Display the mode line string one element by one element. | ||
| 27776 | This is to make the ranges of mouse highlighting | ||
| 27777 | correct. */ | ||
| 27778 | do | ||
| 27779 | { | ||
| 27780 | end = Fnext_single_property_change (start, Qmode_line_elt_no, | ||
| 27781 | mode_string, Qnil); | ||
| 27782 | elt = Fsubstring (mode_string, start, end); | ||
| 27783 | display_string (NULL, elt, Qnil, 0, 0, &it, 0, 0, 0, | ||
| 27784 | STRING_MULTIBYTE (elt)); | ||
| 27785 | start = end; | ||
| 27786 | } | ||
| 27787 | while (!NILP (end)); | ||
| 27772 | } | 27788 | } |
| 27773 | else | 27789 | else |
| 27774 | { | 27790 | { |
| 27775 | /* Compress the mode line. */ | 27791 | /* Compress the mode line. */ |
| 27776 | ptrdiff_t i = 0, i_byte = 0, start = 0; | 27792 | ptrdiff_t i = 0, i_byte = 0; |
| 27777 | int prev = 0; | 27793 | int prev = 0; |
| 27794 | Lisp_Object start = make_fixnum (0), end; | ||
| 27795 | Lisp_Object elt = empty_unibyte_string; | ||
| 27778 | 27796 | ||
| 27779 | while (i < SCHARS (mode_string)) | 27797 | /* Display the mode line string one element by one element. |
| 27798 | This is to make the ranges of mouse highlighting | ||
| 27799 | correct. */ | ||
| 27800 | do | ||
| 27780 | { | 27801 | { |
| 27781 | int c = fetch_string_char_advance (mode_string, &i, &i_byte); | 27802 | end = Fnext_single_property_change (start, |
| 27782 | if (c == ' ' && prev == ' ') | 27803 | Qmode_line_elt_no, |
| 27804 | mode_string, | ||
| 27805 | make_fixnum (SCHARS (mode_string))); | ||
| 27806 | while (i < XFIXNUM (end)) | ||
| 27783 | { | 27807 | { |
| 27784 | Lisp_Object prev_pos = make_fixnum (i - 1); | 27808 | int c = fetch_string_char_advance (mode_string, &i, &i_byte); |
| 27785 | 27809 | if (c == ' ' && prev == ' ') | |
| 27786 | /* SPC characters with 'display' properties are not | ||
| 27787 | really "empty", since they have non-trivial visual | ||
| 27788 | effects on the mode line. */ | ||
| 27789 | if (NILP (Fget_text_property (prev_pos, Qdisplay, | ||
| 27790 | mode_string))) | ||
| 27791 | { | 27810 | { |
| 27792 | display_string (NULL, | 27811 | Lisp_Object prev_pos = make_fixnum (i - 1); |
| 27793 | Fsubstring (mode_string, | 27812 | Lisp_Object display = Fget_text_property (prev_pos, |
| 27794 | make_fixnum (start), | 27813 | Qdisplay, |
| 27795 | prev_pos), | 27814 | mode_string); |
| 27796 | Qnil, 0, 0, &it, 0, 0, 0, | 27815 | |
| 27797 | STRING_MULTIBYTE (mode_string)); | 27816 | /* SPC characters with 'display' properties are not |
| 27798 | /* Skip past the rest of the space characters. */ | 27817 | really "empty", since they have non-trivial visual |
| 27799 | while (c == ' ' && i < SCHARS (mode_string) | 27818 | effects on the mode line. */ |
| 27800 | && NILP (Fget_text_property (make_fixnum (i), | 27819 | if (NILP (display)) |
| 27801 | Qdisplay, | ||
| 27802 | mode_string))) | ||
| 27803 | { | 27820 | { |
| 27804 | c = fetch_string_char_advance (mode_string, | 27821 | elt = concat2 (elt, Fsubstring (mode_string, |
| 27805 | &i, &i_byte); | 27822 | start, |
| 27823 | prev_pos)); | ||
| 27824 | |||
| 27825 | /* Skip past the rest of the space characters. */ | ||
| 27826 | Lisp_Object display = Fget_text_property (make_fixnum (i), | ||
| 27827 | Qdisplay, | ||
| 27828 | mode_string); | ||
| 27829 | while (c == ' ' && i < XFIXNUM (end) | ||
| 27830 | && NILP (display)) | ||
| 27831 | { | ||
| 27832 | c = fetch_string_char_advance (mode_string, | ||
| 27833 | &i, &i_byte); | ||
| 27834 | display = Fget_text_property (make_fixnum (i), | ||
| 27835 | Qdisplay, | ||
| 27836 | mode_string); | ||
| 27837 | } | ||
| 27838 | |||
| 27839 | /* Skip the final space no matter how the loop | ||
| 27840 | above ends. */ | ||
| 27841 | if (c == ' ' && NILP (display)) | ||
| 27842 | start = end; | ||
| 27843 | else | ||
| 27844 | start = make_fixnum (i - 1); | ||
| 27806 | } | 27845 | } |
| 27807 | start = i - 1; | ||
| 27808 | } | 27846 | } |
| 27847 | prev = c; | ||
| 27809 | } | 27848 | } |
| 27810 | prev = c; | ||
| 27811 | } | ||
| 27812 | 27849 | ||
| 27813 | /* Display the final bit. */ | 27850 | /* Append the final bit. */ |
| 27814 | if (start < i) | 27851 | if (XFIXNUM (start) < XFIXNUM (end)) |
| 27815 | display_string (NULL, | 27852 | elt = concat2 (elt, Fsubstring (mode_string, start, end)); |
| 27816 | Fsubstring (mode_string, make_fixnum (start), | 27853 | |
| 27817 | make_fixnum (i)), | 27854 | display_string (NULL, elt, Qnil, 0, 0, &it, 0, 0, 0, |
| 27818 | Qnil, 0, 0, &it, 0, 0, 0, | 27855 | STRING_MULTIBYTE (elt)); |
| 27819 | STRING_MULTIBYTE (mode_string)); | 27856 | elt = empty_unibyte_string; |
| 27857 | start = end; | ||
| 27858 | } | ||
| 27859 | while (XFIXNUM (end) < SCHARS (mode_string)); | ||
| 27820 | } | 27860 | } |
| 27821 | } | 27861 | } |
| 27822 | pop_kboard (); | 27862 | pop_kboard (); |
| @@ -27938,6 +27978,20 @@ display_mode_element (struct it *it, int depth, int field_width, int precision, | |||
| 27938 | if (depth > 100) | 27978 | if (depth > 100) |
| 27939 | elt = build_string ("*too-deep*"); | 27979 | elt = build_string ("*too-deep*"); |
| 27940 | 27980 | ||
| 27981 | /* NOTE: Increment the number only for 'MODE_LINE_STRING', because for | ||
| 27982 | other cases this variable is not used. So we do not need to reset | ||
| 27983 | the variable in every callers of this function. | ||
| 27984 | |||
| 27985 | NOTE: This might result in gaps in the stored element numbers | ||
| 27986 | because some elements are processed recursively. However, we | ||
| 27987 | cannot increment this number when the number is stored because an | ||
| 27988 | element could be stored by parts. For example, "a%b" is stored as | ||
| 27989 | two elements in the 'mode_line_string_list', but they should be | ||
| 27990 | considered as one element, so we cannot increment this number when | ||
| 27991 | "a" is stored. */ | ||
| 27992 | if (mode_line_target == MODE_LINE_STRING) | ||
| 27993 | mode_line_elt_no++; | ||
| 27994 | |||
| 27941 | depth++; | 27995 | depth++; |
| 27942 | 27996 | ||
| 27943 | switch (XTYPE (elt)) | 27997 | switch (XTYPE (elt)) |
| @@ -28035,7 +28089,11 @@ display_mode_element (struct it *it, int depth, int field_width, int precision, | |||
| 28035 | n += store_mode_line_noprop (SSDATA (elt), -1, prec); | 28089 | n += store_mode_line_noprop (SSDATA (elt), -1, prec); |
| 28036 | break; | 28090 | break; |
| 28037 | case MODE_LINE_STRING: | 28091 | case MODE_LINE_STRING: |
| 28038 | n += store_mode_line_string (NULL, elt, true, 0, prec, Qnil); | 28092 | { |
| 28093 | AUTO_LIST2 (src, Qmode_line_elt_no, | ||
| 28094 | make_fixnum (mode_line_elt_no)); | ||
| 28095 | n += store_mode_line_string (NULL, elt, true, 0, prec, src); | ||
| 28096 | } | ||
| 28039 | break; | 28097 | break; |
| 28040 | case MODE_LINE_DISPLAY: | 28098 | case MODE_LINE_DISPLAY: |
| 28041 | n += display_string (NULL, elt, Qnil, 0, 0, it, | 28099 | n += display_string (NULL, elt, Qnil, 0, 0, it, |
| @@ -28088,8 +28146,10 @@ display_mode_element (struct it *it, int depth, int field_width, int precision, | |||
| 28088 | Lisp_Object mode_string | 28146 | Lisp_Object mode_string |
| 28089 | = Fsubstring (elt, make_fixnum (charpos), | 28147 | = Fsubstring (elt, make_fixnum (charpos), |
| 28090 | make_fixnum (endpos)); | 28148 | make_fixnum (endpos)); |
| 28149 | AUTO_LIST2 (src, Qmode_line_elt_no, | ||
| 28150 | make_fixnum (mode_line_elt_no)); | ||
| 28091 | n += store_mode_line_string (NULL, mode_string, false, | 28151 | n += store_mode_line_string (NULL, mode_string, false, |
| 28092 | 0, 0, Qnil); | 28152 | 0, 0, src); |
| 28093 | } | 28153 | } |
| 28094 | break; | 28154 | break; |
| 28095 | case MODE_LINE_DISPLAY: | 28155 | case MODE_LINE_DISPLAY: |
| @@ -28169,6 +28229,8 @@ display_mode_element (struct it *it, int depth, int field_width, int precision, | |||
| 28169 | { | 28229 | { |
| 28170 | Lisp_Object tem = build_string (spec); | 28230 | Lisp_Object tem = build_string (spec); |
| 28171 | props = Ftext_properties_at (make_fixnum (charpos), elt); | 28231 | props = Ftext_properties_at (make_fixnum (charpos), elt); |
| 28232 | props = plist_put (props, Qmode_line_elt_no, | ||
| 28233 | make_fixnum (mode_line_elt_no)); | ||
| 28172 | /* Should only keep face property in props */ | 28234 | /* Should only keep face property in props */ |
| 28173 | n += store_mode_line_string (NULL, tem, false, | 28235 | n += store_mode_line_string (NULL, tem, false, |
| 28174 | field, prec, props); | 28236 | field, prec, props); |
| @@ -28384,6 +28446,9 @@ display_mode_element (struct it *it, int depth, int field_width, int precision, | |||
| 28384 | n += store_mode_line_noprop ("", field_width - n, 0); | 28446 | n += store_mode_line_noprop ("", field_width - n, 0); |
| 28385 | break; | 28447 | break; |
| 28386 | case MODE_LINE_STRING: | 28448 | case MODE_LINE_STRING: |
| 28449 | /* NOTE: Padding implicitly has 'mode-line-elt-no' property | ||
| 28450 | to be nil. Normally padding spaces are between two real | ||
| 28451 | elements, so this should be good. */ | ||
| 28387 | n += store_mode_line_string ("", Qnil, false, field_width - n, 0, | 28452 | n += store_mode_line_string ("", Qnil, false, field_width - n, 0, |
| 28388 | Qnil); | 28453 | Qnil); |
| 28389 | break; | 28454 | break; |
| @@ -28591,6 +28656,7 @@ are the selected window and the WINDOW's buffer). */) | |||
| 28591 | } | 28656 | } |
| 28592 | 28657 | ||
| 28593 | push_kboard (FRAME_KBOARD (it.f)); | 28658 | push_kboard (FRAME_KBOARD (it.f)); |
| 28659 | mode_line_elt_no = 0; | ||
| 28594 | display_mode_element (&it, 0, 0, 0, format, Qnil, false); | 28660 | display_mode_element (&it, 0, 0, 0, format, Qnil, false); |
| 28595 | pop_kboard (); | 28661 | pop_kboard (); |
| 28596 | 28662 | ||
| @@ -37678,6 +37744,7 @@ be let-bound around code that needs to disable messages temporarily. */); | |||
| 37678 | DEFSYM (Qfontification_functions, "fontification-functions"); | 37744 | DEFSYM (Qfontification_functions, "fontification-functions"); |
| 37679 | DEFSYM (Qlong_line_optimizations_in_fontification_functions, | 37745 | DEFSYM (Qlong_line_optimizations_in_fontification_functions, |
| 37680 | "long-line-optimizations-in-fontification-functions"); | 37746 | "long-line-optimizations-in-fontification-functions"); |
| 37747 | DEFSYM (Qmode_line_elt_no, "mode-line-elt-no"); | ||
| 37681 | 37748 | ||
| 37682 | /* Name of the symbol which disables Lisp evaluation in 'display' | 37749 | /* Name of the symbol which disables Lisp evaluation in 'display' |
| 37683 | properties. This is used by enriched.el. */ | 37750 | properties. This is used by enriched.el. */ |