diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index cded3ecd8d0..763a8be7ce9 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index f8d3fc9960e..0e1268f4c9a 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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 diff --git a/lisp/faces.el b/lisp/faces.el index 97620822b88..94da4ba0290 100644 --- a/lisp/faces.el +++ b/lisp/faces.el @@ -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") diff --git a/src/dispextern.h b/src/dispextern.h index 16565b8ad18..d08bd7ee7a9 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -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 }; diff --git a/src/xdisp.c b/src/xdisp.c index 4c46d7711be..b1e9d834aae 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -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); diff --git a/src/xfaces.c b/src/xfaces.c index 6c4267b8afa..010b0e1847e 100644 --- a/src/xfaces.c +++ b/src/xfaces.c @@ -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. */