* bug#73600: 31.0.50; Visual wrap prefix mode and image display @ 2024-10-02 13:04 Gautier Ponsinet 2024-10-02 15:42 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Gautier Ponsinet @ 2024-10-02 13:04 UTC (permalink / raw) To: 73600 [-- Attachment #1: Type: text/plain, Size: 728 bytes --] Hello everyone, The mode visual-wrap-prefix-mode sometimes causes images to be displayed incorrectly in emacs (in a mosaic-like way). To this mail are attached screenshots of the problem and the image file used in the screenshots which is from wikipedia: - https://fr.wikipedia.org/wiki/Olcades - https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Iberia_300BC-fr.svg/langfr-873px-Iberia_300BC-fr.svg.png Note that the error does not occur with all images, but I am not able to identify which characteristic of an image makes the problem appear. Still, I have encountered the problem with multiple image files. In emacs -Q: - enable global-visual-wrap-prefix-mode, - open the attached image. All the best, Gautier. [-- Attachment #2: 873px-Iberia_300BC-fr.svg.png --] [-- Type: image/png, Size: 340812 bytes --] [-- Attachment #3: visual-wrap-prefix-mode-disabled.jpg --] [-- Type: image/jpeg, Size: 162405 bytes --] [-- Attachment #4: visual-wrap-prefix-mode-enabled.jpg --] [-- Type: image/jpeg, Size: 276199 bytes --] [-- Attachment #5: Type: text/plain, Size: 12204 bytes --] ======================================================================== In GNU Emacs 31.0.50 (build 1, x86_64-pc-linux-gnu, X toolkit, cairo version 1.18.2, Xaw3d scroll bars) of 2024-10-02 built on gautier-laptop Repository revision: 4bb62af3263057312021e076dc7e0c8ff195e38f Repository branch: makepkg Windowing system distributor 'The X.Org Foundation', version 11.0.12101013 System Description: Arch Linux Configured using: 'configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/lib --localstatedir=/var --mandir=/usr/share/man --with-gameuser=:games --with-modules --without-m17n-flt --without-gconf --with-native-compilation=yes --with-xinput2 --with-x-toolkit=lucid --without-xft --with-xaw3d --with-sound=no --with-tree-sitter --without-gpm --without-compress-install '--program-transform-name=s/\([ec]tags\)/\1.emacs/' 'CFLAGS=-march=x86-64 -mtune=generic -O2 -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -g -ffile-prefix-map=/home/gautier/Documents/Sources/AUR/emacs-git/src=/usr/src/debug/emacs-git -flto=auto' 'LDFLAGS=-Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,-z,pack-relative-relocs -flto=auto'' Configured features: ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GSETTINGS HARFBUZZ JPEG LCMS2 LIBOTF LIBSYSTEMD LIBXML2 MODULES NATIVE_COMP NOTIFY INOTIFY PDUMPER PNG RSVG SECCOMP SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS TREE_SITTER WEBP X11 XAW3D XDBE XIM XINPUT2 XPM LUCID ZLIB Important settings: value of $LANG: fr_FR.UTF-8 locale-coding-system: utf-8-unix Major mode: Splash Screen Minor modes in effect: visual-fill-column-mode: t pdf-occur-global-minor-mode: t TeX-PDF-mode: t global-git-commit-mode: t magit-auto-revert-mode: t server-mode: t recentf-mode: t marginalia-mode: t global-corfu-mode: t corfu-mode: t vertico-mouse-mode: t vertico-mode: t minibuffer-depth-indicate-mode: t delete-selection-mode: t global-goto-address-mode: t goto-address-mode: t pulsar-global-mode: t pulsar-mode: t lin-global-mode: t override-global-mode: t winner-mode: t repeat-mode: t global-visual-wrap-prefix-mode: t visual-wrap-prefix-mode: t global-word-wrap-whitespace-mode: t word-wrap-whitespace-mode: t global-eldoc-mode: t show-paren-mode: t electric-indent-mode: t mouse-wheel-mode: t file-name-shadow-mode: t global-font-lock-mode: t font-lock-mode: t window-divider-mode: t minibuffer-regexp-mode: t column-number-mode: t line-number-mode: t indent-tabs-mode: t transient-mark-mode: t auto-composition-mode: t auto-encryption-mode: t auto-compression-mode: t Load-path shadows: /home/gautier/.local/share/emacs/packages/ef-themes-1.8.0/theme-loaddefs hides /home/gautier/.local/share/emacs/packages/modus-themes-20240921.616/theme-loaddefs /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-tag hides /usr/share/emacs/site-lisp/notmuch-tag /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch hides /usr/share/emacs/site-lisp/notmuch /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-wash hides /usr/share/emacs/site-lisp/notmuch-wash /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-maildir-fcc hides /usr/share/emacs/site-lisp/notmuch-maildir-fcc /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-hello hides /usr/share/emacs/site-lisp/notmuch-hello /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-jump hides /usr/share/emacs/site-lisp/notmuch-jump /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-mua hides /usr/share/emacs/site-lisp/notmuch-mua /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-query hides /usr/share/emacs/site-lisp/notmuch-query /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-print hides /usr/share/emacs/site-lisp/notmuch-print /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-compat hides /usr/share/emacs/site-lisp/notmuch-compat /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-company hides /usr/share/emacs/site-lisp/notmuch-company /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-message hides /usr/share/emacs/site-lisp/notmuch-message /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-parser hides /usr/share/emacs/site-lisp/notmuch-parser /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-crypto hides /usr/share/emacs/site-lisp/notmuch-crypto /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-address hides /usr/share/emacs/site-lisp/notmuch-address /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-tree hides /usr/share/emacs/site-lisp/notmuch-tree /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-show hides /usr/share/emacs/site-lisp/notmuch-show /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/coolj hides /usr/share/emacs/site-lisp/coolj /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-draft hides /usr/share/emacs/site-lisp/notmuch-draft /home/gautier/.local/share/emacs/packages/notmuch-20240816.2039/notmuch-lib hides /usr/share/emacs/site-lisp/notmuch-lib /home/gautier/.local/share/emacs/packages/transient-20241001.1031/transient hides /usr/share/emacs/31.0.50/lisp/transient /home/gautier/.local/share/emacs/packages/ef-themes-1.8.0/theme-loaddefs hides /usr/share/emacs/31.0.50/lisp/theme-loaddefs Features: (shadow sort mail-extr emacsbug char-fold mule-util visual-fill-column nov esxml-query pdf-sync pdf-occur ibuf-ext ibuffer ibuffer-loaddefs tablist advice tablist-filter semantic/wisent/comp semantic/wisent semantic/wisent/wisent semantic/util-modes semantic/util semantic semantic/tag semantic/lex semantic/fw mode-local cedet pdf-isearch let-alist pdf-misc pdf-tools pdf-view pdf-cache pdf-info tq pdf-util pdf-macs tex texmathp elfeed-show elfeed-search elfeed-csv elfeed elfeed-curl elfeed-log elfeed-db elfeed-lib xml-query ol-notmuch notmuch notmuch-tree notmuch-jump notmuch-hello notmuch-show notmuch-print notmuch-crypto notmuch-mua notmuch-message notmuch-draft notmuch-maildir-fcc notmuch-address notmuch-company notmuch-parser notmuch-wash coolj icalendar notmuch-tag notmuch-lib notmuch-version notmuch-compat org-duration vc-git vc-dispatcher org-contacts org-capture quail oc-basic disp-table org-habit ol-eww eww url-queue mm-url ol-rmail ol-mhe ol-irc ol-info ol-gnus nnselect gnus-art mm-uu mml2015 mm-view mml-smime smime gnutls dig gnus-sum shr pixel-fill kinsoku url-file svg dom gnus-group gnus-undo gnus-start gnus-dbus gnus-cloud nnimap nnmail mail-source utf7 nnoo parse-time gnus-spec gnus-int gnus-range gnus-win gnus nnheader range ol-docview doc-view jka-compr image-mode exif ol-bibtex bibtex iso8601 ol-bbdb ol-w3m ol-doi org-link-doi org-agenda embark-org org-element org-persist org-id org-refile org-element-ast inline avl-tree generator org ob ob-tangle ob-ref ob-lob ob-table ob-exp org-macro org-src sh-script smie treesit executable ob-comint org-pcomplete org-list org-footnote org-faces org-entities noutline outline org-version ob-emacs-lisp ob-core ob-eval org-cycle org-table ol org-fold org-fold-core org-keys oc org-loaddefs find-func org-compat org-macs notifications dbus compile xml appt diary-lib diary-loaddefs cal-menu calendar cal-loaddefs project magit-extras magit-bookmark magit-submodule magit-blame magit-stash magit-reflog magit-bisect magit-push magit-pull magit-fetch magit-clone magit-remote magit-commit magit-sequence magit-notes magit-worktree magit-tag magit-merge magit-branch magit-reset magit-files magit-refs magit-status magit magit-repos magit-apply magit-wip magit-log which-func imenu magit-diff smerge-mode diff diff-mode track-changes git-commit log-edit message sendmail yank-media puny rfc822 mml mml-sec epa derived epg rfc6068 epg-config gnus-util time-date mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047 rfc2045 mm-util ietf-drums mail-prsvr mailabbrev mail-utils gmm-utils mailheader pcvs-util add-log magit-core magit-autorevert autorevert filenotify magit-margin magit-transient magit-process with-editor comp comp-cstr warnings comp-run comp-common rx shell pcomplete comint ansi-osc server ansi-color magit-mode transient benchmark magit-git magit-base magit-section format-spec cursor-sensor crm dash recentf tree-widget gnus-dired dired dired-loaddefs embark-consult embark ffap consult orderless marginalia cape corfu vertico-mouse vertico-directory vertico mb-depth jinx tempel compat delsel fontaine goto-addr modus-operandi-theme pulsar pulse color cus-edit cus-load wid-edit lin hl-line face-remap modus-themes ef-themes edmacro kmacro use-package-bind-key bind-key easy-mmode use-package-ensure use-package-core burly frameset thingatpt bookmark text-property-search pp winner ace-window avy ring repeat visual-wrap word-wrap-mode ace-window-autoloads auctex-autoloads tex-site avy-autoloads bbdb-autoloads beframe-autoloads bufferlo-autoloads burly-autoloads cape-autoloads citar-embark-autoloads citar-autoloads citeproc-autoloads corfu-autoloads cursory-autoloads debbugs-autoloads dired-preview-autoloads eat-autoloads ebdb-autoloads cl-extra help-mode ef-themes-autoloads elfeed-autoloads embark-consult-autoloads consult-autoloads embark-autoloads expand-region-autoloads f-autoloads fontaine-autoloads forge-autoloads closql-autoloads emacsql-autoloads fountain-mode-autoloads ghub-autoloads gnuplot-autoloads htmlize-autoloads hyperbole-autoloads kotl-autoloads hact set hhist indent-bars-autoloads jinx-autoloads ledger-mode-autoloads lin-autoloads magit-autoloads pcase magit-section-autoloads dash-autoloads marginalia-autoloads markdown-mode-autoloads modus-themes-autoloads nov-autoloads esxml-autoloads kv-autoloads ol-notmuch-autoloads notmuch-autoloads orderless-autoloads org-contacts-autoloads org-modern-autoloads osm-autoloads parsebib-autoloads pdf-tools-autoloads popper-autoloads pulsar-autoloads queue-autoloads rainbow-mode-autoloads s-autoloads spacious-padding-autoloads string-inflection-autoloads tablist-autoloads tempel-autoloads tmr-autoloads transient-autoloads trashed-autoloads treepy-autoloads vertico-autoloads visual-fill-column-autoloads wgrep-autoloads info with-editor-autoloads yaml-autoloads xdg package browse-url url url-proxy url-privacy url-expand url-methods url-history url-cookie generate-lisp-file url-domsuf url-util mailcap url-handlers url-parse auth-source cl-seq eieio eieio-core cl-macs icons password-cache json subr-x map byte-opt gv bytecomp byte-compile url-vars cl-loaddefs cl-lib rmc iso-transl tooltip cconv eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type elisp-mode mwheel term/x-win x-win term/common-win x-dnd touch-screen tool-bar dnd fontset image regexp-opt fringe tabulated-list replace newcomment text-mode lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow isearch easymenu timer select scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors frame minibuffer nadvice seq simple cl-generic indonesian philippine cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech european ethiopic indian cyrillic chinese composite emoji-zwj charscript charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs theme-loaddefs faces cus-face macroexp files window text-properties overlay sha1 md5 base64 format env code-pages mule custom widget keymap hashtable-print-readable backquote threads dbusbind inotify lcms2 dynamic-setting system-font-setting font-render-setting cairo x-toolkit xinput2 x multi-tty move-toolbar make-network-process native-compile emacs) Memory information: ((conses 16 641073 63157) (symbols 48 42867 0) (strings 32 167248 6724) (string-bytes 1 5185552) (vectors 16 237023) (vector-slots 8 3126477 384380) (floats 8 770 1) (intervals 56 1489 224) (buffers 992 15)) ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-02 13:04 bug#73600: 31.0.50; Visual wrap prefix mode and image display Gautier Ponsinet @ 2024-10-02 15:42 ` Eli Zaretskii 2024-10-02 15:51 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-02 15:42 UTC (permalink / raw) To: Gautier Ponsinet; +Cc: 73600 > From: Gautier Ponsinet <gautier@gautierponsinet.xyz> > Date: Wed, 02 Oct 2024 16:04:02 +0300 > > The mode visual-wrap-prefix-mode sometimes causes images to be displayed > incorrectly in emacs (in a mosaic-like way). > > To this mail are attached screenshots of the problem and the image file > used in the screenshots which is from wikipedia: > - https://fr.wikipedia.org/wiki/Olcades > - https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Iberia_300BC-fr.svg/langfr-873px-Iberia_300BC-fr.svg.png > > Note that the error does not occur with all images, but I am not able to > identify which characteristic of an image makes the problem appear. > Still, I have encountered the problem with multiple image files. > > In emacs -Q: > - enable global-visual-wrap-prefix-mode, > - open the attached image. In a build with --enable-checking, this recipe causes an assertion violation: dispnew.c:4495: Emacs fatal error: assertion failed: entry || verify_row_hash (row) Thread 1 hit Breakpoint 1, terminate_due_to_signal (sig=22, backtrace_limit=2147483647) at emacs.c:432 432 signal (sig, SIG_DFL); (gdb) bt #0 terminate_due_to_signal (sig=22, backtrace_limit=2147483647) at emacs.c:432 #1 0x0053d2d9 in die (msg=0xc11c78 "entry || verify_row_hash (row)", file=0xc112a7 "dispnew.c", line=4495) at alloc.c:8057 #2 0x0032d713 in add_row_entry (row=0xb9ed5b0) at dispnew.c:4495 #3 0x0032df1d in scrolling_window (w=0xb941fb8, tab_line_p=0) at dispnew.c:4735 #4 0x0032b8eb in update_window (w=0xb941fb8, force_p=true) at dispnew.c:3735 #5 0x0032ae0d in update_window_tree (w=0xb941fb8, force_p=true) at dispnew.c:3491 #6 0x0032a6b4 in update_frame (f=0xb941d98, force_p=true, inhibit_hairy_id_p=false) at dispnew.c:3326 #7 0x0038ac25 in redisplay_internal () at xdisp.c:17561 #8 0x003883e7 in redisplay () at xdisp.c:16656 #9 0x0048a5f0 in read_char (commandflag=1, map=XIL(0xc000000005788d00), prev_event=XIL(0), used_mouse_menu=0x61ff31f, end_time=0x0) at keyboard.c:2673 #10 0x004a2c09 in read_key_sequence (keybuf=0x61ff5f8, prompt=XIL(0), dont_downcase_last=false, can_return_switch_frame=true, fix_current_buffer=true, prevent_redisplay=false, disable_text_conversion_p=false) at keyboard.c:10747 #11 0x004861cb in command_loop_1 () at keyboard.c:1424 #12 0x0057b9ad in internal_condition_case (bfun=0x485b69 <command_loop_1>, handlers=XIL(0x90), hfun=0x484bc2 <cmd_error>) at eval.c:1598 #13 0x004855ce in command_loop_2 (handlers=XIL(0x90)) at keyboard.c:1163 #14 0x0057ab2a in internal_catch (tag=XIL(0x12750), func=0x485597 <command_loop_2>, arg=XIL(0x90)) at eval.c:1277 #15 0x00485539 in command_loop () at keyboard.c:1141 #16 0x00484622 in recursive_edit_1 () at keyboard.c:749 #17 0x004848c0 in Frecursive_edit () at keyboard.c:832 #18 0x0047f9e1 in main (argc=2, argv=0x6322570) at emacs.c:2624 ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-02 15:42 ` Eli Zaretskii @ 2024-10-02 15:51 ` Eli Zaretskii 2024-10-02 16:37 ` Jim Porter 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-02 15:51 UTC (permalink / raw) To: gautier, Jim Porter; +Cc: 73600 > Cc: 73600@debbugs.gnu.org > Date: Wed, 02 Oct 2024 18:42:09 +0300 > From: Eli Zaretskii <eliz@gnu.org> > > > In emacs -Q: > > - enable global-visual-wrap-prefix-mode, > > - open the attached image. > > In a build with --enable-checking, this recipe causes an assertion > violation: And in Emacs 30 the problem doesn't happen, so this is some regression in Emacs 31. Probably due to my changes in commit 71505b723c9f. Jim, any ideas or suggestions? ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-02 15:51 ` Eli Zaretskii @ 2024-10-02 16:37 ` Jim Porter 2024-10-03 10:59 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Jim Porter @ 2024-10-02 16:37 UTC (permalink / raw) To: Eli Zaretskii, gautier; +Cc: 73600 On 10/2/2024 8:51 AM, Eli Zaretskii wrote: >> Cc: 73600@debbugs.gnu.org >> Date: Wed, 02 Oct 2024 18:42:09 +0300 >> From: Eli Zaretskii <eliz@gnu.org> >> >>> In emacs -Q: >>> - enable global-visual-wrap-prefix-mode, >>> - open the attached image. >> >> In a build with --enable-checking, this recipe causes an assertion >> violation: > > And in Emacs 30 the problem doesn't happen, so this is some regression > in Emacs 31. Probably due to my changes in commit 71505b723c9f. > > Jim, any ideas or suggestions? Strangely, this doesn't seem to happen with every image. "splash.png" in the Emacs repository seems fine, for example. I also can't reproduce this in EWW, even by opening the bad image reported in this bug. I haven't looked into the internals of 'image-mode' very much, but I seem to recall that it has some special code for scrolling large images. Maybe that's interacting in some bad way with 'visual-wrap-prefix-mode'? One option might simply be to disable 'visual-wrap-prefix-mode' when in 'image-mode'. I don't think wrapping is useful in that context. ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-02 16:37 ` Jim Porter @ 2024-10-03 10:59 ` Eli Zaretskii 2024-10-04 0:28 ` Jim Porter 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-03 10:59 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Wed, 2 Oct 2024 09:37:48 -0700 > Cc: 73600@debbugs.gnu.org > From: Jim Porter <jporterbugs@gmail.com> > > On 10/2/2024 8:51 AM, Eli Zaretskii wrote: > >> Cc: 73600@debbugs.gnu.org > >> Date: Wed, 02 Oct 2024 18:42:09 +0300 > >> From: Eli Zaretskii <eliz@gnu.org> > >> > >>> In emacs -Q: > >>> - enable global-visual-wrap-prefix-mode, > >>> - open the attached image. > >> > >> In a build with --enable-checking, this recipe causes an assertion > >> violation: > > > > And in Emacs 30 the problem doesn't happen, so this is some regression > > in Emacs 31. Probably due to my changes in commit 71505b723c9f. > > > > Jim, any ideas or suggestions? > > Strangely, this doesn't seem to happen with every image. "splash.png" in > the Emacs repository seems fine, for example. I also can't reproduce > this in EWW, even by opening the bad image reported in this bug. It depends on the width of the image and the contents of the image file. As soon as you show an image whose width is as wide or wider than the window, and/or the image file has newline bytes, the problem happens. Note that displaying splash.svg, the problem happens even through the image is not wider than window. See below why this happens. I've installed a fix which avoids the assertion violations in a build with --enable-checking. The fix does not prevent displaying multiple images where only one should be shown -- because that is AFAICT a bug in visual-wrap-prefix-mode, and should be fixed there. What happens is that displaying an image in a buffer places a single 'display' property on the entire buffer portion which should be displayed as an image, and the value of the property is the image spec. When visiting an image file, that property spans the entire contents of the image file. In this situation, the analysis of the buffer text performed by visual-wrap-prefix-mode, which looks at newlines and tries to calculate the suitable prefix width, is completely wrong, because the buffer text (which are just the raw bytes of the image file) is replaced on display by the image, and the calculations based on character width are all wrong. But visual-wrap-prefix-mode does still make these calculations, and as result could decide that a min-width or align-to display property should be placed on some of these raw bytes. Since visual-wrap-prefix-mode uses add-display-text-property, which attempts to keep existing display properties when it adds additional ones, the result is that the single display property that specified the image is broken into several identical display properties (some with min-width added, some without). And since these properties are not 'eq' to one another, Emacs shows N identical images instead of just one. The solution, I think, is for visual-wrap-prefix-mode to ignore text that is covered by replacing display properties, at least those which specify images, xwidgets, fringes, etc. It's possible that we want to pay attention to display properties whose value is a string, but not to any others. > One option might simply be to disable 'visual-wrap-prefix-mode' when in > 'image-mode'. I don't think wrapping is useful in that context. I think image-mode is just the tip of a very large iceberg, see above: visual-wrap-prefix-mode currently doesn't correctly handle text covered by display properties. I think this bug was uncovered when we removed on master a few artificial restrictions on min-width, such as non-support for display and overlay strings. ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-03 10:59 ` Eli Zaretskii @ 2024-10-04 0:28 ` Jim Porter 2024-10-04 6:28 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Jim Porter @ 2024-10-04 0:28 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 597 bytes --] On 10/3/2024 3:59 AM, Eli Zaretskii wrote: > I think image-mode is just the tip of a very large iceberg, see above: > visual-wrap-prefix-mode currently doesn't correctly handle text > covered by display properties. I think this bug was uncovered when we > removed on master a few artificial restrictions on min-width, such as > non-support for display and overlay strings. How about something like this? With this patch, if the start of a line is in the middle of a multi-line display spec, 'visual-wrap-prefix-mode' won't add any new properties. That should prevent any unwanted interference. [-- Attachment #2: 0001-Don-t-add-visual-wrap-prefix-properties-to-multiline.patch --] [-- Type: text/plain, Size: 1756 bytes --] From 432ac5b4fd4a5f15b0ae59d739ed2dd2b2e38a05 Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to multiline display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--apply-to-line): Bail out if we're in the middle of a multi-line display spec --- lisp/visual-wrap.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..af0b4fc8b0f 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -117,7 +117,16 @@ visual-wrap--apply-to-line "Apply visual-wrapping properties to the logical line starting at POSITION." (save-excursion (goto-char position) - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) + (when-let (;; Don't add wrapping properties if we're in the middle + ;; of a multi-line display spec; it could be doing + ;; absolutely anything, and we don't want to interfere. + ;; (For example, this could be an image containing a + ;; newline-byte being displayed in `image-mode'; see + ;; bug#73600.) + ((or (= position (point-min)) + (not (eq (get-char-property position 'display) + (get-char-property (1- position) 'display))))) + (first-line-prefix (fill-match-adaptive-prefix)) (next-line-prefix (visual-wrap--content-prefix first-line-prefix position))) (when (numberp next-line-prefix) -- 2.25.1 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-04 0:28 ` Jim Porter @ 2024-10-04 6:28 ` Eli Zaretskii 2024-10-04 19:45 ` Jim Porter 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-04 6:28 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Thu, 3 Oct 2024 17:28:14 -0700 > Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz > From: Jim Porter <jporterbugs@gmail.com> > > On 10/3/2024 3:59 AM, Eli Zaretskii wrote: > > I think image-mode is just the tip of a very large iceberg, see above: > > visual-wrap-prefix-mode currently doesn't correctly handle text > > covered by display properties. I think this bug was uncovered when we > > removed on master a few artificial restrictions on min-width, such as > > non-support for display and overlay strings. > > How about something like this? With this patch, if the start of a line > is in the middle of a multi-line display spec, 'visual-wrap-prefix-mode' > won't add any new properties. That should prevent any unwanted interference. Something like that, yes. But unfortunately, it's more complicated, see below. > --- a/lisp/visual-wrap.el > +++ b/lisp/visual-wrap.el > @@ -117,7 +117,16 @@ visual-wrap--apply-to-line > "Apply visual-wrapping properties to the logical line starting at POSITION." > (save-excursion > (goto-char position) > - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) > + (when-let (;; Don't add wrapping properties if we're in the middle > + ;; of a multi-line display spec; it could be doing > + ;; absolutely anything, and we don't want to interfere. > + ;; (For example, this could be an image containing a > + ;; newline-byte being displayed in `image-mode'; see > + ;; bug#73600.) > + ((or (= position (point-min)) ^^^^^^^^^^^^^^^^^^^^^^^^ A.k.a. (bobp), right? Since point is already at position. > + (not (eq (get-char-property position 'display) > + (get-char-property (1- position) 'display))))) I think this is too radical. There are 'display' properties that do not replace the text: (space PROPS), (height HEIGHT), (raise FACT), and a few others. These should not prevent adding the wrapping properties, I think. The challenge is to detect the presence of "allowed" properties and the absence of "forbidden" ones, since a 'display' property's spec could be a list or a vector of several separate values. So the code will need to look inside the list/vector and examine each element. We could also start with the above and document that lines that begin with any 'display' property will not be visually aligned/wrapped -- maybe this is not a severe restriction in practice. ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-04 6:28 ` Eli Zaretskii @ 2024-10-04 19:45 ` Jim Porter 2024-10-05 6:41 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Jim Porter @ 2024-10-04 19:45 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 1884 bytes --] On 10/3/2024 11:28 PM, Eli Zaretskii wrote: > A.k.a. (bobp), right? Since point is already at position. Fixed. Since point is at position, I've modified everything in the function to use (point). That way, there's no risk of confusion if someone looks at this years later and wonders why some parts use "point" while others use "position". > I think this is too radical. There are 'display' properties that do > not replace the text: (space PROPS), (height HEIGHT), (raise FACT), > and a few others. These should not prevent adding the wrapping > properties, I think. The challenge is to detect the presence of > "allowed" properties and the absence of "forbidden" ones, since a > 'display' property's spec could be a list or a vector of several > separate values. So the code will need to look inside the list/vector > and examine each element. I think in addition to that, it's allowed if the display property has changed at the beginning (or end) of the line. For example, if I were to use the 'image' spec to render an image in-line with some other text, then 'visual-wrap-prefix-mode' can set the wrap-prefix as usual with no issues. The simpler case is when the image is at the end: * Here is some text. [IMG] That should get the wrap prefix. The reverse is also true, though it's more complex: [IMG] Here is some text. A mode that does something like that should set 'adaptive-fill-function' to handle this case and return an appropriate fill-prefix. Then 'visual-wrap-prefix-mode' would do the right thing. For example, maybe EWW could do this to support images for bullet points (I'm not sure this is something we *actually* want to do in EWW, but hopefully it serves as an example.) To be on the safe side, I'm only marking 'height' and 'raise' as safe for now, but adding new safe specs should just be a matter of putting them in the list. [-- Attachment #2: 0001-Don-t-add-visual-wrap-prefix-properties-to-multi-lin.patch --] [-- Type: text/plain, Size: 4414 bytes --] From d4d97008452893c0043d585a2730300142f0b32d Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Bail out if we're in the middle of an unsafe multi-line display spec. --- lisp/visual-wrap.el | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..63d6510f2e7 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,34 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes.") + +(defun visual-wrap--display-property-safe-p (position offset) + "Return non-nil if the display property at POSITION is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs', and won't interfere with +the additional text properties that `visual-wrap-prefix-mode' uses. + +OFFSET should be 1 or -1 and tells which direction to look to see if the +display property has changed. If it has changed, then the display +property is always considered safe: since POSITION is at the +beginning (or end) of that property, our additional text properties +don't cause problems." + (let ((pos-limit (if (= offset 1) (point-max) (point-min))) + (display (get-char-property position 'display))) + (or (= position pos-limit) + (not display) + (not (eq (get-char-property (+ position offset) 'display) display)) + (when (or (vectorp display) (listp display)) + (unless (listp (car display)) (setq display (list display))) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq spec visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -117,9 +145,16 @@ visual-wrap--apply-to-line "Apply visual-wrapping properties to the logical line starting at POSITION." (save-excursion (goto-char position) - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) + (when-let ((eol (pos-eol)) + ;; Don't add wrapping properties if we're in the middle + ;; of an unsafe multi-line display spec. (For example, + ;; this could be an image containing a newline-byte being + ;; displayed in `image-mode'; see bug#73600.) + ((visual-wrap--display-property-safe-p (point) -1)) + ((visual-wrap--display-property-safe-p eol 1)) + (first-line-prefix (fill-match-adaptive-prefix)) (next-line-prefix (visual-wrap--content-prefix - first-line-prefix position))) + first-line-prefix (point)))) (when (numberp next-line-prefix) ;; Set a minimum width for the prefix so it lines up correctly ;; with subsequent lines. Make sure not to do this past the end @@ -127,12 +162,12 @@ visual-wrap--apply-to-line ;; potentially return a prefix longer than the current line in ;; the buffer.) (add-display-text-property - position (min (+ position (length first-line-prefix)) - (line-end-position)) + (point) (min (+ (point) (length first-line-prefix)) + eol) '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 + (point) eol 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) -- 2.25.1 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-04 19:45 ` Jim Porter @ 2024-10-05 6:41 ` Eli Zaretskii 2024-10-05 18:07 ` Jim Porter 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-05 6:41 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Fri, 4 Oct 2024 12:45:10 -0700 > Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz > From: Jim Porter <jporterbugs@gmail.com> > > > I think this is too radical. There are 'display' properties that do > > not replace the text: (space PROPS), (height HEIGHT), (raise FACT), > > and a few others. These should not prevent adding the wrapping > > properties, I think. The challenge is to detect the presence of > > "allowed" properties and the absence of "forbidden" ones, since a > > 'display' property's spec could be a list or a vector of several > > separate values. So the code will need to look inside the list/vector > > and examine each element. > > I think in addition to that, it's allowed if the display property has > changed at the beginning (or end) of the line. For example, if I were to > use the 'image' spec to render an image in-line with some other text, > then 'visual-wrap-prefix-mode' can set the wrap-prefix as usual with no > issues. The simpler case is when the image is at the end: > > * Here is some text. [IMG] > > That should get the wrap prefix. I think you will find that we always clip images at window edges, so the above might be trickier. Did you actually try that? How do you get at the width of the image actually displayed in that case? > The reverse is also true, though it's more complex: > > [IMG] Here is some text. What do you expect to happen here, and how does the code handle this, especially the visual width of the image? > +(defun visual-wrap--display-property-safe-p (position offset) > + "Return non-nil if the display property at POSITION is \"safe\". > +A \"safe\" display property is one where all the display specs are > +members of `visual-wrap--safe-display-specs', and won't interfere with > +the additional text properties that `visual-wrap-prefix-mode' uses. This should mention the fact that some 'display' properties replace the text and some don't, otherwise the above doesn't really explain the underlying issue. The text reads as if it says '"Safe" display properties are those which are safe and don't interfere with adding more properties'. Without more details about the cause this is not helpful. > + (when (or (vectorp display) (listp display)) > + (unless (listp (car display)) (setq display (list display))) > + (not (catch 'unsafe > + (mapc (lambda (spec) > + (unless (memq spec visual-wrap--safe-display-specs) > + (throw 'unsafe t))) > + display))))))) I'm not sure I understand this. DISPLAY here could also be a single value, not a list or a vector, but in that case you don't test it against the "safe" specs? Also, (car display) will signal an error if DISPLAY is a vector, but in any case, what is the significance of the first element of DISPLAY being a list? I'm probably missing something. > - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) > + (when-let ((eol (pos-eol)) > + ;; Don't add wrapping properties if we're in the middle > + ;; of an unsafe multi-line display spec. (For example, > + ;; this could be an image containing a newline-byte being > + ;; displayed in `image-mode'; see bug#73600.) > + ((visual-wrap--display-property-safe-p (point) -1)) > + ((visual-wrap--display-property-safe-p eol 1)) > + (first-line-prefix (fill-match-adaptive-prefix)) I'm not sure I can reason about this logic. I'd prefer the code to completely skip text covered by "unsafe" display properties, because reasoning about that text makes no sense: it will not show on display. Specifically, for the "text" that is the bytes of the image file, I don't know how to begin reasoning about newlines embedded in those bytes. Thus, it is hard for me to tell if I agree with the above logic or not. Thanks. ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-05 6:41 ` Eli Zaretskii @ 2024-10-05 18:07 ` Jim Porter 2024-10-06 0:28 ` Jim Porter 2024-10-12 12:00 ` Eli Zaretskii 0 siblings, 2 replies; 18+ messages in thread From: Jim Porter @ 2024-10-05 18:07 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 7335 bytes --] On 10/4/2024 11:41 PM, Eli Zaretskii wrote: >> Date: Fri, 4 Oct 2024 12:45:10 -0700 >> Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz >> From: Jim Porter <jporterbugs@gmail.com> >> >> * Here is some text. [IMG] >> >> That should get the wrap prefix. > > I think you will find that we always clip images at window edges, so > the above might be trickier. Did you actually try that? How do you > get at the width of the image actually displayed in that case? Yes, see attached. "image.html" is a simple page testing both cases, which you can try out with something like this: (progn (setq ;; Show our image inline. shr-max-inline-image-size (cons 30 30) ;; Don't hard-wrap text; use 'visual-line-mode' and ;; 'visual-wrap-prefix-mode'. shr-fill-text nil) (eww "file:///path/to/image.html")) I've also attached a screenshot showing this in action. For this first case, I don't think the width of the image matters to 'visual-wrap-prefix-mode'; the width we want to get here is the width of the prefix string "* ". >> The reverse is also true, though it's more complex: >> >> [IMG] Here is some text. > > What do you expect to happen here, and how does the code handle this, > especially the visual width of the image? Assuming the mode sets 'adaptive-fill-function' correctly, I'd expect to see something like the second block of the attached screenshot, or for a textual approximation, something like this: [IMG] Here is some text, and here is some more. In the attached HTML page, we get the above behavior automatically because SHR inserts the string "*" into the buffer to add the image display spec to. As a result, the fill prefix is "* " (with the "*" having the display spec). Once 'visual-wrap-prefix-mode' passes that to 'string-pixel-width', we get the actual pixel width we expect. As you note, this doesn't quite work if Emacs clips the image; in this example, 'string-pixel-width' just returns the width of the space after the "*" with the image spec. However, the only "problem" this causes is that the wrap prefix is smaller than otherwise expected. In practice, this might even be a *good* thing since it ensures that the wrap prefix isn't wider than the window width. However, note that this case only comes up if the image is *also* on the same logical line as some other text without an image spec. For SHR/EWW, this shouldn't happen unless a user sets 'shr-max-inline-image-size' to some large value (it's nil by default) *and* 'shr-max-image-proportion' to some value >1 (it's 0.9 by default). For 'image-mode', this doesn't apply at all since the entire buffer text has the image spec. Are there other test cases I should look at? >> +(defun visual-wrap--display-property-safe-p (position offset) >> + "Return non-nil if the display property at POSITION is \"safe\". >> +A \"safe\" display property is one where all the display specs are >> +members of `visual-wrap--safe-display-specs', and won't interfere with >> +the additional text properties that `visual-wrap-prefix-mode' uses. > > This should mention the fact that some 'display' properties replace > the text and some don't, otherwise the above doesn't really explain > the underlying issue. The text reads as if it says '"Safe" display > properties are those which are safe and don't interfere with adding > more properties'. Without more details about the cause this is not > helpful. Ok, done. (I moved this explanation to the defvar since that's the place where people might change the setting.) > >> + (when (or (vectorp display) (listp display)) >> + (unless (listp (car display)) (setq display (list display))) >> + (not (catch 'unsafe >> + (mapc (lambda (spec) >> + (unless (memq spec visual-wrap--safe-display-specs) >> + (throw 'unsafe t))) >> + display))))))) > > I'm not sure I understand this. DISPLAY here could also be a single > value, not a list or a vector, but in that case you don't test it > against the "safe" specs? Also, (car display) will signal an error if > DISPLAY is a vector, but in any case, what is the significance of the > first element of DISPLAY being a list? I'm probably missing > something. In the case where DISPLAY is a single value (e.g. a string), we just treat it as unsafe. This code is an attempt to adapt the logic from find_display_property. I've added some comments explaining the logic and fixed the issue you mentioned. If this way looks reasonable, I could add some regression tests for it. Alternately, maybe there should be some function that "normalizes" the display property so that it's always a list of specs (or something like that). Then it would be easier to write a function like I did here that just wants to ask, "Are all the specs on my safe list?" >> - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) >> + (when-let ((eol (pos-eol)) >> + ;; Don't add wrapping properties if we're in the middle >> + ;; of an unsafe multi-line display spec. (For example, >> + ;; this could be an image containing a newline-byte being >> + ;; displayed in `image-mode'; see bug#73600.) >> + ((visual-wrap--display-property-safe-p (point) -1)) >> + ((visual-wrap--display-property-safe-p eol 1)) >> + (first-line-prefix (fill-match-adaptive-prefix)) > > I'm not sure I can reason about this logic. I'd prefer the code to > completely skip text covered by "unsafe" display properties, because > reasoning about that text makes no sense: it will not show on display. > Specifically, for the "text" that is the bytes of the image file, I > don't know how to begin reasoning about newlines embedded in those > bytes. Thus, it is hard for me to tell if I agree with the above > logic or not. The idea here is this: 1. If we have some display property that spans multiple lines (either from the previous line onto our current line, or from the current line onto the next), then we need to be careful and check the specs of this multi-line display property. a. If the multi-line display prop is something safe (e.g. 'raise'), we can proceed with adding our visual-wrap props. b. Otherwise, we can't safely add visual-wrap props, so we bail out. 2. If no display property spans multiple lines, then any display props on our line are *only* on our line, so we can add the visual-wrap props without breaking the 'eq'-ness of any existing display props. That's there to allow the behavior as shown in "image.html". For the image-mode case, if we have one newline-byte, then we hit condition (1): we have the same display property on both the first and second lines. Then we examine each display spec, and find that there's one that's not on the safe list (b), so we bail out. I agree that this logic is fairly complex, and I'm happy to rearrange it or add comments as needed once we're on the same page. I'm also ok with just saying "any multi-line display properties are assumed to be unsafe" for now. That's simpler, and we could add this extra logic later if someone notices a specific issue. [-- Attachment #2: 0001-Don-t-add-visual-wrap-prefix-properties-to-multi-lin.patch --] [-- Type: text/plain, Size: 4414 bytes --] From d4d97008452893c0043d585a2730300142f0b32d Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Bail out if we're in the middle of an unsafe multi-line display spec. --- lisp/visual-wrap.el | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..63d6510f2e7 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,34 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes.") + +(defun visual-wrap--display-property-safe-p (position offset) + "Return non-nil if the display property at POSITION is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs', and won't interfere with +the additional text properties that `visual-wrap-prefix-mode' uses. + +OFFSET should be 1 or -1 and tells which direction to look to see if the +display property has changed. If it has changed, then the display +property is always considered safe: since POSITION is at the +beginning (or end) of that property, our additional text properties +don't cause problems." + (let ((pos-limit (if (= offset 1) (point-max) (point-min))) + (display (get-char-property position 'display))) + (or (= position pos-limit) + (not display) + (not (eq (get-char-property (+ position offset) 'display) display)) + (when (or (vectorp display) (listp display)) + (unless (listp (car display)) (setq display (list display))) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq spec visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -117,9 +145,16 @@ visual-wrap--apply-to-line "Apply visual-wrapping properties to the logical line starting at POSITION." (save-excursion (goto-char position) - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) + (when-let ((eol (pos-eol)) + ;; Don't add wrapping properties if we're in the middle + ;; of an unsafe multi-line display spec. (For example, + ;; this could be an image containing a newline-byte being + ;; displayed in `image-mode'; see bug#73600.) + ((visual-wrap--display-property-safe-p (point) -1)) + ((visual-wrap--display-property-safe-p eol 1)) + (first-line-prefix (fill-match-adaptive-prefix)) (next-line-prefix (visual-wrap--content-prefix - first-line-prefix position))) + first-line-prefix (point)))) (when (numberp next-line-prefix) ;; Set a minimum width for the prefix so it lines up correctly ;; with subsequent lines. Make sure not to do this past the end @@ -127,12 +162,12 @@ visual-wrap--apply-to-line ;; potentially return a prefix longer than the current line in ;; the buffer.) (add-display-text-property - position (min (+ position (length first-line-prefix)) - (line-end-position)) + (point) (min (+ (point) (length first-line-prefix)) + eol) '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 + (point) eol 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) -- 2.25.1 [-- Attachment #3: image.html --] [-- Type: text/html, Size: 3166 bytes --] [-- Attachment #4: rendered.png --] [-- Type: image/png, Size: 26584 bytes --] ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-05 18:07 ` Jim Porter @ 2024-10-06 0:28 ` Jim Porter 2024-10-12 12:00 ` Eli Zaretskii 1 sibling, 0 replies; 18+ messages in thread From: Jim Porter @ 2024-10-06 0:28 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 135 bytes --] On 10/5/2024 11:07 AM, Jim Porter wrote: > Yes, see attached. It would probably help if I attached the current version of my patch... [-- Attachment #2: 0001-Don-t-add-visual-wrap-prefix-properties-to-multi-lin.patch --] [-- Type: text/plain, Size: 5056 bytes --] From aa679b0945ffb298a8556a60e163727acb7fe68f Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Bail out if we're in the middle of an unsafe multi-line display spec. --- lisp/visual-wrap.el | 57 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..d798302d091 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,46 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes. +A \"safe\" display spec is one that won't interfere with the additional +text properties that `visual-wrap-prefix-mode' uses. + +Specs that replace the text are unsafe, since they generally determine +the range of text to replace via `eq'. If `visual-wrap-prefix-mode' +were to add text properties to some subset of this range, it would +violate this assumption.") + +(defun visual-wrap--display-property-safe-p (position offset) + "Return non-nil if the display property at POSITION is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs' (which see). + +OFFSET should be 1 or -1 and tells which direction to look to see if the +display property has changed. If it has changed, then the display +property is always considered safe: since POSITION is at the +beginning (or end) of that property, our additional text properties +don't cause problems." + (let ((pos-limit (if (= offset 1) (point-max) (point-min))) + (display (get-char-property position 'display))) + (or (= position pos-limit) + (not display) + (not (eq (get-char-property (+ position offset) 'display) display)) + ;; Iterate over all the display specs to check if they're safe. + ;; Assume any display property other than a vector or list + ;; (e.g. a string) is unsafe. + (when (or (vectorp display) (listp display)) + ;; The display property could be a single display spec; if so, + ;; wrap it in a list to iterate over. + (unless (consp (car-safe display)) (setq display (list display))) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq (car-safe spec) + visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -117,9 +157,16 @@ visual-wrap--apply-to-line "Apply visual-wrapping properties to the logical line starting at POSITION." (save-excursion (goto-char position) - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) + (when-let ((eol (pos-eol)) + ;; Don't add wrapping properties if we're in the middle + ;; of an unsafe multi-line display spec. (For example, + ;; this could be an image containing a newline-byte being + ;; displayed in `image-mode'; see bug#73600.) + ((visual-wrap--display-property-safe-p (point) -1)) + ((visual-wrap--display-property-safe-p eol 1)) + (first-line-prefix (fill-match-adaptive-prefix)) (next-line-prefix (visual-wrap--content-prefix - first-line-prefix position))) + first-line-prefix (point)))) (when (numberp next-line-prefix) ;; Set a minimum width for the prefix so it lines up correctly ;; with subsequent lines. Make sure not to do this past the end @@ -127,12 +174,12 @@ visual-wrap--apply-to-line ;; potentially return a prefix longer than the current line in ;; the buffer.) (add-display-text-property - position (min (+ position (length first-line-prefix)) - (line-end-position)) + (point) (min (+ (point) (length first-line-prefix)) + eol) '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 + (point) eol 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) -- 2.25.1 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-05 18:07 ` Jim Porter 2024-10-06 0:28 ` Jim Porter @ 2024-10-12 12:00 ` Eli Zaretskii 2024-10-13 1:36 ` Jim Porter 1 sibling, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-12 12:00 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Sat, 5 Oct 2024 11:07:39 -0700 > Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz > From: Jim Porter <jporterbugs@gmail.com> > > >> - (when-let ((first-line-prefix (fill-match-adaptive-prefix)) > >> + (when-let ((eol (pos-eol)) > >> + ;; Don't add wrapping properties if we're in the middle > >> + ;; of an unsafe multi-line display spec. (For example, > >> + ;; this could be an image containing a newline-byte being > >> + ;; displayed in `image-mode'; see bug#73600.) > >> + ((visual-wrap--display-property-safe-p (point) -1)) > >> + ((visual-wrap--display-property-safe-p eol 1)) > >> + (first-line-prefix (fill-match-adaptive-prefix)) > > > > I'm not sure I can reason about this logic. I'd prefer the code to > > completely skip text covered by "unsafe" display properties, because > > reasoning about that text makes no sense: it will not show on display. > > Specifically, for the "text" that is the bytes of the image file, I > > don't know how to begin reasoning about newlines embedded in those > > bytes. Thus, it is hard for me to tell if I agree with the above > > logic or not. > > The idea here is this: > > 1. If we have some display property that spans multiple lines (either > from the previous line onto our current line, or from the current > line onto the next), then we need to be careful and check the specs > of this multi-line display property. > > a. If the multi-line display prop is something safe (e.g. 'raise'), we > can proceed with adding our visual-wrap props. > > b. Otherwise, we can't safely add visual-wrap props, so we bail out. > > 2. If no display property spans multiple lines, then any display props > on our line are *only* on our line, so we can add the visual-wrap > props without breaking the 'eq'-ness of any existing display props. > That's there to allow the behavior as shown in "image.html". > > For the image-mode case, if we have one newline-byte, then we hit > condition (1): we have the same display property on both the first and > second lines. Then we examine each display spec, and find that there's > one that's not on the safe list (b), so we bail out. > > I agree that this logic is fairly complex, and I'm happy to rearrange it > or add comments as needed once we're on the same page. I'm also ok with > just saying "any multi-line display properties are assumed to be unsafe" > for now. That's simpler, and we could add this extra logic later if > someone notices a specific issue. I'd prefer if we had code that, when it detects an unsafe display property, would completely skip all of it. With the above code, I fond it difficult to reason whether it sometimes considers newlines inside unsafe properties. ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-12 12:00 ` Eli Zaretskii @ 2024-10-13 1:36 ` Jim Porter 2024-10-13 5:36 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Jim Porter @ 2024-10-13 1:36 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 516 bytes --] On 10/12/2024 5:00 AM, Eli Zaretskii wrote: > I'd prefer if we had code that, when it detects an unsafe display > property, would completely skip all of it. With the above code, I > fond it difficult to reason whether it sometimes considers newlines > inside unsafe properties. How about this implementation instead? I think it should do the same thing as my previous implementation, at least in all the cases we care about. I've also added some regression tests to verify that the code works the way we expect. [-- Attachment #2: 0001-Don-t-add-visual-wrap-prefix-properties-to-unsafe-mu.patch --] [-- Type: text/plain, Size: 8220 bytes --] From 48d57c0f8e70e31bb9ed2c6b485d8d5cf2950d99 Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to unsafe multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Use 'pos-eol'; we don't want to respect field boundaries here. (visual-wrap-prefix-function): Check for unsafe display properties at the end of the line and skip past them if present. * test/lisp/visual-wrap-tests.el: New test file. --- lisp/visual-wrap.el | 48 +++++++++++++++++-- test/lisp/visual-wrap-tests.el | 87 ++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 test/lisp/visual-wrap-tests.el diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..26333089189 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,36 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes. +A \"safe\" display spec is one that won't interfere with the additional +text properties that `visual-wrap-prefix-mode' uses. + +Specs that replace the text are unsafe, since they generally determine +the range of text to replace via `eq'. If `visual-wrap-prefix-mode' +were to add text properties to some subset of this range, it would +violate this assumption.") + +(defun visual-wrap--display-property-safe-p (display) + "Return non-nil if the display property DISPLAY is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs' (which see)." + ;; The display property could be a single display spec; if so, wrap it + ;; in a list so we can iterate over it in our loop below. + (when (and (consp display) (not (consp (car display)))) + (setq display (list display))) + ;; Loop over all the display specs to check if they're safe. Assume + ;; any display property other than a vector or list (e.g. a string) is + ;; unsafe. + (when (or (vectorp display) (listp display)) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq (car-safe spec) + visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -128,11 +158,11 @@ visual-wrap--apply-to-line ;; the buffer.) (add-display-text-property position (min (+ position (length first-line-prefix)) - (line-end-position)) + (pos-eol)) '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 + position (pos-eol) 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) @@ -206,8 +236,18 @@ visual-wrap-prefix-function (forward-line 0) (setq beg (point)) (while (< (point) end) - (visual-wrap--apply-to-line (point)) - (forward-line)) + ;; Check if the display property at the end of this line is "safe". + (if (visual-wrap--display-property-safe-p + (get-char-property (pos-eol) 'display)) + ;; If so, we can apply our visual wrapping properties to this + ;; line and continue to the next line. + (progn + (visual-wrap--apply-to-line (point)) + (forward-line)) + ;; Otherwise, skip ahead to the beginning of the first line after + ;; the unsafe display property changes. + (goto-char (next-single-char-property-change (pos-eol) 'display)) + (unless (bolp) (forward-line 1)))) `(jit-lock-bounds ,beg . ,end)) ;;;###autoload diff --git a/test/lisp/visual-wrap-tests.el b/test/lisp/visual-wrap-tests.el new file mode 100644 index 00000000000..5cc4a8ac8a7 --- /dev/null +++ b/test/lisp/visual-wrap-tests.el @@ -0,0 +1,87 @@ +;;; visual-wrap-tests.el --- Tests for `visual-wrap-prefix-mode' -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tets for `visual-wrap-prefix-mode'. + +;;; Code: + +(require 'visual-wrap) +(require 'ert) + +;;; Tests: + +(ert-deftest visual-wrap-tests/simple () + "Test adding wrapping properties to text without display properties." + (with-temp-buffer + (insert "greetings\n* hello\n* hi") + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("greetings\n* hello\n* hi" + 10 12 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 12 17 ( wrap-prefix (space :align-to (2 . width))) + 18 20 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 20 22 ( wrap-prefix (space :align-to (2 . width)))))))) + +(ert-deftest visual-wrap-tests/safe-display () + "Test adding wrapping properties to text with safe display properties." + (with-temp-buffer + (insert #("* hello" 2 7 (display (raise 1)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (message "%S" (buffer-string)) + (should (equal-including-properties + (buffer-string) + #("* hello" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (raise 1))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/within-line () + "Test adding wrapping properties to text with unsafe display properties. +When these properties don't extend across multiple lines, +`visual-wrap-prefix-mode' can still add wrapping properties." + (with-temp-buffer + (insert #("* [img]" 2 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (message "%S" (buffer-string)) + (should (equal-including-properties + (buffer-string) + #("* [img]" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/spanning-lines () + "Test adding wrapping properties to text with unsafe display properties. +When these properties do extend across multiple lines, +`visual-wrap-prefix-mode' must avoid adding wrapping properties." + (with-temp-buffer + (insert #("* a\n* b" 0 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b" 0 7 (display (image :type bmp))))))) + +;; visual-wrap-tests.el ends here -- 2.25.1 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-13 1:36 ` Jim Porter @ 2024-10-13 5:36 ` Eli Zaretskii 2024-10-13 17:38 ` Jim Porter 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-13 5:36 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Sat, 12 Oct 2024 18:36:27 -0700 > Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz > From: Jim Porter <jporterbugs@gmail.com> > > + ;; Check if the display property at the end of this line is "safe". > + (if (visual-wrap--display-property-safe-p > + (get-char-property (pos-eol) 'display)) > + ;; If so, we can apply our visual wrapping properties to this > + ;; line and continue to the next line. > + (progn > + (visual-wrap--apply-to-line (point)) > + (forward-line)) > + ;; Otherwise, skip ahead to the beginning of the first line after > + ;; the unsafe display property changes. > + (goto-char (next-single-char-property-change (pos-eol) 'display)) Yes, this is what I had in mind, thanks. But does next-single-char-property-change really fit the bill? What if you have several overlapping 'display' properties, and in particular one property "enclosed" in another? Did you test such a scenario? ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-13 5:36 ` Eli Zaretskii @ 2024-10-13 17:38 ` Jim Porter 2024-10-13 18:51 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Jim Porter @ 2024-10-13 17:38 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 1671 bytes --] On 10/12/2024 10:36 PM, Eli Zaretskii wrote: > Yes, this is what I had in mind, thanks. But does > next-single-char-property-change really fit the bill? What if you > have several overlapping 'display' properties, and in particular one > property "enclosed" in another? Did you test such a scenario? I had tested those manually and they worked (according to my understanding of display properties, anyway). I've added some test cases to that effect in the latest patch. Just to make sure we're on the same page: my (admittedly limited) understanding of xdisp.c is that, for replacing-display-properties, Emacs determines the range of buffer text to replace by using 'eq' on the 'display' property. So if you have an 'image' spec from positions 1 to 10, but add a 'height' spec from positions 3 to 4, Emacs will now render the image three times: one each for the ranges [1, 3), [3, 4), and [4, 10). This means that when 'visual-wrap-prefix-mode' sees the start of a replacing-display-property[1], we know we shouldn't interfere with the 'eq'-ness of that run of text, so we move past it to the first position that's not 'eq' to the start. If you like, we treat the text-to-be-replaced as atomic, since (I think) that's roughly what xdisp.c does too. [1] Technically, the visual-wrap code avoids adding properties to some non-replacing properties too, but since "do nothing" should always be safe, that shouldn't be a problem. (It could result in some text not having a wrap-prefix when we'd like it too, but we can always add new "safe" display specs if someone notices an issue; I wanted to err on the side of caution with what we consider safe.) [-- Attachment #2: 0001-Don-t-add-visual-wrap-prefix-properties-to-unsafe-mu.patch --] [-- Type: text/plain, Size: 10033 bytes --] From 33ddaae3aacd5b5eb1c823f3461019b53ca02c2e Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to unsafe multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Use 'pos-eol'; we don't want to respect field boundaries here. (visual-wrap-prefix-function): Check for unsafe display properties at the end of the line and skip past them if present. * test/lisp/visual-wrap-tests.el: New test file. --- lisp/visual-wrap.el | 48 +++++++++++-- test/lisp/visual-wrap-tests.el | 124 +++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 test/lisp/visual-wrap-tests.el diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..26333089189 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,36 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes. +A \"safe\" display spec is one that won't interfere with the additional +text properties that `visual-wrap-prefix-mode' uses. + +Specs that replace the text are unsafe, since they generally determine +the range of text to replace via `eq'. If `visual-wrap-prefix-mode' +were to add text properties to some subset of this range, it would +violate this assumption.") + +(defun visual-wrap--display-property-safe-p (display) + "Return non-nil if the display property DISPLAY is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs' (which see)." + ;; The display property could be a single display spec; if so, wrap it + ;; in a list so we can iterate over it in our loop below. + (when (and (consp display) (not (consp (car display)))) + (setq display (list display))) + ;; Loop over all the display specs to check if they're safe. Assume + ;; any display property other than a vector or list (e.g. a string) is + ;; unsafe. + (when (or (vectorp display) (listp display)) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq (car-safe spec) + visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -128,11 +158,11 @@ visual-wrap--apply-to-line ;; the buffer.) (add-display-text-property position (min (+ position (length first-line-prefix)) - (line-end-position)) + (pos-eol)) '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 + position (pos-eol) 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) @@ -206,8 +236,18 @@ visual-wrap-prefix-function (forward-line 0) (setq beg (point)) (while (< (point) end) - (visual-wrap--apply-to-line (point)) - (forward-line)) + ;; Check if the display property at the end of this line is "safe". + (if (visual-wrap--display-property-safe-p + (get-char-property (pos-eol) 'display)) + ;; If so, we can apply our visual wrapping properties to this + ;; line and continue to the next line. + (progn + (visual-wrap--apply-to-line (point)) + (forward-line)) + ;; Otherwise, skip ahead to the beginning of the first line after + ;; the unsafe display property changes. + (goto-char (next-single-char-property-change (pos-eol) 'display)) + (unless (bolp) (forward-line 1)))) `(jit-lock-bounds ,beg . ,end)) ;;;###autoload diff --git a/test/lisp/visual-wrap-tests.el b/test/lisp/visual-wrap-tests.el new file mode 100644 index 00000000000..7cae3761b6a --- /dev/null +++ b/test/lisp/visual-wrap-tests.el @@ -0,0 +1,124 @@ +;;; visual-wrap-tests.el --- Tests for `visual-wrap-prefix-mode' -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tets for `visual-wrap-prefix-mode'. + +;;; Code: + +(require 'visual-wrap) +(require 'ert) + +;;; Tests: + +(ert-deftest visual-wrap-tests/simple () + "Test adding wrapping properties to text without display properties." + (with-temp-buffer + (insert "greetings\n* hello\n* hi") + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("greetings\n* hello\n* hi" + 10 12 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 12 17 ( wrap-prefix (space :align-to (2 . width))) + 18 20 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 20 22 ( wrap-prefix (space :align-to (2 . width)))))))) + +(ert-deftest visual-wrap-tests/safe-display () + "Test adding wrapping properties to text with safe display properties." + (with-temp-buffer + (insert #("* hello" 2 7 (display (raise 1)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (message "%S" (buffer-string)) + (should (equal-including-properties + (buffer-string) + #("* hello" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (raise 1))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/within-line () + "Test adding wrapping properties to text with unsafe display properties. +When these properties don't extend across multiple lines, +`visual-wrap-prefix-mode' can still add wrapping properties." + (with-temp-buffer + (insert #("* [img]" 2 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (message "%S" (buffer-string)) + (should (equal-including-properties + (buffer-string) + #("* [img]" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/spanning-lines () + "Test adding wrapping properties to text with unsafe display properties. +When these properties do extend across multiple lines, +`visual-wrap-prefix-mode' must avoid adding wrapping properties." + (with-temp-buffer + (insert #("* a\n* b" 0 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b" 0 7 (display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/multiple-1 () + "Test adding wrapping properties to text with unsafe display properties. +This tests a multi-line unsafe display prop immediately followed by a +single-line unsafe display prop. `visual-wrap-prefix-mode' should *not* +add wrapping properties to the first block, but do add them to the +second." + (with-temp-buffer + (insert #("* a\n* b" + 0 4 (display ((image :type bmp))) + 4 7 (display ((image :type bmp) (height 1.5))))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b" + 0 4 ( display ((image :type bmp))) + 4 6 ( wrap-prefix (space :align-to (2 . width)) + display ((min-width ((2 . width))) + (image :type bmp) (height 1.5))) + 6 7 ( wrap-prefix (space :align-to (2 . width)) + display ((image :type bmp) (height 1.5)))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/multiple-2 () + "Test adding wrapping properties to text with unsafe display properties. +This tests a multi-line unsafe display prop immediately followed by +another multi-line unsafe display prop. `visual-wrap-prefix-mode' +should *not* add wrapping properties to either block." + (with-temp-buffer + (insert #("* a\n* b\n" + 0 4 (display ((image :type bmp))) + 4 8 (display ((image :type bmp) (height 1.5))))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b\n" + 0 4 (display ((image :type bmp))) + 4 8 (display ((image :type bmp) (height 1.5)))))))) + +;; visual-wrap-tests.el ends here -- 2.25.1 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-13 17:38 ` Jim Porter @ 2024-10-13 18:51 ` Eli Zaretskii 2024-10-13 20:50 ` Jim Porter 0 siblings, 1 reply; 18+ messages in thread From: Eli Zaretskii @ 2024-10-13 18:51 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Sun, 13 Oct 2024 10:38:09 -0700 > Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz > From: Jim Porter <jporterbugs@gmail.com> > > On 10/12/2024 10:36 PM, Eli Zaretskii wrote: > > Yes, this is what I had in mind, thanks. But does > > next-single-char-property-change really fit the bill? What if you > > have several overlapping 'display' properties, and in particular one > > property "enclosed" in another? Did you test such a scenario? > > I had tested those manually and they worked (according to my > understanding of display properties, anyway). I've added some test cases > to that effect in the latest patch. > > Just to make sure we're on the same page: my (admittedly limited) > understanding of xdisp.c is that, for replacing-display-properties, > Emacs determines the range of buffer text to replace by using 'eq' on > the 'display' property. So if you have an 'image' spec from positions 1 > to 10, but add a 'height' spec from positions 3 to 4, Emacs will now > render the image three times: one each for the ranges [1, 3), [3, 4), > and [4, 10). That is probably correct, at least in some cases, but why should we rely on that for this purpose? Isn't it more reliable and future-proof to skip the entire span of the outermost "replacing" display property, and never look at properties inside that text? Does it really matter for visual-wrap-prefix-mode to look inside? IOW, I prefer simplicity of the logic to sophisticated design based on what we actually see Emacs do in tricky cases, because time and again I learned the hard way that my mental models of what actually happens can be erroneous in the fine details. ^ permalink raw reply [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-13 18:51 ` Eli Zaretskii @ 2024-10-13 20:50 ` Jim Porter 2024-10-19 8:23 ` Eli Zaretskii 0 siblings, 1 reply; 18+ messages in thread From: Jim Porter @ 2024-10-13 20:50 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 73600, gautier [-- Attachment #1: Type: text/plain, Size: 2557 bytes --] On 10/13/2024 11:51 AM, Eli Zaretskii wrote: > That is probably correct, at least in some cases, but why should we > rely on that for this purpose? Isn't it more reliable and > future-proof to skip the entire span of the outermost "replacing" > display property, and never look at properties inside that text? Does > it really matter for visual-wrap-prefix-mode to look inside? As I understand this bug, we just need to make sure that visual-wrap.el doesn't change the display property of any single/atomic span of text that xdisp.c will replace. Otherwise, xdisp.c will stop at every change to 'display' in turn, leading to duplicated images (or whatever we're displaying). I think the most reliable/future-proof way to fix this would be work as much like xdisp.c as we can. From what I can tell, xdisp.c is using 'next-single-char-property-change' for this: for replacing-display-props, 'handle_single_display_spec' calls 'display_prop_end', which calls 'Fnext_single_char_property_change'. That's what my patch is doing, though it's in Lisp, and it's erring on the side of caution with what display specs could be "unsafe". > IOW, I prefer simplicity of the logic to sophisticated design based on > what we actually see Emacs do in tricky cases, because time and again > I learned the hard way that my mental models of what actually happens > can be erroneous in the fine details. I've attached a patch that isn't *exactly* what you suggested, but should add an extra layer of safety at the cost of possibly failing to add wrapping properties in when we should/could. In this version ("be-extra-careful.patch"), whenever we find an unsafe display property at the end of a line, we just skip ahead until we find the first known-safe display property. This is the best I could come up with without adding a lot of extra complexity to visual-wrap.el, which I think would have just made it more prone to mistakes. If you have any other ideas though, let me know. I also attached the patch I prefer ("match-xdisp.patch"), which is just what I posted previously with some additional details in the comments. Would you feel better about this version if I could add regression tests that ensure xdisp.c does what we expect? In previous work on visual-wrap, I looked a bit at doing this, and I think it would probably work if we ran the tests in a GUI frame. That probably requires some additional scaffolding in the Makefiles, but if it helps improve our confidence in the display engine, I think it's worth my time to attempt it. [-- Attachment #2: be-extra-careful.patch --] [-- Type: text/plain, Size: 10516 bytes --] From 41d4b59dc4e6d075b8cce244ecf65ae5018637da Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to unsafe multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Use 'pos-eol'; we don't want to respect field boundaries here. (visual-wrap-prefix-function): Check for unsafe display properties at the end of the line and skip past them if present. * test/lisp/visual-wrap-tests.el: New test file. --- lisp/visual-wrap.el | 57 ++++++++++++++-- test/lisp/visual-wrap-tests.el | 120 +++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 test/lisp/visual-wrap-tests.el diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..c3e28e6dd06 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,36 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes. +A \"safe\" display spec is one that won't interfere with the additional +text properties that `visual-wrap-prefix-mode' uses. + +Specs that replace the text are unsafe, since they generally determine +the range of text to replace via `eq'. If `visual-wrap-prefix-mode' +were to add text properties to some subset of this range, it would +violate this assumption.") + +(defun visual-wrap--display-property-safe-p (display) + "Return non-nil if the display property DISPLAY is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs' (which see)." + ;; The display property could be a single display spec; if so, wrap it + ;; in a list so we can iterate over it in our loop below. + (when (and (consp display) (not (consp (car display)))) + (setq display (list display))) + ;; Loop over all the display specs to check if they're safe. Assume + ;; any display property other than a vector or list (e.g. a string) is + ;; unsafe. + (when (or (vectorp display) (listp display)) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq (car-safe spec) + visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -128,11 +158,11 @@ visual-wrap--apply-to-line ;; the buffer.) (add-display-text-property position (min (+ position (length first-line-prefix)) - (line-end-position)) + (pos-eol)) '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 + position (pos-eol) 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) @@ -206,8 +236,27 @@ visual-wrap-prefix-function (forward-line 0) (setq beg (point)) (while (< (point) end) - (visual-wrap--apply-to-line (point)) - (forward-line)) + ;; Check if the display property at the end of this line is "safe". + (if (visual-wrap--display-property-safe-p + (get-char-property (pos-eol) 'display)) + ;; If so, we can apply our visual wrapping properties to this + ;; line and continue to the next line. + (progn + (visual-wrap--apply-to-line (point)) + (forward-line)) + ;; Otherwise, skip ahead until the end of any unsafe display + ;; properties. NOTE: We do this out of an abundance of caution to + ;; be as certain as possible that we're not interfering with the + ;; display engine. If this results in cases where we fail to add + ;; wrapping properties when we should, then we should remove the + ;; `while' loop below. Without that loop, this should be the same + ;; logic `handle_single_display_spec' in xdisp.c uses for + ;; determining what text to replace. See bug#73600. + (goto-char (next-single-char-property-change (pos-eol) 'display)) + (while (not (visual-wrap--display-property-safe-p + (get-char-property (point) 'display))) + (goto-char (next-single-char-property-change (point) 'display))) + (unless (bolp) (forward-line 1)))) `(jit-lock-bounds ,beg . ,end)) ;;;###autoload diff --git a/test/lisp/visual-wrap-tests.el b/test/lisp/visual-wrap-tests.el new file mode 100644 index 00000000000..04977afe207 --- /dev/null +++ b/test/lisp/visual-wrap-tests.el @@ -0,0 +1,120 @@ +;;; visual-wrap-tests.el --- Tests for `visual-wrap-prefix-mode' -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tets for `visual-wrap-prefix-mode'. + +;;; Code: + +(require 'visual-wrap) +(require 'ert) + +;;; Tests: + +(ert-deftest visual-wrap-tests/simple () + "Test adding wrapping properties to text without display properties." + (with-temp-buffer + (insert "greetings\n* hello\n* hi") + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("greetings\n* hello\n* hi" + 10 12 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 12 17 ( wrap-prefix (space :align-to (2 . width))) + 18 20 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 20 22 ( wrap-prefix (space :align-to (2 . width)))))))) + +(ert-deftest visual-wrap-tests/safe-display () + "Test adding wrapping properties to text with safe display properties." + (with-temp-buffer + (insert #("* hello" 2 7 (display (raise 1)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* hello" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (raise 1))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/within-line () + "Test adding wrapping properties to text with unsafe display properties. +When these properties don't extend across multiple lines, +`visual-wrap-prefix-mode' can still add wrapping properties." + (with-temp-buffer + (insert #("* [img]" 2 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* [img]" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/spanning-lines () + "Test adding wrapping properties to text with unsafe display properties. +When these properties do extend across multiple lines, +`visual-wrap-prefix-mode' must avoid adding wrapping properties." + (with-temp-buffer + (insert #("* a\n* b" 0 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b" 0 7 (display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/multiple-1 () + "Test adding wrapping properties to text with unsafe display properties. +This tests a multi-line unsafe display prop immediately followed by a +single-line unsafe display prop. `visual-wrap-prefix-mode' should *not* +add wrapping properties to either block." + (with-temp-buffer + (insert #("* a\n* b" + 0 4 (display ((image :type bmp))) + 4 7 (display ((image :type bmp) (height 1.5))))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + ;; NOTE: See the note in `visual-wrap-prefix-function'. If + ;; applying the change mentioned there, then this case + ;; should add wrapping properties to the second block. + #("* a\n* b" + 0 4 (display ((image :type bmp))) + 4 7 (display ((image :type bmp) (height 1.5)))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/multiple-2 () + "Test adding wrapping properties to text with unsafe display properties. +This tests a multi-line unsafe display prop immediately followed by +another multi-line unsafe display prop. `visual-wrap-prefix-mode' +should *not* add wrapping properties to either block." + (with-temp-buffer + (insert #("* a\n* b\n" + 0 4 (display ((image :type bmp))) + 4 8 (display ((image :type bmp) (height 1.5))))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b\n" + 0 4 (display ((image :type bmp))) + 4 8 (display ((image :type bmp) (height 1.5)))))))) + +;; visual-wrap-tests.el ends here -- 2.25.1 [-- Attachment #3: match-xdisp.patch --] [-- Type: text/plain, Size: 10085 bytes --] From 6d4597e27c99548105317d483121325d2c98bfda Mon Sep 17 00:00:00 2001 From: Jim Porter <jporterbugs@gmail.com> Date: Thu, 3 Oct 2024 17:24:18 -0700 Subject: [PATCH] Don't add visual-wrap-prefix properties to unsafe multi-line display specs This makes sure we don't interfere with other display specs, e.g. for images displayed in 'image-mode' (bug#73600). * lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable. (visual-wrap--display-property-safe-p): New function. (visual-wrap--apply-to-line): Use 'pos-eol'; we don't want to respect field boundaries here. (visual-wrap-prefix-function): Check for unsafe display properties at the end of the line and skip past them if present. * test/lisp/visual-wrap-tests.el: New test file. --- lisp/visual-wrap.el | 50 ++++++++++++-- test/lisp/visual-wrap-tests.el | 122 +++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 test/lisp/visual-wrap-tests.el diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el index 76276c0f474..87dd0bccacd 100644 --- a/lisp/visual-wrap.el +++ b/lisp/visual-wrap.el @@ -73,6 +73,36 @@ visual-wrap--face-extend-p (face-extend-p face nil t) (face-background face nil t))))) +(defvar visual-wrap--safe-display-specs + '(height raise) + "A list of display specs that don't interfere with wrap prefixes. +A \"safe\" display spec is one that won't interfere with the additional +text properties that `visual-wrap-prefix-mode' uses. + +Specs that replace the text are unsafe, since they generally determine +the range of text to replace via `eq'. If `visual-wrap-prefix-mode' +were to add text properties to some subset of this range, it would +violate this assumption.") + +(defun visual-wrap--display-property-safe-p (display) + "Return non-nil if the display property DISPLAY is \"safe\". +A \"safe\" display property is one where all the display specs are +members of `visual-wrap--safe-display-specs' (which see)." + ;; The display property could be a single display spec; if so, wrap it + ;; in a list so we can iterate over it in our loop below. + (when (and (consp display) (not (consp (car display)))) + (setq display (list display))) + ;; Loop over all the display specs to check if they're safe. Assume + ;; any display property other than a vector or list (e.g. a string) is + ;; unsafe. + (when (or (vectorp display) (listp display)) + (not (catch 'unsafe + (mapc (lambda (spec) + (unless (memq (car-safe spec) + visual-wrap--safe-display-specs) + (throw 'unsafe t))) + display))))) + (defun visual-wrap--prefix-face (fcp _beg end) ;; If the fill-context-prefix already specifies a face, just use that. (cond ((get-text-property 0 'face fcp)) @@ -128,11 +158,11 @@ visual-wrap--apply-to-line ;; the buffer.) (add-display-text-property position (min (+ position (length first-line-prefix)) - (line-end-position)) + (pos-eol)) '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 + position (pos-eol) 'wrap-prefix (if (numberp next-line-prefix) `(space :align-to (,next-line-prefix . width)) next-line-prefix))))) @@ -206,8 +236,20 @@ visual-wrap-prefix-function (forward-line 0) (setq beg (point)) (while (< (point) end) - (visual-wrap--apply-to-line (point)) - (forward-line)) + ;; Check if the display property at the end of this line is "safe". + (if (visual-wrap--display-property-safe-p + (get-char-property (pos-eol) 'display)) + ;; If so, we can apply our visual wrapping properties to this + ;; line and continue to the next line. + (progn + (visual-wrap--apply-to-line (point)) + (forward-line)) + ;; Otherwise, skip ahead to the beginning of the first line after + ;; the end of the display property. This is the same logic + ;; `handle_single_display_spec' in xdisp.c uses for determining + ;; what text to replace. + (goto-char (next-single-char-property-change (pos-eol) 'display)) + (unless (bolp) (forward-line 1)))) `(jit-lock-bounds ,beg . ,end)) ;;;###autoload diff --git a/test/lisp/visual-wrap-tests.el b/test/lisp/visual-wrap-tests.el new file mode 100644 index 00000000000..b62c1e5a05a --- /dev/null +++ b/test/lisp/visual-wrap-tests.el @@ -0,0 +1,122 @@ +;;; visual-wrap-tests.el --- Tests for `visual-wrap-prefix-mode' -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tets for `visual-wrap-prefix-mode'. + +;;; Code: + +(require 'visual-wrap) +(require 'ert) + +;;; Tests: + +(ert-deftest visual-wrap-tests/simple () + "Test adding wrapping properties to text without display properties." + (with-temp-buffer + (insert "greetings\n* hello\n* hi") + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("greetings\n* hello\n* hi" + 10 12 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 12 17 ( wrap-prefix (space :align-to (2 . width))) + 18 20 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 20 22 ( wrap-prefix (space :align-to (2 . width)))))))) + +(ert-deftest visual-wrap-tests/safe-display () + "Test adding wrapping properties to text with safe display properties." + (with-temp-buffer + (insert #("* hello" 2 7 (display (raise 1)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* hello" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (raise 1))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/within-line () + "Test adding wrapping properties to text with unsafe display properties. +When these properties don't extend across multiple lines, +`visual-wrap-prefix-mode' can still add wrapping properties." + (with-temp-buffer + (insert #("* [img]" 2 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* [img]" + 0 2 ( wrap-prefix (space :align-to (2 . width)) + display (min-width ((2 . width)))) + 2 7 ( wrap-prefix (space :align-to (2 . width)) + display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/spanning-lines () + "Test adding wrapping properties to text with unsafe display properties. +When these properties do extend across multiple lines, +`visual-wrap-prefix-mode' must avoid adding wrapping properties." + (with-temp-buffer + (insert #("* a\n* b" 0 7 (display (image :type bmp)))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b" 0 7 (display (image :type bmp))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/multiple-1 () + "Test adding wrapping properties to text with unsafe display properties. +This tests a multi-line unsafe display prop immediately followed by a +single-line unsafe display prop. `visual-wrap-prefix-mode' should *not* +add wrapping properties to the first block, but do add them to the +second." + (with-temp-buffer + (insert #("* a\n* b" + 0 4 (display ((image :type bmp))) + 4 7 (display ((image :type bmp) (height 1.5))))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b" + 0 4 ( display ((image :type bmp))) + 4 6 ( wrap-prefix (space :align-to (2 . width)) + display ((min-width ((2 . width))) + (image :type bmp) (height 1.5))) + 6 7 ( wrap-prefix (space :align-to (2 . width)) + display ((image :type bmp) (height 1.5)))))))) + +(ert-deftest visual-wrap-tests/unsafe-display/multiple-2 () + "Test adding wrapping properties to text with unsafe display properties. +This tests a multi-line unsafe display prop immediately followed by +another multi-line unsafe display prop. `visual-wrap-prefix-mode' +should *not* add wrapping properties to either block." + (with-temp-buffer + (insert #("* a\n* b\n" + 0 4 (display ((image :type bmp))) + 4 8 (display ((image :type bmp) (height 1.5))))) + (visual-wrap-prefix-function (point-min) (point-max)) + (should (equal-including-properties + (buffer-string) + #("* a\n* b\n" + 0 4 (display ((image :type bmp))) + 4 8 (display ((image :type bmp) (height 1.5)))))))) + +;; visual-wrap-tests.el ends here -- 2.25.1 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* bug#73600: 31.0.50; Visual wrap prefix mode and image display 2024-10-13 20:50 ` Jim Porter @ 2024-10-19 8:23 ` Eli Zaretskii 0 siblings, 0 replies; 18+ messages in thread From: Eli Zaretskii @ 2024-10-19 8:23 UTC (permalink / raw) To: Jim Porter; +Cc: 73600, gautier > Date: Sun, 13 Oct 2024 13:50:25 -0700 > Cc: 73600@debbugs.gnu.org, gautier@gautierponsinet.xyz > From: Jim Porter <jporterbugs@gmail.com> > > On 10/13/2024 11:51 AM, Eli Zaretskii wrote: > > That is probably correct, at least in some cases, but why should we > > rely on that for this purpose? Isn't it more reliable and > > future-proof to skip the entire span of the outermost "replacing" > > display property, and never look at properties inside that text? Does > > it really matter for visual-wrap-prefix-mode to look inside? > > As I understand this bug, we just need to make sure that visual-wrap.el > doesn't change the display property of any single/atomic span of text > that xdisp.c will replace. Otherwise, xdisp.c will stop at every change > to 'display' in turn, leading to duplicated images (or whatever we're > displaying). > > I think the most reliable/future-proof way to fix this would be work as > much like xdisp.c as we can. From what I can tell, xdisp.c is using > 'next-single-char-property-change' for this: for > replacing-display-props, 'handle_single_display_spec' calls > 'display_prop_end', which calls 'Fnext_single_char_property_change'. That's what xdisp.c does to detect the beginning of a property. But is that what it does to find the end of the property which is being processed? > That's what my patch is doing, though it's in Lisp, and it's erring on > the side of caution with what display specs could be "unsafe". > > > IOW, I prefer simplicity of the logic to sophisticated design based on > > what we actually see Emacs do in tricky cases, because time and again > > I learned the hard way that my mental models of what actually happens > > can be erroneous in the fine details. > > I've attached a patch that isn't *exactly* what you suggested, but > should add an extra layer of safety at the cost of possibly failing to > add wrapping properties in when we should/could. In this version > ("be-extra-careful.patch"), whenever we find an unsafe display property > at the end of a line, we just skip ahead until we find the first > known-safe display property. > > This is the best I could come up with without adding a lot of extra > complexity to visual-wrap.el, which I think would have just made it more > prone to mistakes. If you have any other ideas though, let me know. > > I also attached the patch I prefer ("match-xdisp.patch"), which is just > what I posted previously with some additional details in the comments. > Would you feel better about this version if I could add regression tests > that ensure xdisp.c does what we expect? In previous work on > visual-wrap, I looked a bit at doing this, and I think it would probably > work if we ran the tests in a GUI frame. That probably requires some > additional scaffolding in the Makefiles, but if it helps improve our > confidence in the display engine, I think it's worth my time to attempt it. I think at this stage it is basically up to you which version to install. Adding tests is always welcome, of course, regardless of which version is installed. Thanks. ^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2024-10-19 8:23 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-10-02 13:04 bug#73600: 31.0.50; Visual wrap prefix mode and image display Gautier Ponsinet 2024-10-02 15:42 ` Eli Zaretskii 2024-10-02 15:51 ` Eli Zaretskii 2024-10-02 16:37 ` Jim Porter 2024-10-03 10:59 ` Eli Zaretskii 2024-10-04 0:28 ` Jim Porter 2024-10-04 6:28 ` Eli Zaretskii 2024-10-04 19:45 ` Jim Porter 2024-10-05 6:41 ` Eli Zaretskii 2024-10-05 18:07 ` Jim Porter 2024-10-06 0:28 ` Jim Porter 2024-10-12 12:00 ` Eli Zaretskii 2024-10-13 1:36 ` Jim Porter 2024-10-13 5:36 ` Eli Zaretskii 2024-10-13 17:38 ` Jim Porter 2024-10-13 18:51 ` Eli Zaretskii 2024-10-13 20:50 ` Jim Porter 2024-10-19 8:23 ` 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.