1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-05-31 01:32:00 -07:00

Introduce 'margin' face for window margin background

A new basic face 'margin' is used for text displayed in the left and
right margin areas, i.e., the areas typically used by VCS and LSP
packages for per-line annotations.  Its background defaults to the
frame default, preserving existing behavior for users who do not
customize it.
* etc/NEWS: Document the new 'margin' face.
* lisp/faces.el (margin): Add 'margin' face, inheriting from 'default'.
* src/dispextern.h (face_id): Add MARGIN_FACE_ID.
* src/xdisp.c (face_at_pos): Use 'margin' as the base face for
strings displayed in margin areas so that they inherit the gutter
background by default.
(extend_face_to_end_of_line): Compute 'margin_fill_face_id' from the
'margin' face.  Use while loops to explicitly fill all empty character
slots in both left and right margins for both GUI and TTY branches.
(display_line): Call 'extend_face_to_end_of_line' for beyond-EOB rows
when the window has margins.  Also extend the existing condition for
text rows with empty margins to trigger when the 'margin' face
background differs from the frame default, not only when the default
face is remapped.
* src/xfaces.c (realize_basic_faces): Realize 'margin' as a basic
face to support face-remapping and efficient lookup.
(Bug#80693)
This commit is contained in:
Andrea Alberti 2026-05-03 21:25:14 +02:00 committed by Eli Zaretskii
parent c878753d39
commit d24b10ca75
6 changed files with 184 additions and 51 deletions

View file

@ -3817,6 +3817,11 @@ Basic faces used for the corresponding decorations of GUI frames.
@item cursor
The basic face used for the text cursor.
@item margin
The basic face used for window margins, both on the left and on the
right. It is commonly used to customize the background of the empty
areas of the margins. It inherits from the @code{default} face.
@item mouse
The basic face used for displaying mouse-sensitive text when the mouse
pointer is on that text.
@ -5909,12 +5914,14 @@ that text, put on that text an overlay with a @code{before-string}
property, and put the margin display specification on the contents of
the before-string.
Note that if the string to be displayed in the margin doesn't
specify a face, its face is determined using the same rules and
priorities as it is for strings displayed in the text area
(@pxref{Displaying Faces}). If this results in undesirable
``leaking'' of faces into the margin, make sure the string has an
explicit face specified for it.
Note that if the string to be displayed in the margin doesn't fully
specify its face, the nonspecified attributes are inherited from the
@code{margin} face (@pxref{Basic Faces}). The face merging mechanism
ensures that the margin background remains consistent when margin
annotations specify only a foreground color. If you want a margin
string to have a specific appearance independent of the @code{margin}
face, make sure the string has a face specifying all required
attributes.
Before the display margins can display anything, you must give
them a nonzero width. The usual way to do that is to set these

View file

@ -24,6 +24,14 @@ applies, and please also update docstrings as needed.
* Installation Changes in Emacs 31.1
+++
** New face 'margin' for the window margin background.
A new basic face 'margin' is used by default for text displayed in the
left and right margin areas, i.e., the areas reserved by packages such as
git-gutter, lsp-mode, and hl-diff for per-line annotations. Its
background defaults to the frame default, so existing behavior is
unchanged for users who do not customize it.
+++
** Unexec dumper removed.
The traditional unexec dumper, deprecated since Emacs 27, has been

View file

@ -2913,6 +2913,13 @@ used to display the prompt text."
(setq minibuffer-prompt-properties
(append minibuffer-prompt-properties (list 'face 'minibuffer-prompt)))
(defface margin
'((t :inherit default))
"Basic face for window margins (both left and right).
This face is used to customize the appearance of the margin areas."
:version "31.1"
:group 'basic-faces)
(defface fringe
'((((class color) (background light))
:background "grey95")

View file

@ -1966,6 +1966,7 @@ enum face_id
TAB_BAR_FACE_ID,
TAB_LINE_ACTIVE_FACE_ID,
TAB_LINE_INACTIVE_FACE_ID,
MARGIN_FACE_ID,
BASIC_FACE_ID_SENTINEL
};

View file

@ -4874,6 +4874,17 @@ face_at_pos (const struct it *it, enum lface_attribute_index attr_filter)
: underlying_face_id (it);
}
/* For strings displayed in a margin area via a 'display' property,
use the realized 'margin' face as the base so that unspecified
attributes (notably background) are inherited from 'margin'
rather than from 'default' or the buffer face at point. This
allows packages to specify only a foreground color for a margin
annotation and have the margin background fill in automatically. */
if (it->string_from_display_prop_p
&& it->area != TEXT_AREA
&& it->w)
base_face_id = lookup_basic_face (it->w, it->f, MARGIN_FACE_ID);
return face_at_string_position (it->w,
it->string,
IT_STRING_CHARPOS (*it),
@ -24285,13 +24296,14 @@ append_space_for_newline (struct it *it, bool default_face_p)
return false;
}
/* Extend the face of the last glyph in the text area of IT->glyph_row
to the end of the display line. Called from display_line. If the
glyph row is empty, add a space glyph to it so that we know the
face to draw. Set the glyph row flag fill_line_p. If the glyph
row is R2L, prepend a stretch glyph to cover the empty space to the
left of the leftmost glyph. */
/* Extend the face of the last glyph in the text area of IT->glyph_row
to the end of the display line. Also fill the window margins with
the 'margin' face. If the text area is empty, a space glyph is
added to it to carry the face used for clearing the line. In the
margin areas, empty cells are explicitly filled with space glyphs
(TTY) or a single stretch glyph (GUI). Set the glyph row flag
fill_line_p. If the glyph row is R2L, prepend a stretch glyph to
cover the empty space to the left of the leftmost glyph. */
static void
extend_face_to_end_of_line (struct it *it)
@ -24340,6 +24352,18 @@ extend_face_to_end_of_line (struct it *it)
? it->saved_face_id
: extend_face_id));
/* Use the 'margin' face to fill empty cells in the left and right
margin areas. That face defaults to the frame default, so it is a
no-op unless the user customizes its background. This is the only
way to give the margin area below point-max (where no overlay can
place glyphs) a non-default background. The approach is analogous
to 'maybe_produce_line_number' for the line-number area. */
int margin_fill_face_id = lookup_basic_face (it->w, f, MARGIN_FACE_ID);
/* Skip if none of the conditions that require extending the face to the
end of line are met: text face extends to EOL, box/underline/etc are
drawn, fill-column indicator is shown, or the 'margin' face has a
non-default background in a window that has margins. */
if (FRAME_WINDOW_P (f)
&& MATRIX_ROW_DISPLAYS_TEXT_P (it->glyph_row)
&& face->box == FACE_NO_BOX
@ -24351,7 +24375,11 @@ extend_face_to_end_of_line (struct it *it)
&& !face->stipple
#endif
&& !it->glyph_row->reversed_p
&& !display_fill_column_indicator)
&& !display_fill_column_indicator
&& !(FACE_FROM_ID (f, margin_fill_face_id)->background
!= FRAME_BACKGROUND_PIXEL (f)
&& (WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0
|| WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0)))
return;
/* Set the glyph row flag indicating that the face of the last glyph
@ -24391,23 +24419,81 @@ extend_face_to_end_of_line (struct it *it)
#endif
))
{
/* The third condition is a safety bound preventing writes
past the end of the left-margin glyph array. In the
non-window-system branch the equivalent bound is expressed
via a pointer 'g' initialized to the first empty slot and
advanced by 'g++', so the limit arises naturally from the
iteration. Here we index by 'n = used' directly and
recompute the equivalent pointer bound inline. */
/* Left Margin GUI; GUI-specific optimization: use one stretch glyph
to fill the rest. */
if (WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0
&& it->glyph_row->used[LEFT_MARGIN_AREA] == 0)
&& (it->glyph_row->used[LEFT_MARGIN_AREA]
< WINDOW_LEFT_MARGIN_WIDTH (it->w)))
{
it->glyph_row->glyphs[LEFT_MARGIN_AREA][0] = space_glyph;
it->glyph_row->glyphs[LEFT_MARGIN_AREA][0].face_id =
default_face->id;
it->glyph_row->glyphs[LEFT_MARGIN_AREA][0].frame = f;
it->glyph_row->used[LEFT_MARGIN_AREA] = 1;
int used = it->glyph_row->used[LEFT_MARGIN_AREA];
int remaining_pixels = (WINDOW_LEFT_MARGIN_WIDTH (it->w)
* FRAME_COLUMN_WIDTH (f));
/* Subtract width of existing glyphs. */
struct glyph *g = it->glyph_row->glyphs[LEFT_MARGIN_AREA];
for (int i = 0; i < used; ++i)
remaining_pixels -= (g++)->pixel_width;
if (remaining_pixels > 0)
{
int saved_face_id = it->face_id;
enum glyph_row_area saved_area = it->area;
struct text_pos saved_pos = it->position;
it->face_id = margin_fill_face_id;
it->area = LEFT_MARGIN_AREA;
/* Set position to 0 for the filler glyph. */
clear_position (it);
append_stretch_glyph (it, Qnil, remaining_pixels,
it->ascent + it->descent, it->max_ascent);
it->position = saved_pos;
it->face_id = saved_face_id;
it->area = saved_area;
}
}
/* Right Margin GUI; GUI-specific optimization: use one stretch glyph
to fill the rest. */
if (WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0
&& it->glyph_row->used[RIGHT_MARGIN_AREA] == 0)
&& (it->glyph_row->used[RIGHT_MARGIN_AREA]
< WINDOW_RIGHT_MARGIN_WIDTH (it->w)))
{
it->glyph_row->glyphs[RIGHT_MARGIN_AREA][0] = space_glyph;
it->glyph_row->glyphs[RIGHT_MARGIN_AREA][0].face_id =
default_face->id;
it->glyph_row->glyphs[RIGHT_MARGIN_AREA][0].frame = f;
it->glyph_row->used[RIGHT_MARGIN_AREA] = 1;
int used = it->glyph_row->used[RIGHT_MARGIN_AREA];
int remaining_pixels = (WINDOW_RIGHT_MARGIN_WIDTH (it->w)
* FRAME_COLUMN_WIDTH (f));
/* Subtract width of existing glyphs. */
struct glyph *g = it->glyph_row->glyphs[RIGHT_MARGIN_AREA];
for (int i = 0; i < used; ++i)
remaining_pixels -= (g++)->pixel_width;
if (remaining_pixels > 0)
{
int saved_face_id = it->face_id;
enum glyph_row_area saved_area = it->area;
struct text_pos saved_pos = it->position;
it->face_id = margin_fill_face_id;
it->area = RIGHT_MARGIN_AREA;
/* Set position to 0 for the filler glyph. */
clear_position (it);
append_stretch_glyph (it, Qnil, remaining_pixels,
it->ascent + it->descent, it->max_ascent);
it->position = saved_pos;
it->face_id = saved_face_id;
it->area = saved_area;
}
}
struct font *font = (default_face->font
@ -24576,11 +24662,16 @@ extend_face_to_end_of_line (struct it *it)
it->c = it->char_to_display = ' ';
it->len = 1;
/* Fill the left margin if it is only partially covered by glyphs and
the margin face or the extended face differ from the frame default.
Mode-line rows are excluded because they have no margin area. */
if (WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0
&& (it->glyph_row->used[LEFT_MARGIN_AREA]
< WINDOW_LEFT_MARGIN_WIDTH (it->w))
&& !it->glyph_row->mode_line_p
&& face->background != FRAME_BACKGROUND_PIXEL (f))
&& (face->background != FRAME_BACKGROUND_PIXEL (f)
|| FACE_FROM_ID (f, margin_fill_face_id)->background
!= FRAME_BACKGROUND_PIXEL (f)))
{
struct glyph *g = it->glyph_row->glyphs[LEFT_MARGIN_AREA];
struct glyph *e = g + it->glyph_row->used[LEFT_MARGIN_AREA];
@ -24593,7 +24684,7 @@ extend_face_to_end_of_line (struct it *it)
it->wrap_prefix_width = it->current_x;
it->area = LEFT_MARGIN_AREA;
it->face_id = default_face->id;
it->face_id = margin_fill_face_id;
while (it->glyph_row->used[LEFT_MARGIN_AREA]
< WINDOW_LEFT_MARGIN_WIDTH (it->w)
&& g < it->glyph_row->glyphs[TEXT_AREA])
@ -24655,7 +24746,9 @@ extend_face_to_end_of_line (struct it *it)
&& (it->glyph_row->used[RIGHT_MARGIN_AREA]
< WINDOW_RIGHT_MARGIN_WIDTH (it->w))
&& !it->glyph_row->mode_line_p
&& face->background != FRAME_BACKGROUND_PIXEL (f))
&& (face->background != FRAME_BACKGROUND_PIXEL (f)
|| FACE_FROM_ID (f, margin_fill_face_id)->background
!= FRAME_BACKGROUND_PIXEL (f)))
{
struct glyph *g = it->glyph_row->glyphs[RIGHT_MARGIN_AREA];
struct glyph *e = g + it->glyph_row->used[RIGHT_MARGIN_AREA];
@ -24664,7 +24757,7 @@ extend_face_to_end_of_line (struct it *it)
it->current_x += g->pixel_width;
it->area = RIGHT_MARGIN_AREA;
it->face_id = default_face->id;
it->face_id = margin_fill_face_id;
while (it->glyph_row->used[RIGHT_MARGIN_AREA]
< WINDOW_RIGHT_MARGIN_WIDTH (it->w)
&& g < it->glyph_row->glyphs[LAST_AREA])
@ -25915,17 +26008,21 @@ display_line (struct it *it, int cursor_vpos)
it->font_height = Qnil;
it->voffset = 0;
row->ends_at_zv_p = true;
/* A row that displays right-to-left text must always have
its last face extended all the way to the end of line,
even if this row ends in ZV, because we still write to
the screen left to right. We also need to extend the
last face if the default face is remapped to some
different face, otherwise the functions that clear
portions of the screen will clear with the default face's
background color. */
/* A row that displays right-to-left text must always have its
last face extended all the way to the end of line, even if
this row ends in ZV, because we still write to the screen
left to right. We also need to extend the last face if the
default face is remapped to some different face, otherwise
the functions that clear portions of the screen will clear
with the default face's background color. We also call
extend_face_to_end_of_line when the window has a left or
right margin so that the empty areas of the margins are
filled with the 'margin' face background. */
if (row->reversed_p
|| lookup_basic_face (it->w, it->f, DEFAULT_FACE_ID)
!= DEFAULT_FACE_ID)
!= DEFAULT_FACE_ID
|| WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0
|| WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0)
extend_face_to_end_of_line (it);
break;
}
@ -26502,18 +26599,6 @@ display_line (struct it *it, int cursor_vpos)
}
it->hpos = hpos_before;
}
/* If the default face is remapped, and the window has
display margins, and no glyphs were written yet to the
margins on this screen line, we must add one space
glyph to the margin area to make sure the margins use
the background of the remapped default face. */
if (lookup_basic_face (it->w, it->f, DEFAULT_FACE_ID)
!= DEFAULT_FACE_ID /* default face is remapped */
&& ((WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0
&& it->glyph_row->used[LEFT_MARGIN_AREA] == 0)
|| (WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0
&& it->glyph_row->used[RIGHT_MARGIN_AREA] == 0)))
extend_face_to_end_of_line (it);
}
else if (IT_OVERFLOW_NEWLINE_INTO_FRINGE (it))
{
@ -26536,6 +26621,29 @@ display_line (struct it *it, int cursor_vpos)
it->hpos = hpos_before;
}
/* If the default face is remapped or the 'margin' face has a
non-default background, and the window has display margins,
and no glyphs were written yet to the margins on this screen
line, fill the margin area so that the margins use the
correct background. Placed here, after the if/else-if chain
above, so it fires for all three truncation paths: TTY/no-fringe
truncation glyph, GUI newline-overflow-into-fringe, and GUI
regular truncation where the indicator is drawn as a fringe
bitmap. */
{
int margin_face_id =
lookup_basic_face (it->w, it->f, MARGIN_FACE_ID);
if ((lookup_basic_face (it->w, it->f, DEFAULT_FACE_ID)
!= DEFAULT_FACE_ID
|| FACE_FROM_ID (it->f, margin_face_id)->background
!= FRAME_BACKGROUND_PIXEL (it->f))
&& ((WINDOW_LEFT_MARGIN_WIDTH (it->w) > 0
&& it->glyph_row->used[LEFT_MARGIN_AREA] == 0)
|| (WINDOW_RIGHT_MARGIN_WIDTH (it->w) > 0
&& it->glyph_row->used[RIGHT_MARGIN_AREA] == 0)))
extend_face_to_end_of_line (it);
}
row->truncated_on_right_p = true;
it->continuation_lines_width = 0;
reseat_at_next_visible_line_start (it, false);

View file

@ -5211,6 +5211,7 @@ lookup_basic_face (struct window *w, struct frame *f, int face_id)
case WINDOW_DIVIDER_LAST_PIXEL_FACE_ID: name = Qwindow_divider_last_pixel; break;
case INTERNAL_BORDER_FACE_ID: name = Qinternal_border; break;
case CHILD_FRAME_BORDER_FACE_ID: name = Qchild_frame_border; break;
case MARGIN_FACE_ID: name = Qmargin; break;
default:
emacs_abort (); /* the caller is supposed to pass us a basic face id */
@ -5978,6 +5979,7 @@ realize_basic_faces (struct frame *f)
realize_named_face (f, Qtab_bar, TAB_BAR_FACE_ID);
realize_named_face (f, Qtab_line_active, TAB_LINE_ACTIVE_FACE_ID);
realize_named_face (f, Qtab_line_inactive, TAB_LINE_INACTIVE_FACE_ID);
realize_named_face (f, Qmargin, MARGIN_FACE_ID);
unbind_to (count, Qnil);
/* Reflect changes in the `menu' face in menu bars. */