* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
@ 2024-06-17 2:56 Jim Porter
2024-06-17 11:37 ` Eli Zaretskii
2024-06-17 14:23 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
0 siblings, 2 replies; 27+ messages in thread
From: Jim Porter @ 2024-06-17 2:56 UTC (permalink / raw)
To: 71605
[-- Attachment #1: Type: text/plain, Size: 1095 bytes --]
(Note: I plan to merge this only after we cut the Emacs 30 release
branch, since it seems a bit too substantial a change to sneak in right
near the end. However, I think the patch is mostly done aside from one
remaining issue, so any feedback is very welcome.)
'visual-wrap-prefix-mode' has one small issue: since the wrap prefix is
just a string, the wrapped text may not line up for variable-width
fonts. This is mainly in cases like so:
* here is some text that
got visually wrapped
If the "* " is variable-width, the second line will probably be indented
wrong by a few pixels.
The attached patch adds a display spec in this case so that the text
lines up perfectly. There's currently one problem though: I'm not sure
how to regenerate the wrap prefix automatically if the face changes.
It's not hard to handle for 'text-scale-adjust', but I don't know how to
handle 'global-text-scale-adjust' (or other things that could change the
face[1]).
Does anyone have any ideas for this part?
[1] There's 'after-setting-font-hook', but that doesn't cover everything
either.
[-- Attachment #2: 0001-Add-support-for-variable-width-text-in-visual-wrap-p.patch --]
[-- Type: text/plain, Size: 9101 bytes --]
From 696a271601457f63dd7127261242e21432713402 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 16 Jun 2024 15:21:52 -0700
Subject: [PATCH] Add support for variable-width text in
'visual-wrap-prefix-mode'
This uses a display spec to set the width correctly when indenting with
spaces.
* lisp/emacs-lisp/subr-x.el (string-pixel-width): New argument BUFFER.
* lisp/visual-wrap.el (visual-wrap--adjust-display-width)
(visual-wrap--content-prefix): New functions.
(visual-wrap--extra-indent): Rename from 'visual-wrap--prefix' and call
'visual-wrap--adjust-display-width'.
(visual-wrap-fill-context-prefix): Support display width.
(visual-wrap-prefix-function): Allow 'lbp' to be at 'point-min'.
(visual-wrap-prefix-mode): Refontify when changing text scale.
* doc/lispref/display.texi (Size of Displayed Text): Document BUFFER
argument for 'string-pixel-width'.
* etc/NEWS: Announce this change.
---
doc/lispref/display.texi | 6 ++--
etc/NEWS | 8 ++++-
lisp/emacs-lisp/subr-x.el | 11 ++++--
lisp/visual-wrap.el | 73 +++++++++++++++++++++++++++------------
4 files changed, 70 insertions(+), 28 deletions(-)
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index d5c96d13e02..52957f2ad07 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -2351,9 +2351,11 @@ Size of Displayed Text
meaning as with @code{window-text-pixel-size}.
@end defun
-@defun string-pixel-width string
+@defun string-pixel-width string &optional buffer
This is a convenience function that uses @code{window-text-pixel-size}
-to compute the width of @var{string} (in pixels).
+to compute the width of @var{string} (in pixels). If @var{buffer} is
+non-@code{nil}, use the face remappings from that buffer when
+determining the width (@pxref{Face Remapping}).
@end defun
@defun line-pixel-height
diff --git a/etc/NEWS b/etc/NEWS
index b2fdbc4a88f..27a4fd11a87 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -549,7 +549,8 @@ text in any way. The global minor mode
buffers.
(This minor mode is the 'adaptive-wrap' ELPA package renamed and
-lightly edited for inclusion in Emacs.)
+enhanced for inclusion in Emacs. It additionally supports prefixes for
+variable-width text.)
+++
** New user option 'gud-highlight-current-line'.
@@ -2789,6 +2790,11 @@ These functions are like 'user-uid' and 'group-gid', respectively, but
are aware of file name handlers, so they will return the remote UID or
GID for remote files (or -1 if the connection has no associated user).
++++
+** 'string-pixel-width' now accepts a BUFFER argument.
+If BUFFER is non-nil, 'string-pixel-width' will apply BUFFER's face
+remappings when computing the string's width.
+
+++
** 'fset', 'defalias' and 'defvaralias' now signal an error for cyclic aliases.
Previously, 'fset', 'defalias' and 'defvaralias' could be made to
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index 699be767ee7..2cbe1beb9f1 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -333,8 +333,10 @@ named-let
. ,aargs)))
;;;###autoload
-(defun string-pixel-width (string)
- "Return the width of STRING in pixels."
+(defun string-pixel-width (string &optional buffer)
+ "Return the width of STRING in pixels.
+If BUFFER is non-nil, use the face remappings from that buffer when
+determining the width."
(declare (important-return-value t))
(if (zerop (length string))
0
@@ -348,6 +350,11 @@ string-pixel-width
;; Disable line-prefix and wrap-prefix, for the same reason.
(setq line-prefix nil
wrap-prefix nil)
+ (if buffer
+ (setq-local face-remapping-alist
+ (with-current-buffer buffer
+ face-remapping-alist))
+ (kill-local-variable 'face-remapping-alist))
(insert (propertize string 'line-prefix nil 'wrap-prefix nil))
(car (buffer-text-pixel-size nil nil t)))))
diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el
index d95cf4bb569..241cd337148 100644
--- a/lisp/visual-wrap.el
+++ b/lisp/visual-wrap.el
@@ -97,38 +97,60 @@ visual-wrap--prefix-face
(if (visual-wrap--face-extend-p f) f))
eol-face)))))))
-(defun visual-wrap--prefix (fcp)
+(defun visual-wrap--adjust-display-width (fcp n)
+ (when-let ((display (get-text-property 0 'display fcp))
+ ((eq (car-safe display) 'space))
+ (width (car (plist-get (cdr display) :width))))
+ (put-text-property 0 (length fcp) 'display
+ `(space :width (,(+ width n))) fcp))
+ fcp)
+
+(defun visual-wrap--extra-indent (fcp)
(let ((fcp-len (string-width fcp)))
(cond
((= 0 visual-wrap-extra-indent)
fcp)
((< 0 visual-wrap-extra-indent)
- (concat fcp (make-string visual-wrap-extra-indent ?\s)))
+ (let* ((extra (make-string visual-wrap-extra-indent ?\s))
+ (result (concat fcp extra)))
+ (visual-wrap--adjust-display-width
+ result (string-pixel-width extra (current-buffer)))))
((< 0 (+ visual-wrap-extra-indent fcp-len))
- (substring fcp
- 0
- (+ visual-wrap-extra-indent fcp-len)))
+ (let* ((idx (+ visual-wrap-extra-indent fcp-len))
+ (trim (substring fcp idx))
+ (result (substring fcp 0 idx)))
+ (remove-text-properties 0 (length trim) '(display) trim)
+ (visual-wrap--adjust-display-width
+ result (- (string-pixel-width trim (current-buffer))))))
(t
""))))
+(defun visual-wrap--content-prefix (position)
+ "Get the content prefix for the line starting at POSITION.
+This is like `fill-content-prefix' but doesn't check subsequent lines
+and uses display specs to handle variable-width faces."
+ (save-excursion
+ (goto-char position)
+ (if (eolp) (forward-line 1))
+ ;; Move to the second line unless there is just one.
+ (move-to-left-margin)
+ (let ((prefix (fill-match-adaptive-prefix)))
+ (if (or (and adaptive-fill-first-line-regexp
+ (string-match adaptive-fill-first-line-regexp prefix))
+ (and comment-start-skip
+ (string-match comment-start-skip prefix)))
+ prefix
+ (propertize
+ (make-string (string-width prefix) ?\s)
+ 'display `(space :width (,(string-pixel-width
+ prefix (current-buffer)))))))))
+
(defun visual-wrap-fill-context-prefix (beg end)
"Compute visual wrap prefix from text between BEG and END.
-This is like `fill-context-prefix', but with prefix length adjusted
-by `visual-wrap-extra-indent'."
- (let* ((fcp
- ;; `fill-context-prefix' ignores prefixes that look like
- ;; paragraph starts, in order to avoid inadvertently
- ;; creating a new paragraph while filling, but here we're
- ;; only dealing with single-line "paragraphs" and we don't
- ;; actually modify the buffer, so this restriction doesn't
- ;; make much sense (and is positively harmful in
- ;; taskpaper-mode where paragraph-start matches everything).
- (or (let ((paragraph-start regexp-unmatchable))
- (fill-context-prefix beg end))
- ;; Note: fill-context-prefix may return nil; See:
- ;; http://article.gmane.org/gmane.emacs.devel/156285
- ""))
- (prefix (visual-wrap--prefix fcp))
+This is like `fill-context-prefix', but supporting variable-width faces
+and with the prefix length adjusted by `visual-wrap-extra-indent'."
+ (let* ((fcp (visual-wrap--content-prefix beg))
+ (prefix (visual-wrap--extra-indent fcp))
(face (visual-wrap--prefix-face fcp beg end)))
(if face
(propertize prefix 'face face)
@@ -160,7 +182,8 @@ visual-wrap-prefix-function
(remove-text-properties
0 (length pfx) '(wrap-prefix) pfx)
(let ((dp (get-text-property 0 'display pfx)))
- (when (and dp (eq dp (get-text-property (1- lbp) 'display)))
+ (when (and dp (> lbp (point-min))
+ (eq dp (get-text-property (1- lbp) 'display)))
;; There's a `display' property which covers not just the
;; prefix but also the previous newline. So it's not
;; just making the prefix more pretty and could interfere
@@ -187,8 +210,12 @@ visual-wrap-prefix-mode
;; of the hook (bug#15155).
(add-hook 'jit-lock-functions
#'visual-wrap-prefix-function 'append t)
- (jit-lock-register #'visual-wrap-prefix-function))
+ (jit-lock-register #'visual-wrap-prefix-function)
+ ;; FIXME: What should we do about `global-text-scale-adjust' or
+ ;; other things that can change the text size?
+ (add-hook 'text-scale-mode-hook #'jit-lock-refontify nil t))
(jit-lock-unregister #'visual-wrap-prefix-function)
+ (remove-hook 'text-scale-mode-hook #'jit-lock-refontify)
(with-silent-modifications
(save-restriction
(widen)
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 2:56 bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode' Jim Porter
@ 2024-06-17 11:37 ` Eli Zaretskii
2024-06-17 17:42 ` Jim Porter
2024-06-17 14:23 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
1 sibling, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-17 11:37 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Sun, 16 Jun 2024 19:56:44 -0700
> From: Jim Porter <jporterbugs@gmail.com>
>
> (Note: I plan to merge this only after we cut the Emacs 30 release
> branch, since it seems a bit too substantial a change to sneak in right
> near the end. However, I think the patch is mostly done aside from one
> remaining issue, so any feedback is very welcome.)
OK.
> 'visual-wrap-prefix-mode' has one small issue: since the wrap prefix is
> just a string, the wrapped text may not line up for variable-width
> fonts. This is mainly in cases like so:
>
> * here is some text that
> got visually wrapped
>
> If the "* " is variable-width, the second line will probably be indented
> wrong by a few pixels.
It also means the line after "*", the one that begins with "here is",
will also move horizontally. Isn't that a misfeature? Perhaps this
mode should align the text to some fixed pixel-coordinate, in which
case changes in font should not matter? Or am I missing something?
> The attached patch adds a display spec in this case so that the text
> lines up perfectly.
Can you explain the idea of the patch? I don't think I understand why
you use '(space :width)' rather than '(space :align-to)'.
> There's currently one problem though: I'm not sure
> how to regenerate the wrap prefix automatically if the face changes.
> It's not hard to handle for 'text-scale-adjust', but I don't know how to
> handle 'global-text-scale-adjust' (or other things that could change the
> face[1]).
>
> Does anyone have any ideas for this part?
Perhaps we could provide a function "face-change (&optional frame)"
which would access the frame's face_change flag and the global
face_change flag. Then you could test those in a post-command-hook or
somesuch. (However, using :align-to, if feasible, sounds like a
better solution to me.)
> -@defun string-pixel-width string
> +@defun string-pixel-width string &optional buffer
> This is a convenience function that uses @code{window-text-pixel-size}
> -to compute the width of @var{string} (in pixels).
> +to compute the width of @var{string} (in pixels). If @var{buffer} is
> +non-@code{nil}, use the face remappings from that buffer when
> +determining the width (@pxref{Face Remapping}).
An alternative would be to provide a face to use.
In any case, using BUFFER only for face-remapping-alist is only a
small part of what a buffer can do to a string: there's the major mode
with its fontifications and whatnot.
> +(defun string-pixel-width (string &optional buffer)
> + "Return the width of STRING in pixels.
> +If BUFFER is non-nil, use the face remappings from that buffer when
> +determining the width."
> (declare (important-return-value t))
> (if (zerop (length string))
> 0
> @@ -348,6 +350,11 @@ string-pixel-width
> ;; Disable line-prefix and wrap-prefix, for the same reason.
> (setq line-prefix nil
> wrap-prefix nil)
> + (if buffer
^^^^^^^^^
This should test buffer-live-p, I think, not just buffer non-nil.
> +(defun visual-wrap--adjust-display-width (fcp n)
> + (when-let ((display (get-text-property 0 'display fcp))
> + ((eq (car-safe display) 'space))
Doesn't this only work with very simple 'display' specs? The 'space'
part could be in some place deep in the spec, not just the second
symbol.
> (defun visual-wrap-fill-context-prefix (beg end)
> "Compute visual wrap prefix from text between BEG and END.
> -This is like `fill-context-prefix', but with prefix length adjusted
> -by `visual-wrap-extra-indent'."
> - (let* ((fcp
> - ;; `fill-context-prefix' ignores prefixes that look like
> - ;; paragraph starts, in order to avoid inadvertently
> - ;; creating a new paragraph while filling, but here we're
> - ;; only dealing with single-line "paragraphs" and we don't
> - ;; actually modify the buffer, so this restriction doesn't
> - ;; make much sense (and is positively harmful in
> - ;; taskpaper-mode where paragraph-start matches everything).
> - (or (let ((paragraph-start regexp-unmatchable))
> - (fill-context-prefix beg end))
> - ;; Note: fill-context-prefix may return nil; See:
> - ;; http://article.gmane.org/gmane.emacs.devel/156285
> - ""))
The comment above and the URL it included are deleted: is that because
they are no longer relevant? If not, maybe move them with the code,
so that the information is not lost.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 2:56 bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode' Jim Porter
2024-06-17 11:37 ` Eli Zaretskii
@ 2024-06-17 14:23 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-17 16:13 ` Jim Porter
1 sibling, 1 reply; 27+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-06-17 14:23 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
Jim Porter <jporterbugs@gmail.com> writes:
> (Note: I plan to merge this only after we cut the Emacs 30 release
> branch, since it seems a bit too substantial a change to sneak in
> right near the end. However, I think the patch is mostly done aside
> from one remaining issue, so any feedback is very welcome.)
>
> 'visual-wrap-prefix-mode' has one small issue: since the wrap prefix
> is just a string, the wrapped text may not line up for variable-width
> fonts. This is mainly in cases like so:
>
> * here is some text that
> got visually wrapped
>
> If the "* " is variable-width, the second line will probably be
> indented wrong by a few pixels.
>
> The attached patch adds a display spec in this case so that the text
> lines up perfectly. There's currently one problem though: I'm not sure
> how to regenerate the wrap prefix automatically if the face
> changes. It's not hard to handle for 'text-scale-adjust', but I don't
Actually, there's more than just this one problem. To start with, the
existing format of the generated line prefix properties enables the
extracted fill prefix to be displayed consistently on any frame,
whatever the metrics of its default font/face. This I consider a far
more critical capability than perfect alignment of wrapped text in the
presence of a variable-pitch default font, as the latter is virtually
unknown among programmers, and also unimplementable within reasonable
standards of performance with pixelwise spacers. If this is to be
installed, please condition it behind a user option and restore the
existing logic that you have effaced as the default.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 14:23 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-06-17 16:13 ` Jim Porter
2024-06-17 18:17 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-17 16:13 UTC (permalink / raw)
To: Po Lu; +Cc: 71605
On 6/17/2024 7:23 AM, Po Lu via Bug reports for GNU Emacs, the Swiss
army knife of text editors wrote:
> Actually, there's more than just this one problem. To start with, the
> existing format of the generated line prefix properties enables the
> extracted fill prefix to be displayed consistently on any frame,
> whatever the metrics of its default font/face. This I consider a far
> more critical capability than perfect alignment of wrapped text in the
> presence of a variable-pitch default font, as the latter is virtually
> unknown among programmers, and also unimplementable within reasonable
> standards of performance with pixelwise spacers.
That's a good point. In practice, we should only do this if the
first-line prefix uses a variable-pitch font, since otherwise we might
as well just use the requisite number of space characters as before,
sans display-spec.
> If this is to be
> installed, please condition it behind a user option and restore the
> existing logic that you have effaced as the default.
I'll add a check for variable-pitch fonts and a user option. With the
variable-pitch check, I think it should be ok for the user option to
default to enabling this new behavior? For fixed-pitch fonts, the new
code would behave the same as before regardless of the option.
Note that I haven't removed any of the old logic though; it's all there
as before, since the space characters are useful for terminals (where
the pixelwise :width display-spec doesn't do anything for us, if I
understand correctly). I did extract parts of 'fill-content-prefix' into
'visual-wrap--content-prefix', but the only parts I removed were the
ones for handling multiple lines; those don't apply here, since
'visual-wrap-prefix-function' gets the prefixes one line at a time.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 11:37 ` Eli Zaretskii
@ 2024-06-17 17:42 ` Jim Porter
2024-06-17 18:20 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-17 17:42 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
[-- Attachment #1: Type: text/plain, Size: 6428 bytes --]
On 6/17/2024 4:37 AM, Eli Zaretskii wrote:
>> Date: Sun, 16 Jun 2024 19:56:44 -0700
>> From: Jim Porter <jporterbugs@gmail.com>
>> The attached patch adds a display spec in this case so that the text
>> lines up perfectly.
>
> Can you explain the idea of the patch? I don't think I understand why
> you use '(space :width)' rather than '(space :align-to)'.
I tried using :align-to, and it doesn't seem to take effect for the
'wrap-prefix' text property. I haven't looked closely at why that
doesn't work, but even if it did, I think it might make things more
complex than they already are.
I'll try to describe the current process:
1. 'visual-wrap-prefix-mode' goes a line at a time, finding the
first-line prefix (for a bulleted item, this is something like "* ").
2. Then it constructs the wrap-prefix string (for a bulleted item,
something like " "; for other items it might be the same as the
first-line prefix).
3. Finally, it applies the the wrap-prefix to the entire line it's
examining.
The problem comes up for variable-pitch fonts, where "* " and " " have
different pixel widths. Before my patch, this results in the second line
not lining up correctly. See the attached image for an example.
My patch just sets a display-spec on the " " to make it have the same
pixel-width as "* ". Then it all lines up.
If I understand your :align-to suggestion, setting :align-to on
everything after the "* " bullet could work in theory, but I don't know
what value you could set there to make everything correct. If it's a
fixed number of pixels, then scaling up the text could mean the "* "
becomes too wide for the space we reserved for it, and then things would
probably look wrong. If it's based on the canonical character width,
that might work so long as that updates when needed, but it might still
look off depending on how the canonical width and the pixel width
compare. (Ideally, we'd align to the exact pixel-width of "* " or
whatever the first-line prefix is.) I couldn't get :align-to to work in
the first place though so this is all hypothetical...
>> There's currently one problem though: I'm not sure
>> how to regenerate the wrap prefix automatically if the face changes.
>> It's not hard to handle for 'text-scale-adjust', but I don't know how to
>> handle 'global-text-scale-adjust' (or other things that could change the
>> face[1]).
>>
>> Does anyone have any ideas for this part?
>
> Perhaps we could provide a function "face-change (&optional frame)"
> which would access the frame's face_change flag and the global
> face_change flag. Then you could test those in a post-command-hook or
> somesuch. (However, using :align-to, if feasible, sounds like a
> better solution to me.)
The 'face-change' idea could work, or here's another possibility: what
about using :relative-width? If I set that correctly, then the
pixel-size should adjust as the text scales. It wouldn't handle the case
where the actual font changes though. It would also have some loss of
precision, but I tested out a hacky patch using :relative-width and it
looks good in practice.
>> -@defun string-pixel-width string
>> +@defun string-pixel-width string &optional buffer
>> This is a convenience function that uses @code{window-text-pixel-size}
>> -to compute the width of @var{string} (in pixels).
>> +to compute the width of @var{string} (in pixels). If @var{buffer} is
>> +non-@code{nil}, use the face remappings from that buffer when
>> +determining the width (@pxref{Face Remapping}).
>
> An alternative would be to provide a face to use.
>
> In any case, using BUFFER only for face-remapping-alist is only a
> small part of what a buffer can do to a string: there's the major mode
> with its fontifications and whatnot.
Yeah, I'm not entirely happy with this BUFFER argument either. I don't
think we need to worry about fontification in this case though, since
you can pass in a fontified string.
Maybe this should take the face-remapping-alist directly? Or maybe we
should pass in a window? The latter might be better for handling things
like frame-specific font settings. (Although as Po Lu points out,
frame-specific fonts are challenging to handle correctly here.)
>> +(defun visual-wrap--adjust-display-width (fcp n)
>> + (when-let ((display (get-text-property 0 'display fcp))
>> + ((eq (car-safe display) 'space))
>
> Doesn't this only work with very simple 'display' specs? The 'space'
> part could be in some place deep in the spec, not just the second
> symbol.
Yeah, though the FCP argument is always the prefix we constructed, so we
know what the display-spec looks like if it's present. The extra checks
are just my natural paranoia. I've added a comment here explaining though.
>> (defun visual-wrap-fill-context-prefix (beg end)
>> "Compute visual wrap prefix from text between BEG and END.
>> -This is like `fill-context-prefix', but with prefix length adjusted
>> -by `visual-wrap-extra-indent'."
>> - (let* ((fcp
>> - ;; `fill-context-prefix' ignores prefixes that look like
>> - ;; paragraph starts, in order to avoid inadvertently
>> - ;; creating a new paragraph while filling, but here we're
>> - ;; only dealing with single-line "paragraphs" and we don't
>> - ;; actually modify the buffer, so this restriction doesn't
>> - ;; make much sense (and is positively harmful in
>> - ;; taskpaper-mode where paragraph-start matches everything).
>> - (or (let ((paragraph-start regexp-unmatchable))
>> - (fill-context-prefix beg end))
>> - ;; Note: fill-context-prefix may return nil; See:
>> - ;; http://article.gmane.org/gmane.emacs.devel/156285
>> - ""))
>
> The comment above and the URL it included are deleted: is that because
> they are no longer relevant? If not, maybe move them with the code,
> so that the information is not lost.
Correct, they're no longer relevant. I extracted the logic that we need
out of 'fill-content-prefix' and into 'visual-wrap--content-prefix'. The
former didn't behave quite the way we wanted (hence all the comments),
and it made handling the display-spec parts of my patch even harder, so
I just took the relevant logic out and made a function that does exactly
what we want. I've added more detail to the commit message explaining
the change.
[-- Attachment #2: misaligned.png --]
[-- Type: image/png, Size: 24816 bytes --]
[-- Attachment #3: 0001-Add-support-for-variable-width-text-in-visual-wrap-p.patch --]
[-- Type: text/plain, Size: 9981 bytes --]
From 120748358b6a717c740e8d4f139ce62a30be7606 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 16 Jun 2024 15:21:52 -0700
Subject: [PATCH 1/2] Add support for variable-width text in
'visual-wrap-prefix-mode'
This uses a display spec to set the width correctly when indenting with
spaces.
* lisp/emacs-lisp/subr-x.el (string-pixel-width): New argument BUFFER.
* lisp/visual-wrap.el (visual-wrap--content-prefix)
(visual-wrap--adjust-display-width): New functions.
(visual-wrap--extra-indent): Rename from 'visual-wrap--prefix' and call
'visual-wrap--adjust-display-width'.
(visual-wrap-fill-context-prefix): Support display width. Use
'visual-wrap--content-prefix' instead of 'fill-content-prefix', which
lets us remove the old workarounds.
(visual-wrap-prefix-function): Allow 'lbp' to be at 'point-min'.
(visual-wrap-prefix-mode): Refontify when changing text scale.
* doc/lispref/display.texi (Size of Displayed Text): Document BUFFER
argument for 'string-pixel-width'.
* etc/NEWS: Announce this change.
---
doc/lispref/display.texi | 6 ++-
etc/NEWS | 8 +++-
lisp/emacs-lisp/subr-x.el | 11 ++++-
lisp/visual-wrap.el | 86 ++++++++++++++++++++++++++++-----------
4 files changed, 83 insertions(+), 28 deletions(-)
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index d5c96d13e02..52957f2ad07 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -2351,9 +2351,11 @@ Size of Displayed Text
meaning as with @code{window-text-pixel-size}.
@end defun
-@defun string-pixel-width string
+@defun string-pixel-width string &optional buffer
This is a convenience function that uses @code{window-text-pixel-size}
-to compute the width of @var{string} (in pixels).
+to compute the width of @var{string} (in pixels). If @var{buffer} is
+non-@code{nil}, use the face remappings from that buffer when
+determining the width (@pxref{Face Remapping}).
@end defun
@defun line-pixel-height
diff --git a/etc/NEWS b/etc/NEWS
index b2fdbc4a88f..27a4fd11a87 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -549,7 +549,8 @@ text in any way. The global minor mode
buffers.
(This minor mode is the 'adaptive-wrap' ELPA package renamed and
-lightly edited for inclusion in Emacs.)
+enhanced for inclusion in Emacs. It additionally supports prefixes for
+variable-width text.)
+++
** New user option 'gud-highlight-current-line'.
@@ -2789,6 +2790,11 @@ These functions are like 'user-uid' and 'group-gid', respectively, but
are aware of file name handlers, so they will return the remote UID or
GID for remote files (or -1 if the connection has no associated user).
++++
+** 'string-pixel-width' now accepts a BUFFER argument.
+If BUFFER is non-nil, 'string-pixel-width' will apply BUFFER's face
+remappings when computing the string's width.
+
+++
** 'fset', 'defalias' and 'defvaralias' now signal an error for cyclic aliases.
Previously, 'fset', 'defalias' and 'defvaralias' could be made to
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index 699be767ee7..2cbe1beb9f1 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -333,8 +333,10 @@ named-let
. ,aargs)))
;;;###autoload
-(defun string-pixel-width (string)
- "Return the width of STRING in pixels."
+(defun string-pixel-width (string &optional buffer)
+ "Return the width of STRING in pixels.
+If BUFFER is non-nil, use the face remappings from that buffer when
+determining the width."
(declare (important-return-value t))
(if (zerop (length string))
0
@@ -348,6 +350,11 @@ string-pixel-width
;; Disable line-prefix and wrap-prefix, for the same reason.
(setq line-prefix nil
wrap-prefix nil)
+ (if buffer
+ (setq-local face-remapping-alist
+ (with-current-buffer buffer
+ face-remapping-alist))
+ (kill-local-variable 'face-remapping-alist))
(insert (propertize string 'line-prefix nil 'wrap-prefix nil))
(car (buffer-text-pixel-size nil nil t)))))
diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el
index d95cf4bb569..886219cff54 100644
--- a/lisp/visual-wrap.el
+++ b/lisp/visual-wrap.el
@@ -97,38 +97,73 @@ visual-wrap--prefix-face
(if (visual-wrap--face-extend-p f) f))
eol-face)))))))
-(defun visual-wrap--prefix (fcp)
+(defun visual-wrap--content-prefix (position)
+ "Get the content prefix for the line starting at POSITION.
+This is like `fill-content-prefix' but doesn't check subsequent lines
+and uses display specs to handle variable-pitch faces."
+ (save-excursion
+ (goto-char position)
+ (if (eolp) (forward-line 1))
+ ;; Move to the second line unless there is just one.
+ (move-to-left-margin)
+ (let ((prefix (fill-match-adaptive-prefix)))
+ ;; Check whether we should use our first-line content prefix.
+ (if (or (and adaptive-fill-first-line-regexp
+ (string-match adaptive-fill-first-line-regexp prefix))
+ (and comment-start-skip
+ (string-match comment-start-skip prefix)))
+ prefix
+ ;; We want the prefix to be whitespace of the same width as the
+ ;; first-line prefix.
+ (let ((spaces (make-string (string-width prefix) ?\s)))
+ ;; If the font for our first-line prefix is variable-pitch,
+ ;; use a display spec to line the subsequent lines up
+ ;; correctly.
+ (when-let ((font (font-at position))
+ ((memq (font-get font :spacing) '(nil 0))))
+ (put-text-property 0 (length spaces) 'display
+ `(space :width (,(string-pixel-width
+ prefix (current-buffer))))
+ spaces))
+ spaces)))))
+
+(defun visual-wrap--adjust-display-width (fcp n)
+ (when-let ((display (get-text-property 0 'display fcp))
+ ;; If we have a display spec here, it should be what we
+ ;; specified in `visual-wrap--content-prefix', but
+ ;; double-check just to be safe.
+ ((eq (car-safe display) 'space))
+ (width (car (plist-get (cdr display) :width))))
+ (put-text-property 0 (length fcp) 'display
+ `(space :width (,(+ width n))) fcp))
+ fcp)
+
+(defun visual-wrap--extra-indent (fcp)
(let ((fcp-len (string-width fcp)))
(cond
((= 0 visual-wrap-extra-indent)
fcp)
((< 0 visual-wrap-extra-indent)
- (concat fcp (make-string visual-wrap-extra-indent ?\s)))
+ (let* ((extra (make-string visual-wrap-extra-indent ?\s))
+ (result (concat fcp extra)))
+ (visual-wrap--adjust-display-width
+ result (string-pixel-width extra (current-buffer)))))
((< 0 (+ visual-wrap-extra-indent fcp-len))
- (substring fcp
- 0
- (+ visual-wrap-extra-indent fcp-len)))
+ (let* ((idx (+ visual-wrap-extra-indent fcp-len))
+ (trim (substring fcp idx))
+ (result (substring fcp 0 idx)))
+ (remove-text-properties 0 (length trim) '(display) trim)
+ (visual-wrap--adjust-display-width
+ result (- (string-pixel-width trim (current-buffer))))))
(t
""))))
(defun visual-wrap-fill-context-prefix (beg end)
"Compute visual wrap prefix from text between BEG and END.
-This is like `fill-context-prefix', but with prefix length adjusted
-by `visual-wrap-extra-indent'."
- (let* ((fcp
- ;; `fill-context-prefix' ignores prefixes that look like
- ;; paragraph starts, in order to avoid inadvertently
- ;; creating a new paragraph while filling, but here we're
- ;; only dealing with single-line "paragraphs" and we don't
- ;; actually modify the buffer, so this restriction doesn't
- ;; make much sense (and is positively harmful in
- ;; taskpaper-mode where paragraph-start matches everything).
- (or (let ((paragraph-start regexp-unmatchable))
- (fill-context-prefix beg end))
- ;; Note: fill-context-prefix may return nil; See:
- ;; http://article.gmane.org/gmane.emacs.devel/156285
- ""))
- (prefix (visual-wrap--prefix fcp))
+This is like `fill-context-prefix', but supporting variable-width faces
+and with the prefix length adjusted by `visual-wrap-extra-indent'."
+ (let* ((fcp (visual-wrap--content-prefix beg))
+ (prefix (visual-wrap--extra-indent fcp))
(face (visual-wrap--prefix-face fcp beg end)))
(if face
(propertize prefix 'face face)
@@ -160,7 +195,8 @@ visual-wrap-prefix-function
(remove-text-properties
0 (length pfx) '(wrap-prefix) pfx)
(let ((dp (get-text-property 0 'display pfx)))
- (when (and dp (eq dp (get-text-property (1- lbp) 'display)))
+ (when (and dp (> lbp (point-min))
+ (eq dp (get-text-property (1- lbp) 'display)))
;; There's a `display' property which covers not just the
;; prefix but also the previous newline. So it's not
;; just making the prefix more pretty and could interfere
@@ -187,8 +223,12 @@ visual-wrap-prefix-mode
;; of the hook (bug#15155).
(add-hook 'jit-lock-functions
#'visual-wrap-prefix-function 'append t)
- (jit-lock-register #'visual-wrap-prefix-function))
+ (jit-lock-register #'visual-wrap-prefix-function)
+ ;; FIXME: What should we do about `global-text-scale-adjust' or
+ ;; other things that can change the text size?
+ (add-hook 'text-scale-mode-hook #'jit-lock-refontify nil t))
(jit-lock-unregister #'visual-wrap-prefix-function)
+ (remove-hook 'text-scale-mode-hook #'jit-lock-refontify)
(with-silent-modifications
(save-restriction
(widen)
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 16:13 ` Jim Porter
@ 2024-06-17 18:17 ` Jim Porter
2024-06-17 19:55 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-17 18:17 UTC (permalink / raw)
To: Po Lu; +Cc: 71605
On 6/17/2024 9:13 AM, Jim Porter wrote:
> Note that I haven't removed any of the old logic though; it's all there
> as before, since the space characters are useful for terminals (where
> the pixelwise :width display-spec doesn't do anything for us, if I
> understand correctly).
Evidently I wasn't understanding this correctly, since further testing
and consulting the code shows that the :width display-spec works just
fine on terminals. It does make it harder to get this patch working well
though...
Maybe :align-to could be made to work. It doesn't seem to work at all
for me, even in a minimal test case like calling this on a long line of
text:
(put-text-property (point-min) (point-max) 'wrap-prefix '(space
:align-to 4))
Replacing :align-to with :width works just fine though. It's entirely
possible I'm just doing something wrong, but maybe this is a bug?
(Now what would be great is if :align-to supported a *string* value that
meant "use the pixel-width of this string". Which actually gives me an
idea... what if the wrap-prefix is just the original text like "* " and
we fontify it to be invisible? It's not part of the buffer and you can't
select it anyway, so it doesn't really matter what the text is so long
as it's not visible.)
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 17:42 ` Jim Porter
@ 2024-06-17 18:20 ` Eli Zaretskii
2024-06-17 18:44 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-17 18:20 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Mon, 17 Jun 2024 10:42:56 -0700
> Cc: 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 6/17/2024 4:37 AM, Eli Zaretskii wrote:
> >> Date: Sun, 16 Jun 2024 19:56:44 -0700
> >> From: Jim Porter <jporterbugs@gmail.com>
> >> The attached patch adds a display spec in this case so that the text
> >> lines up perfectly.
> >
> > Can you explain the idea of the patch? I don't think I understand why
> > you use '(space :width)' rather than '(space :align-to)'.
>
> I tried using :align-to, and it doesn't seem to take effect for the
> 'wrap-prefix' text property. I haven't looked closely at why that
> doesn't work, but even if it did, I think it might make things more
> complex than they already are.
What exactly did you try? I might be misremembering, but I'm not
aware of any limitations wrt use of :align-to in wrap-prefix. In
fact, the ELisp reference manual explicitly mentions :align-to in its
description of wrap-prefix.
> If I understand your :align-to suggestion, setting :align-to on
> everything after the "* " bullet could work in theory, but I don't know
> what value you could set there to make everything correct.
Some multiple of the width of the default font's representative
character, or of its average-width property?
> The 'face-change' idea could work, or here's another possibility: what
> about using :relative-width?
:relative-width could work, but you'd need to make sure it takes the
width from a fixed character, otherwise different paragraphs won't
align the same. The ELisp manual says:
‘:relative-width FACTOR’
Specifies that the width of the stretch should be computed from the
first character in the group of consecutive characters that have
the same ‘display’ property. The space width is the pixel width of
that character, multiplied by FACTOR.
> If I set that correctly, then the pixel-size should adjust as the
> text scales. It wouldn't handle the case where the actual font
> changes though.
Why not?
> >> -@defun string-pixel-width string
> >> +@defun string-pixel-width string &optional buffer
> >> This is a convenience function that uses @code{window-text-pixel-size}
> >> -to compute the width of @var{string} (in pixels).
> >> +to compute the width of @var{string} (in pixels). If @var{buffer} is
> >> +non-@code{nil}, use the face remappings from that buffer when
> >> +determining the width (@pxref{Face Remapping}).
> >
> > An alternative would be to provide a face to use.
> >
> > In any case, using BUFFER only for face-remapping-alist is only a
> > small part of what a buffer can do to a string: there's the major mode
> > with its fontifications and whatnot.
>
> Yeah, I'm not entirely happy with this BUFFER argument either. I don't
> think we need to worry about fontification in this case though, since
> you can pass in a fontified string.
>
> Maybe this should take the face-remapping-alist directly? Or maybe we
> should pass in a window?
If you can pass a window, you can use window-text-pixel-size instead.
> >> +(defun visual-wrap--adjust-display-width (fcp n)
> >> + (when-let ((display (get-text-property 0 'display fcp))
> >> + ((eq (car-safe display) 'space))
> >
> > Doesn't this only work with very simple 'display' specs? The 'space'
> > part could be in some place deep in the spec, not just the second
> > symbol.
>
> Yeah, though the FCP argument is always the prefix we constructed, so we
> know what the display-spec looks like if it's present.
Sure, but that means the code is fragile, and you need to comment
prominently that if the form of the display spec changes in the
future, this code will need to be adapted.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 18:20 ` Eli Zaretskii
@ 2024-06-17 18:44 ` Jim Porter
2024-06-18 11:37 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-17 18:44 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
Replying to your individual points below, but in the other subthread, I
had the idea that maybe a better way to do this would be for the wrap
prefix to always be the first-line prefix but to make it transparent
when desired. So for the "* some text" example, the wrap-prefix would be
"* " but fontified(?) such that you can't see it.
A face transparency attribute might do the trick, and be useful for
other things too:
<https://lists.gnu.org/archive/html/emacs-devel/2024-01/msg00657.html>.
Or maybe :align-to could take a string value, which would mean "use the
pixel-width of this string as the value".
On 6/17/2024 11:20 AM, Eli Zaretskii wrote:
>> Date: Mon, 17 Jun 2024 10:42:56 -0700
>> Cc: 71605@debbugs.gnu.org
>> From: Jim Porter <jporterbugs@gmail.com>
>>
>> I tried using :align-to, and it doesn't seem to take effect for the
>> 'wrap-prefix' text property. I haven't looked closely at why that
>> doesn't work, but even if it did, I think it might make things more
>> complex than they already are.
>
> What exactly did you try? I might be misremembering, but I'm not
> aware of any limitations wrt use of :align-to in wrap-prefix. In
> fact, the ELisp reference manual explicitly mentions :align-to in its
> description of wrap-prefix.
My minimal test case is to open a buffer, put some random text in (long
enough that it would wrap), and then call:
(put-text-property (point-min) (point-max) 'wrap-prefix '(space
:align-to 4))
Nothing changes for me. If I replace :align-to with :width, I see the
continuation lines get indented by 4 characters.
>> The 'face-change' idea could work, or here's another possibility: what
>> about using :relative-width?
>
> :relative-width could work, but you'd need to make sure it takes the
> width from a fixed character, otherwise different paragraphs won't
> align the same.
In this case, the first character is always a space so that's ok.
>> If I set that correctly, then the pixel-size should adjust as the
>> text scales. It wouldn't handle the case where the actual font
>> changes though.
>
> Why not?
I was planning to set :relative-width to <first_line_prefix_width> /
<width_of_one_space>. If the font changes, the result of that
calculation can change.
>>>> -@defun string-pixel-width string
>>>> +@defun string-pixel-width string &optional buffer
[snip]
>> Maybe this should take the face-remapping-alist directly? Or maybe we
>> should pass in a window?
>
> If you can pass a window, you can use window-text-pixel-size instead.
I think 'window-text-pixel-size' would compute the size of the text
already in the buffer, and I was looking for a function that told the
width of some text if I were to later display it in that window. In any
case, I might not need to use this function at all depending on how I do
things...
>> Yeah, though the FCP argument is always the prefix we constructed, so we
>> know what the display-spec looks like if it's present.
>
> Sure, but that means the code is fragile, and you need to comment
> prominently that if the form of the display spec changes in the
> future, this code will need to be adapted.
Assuming I keep going down this route, I'll be sure to comment this
extensively.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 18:17 ` Jim Porter
@ 2024-06-17 19:55 ` Eli Zaretskii
2024-06-17 20:08 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-17 19:55 UTC (permalink / raw)
To: Jim Porter; +Cc: luangruo, 71605
> Cc: 71605@debbugs.gnu.org
> Date: Mon, 17 Jun 2024 11:17:45 -0700
> From: Jim Porter <jporterbugs@gmail.com>
>
> Maybe :align-to could be made to work. It doesn't seem to work at all
> for me, even in a minimal test case like calling this on a long line of
> text:
>
> (put-text-property (point-min) (point-max) 'wrap-prefix '(space
> :align-to 4))
>
> Replacing :align-to with :width works just fine though. It's entirely
> possible I'm just doing something wrong, but maybe this is a bug?
It's a bug in Emacs 29 and Emacs 30. It works in Emacs 27.
> (Now what would be great is if :align-to supported a *string* value that
> meant "use the pixel-width of this string".
It's already possible: just have a variable that holds the width, and
use it in the :align-to expression.
> Which actually gives me an
> idea... what if the wrap-prefix is just the original text like "* " and
> we fontify it to be invisible? It's not part of the buffer and you can't
> select it anyway, so it doesn't really matter what the text is so long
> as it's not visible.)
I don't think I follow: how is this relevant to the issue at hand?
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 19:55 ` Eli Zaretskii
@ 2024-06-17 20:08 ` Jim Porter
2024-06-18 3:02 ` Jim Porter
2024-06-18 12:39 ` Eli Zaretskii
0 siblings, 2 replies; 27+ messages in thread
From: Jim Porter @ 2024-06-17 20:08 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: luangruo, 71605
On 6/17/2024 12:55 PM, Eli Zaretskii wrote:
>> Cc: 71605@debbugs.gnu.org
>> Date: Mon, 17 Jun 2024 11:17:45 -0700
>> From: Jim Porter <jporterbugs@gmail.com>
>>
>> Replacing :align-to with :width works just fine though. It's entirely
>> possible I'm just doing something wrong, but maybe this is a bug?
>
> It's a bug in Emacs 29 and Emacs 30. It works in Emacs 27.
Ah, that explains it.
>> (Now what would be great is if :align-to supported a *string* value that
>> meant "use the pixel-width of this string".
>
> It's already possible: just have a variable that holds the width, and
> use it in the :align-to expression.
That would make the code simpler, but I'd still need to figure out when
to reevaluate the variable to update things.
>> Which actually gives me an
>> idea... what if the wrap-prefix is just the original text like "* " and
>> we fontify it to be invisible? It's not part of the buffer and you can't
>> select it anyway, so it doesn't really matter what the text is so long
>> as it's not visible.)
>
> I don't think I follow: how is this relevant to the issue at hand?
I was thinking you could wrap "* this is some text" like so:
* this is
* some text
The second "* " would come from the wrap-prefix, but we'd make the
foreground transparent (or the same color as the background) so it
wouldn't be visible. Then it would always take up the same width as the
first "* " because it's the same string with the same font and everything.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 20:08 ` Jim Porter
@ 2024-06-18 3:02 ` Jim Porter
2024-06-18 6:27 ` Jim Porter
2024-06-18 12:39 ` Eli Zaretskii
1 sibling, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-18 3:02 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: luangruo, 71605
On 6/17/2024 1:08 PM, Jim Porter wrote:
> On 6/17/2024 12:55 PM, Eli Zaretskii wrote:
>>> Cc: 71605@debbugs.gnu.org
>>> Date: Mon, 17 Jun 2024 11:17:45 -0700
>>> From: Jim Porter <jporterbugs@gmail.com>
>>>
>>> Replacing :align-to with :width works just fine though. It's entirely
>>> possible I'm just doing something wrong, but maybe this is a bug?
>>
>> It's a bug in Emacs 29 and Emacs 30. It works in Emacs 27.
>
> Ah, that explains it.
Just so others are aware I'm going to try and bisect this. I'll report
back when it's finished.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-18 3:02 ` Jim Porter
@ 2024-06-18 6:27 ` Jim Porter
2024-06-18 12:53 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-18 6:27 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: luangruo, 71605
On 6/17/2024 8:02 PM, Jim Porter wrote:
> Just so others are aware I'm going to try and bisect this. I'll report
> back when it's finished.
Looks like this is the commit that regressed this:
commit fb3d582e7ba595b7680e2c2adf22c7ab699e5792
Author: Eli Zaretskii <eliz@gnu.org>
Date: Wed Jun 29 20:15:36 2022 +0300
Fix hscrolling of :align-to when display-line-numbers is in effect
* src/dispextern.h (struct it): Rename 'tab_offset' member to
'stretch_adjust'.
* src/xdisp.c (gui_produce_glyphs, produce_stretch_glyph)
(display_line): All users of 'tab_offset' changed.
(produce_stretch_glyph): Fix calculation of ':align-to' when
line numbers are displayed and the window is hscrolled.
(calc_pixel_width_or_height): Fix calculation of width of 'space'
display property when 'display-line-numbers' is turned on, but the
line number was not yet produced for the current glyph row.
(Bug#56176)
I'll take a look in more detail tomorrow, but if anyone else wants to
fix this, go ahead. I also have a bit of Elisp that checks whether this
feature works, and I should be able to make it into a proper regression
test with a bit of work.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 18:44 ` Jim Porter
@ 2024-06-18 11:37 ` Eli Zaretskii
2024-06-18 22:17 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-18 11:37 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Mon, 17 Jun 2024 11:44:47 -0700
> Cc: 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> Replying to your individual points below, but in the other subthread, I
> had the idea that maybe a better way to do this would be for the wrap
> prefix to always be the first-line prefix but to make it transparent
> when desired. So for the "* some text" example, the wrap-prefix would be
> "* " but fontified(?) such that you can't see it.
I don't think I understand how would this do the job. Surely, the
indentation space should be visible?
> A face transparency attribute might do the trick, and be useful for
> other things too:
It isn't universally supported, AFAIK.
> Or maybe :align-to could take a string value, which would mean "use the
> pixel-width of this string as the value".
How is that different from using a column (as opposed to pixel) value
for :align-to?
> >> If I set that correctly, then the pixel-size should adjust as the
> >> text scales. It wouldn't handle the case where the actual font
> >> changes though.
> >
> > Why not?
>
> I was planning to set :relative-width to <first_line_prefix_width> /
> <width_of_one_space>. If the font changes, the result of that
> calculation can change.
The idea is to set it to the multiple of the character's width, which
will then scale with the font.
> > If you can pass a window, you can use window-text-pixel-size instead.
>
> I think 'window-text-pixel-size' would compute the size of the text
> already in the buffer
Yes, and we have with-current-buffer...
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-17 20:08 ` Jim Porter
2024-06-18 3:02 ` Jim Porter
@ 2024-06-18 12:39 ` Eli Zaretskii
1 sibling, 0 replies; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-18 12:39 UTC (permalink / raw)
To: Jim Porter; +Cc: luangruo, 71605
> Date: Mon, 17 Jun 2024 13:08:47 -0700
> Cc: luangruo@yahoo.com, 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> >> Which actually gives me an
> >> idea... what if the wrap-prefix is just the original text like "* " and
> >> we fontify it to be invisible? It's not part of the buffer and you can't
> >> select it anyway, so it doesn't really matter what the text is so long
> >> as it's not visible.)
> >
> > I don't think I follow: how is this relevant to the issue at hand?
>
> I was thinking you could wrap "* this is some text" like so:
>
> * this is
> * some text
>
> The second "* " would come from the wrap-prefix, but we'd make the
> foreground transparent (or the same color as the background) so it
> wouldn't be visible. Then it would always take up the same width as the
> first "* " because it's the same string with the same font and everything.
If it's transparent, some change in the faces could reveal it, which I
think will be perceived as a bug. And invisible text cannot take up
screen estate, so I'm not sure how would that help...
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-18 6:27 ` Jim Porter
@ 2024-06-18 12:53 ` Eli Zaretskii
0 siblings, 0 replies; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-18 12:53 UTC (permalink / raw)
To: Jim Porter; +Cc: luangruo, 71605
> Date: Mon, 17 Jun 2024 23:27:49 -0700
> From: Jim Porter <jporterbugs@gmail.com>
> Cc: luangruo@yahoo.com, 71605@debbugs.gnu.org
>
> On 6/17/2024 8:02 PM, Jim Porter wrote:
> > Just so others are aware I'm going to try and bisect this. I'll report
> > back when it's finished.
>
> Looks like this is the commit that regressed this:
>
>
> commit fb3d582e7ba595b7680e2c2adf22c7ab699e5792
> Author: Eli Zaretskii <eliz@gnu.org>
> Date: Wed Jun 29 20:15:36 2022 +0300
>
> Fix hscrolling of :align-to when display-line-numbers is in effect
>
> * src/dispextern.h (struct it): Rename 'tab_offset' member to
> 'stretch_adjust'.
> * src/xdisp.c (gui_produce_glyphs, produce_stretch_glyph)
> (display_line): All users of 'tab_offset' changed.
> (produce_stretch_glyph): Fix calculation of ':align-to' when
> line numbers are displayed and the window is hscrolled.
> (calc_pixel_width_or_height): Fix calculation of width of 'space'
> display property when 'display-line-numbers' is turned on, but the
> line number was not yet produced for the current glyph row.
> (Bug#56176)
Thanks.
Unfortunately, it means the current behavior is not a bug, but rather
an unintended consequence of another bugfix. The above changeset
changed :align-to to count from the beginning of the physical line,
not the screen line. So, for example, if you have an 80-column line,
the continuation line begins from column 80, not 0. Thus, something
like ':align-to 4' will never work on continuation lines, because we
got past column 4 long ago...
So I need to think whether and how to restore the capability of using
:align-to in wrap-prefix.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-18 11:37 ` Eli Zaretskii
@ 2024-06-18 22:17 ` Jim Porter
2024-06-19 11:45 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-18 22:17 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
On 6/18/2024 4:37 AM, Eli Zaretskii wrote:
> I don't think I understand how would this do the job. Surely, the
> indentation space should be visible?
I mean the second-line "* " prefix would be visible but transparent.
>> A face transparency attribute might do the trick, and be useful for
>> other things too:
>
> It isn't universally supported, AFAIK.
I think it would be feasible to support an opacity level of 1.0 and 0.0.
opacity=0.0 could just allocate the space for the text but not actually
draw the glyphs. (Whether we want to go this route is another question.)
>> Or maybe :align-to could take a string value, which would mean "use the
>> pixel-width of this string as the value".
>
> How is that different from using a column (as opposed to pixel) value
> for :align-to?
A column wouldn't work, since for a variable-pitch font, N columns is
just N * <canonical character width>. If the actual characters you're
trying to align to are narrower than the canonical width, they won't
line up correctly.
Po Lu also raised the issue that in some cases, different frames can be
displaying the same buffer using different fonts. Conceptually, I'm
really trying to tell the display engine, "Put a space here exactly as
wide as <some text> using whatever font you end up using." At the buffer
level, I can't provide a numeric width here that works everywhere, since
it might really be multiple numbers, one for each frame.
Providing a number in pixels is also challenging because then I need to
be able to determine when to recompute that number.
>>>> If I set that correctly, then the pixel-size should adjust as the
>>>> text scales. It wouldn't handle the case where the actual font
>>>> changes though.
>>>
>>> Why not?
>>
>> I was planning to set :relative-width to <first_line_prefix_width> /
>> <width_of_one_space>. If the font changes, the result of that
>> calculation can change.
>
> The idea is to set it to the multiple of the character's width, which
> will then scale with the font.
Imagine two fonts A and B, where the only difference is that the space
character in B is twice as wide. So:
<asterisk_width> = 15
<space_width_A> = 10
<space_width_B> = 20
<first_line_prefix_width_A> = 15 + 10 = 25
<first_line_prefix_width_B> = 15 + 20 = 35
If I compute :relative-width for font A, the result is 25/10 = 2.5. Then,
2.5 * <space_width_A> = 25 = <first_line_prefix_width_A> (good)
2.5 * <space_width_B> = 50 != <first_line_prefix_width_B> (bad!)
So we'd need a way of keeping the width in-sync with any font changes.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-18 22:17 ` Jim Porter
@ 2024-06-19 11:45 ` Eli Zaretskii
2024-06-19 19:53 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-19 11:45 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Tue, 18 Jun 2024 15:17:29 -0700
> Cc: 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 6/18/2024 4:37 AM, Eli Zaretskii wrote:
> >> Or maybe :align-to could take a string value, which would mean "use the
> >> pixel-width of this string as the value".
> >
> > How is that different from using a column (as opposed to pixel) value
> > for :align-to?
>
> A column wouldn't work, since for a variable-pitch font, N columns is
> just N * <canonical character width>. If the actual characters you're
> trying to align to are narrower than the canonical width, they won't
> line up correctly.
If both the first line of the paragraph and the rest are aligned on
that width, they will all line up. IOW, the idea is to make the text
of all the lines :align-to to the same column number, like this:
* some text
some other text
Then you don't care about the actual pixel width of "* ".
> Po Lu also raised the issue that in some cases, different frames can be
> displaying the same buffer using different fonts. Conceptually, I'm
> really trying to tell the display engine, "Put a space here exactly as
> wide as <some text> using whatever font you end up using." At the buffer
> level, I can't provide a numeric width here that works everywhere, since
> it might really be multiple numbers, one for each frame.
IMNSHO, this is over-engineering, as we already have the means to do
the same with existing features. Asking the display engine to measure
and record somewhere the pixel width of a string is a significant
complication to how the display code works, because the display
routines can be called to start their job at any arbitrary place in
the buffer.
> Providing a number in pixels is also challenging because then I need to
> be able to determine when to recompute that number.
Which is why I didn't suggest that. I suggested columns because they
scale with text-scale-adjust.
> >>>> If I set that correctly, then the pixel-size should adjust as the
> >>>> text scales. It wouldn't handle the case where the actual font
> >>>> changes though.
> >>>
> >>> Why not?
> >>
> >> I was planning to set :relative-width to <first_line_prefix_width> /
> >> <width_of_one_space>. If the font changes, the result of that
> >> calculation can change.
> >
> > The idea is to set it to the multiple of the character's width, which
> > will then scale with the font.
>
> Imagine two fonts A and B, where the only difference is that the space
> character in B is twice as wide. So:
>
> <asterisk_width> = 15
> <space_width_A> = 10
> <space_width_B> = 20
> <first_line_prefix_width_A> = 15 + 10 = 25
> <first_line_prefix_width_B> = 15 + 20 = 35
>
> If I compute :relative-width for font A, the result is 25/10 = 2.5. Then,
>
> 2.5 * <space_width_A> = 25 = <first_line_prefix_width_A> (good)
> 2.5 * <space_width_B> = 50 != <first_line_prefix_width_B> (bad!)
>
> So we'd need a way of keeping the width in-sync with any font changes.
I don't understand the example. If these are two different
paragraphs, then their indentation cannot be guaranteed to be the same
anyway, because no one can assure you all the paragraphs will use the
same font. So whatever you mean by <width_of_one_space>, it will
always be different for different fonts, and I don't see how this can
be solved for an arbitrary combination of fonts.
So my suggestion is to go back to the simpler idea of using :align-to
with an absolute value, either in pixels or in columns (which AFAIR is
interpreted in units of the frame's default font's width), and if
needed augment that by recalculation when necessary.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-19 11:45 ` Eli Zaretskii
@ 2024-06-19 19:53 ` Jim Porter
2024-06-20 4:58 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-19 19:53 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
On 6/19/2024 4:45 AM, Eli Zaretskii wrote:
> If both the first line of the paragraph and the rest are aligned on
> that width, they will all line up. IOW, the idea is to make the text
> of all the lines :align-to to the same column number, like this:
>
> * some text
> some other text
>
> Then you don't care about the actual pixel width of "* ".
Just so I'm sure I understand this: the idea is that the buffer's
contents are "* some text some other text" and then we set the :align-to
property on "some text some other text" so that the result is displayed
like this?
+---- Here is the align-to column
v
* _some text
___some other text
The underscores represent the space added by :align-to. In practice, the
first line might not have any extra space from :align-to, but the
pixel-width of "* " could be less than 2 * <canonical character width>
for variable-pitch fonts.
I think that would work, provided :align-to were fixed so that it could
handle wrapping again. (And so long as we're ok with there being a bit
of extra space after the first-line prefix when using some fonts, but I
don't see why that would be an issue.)
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-19 19:53 ` Jim Porter
@ 2024-06-20 4:58 ` Eli Zaretskii
2024-06-20 5:37 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-20 4:58 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Wed, 19 Jun 2024 12:53:33 -0700
> Cc: 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 6/19/2024 4:45 AM, Eli Zaretskii wrote:
> > If both the first line of the paragraph and the rest are aligned on
> > that width, they will all line up. IOW, the idea is to make the text
> > of all the lines :align-to to the same column number, like this:
> >
> > * some text
> > some other text
> >
> > Then you don't care about the actual pixel width of "* ".
>
> Just so I'm sure I understand this: the idea is that the buffer's
> contents are "* some text some other text" and then we set the :align-to
> property on "some text some other text" so that the result is displayed
> like this?
>
> +---- Here is the align-to column
> v
> * _some text
> ___some other text
Yes.
> The underscores represent the space added by :align-to. In practice, the
> first line might not have any extra space from :align-to, but the
> pixel-width of "* " could be less than 2 * <canonical character width>
> for variable-pitch fonts.
The value calculated for :align-to needs to make sure that never
happens.
> I think that would work, provided :align-to were fixed so that it could
> handle wrapping again.
Stay tuned.
> (And so long as we're ok with there being a bit of extra space after
> the first-line prefix when using some fonts, but I don't see why
> that would be an issue.)
We _should_ be ok with some extra white space, because that's what I
see in word processors all around me. Their indentation of the
itemized lists always indents by more than just one SPC after the item
symbol (bullet or number or letter).
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-20 4:58 ` Eli Zaretskii
@ 2024-06-20 5:37 ` Jim Porter
2024-06-20 9:58 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-20 5:37 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
On 6/19/2024 9:58 PM, Eli Zaretskii wrote:
>> I think that would work, provided :align-to were fixed so that it could
>> handle wrapping again.
>
> Stay tuned.
Thanks. No rush on this, since Emacs 30 takes priority over this small
nice-to-have enhancement. (I do have a patch for EWW that would benefit
from fixing this bug, but that too can wait.)
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-20 5:37 ` Jim Porter
@ 2024-06-20 9:58 ` Eli Zaretskii
2024-06-20 17:36 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-20 9:58 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Wed, 19 Jun 2024 22:37:33 -0700
> Cc: 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 6/19/2024 9:58 PM, Eli Zaretskii wrote:
> >> I think that would work, provided :align-to were fixed so that it could
> >> handle wrapping again.
> >
> > Stay tuned.
>
> Thanks. No rush on this, since Emacs 30 takes priority over this small
> nice-to-have enhancement. (I do have a patch for EWW that would benefit
> from fixing this bug, but that too can wait.)
Should be fixed now on the master branch.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-20 9:58 ` Eli Zaretskii
@ 2024-06-20 17:36 ` Jim Porter
2024-06-20 18:08 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-20 17:36 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
On 6/20/2024 2:58 AM, Eli Zaretskii wrote:
> Should be fixed now on the master branch.
Thanks, it all seems to work now.
I think I have a way forward to implement this that should avoid issues,
though it's a bit more complex than I'd initially thought. I think there
are three cases:
"* here is some text":
Text Properties
"* " (display (min-width (2)))
"here is some text" (wrap-prefix (space :align-to 2))
" here is some text":
Text Properties
" " (display "")
" here is some text" (line-prefix (space :align-to 2)
wrap-prefix (space :align-to 2))
"// here is some text" (in c-mode):
Text Properties
"// here is some text" (wrap-prefix "// ")
(The last case is just doing what visual-wrap-prefix-mode currently
does.) I'm pretty sure all these should be unproblematic in the
scenarios Po Lu mentioned. The only case I haven't considered is how
this interacts with 'visual-wrap-extra-indent'. I'll have to think about
that some more.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-20 17:36 ` Jim Porter
@ 2024-06-20 18:08 ` Eli Zaretskii
2024-06-20 19:01 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-06-20 18:08 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605
> Date: Thu, 20 Jun 2024 10:36:23 -0700
> Cc: 71605@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
>
> On 6/20/2024 2:58 AM, Eli Zaretskii wrote:
> > Should be fixed now on the master branch.
>
> Thanks, it all seems to work now.
Does this mean we can close this bug, or is there anything else to do
here?
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-20 18:08 ` Eli Zaretskii
@ 2024-06-20 19:01 ` Jim Porter
2024-07-28 4:53 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-06-20 19:01 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605
On 6/20/2024 11:08 AM, Eli Zaretskii wrote:
> Does this mean we can close this bug, or is there anything else to do
> here?
I need to implement a new version of my patch that uses :align-to and
such. So there are still things to do in this bug, but the :align-to
issue that was blocking progress on this bug is now resolved.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-06-20 19:01 ` Jim Porter
@ 2024-07-28 4:53 ` Jim Porter
2024-08-02 7:27 ` Eli Zaretskii
0 siblings, 1 reply; 27+ messages in thread
From: Jim Porter @ 2024-07-28 4:53 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605, Po Lu
[-- Attachment #1: Type: text/plain, Size: 1252 bytes --]
On 6/20/2024 12:01 PM, Jim Porter wrote:
> On 6/20/2024 11:08 AM, Eli Zaretskii wrote:
>> Does this mean we can close this bug, or is there anything else to do
>> here?
>
> I need to implement a new version of my patch that uses :align-to and
> such. So there are still things to do in this bug, but the :align-to
> issue that was blocking progress on this bug is now resolved.
After some time away from this bug, I've made a new version of this
patch which uses ':align-to' to line up the wrapped lines. All of the
text properties in this patch use widths defined in terms of the average
width for the current face (using a spec like "(N . width)"), which I
think should work correctly in all situations.
I've also set the min-width of the first-line prefix to ensure
everything lines up just right. That makes it easier to line things up
this way, rather than my previous brittle attempts at computing the
exact pixel width of the first-line prefix (that width can change for
all sorts of reasons).
Also attached is a test script I wrote to preview the effects of the
patch. If you pass an additional numeric argument on the command line
when loading it, it will set 'visual-wrap-extra-indent' so you can see
how that affects things.
[-- Attachment #2: 0001-Add-support-for-variable-pitch-fonts-in-visual-wrap-.patch --]
[-- Type: text/plain, Size: 10149 bytes --]
From 1509d9bf0acfab6ea486b96cd6765fc95ccf2b2b Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sat, 27 Jul 2024 20:48:38 -0700
Subject: [PATCH] Add support for variable-pitch fonts in
'visual-wrap-prefix-mode'
* lisp/emacs-lisp/subr-x.el (string-pixel-width): Allow passing BUFFER
to use the face remappings from that buffer when calculating the width.
* lisp/visual-wrap.el (visual-wrap--prefix): Rename to...
(visual-wrap--adjust-prefix): ... this, and support PREFIX as a number.
(visual-wrap-fill-context-prefix): Make obsolete in favor of...
(visual-wrap--content-prefix): ... this.
(visual-wrap-prefix-function): Extract inside of loop into...
(visual-wrap--apply-to-line): ... this.
* doc/lispref/display.texi (Size of Displayed Text): Update
documentation for 'string-pixel-width'.
* etc/NEWS: Announce this change.
---
doc/lispref/display.texi | 6 +-
etc/NEWS | 11 ++++
lisp/emacs-lisp/subr-x.el | 11 +++-
lisp/visual-wrap.el | 114 ++++++++++++++++++++++++++------------
4 files changed, 102 insertions(+), 40 deletions(-)
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index 195464ef7f5..d28ff9ead26 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -2385,9 +2385,11 @@ Size of Displayed Text
meaning as with @code{window-text-pixel-size}.
@end defun
-@defun string-pixel-width string
+@defun string-pixel-width string &optional buffer
This is a convenience function that uses @code{window-text-pixel-size}
-to compute the width of @var{string} (in pixels).
+to compute the width of @var{string} (in pixels). If @var{buffer} is
+non-@code{nil}, use any face remappings (@pxref{Face Remapping}) from
+that buffer when computing the width of @var{string}.
@end defun
@defun line-pixel-height
diff --git a/etc/NEWS b/etc/NEWS
index c907ec40fa1..0c6cd9771e1 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -52,6 +52,12 @@ usual minibuffer history commands. Each command has a separate history.
*** New language-environment and input method for Tifinagh.
The Tifinagh script is used to write the Berber languages.
+---
+** 'visual-wrap-prefix-mode' now supports variable-pitch fonts.
+When using 'visual-wrap-prefix-mode' in buffers with variable-pitch
+fonts, the wrapped text will now be lined up correctly so that it's
+exactly below the text after the prefix on the first line.
+
\f
* Changes in Specialized Modes and Packages in Emacs 31.1
@@ -194,6 +200,11 @@ authorize the invoked D-Bus method (for example via polkit).
** The customization group 'wp' has been removed.
It has been obsolete since Emacs 26.1. Use the group 'text' instead.
++++
+** New optional BUFFER argument for 'string-pixel-width'.
+If supplied, 'string-pixel-width' will use any face remappings from
+BUFFER when computing the string's width.
+
\f
* Changes in Emacs 31.1 on Non-Free Operating Systems
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index e725c490aba..058c06bc5f6 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -337,8 +337,10 @@ named-let
. ,aargs)))
;;;###autoload
-(defun string-pixel-width (string)
- "Return the width of STRING in pixels."
+(defun string-pixel-width (string &optional buffer)
+ "Return the width of STRING in pixels.
+If BUFFER is non-nil, use the face remappings from that buffer when
+determining the width."
(declare (important-return-value t))
(if (zerop (length string))
0
@@ -352,6 +354,11 @@ string-pixel-width
;; Disable line-prefix and wrap-prefix, for the same reason.
(setq line-prefix nil
wrap-prefix nil)
+ (if buffer
+ (setq-local face-remapping-alist
+ (with-current-buffer buffer
+ face-remapping-alist))
+ (kill-local-variable 'face-remapping-alist))
(insert (propertize string 'line-prefix nil 'wrap-prefix nil))
(car (buffer-text-pixel-size nil nil t)))))
diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el
index d95cf4bb569..d2ceb0b17ca 100644
--- a/lisp/visual-wrap.el
+++ b/lisp/visual-wrap.el
@@ -97,24 +97,86 @@ visual-wrap--prefix-face
(if (visual-wrap--face-extend-p f) f))
eol-face)))))))
-(defun visual-wrap--prefix (fcp)
- (let ((fcp-len (string-width fcp)))
- (cond
- ((= 0 visual-wrap-extra-indent)
- fcp)
- ((< 0 visual-wrap-extra-indent)
- (concat fcp (make-string visual-wrap-extra-indent ?\s)))
- ((< 0 (+ visual-wrap-extra-indent fcp-len))
- (substring fcp
- 0
- (+ visual-wrap-extra-indent fcp-len)))
- (t
- ""))))
+(defun visual-wrap--adjust-prefix (prefix)
+ "Adjust PREFIX with `visual-wrap-extra-indent'."
+ (if (numberp prefix)
+ (+ visual-wrap-extra-indent prefix)
+ (let ((prefix-len (string-width prefix)))
+ (cond
+ ((= 0 visual-wrap-extra-indent)
+ prefix)
+ ((< 0 visual-wrap-extra-indent)
+ (concat prefix (make-string visual-wrap-extra-indent ?\s)))
+ ((< 0 (+ visual-wrap-extra-indent prefix-len))
+ (substring prefix
+ 0 (+ visual-wrap-extra-indent prefix-len)))
+ (t
+ "")))))
+
+(defun visual-wrap--apply-to-line (position)
+ "Apply visual-wrapping properties to the logical line starting at POSITION."
+ (save-excursion
+ (goto-char position)
+ (let* ((first-line-prefix (fill-match-adaptive-prefix))
+ (next-line-prefix (visual-wrap--content-prefix
+ first-line-prefix position)))
+ (when next-line-prefix
+ (when (numberp next-line-prefix)
+ (put-text-property
+ position (+ position (length first-line-prefix)) 'display
+ `(min-width ((,next-line-prefix . width)))))
+ (setq next-line-prefix (visual-wrap--adjust-prefix next-line-prefix))
+ (put-text-property
+ position (line-end-position) 'wrap-prefix
+ (if (numberp next-line-prefix)
+ `(space :align-to (,next-line-prefix . width))
+ next-line-prefix))))))
+
+(defun visual-wrap--content-prefix (prefix position)
+ "Get the next-line prefix for the specified first-line PREFIX.
+POSITION is the position in the buffer where PREFIX is located.
+
+This returns a string prefix to use for subsequent lines; an integer,
+indicating the number of canonical-width spaces to use; or nil, if
+PREFIX was empty."
+ (cond
+ ((string= prefix "")
+ nil)
+ ((string-match (rx bos (+ blank) eos) prefix)
+ ;; If the first-line prefix is all spaces, return its width in
+ ;; characters. This way, we can set the prefix for all lines to use
+ ;; the canonical-width of the font, which helps for variable-pitch
+ ;; fonts where space characters are usually quite narrow.
+ (string-width prefix))
+ ((or (and adaptive-fill-first-line-regexp
+ (string-match adaptive-fill-first-line-regexp prefix))
+ (and comment-start-skip
+ (string-match comment-start-skip prefix)))
+ ;; If we want to repeat the first-line prefix on subsequent lines,
+ ;; return its string value. However, we remove any `wrap-prefix'
+ ;; property that might have been added earlier. Otherwise, we end
+ ;; up with a string containing a `wrap-prefix' string containing a
+ ;; `wrap-prefix' string...
+ (remove-text-properties 0 (length prefix) '(wrap-prefix) prefix)
+ prefix)
+ (t
+ ;; Otherwise, we want the prefix to be whitespace of the same width
+ ;; as the first-line prefix. If possible, compute the real pixel
+ ;; width of the first-line prefix in canonical-width characters.
+ ;; This is useful if the first-line prefix uses some very-wide
+ ;; characters.
+ (if-let ((font (font-at position))
+ (info (query-font font)))
+ (max (string-width prefix)
+ (ceiling (string-pixel-width prefix (current-buffer))
+ (aref info 7)))
+ (string-width prefix)))))
(defun visual-wrap-fill-context-prefix (beg end)
"Compute visual wrap prefix from text between BEG and END.
This is like `fill-context-prefix', but with prefix length adjusted
by `visual-wrap-extra-indent'."
+ (declare (obsolete nil "31.1"))
(let* ((fcp
;; `fill-context-prefix' ignores prefixes that look like
;; paragraph starts, in order to avoid inadvertently
@@ -128,7 +190,7 @@ visual-wrap-fill-context-prefix
;; Note: fill-context-prefix may return nil; See:
;; http://article.gmane.org/gmane.emacs.devel/156285
""))
- (prefix (visual-wrap--prefix fcp))
+ (prefix (visual-wrap--adjust-prefix fcp))
(face (visual-wrap--prefix-face fcp beg end)))
(if face
(propertize prefix 'face face)
@@ -147,28 +209,8 @@ visual-wrap-prefix-function
(forward-line 0)
(setq beg (point))
(while (< (point) end)
- (let ((lbp (point)))
- (put-text-property
- (point) (progn (search-forward "\n" end 'move) (point))
- 'wrap-prefix
- (let ((pfx (visual-wrap-fill-context-prefix
- lbp (point))))
- ;; Remove any `wrap-prefix' property that might have been
- ;; added earlier. Otherwise, we end up with a string
- ;; containing a `wrap-prefix' string containing a
- ;; `wrap-prefix' string ...
- (remove-text-properties
- 0 (length pfx) '(wrap-prefix) pfx)
- (let ((dp (get-text-property 0 'display pfx)))
- (when (and dp (eq dp (get-text-property (1- lbp) 'display)))
- ;; There's a `display' property which covers not just the
- ;; prefix but also the previous newline. So it's not
- ;; just making the prefix more pretty and could interfere
- ;; or even defeat our efforts (e.g. it comes from
- ;; `adaptive-fill-mode').
- (remove-text-properties
- 0 (length pfx) '(display) pfx)))
- pfx))))
+ (visual-wrap--apply-to-line (point))
+ (forward-line))
`(jit-lock-bounds ,beg . ,end))
;;;###autoload
--
2.25.1
[-- Attachment #3: wrap.el --]
[-- Type: text/plain, Size: 676 bytes --]
(switch-to-buffer (get-buffer-create "demo"))
(buffer-face-set 'variable-pitch)
(setq words "Voluptatem est nostrum impedit nesciunt eum. Recusandae voluptatem quaerat hic harum. Consequatur in fuga nihil aliquid commodi rem sunt. Aperiam odio odio amet.")
(insert words "\n\n")
(insert " " words "\n\n")
(insert "* " words "\n\n")
(insert "## " words "\n\n")
(insert (propertize (concat "## " words "\n")
'face '(:background "red" :height 300)))
(goto-char (point-min))
(setq extra-indent (pop command-line-args-left))
(when extra-indent
(setq visual-wrap-extra-indent (string-to-number extra-indent)))
(visual-line-mode)
(visual-wrap-prefix-mode)
^ permalink raw reply related [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-07-28 4:53 ` Jim Porter
@ 2024-08-02 7:27 ` Eli Zaretskii
2024-08-04 19:24 ` Jim Porter
0 siblings, 1 reply; 27+ messages in thread
From: Eli Zaretskii @ 2024-08-02 7:27 UTC (permalink / raw)
To: Jim Porter; +Cc: 71605, luangruo
> Date: Sat, 27 Jul 2024 21:53:25 -0700
> From: Jim Porter <jporterbugs@gmail.com>
> Cc: 71605@debbugs.gnu.org, Po Lu <luangruo@yahoo.com>
>
> On 6/20/2024 12:01 PM, Jim Porter wrote:
> > On 6/20/2024 11:08 AM, Eli Zaretskii wrote:
> >> Does this mean we can close this bug, or is there anything else to do
> >> here?
> >
> > I need to implement a new version of my patch that uses :align-to and
> > such. So there are still things to do in this bug, but the :align-to
> > issue that was blocking progress on this bug is now resolved.
>
> After some time away from this bug, I've made a new version of this
> patch which uses ':align-to' to line up the wrapped lines. All of the
> text properties in this patch use widths defined in terms of the average
> width for the current face (using a spec like "(N . width)"), which I
> think should work correctly in all situations.
>
> I've also set the min-width of the first-line prefix to ensure
> everything lines up just right. That makes it easier to line things up
> this way, rather than my previous brittle attempts at computing the
> exact pixel width of the first-line prefix (that width can change for
> all sorts of reasons).
>
> Also attached is a test script I wrote to preview the effects of the
> patch. If you pass an additional numeric argument on the command line
> when loading it, it will set 'visual-wrap-extra-indent' so you can see
> how that affects things.
LGTM, thanks. Feel free to install on master.
^ permalink raw reply [flat|nested] 27+ messages in thread
* bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode'
2024-08-02 7:27 ` Eli Zaretskii
@ 2024-08-04 19:24 ` Jim Porter
0 siblings, 0 replies; 27+ messages in thread
From: Jim Porter @ 2024-08-04 19:24 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 71605-done, luangruo
On 8/2/2024 12:27 AM, Eli Zaretskii wrote:
> LGTM, thanks. Feel free to install on master.
Thanks, merged to master as f70a6ea0ea8.
Hopefully this patch maintains all the existing correct behaviors and
fixes most of the issues with variable-pitch text. To summarize, this is
how things *should* behave (anything else is probably a mistake; just
let me know and I'll try to fix it):
1. With fixed-pitch fonts, everything should look the same as before.
2. With variable-pitch fonts in a terminal frame, everything should look
the same as before, but...
a. If you then open that buffer in a GUI frame, there's a small
chance that the first line of a bulleted list will be too wide.
This can happen if the prefix contains wider-than-average
characters in the specified font.
3. With variable-pitch fonts in a GUI frame, wrapped lines in bulleted
lists and the like should now lined up correctly in GUI , but...
a. The first line might have a bit of extra space after the bullet.
(The space occupied by "* " or what-have-you is rounded up to an
integer number of canonical-width spaces.)
b. If you then open that same buffer in a terminal frame, there's a
small chance that the text after the bullet will have an extra
space (same with the wrapped text). This can happen if the prefix
contains wider-than-average characters in the specified font.
c. If you change the font of the wrapped text to something with
different metrics, there's a small chance that the first line of a
bulleted list will be too wide.
2a and 3b would probably be fixable by using overlays restricted to a
particular window. 3c would probably be fixable by calling
'jit-lock-refontify' at the appropriate time.
While that's quite a few caveats with the current implementation, I
think overall they should only come up in rare cases, and should never
affect fixed-pitch text (which is the only thing
'visual-wrap-prefix-mode' could work correctly with before).
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2024-08-04 19:24 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-17 2:56 bug#71605: 30.0.50; [PATCH] Support variable-width text in 'visual-wrap-prefix-mode' Jim Porter
2024-06-17 11:37 ` Eli Zaretskii
2024-06-17 17:42 ` Jim Porter
2024-06-17 18:20 ` Eli Zaretskii
2024-06-17 18:44 ` Jim Porter
2024-06-18 11:37 ` Eli Zaretskii
2024-06-18 22:17 ` Jim Porter
2024-06-19 11:45 ` Eli Zaretskii
2024-06-19 19:53 ` Jim Porter
2024-06-20 4:58 ` Eli Zaretskii
2024-06-20 5:37 ` Jim Porter
2024-06-20 9:58 ` Eli Zaretskii
2024-06-20 17:36 ` Jim Porter
2024-06-20 18:08 ` Eli Zaretskii
2024-06-20 19:01 ` Jim Porter
2024-07-28 4:53 ` Jim Porter
2024-08-02 7:27 ` Eli Zaretskii
2024-08-04 19:24 ` Jim Porter
2024-06-17 14:23 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-17 16:13 ` Jim Porter
2024-06-17 18:17 ` Jim Porter
2024-06-17 19:55 ` Eli Zaretskii
2024-06-17 20:08 ` Jim Porter
2024-06-18 3:02 ` Jim Porter
2024-06-18 6:27 ` Jim Porter
2024-06-18 12:53 ` Eli Zaretskii
2024-06-18 12:39 ` Eli Zaretskii
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.