From a13b437c81f1f2e54555e7281480ea7e8eee8753 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Wed, 24 Nov 2021 11:55:53 +0100 Subject: [PATCH] Add support for the min-width display property * doc/lispref/display.texi (Display Property): Document get-display-property. (Other Display Specs): Document min-width property. * src/dispextern.h (struct it): Add fields for min-width handling. * src/xdisp.c (find_display_property, get_display_property): New helper functions. (display_min_width): Insert stretch glyphs based on the min width. (Fget_display_property): New defun. (handle_display_prop): Handle min-width ends. (handle_single_display_spec): Handle min-width starts. --- doc/lispref/display.texi | 42 +++++++++- etc/NEWS | 7 ++ src/dispextern.h | 6 ++ src/xdisp.c | 172 ++++++++++++++++++++++++++++++++++++++- test/src/xdisp-tests.el | 16 ++++ 5 files changed, 237 insertions(+), 6 deletions(-) diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 6b1c52b4859..dc53eeff9bf 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -4874,9 +4874,7 @@ window on a minibuffer-less frame. The @code{display} text property (or overlay property) is used to insert images into text, and to control other aspects of how text -displays. The value of the @code{display} property should be a -display specification, or a list or vector containing several display -specifications. Display specifications in the same @code{display} +displays. Display specifications in the same @code{display} property value generally apply in parallel to the text they cover. If several sources (overlays and/or a text property) specify values @@ -4884,6 +4882,28 @@ for the @code{display} property, only one of the values takes effect, following the rules of @code{get-char-property}. @xref{Examining Properties}. + The value of the @code{display} property should be a display +specification, or a list or vector containing several display +specifications. + +@defun get-display-property position prop &optional object properties +This convenience function can be used to get a specific display +property, no matter whether the @code{display} property is a vector, a +list or a simple property. This is like @code{get-text-property} +(@pxref{Examining Properties}), but works on the @code{display} +property only. + +@var{position} is the position in the buffer or string to examine, and +@var{prop} is the @code{display} property to return. The optional +@var{object} argument should be either a string or a buffer, and +defaults to the current buffer. If the optional @var{properties} +argument is non-@code{nil}, it should be a @code{display} property, +and in that case, @var{position} and @var{object} are ignored. (This +can be useful if you've already gotten the @code{display} property +with @code{get-char-property}, for instance (@pxref{Examining +Properties}). +@end defun + @cindex display property, unsafe evaluation @cindex security, and display specifications Some of the display specifications allow inclusion of Lisp forms, @@ -5159,6 +5179,22 @@ text that has the specification. It displays all of these spaces be an integer or float. Characters other than spaces are not affected at all; in particular, this has no effect on tab characters. +@item (min-width (@var{width})) +This display specification adds padding to the end of the text if the +text is shorter than @var{width}. The text is partitioned using the +identity of the parameter, which is why the parameter is a list with +one element. For instance: + +@lisp +(insert (propertize "foo" '(display (min-width (6.0))))) +@end lisp + +This will add padding after @samp{foo} bringing the total width up to +the width of six normal characters. Note that the ``range'' is +identified by the @code{(6.0)} list, compared with @code{eq}. The +width can be either a character width or a pixel specification +(@pxref{Pixel Specification}). + @item (height @var{height}) This display specification makes the text taller or shorter. Here are the possibilities for @var{height}: diff --git a/etc/NEWS b/etc/NEWS index 0bf3d9368b6..1cd49c5289c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -668,6 +668,13 @@ Use 'exif-parse-file' and 'exif-field' instead. * Lisp Changes in Emacs 29.1 +** New function 'get-display-property'. +This is like 'get-text-property', but works on the 'display' text +property. + +** New 'min-width' 'display' property. +This allows setting a minimum width for a region. + ** Keymaps and key definitions +++ diff --git a/src/dispextern.h b/src/dispextern.h index a698f6546b1..088297157ac 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -2746,6 +2746,12 @@ struct it /* For iterating over bidirectional text. */ struct bidi_it bidi_it; bidi_dir_t paragraph_embedding; + + /* For handling the :min-width property. The object is the text + property we're testing the `eq' of (nil if none), and the integer + is the x position of the start of the run of glyphs. */ + Lisp_Object min_width_property; + int min_width_start; }; diff --git a/src/xdisp.c b/src/xdisp.c index 11ea8360343..4d3b4878058 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -822,6 +822,9 @@ bool help_echo_showing_p; /* Functions to mark elements as needing redisplay. */ enum { REDISPLAY_SOME = 2}; /* Arbitrary choice. */ +static bool calc_pixel_width_or_height (double *, struct it *, Lisp_Object, + struct font *, bool, int *); + void redisplay_other_windows (void) { @@ -5141,6 +5144,149 @@ setup_for_ellipsis (struct it *it, int len) it->ellipsis_p = true; } + +static Lisp_Object +find_display_property (Lisp_Object disp, Lisp_Object prop) +{ + if (NILP (disp)) + return Qnil; + /* We have a vector of display specs. */ + if (VECTORP (disp)) + { + for (ptrdiff_t i = 0; i < ASIZE (disp); i++) + { + Lisp_Object elem = AREF (disp, i); + if (CONSP (elem) + && CONSP (XCDR (elem)) + && EQ (XCAR (elem), prop)) + return XCAR (XCDR (elem)); + } + return Qnil; + } + /* We have a list of display specs. */ + else if (CONSP (disp) + && CONSP (XCAR (disp))) + { + while (!NILP (disp)) + { + Lisp_Object elem = XCAR (disp); + if (CONSP (elem) + && CONSP (XCDR (elem)) + && EQ (XCAR (elem), prop)) + return XCAR (XCDR (elem)); + + /* Check that we have a proper list before going to the next + element. */ + if (CONSP (XCDR (disp))) + disp = XCDR (disp); + else + disp = Qnil; + } + return Qnil; + } + /* A simple display spec. */ + else if (CONSP (disp) + && CONSP (XCDR (disp)) + && EQ (XCAR (disp), prop)) + return XCAR (XCDR (disp)); + else + return Qnil; +} + +static Lisp_Object get_display_property (ptrdiff_t bufpos, Lisp_Object prop, + Lisp_Object object) +{ + return find_display_property (Fget_text_property (make_fixnum (bufpos), + + Qdisplay, object), + prop); +} + +static void +display_min_width (struct it *it, ptrdiff_t bufpos, + Lisp_Object object, Lisp_Object width_spec) +{ + /* We're being called at the end of the `min-width' sequence, + probably. */ + if (!NILP (it->min_width_property) + && !EQ (width_spec, it->min_width_property)) + { + if (!it->glyph_row) + return; + + /* Check that we're really right after the sequence of + characters covered by this `min-width'. */ + if (bufpos > BEGV + && EQ (it->min_width_property, + get_display_property (bufpos - 1, Qmin_width, object))) + { + Lisp_Object w = Qnil; + double width; +#ifdef HAVE_WINDOW_SYSTEM + if (FRAME_WINDOW_P (it->f)) + { + struct font *font = NULL; + struct face *face = FACE_FROM_ID (it->f, it->face_id); + font = face->font ? face->font : FRAME_FONT (it->f); + calc_pixel_width_or_height (&width, it, + XCAR (it->min_width_property), + font, true, NULL); + width -= it->current_x - it->min_width_start; + w = list1 (make_int (width)); + } + else +#endif + { + calc_pixel_width_or_height (&width, it, + XCAR (it->min_width_property), + NULL, true, NULL); + width -= (it->current_x - it->min_width_start) / + FRAME_COLUMN_WIDTH (it->f); + w = make_int (width); + } + + /* Insert the stretch glyph. */ + it->object = list3 (Qspace, QCwidth, w); + produce_stretch_glyph (it); + it->min_width_property = Qnil; + } + } + + /* We're at the start of a `min-width' sequence -- record the + position and the property, so that we can later see if we're at + the end. */ + if (CONSP (width_spec)) + { + if (bufpos == BEGV + || (bufpos > BEGV + && !EQ (width_spec, + get_display_property (bufpos - 1, Qmin_width, object)))) + { + it->min_width_property = width_spec; + it->min_width_start = it->current_x; + } + } +} + +DEFUN ("get-display-property", Fget_display_property, + Sget_display_property, 2, 4, 0, + doc: /* Get the `display' property PROP at POSITION. +If OBJECT, this should be a buffer or string where the property is +fetched from. This defaults to the current buffer. + +If PROPERTIES, use those properties instead of the properties at +POSITION. */) + (Lisp_Object position, Lisp_Object prop, Lisp_Object object, + Lisp_Object properties) +{ + if (NILP (properties)) + properties = Fget_text_property (position, Qdisplay, object); + else + CHECK_LIST (properties); + + return find_display_property (properties, prop); +} + /*********************************************************************** @@ -5187,16 +5333,22 @@ handle_display_prop (struct it *it) if (!it->string_from_display_prop_p) it->area = TEXT_AREA; + if (!STRINGP (it->string)) + object = it->w->contents; + propval = get_char_property_and_overlay (make_fixnum (position->charpos), Qdisplay, object, &overlay); + + /* Handle min-width ends. */ + if (! NILP (it->min_width_property) + && NILP (find_display_property (propval, Qmin_width))) + display_min_width (it, bufpos, object, Qnil); + if (NILP (propval)) return HANDLED_NORMALLY; /* Now OVERLAY is the overlay that gave us this property, or nil if it was a text property. */ - if (!STRINGP (it->string)) - object = it->w->contents; - display_replaced = handle_display_spec (it, propval, object, overlay, position, bufpos, FRAME_WINDOW_P (it->f)); @@ -5250,6 +5402,7 @@ handle_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object, && !(CONSP (XCAR (spec)) && EQ (XCAR (XCAR (spec)), Qmargin)) && !EQ (XCAR (spec), Qleft_fringe) && !EQ (XCAR (spec), Qright_fringe) + && !EQ (XCAR (spec), Qmin_width) && !NILP (XCAR (spec))) { for (; CONSP (spec); spec = XCDR (spec)) @@ -5483,6 +5636,17 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object, return 0; } + /* Handle `(min-width (WIDTH))'. */ + if (CONSP (spec) + && EQ (XCAR (spec), Qmin_width) + && CONSP (XCDR (spec)) + && CONSP (XCAR (XCDR (spec)))) + { + if (it) + display_min_width (it, bufpos, object, XCAR (XCDR (spec))); + return 0; + } + /* Handle `(slice X Y WIDTH HEIGHT)'. */ if (CONSP (spec) && EQ (XCAR (spec), Qslice)) @@ -7186,6 +7350,7 @@ reseat_1 (struct it *it, struct text_pos pos, bool set_stop_p) } /* This make the information stored in it->cmp_it invalidate. */ it->cmp_it.id = -1; + it->min_width_property = Qnil; } @@ -35121,6 +35286,7 @@ be let-bound around code that needs to disable messages temporarily. */); defsubr (&Smove_point_visually); defsubr (&Sbidi_find_overridden_directionality); defsubr (&Sdisplay__line_is_continued_p); + defsubr (&Sget_display_property); DEFSYM (Qmenu_bar_update_hook, "menu-bar-update-hook"); DEFSYM (Qoverriding_terminal_local_map, "overriding-terminal-local-map"); diff --git a/test/src/xdisp-tests.el b/test/src/xdisp-tests.el index cc67aef8e15..ae4aacd9c7c 100644 --- a/test/src/xdisp-tests.el +++ b/test/src/xdisp-tests.el @@ -154,4 +154,20 @@ int main () { nil) 138)))) +(ert-deftest test-get-display-property () + (with-temp-buffer + (insert (propertize "foo" 'face 'bold 'display '(height 2.0))) + (should (equal (get-display-property 2 'height) 2.0))) + (with-temp-buffer + (insert (propertize "foo" 'face 'bold 'display '((height 2.0) + (space-width 2.0)))) + (should (equal (get-display-property 2 'height) 2.0)) + (should (equal (get-display-property 2 'space-width) 2.0))) + (with-temp-buffer + (insert (propertize "foo bar" 'face 'bold + 'display '[(height 2.0) + (space-width 20)])) + (should (equal (get-display-property 2 'height) 2.0)) + (should (equal (get-display-property 2 'space-width) 20)))) + ;;; xdisp-tests.el ends here -- 2.39.5