Subject: bug#56335: 29.0.50; [PATCH] Add more breakpoint chars support to longlines-mode

From: Manuel Giraud <[email-masked]>
Date: Fri, 01 Jul 2022 12:35:08 +0200

Hi,

Regarding this conversation:
https://lists.gnu.org/archive/html/help-gnu-emacs/2022-06/msg00638.html

I have try to improve longlines-mode to detect more (and user settable)
breakpoints into long lines.  I have tested it on the file from the
cited conversation and a book (sentences with spaces) on one line.  It
seems to work for me but it might need more testing.

I'd like to prepare another patch afterward to be able to find
breakpoint past fill-column.  For example, the file from the
conversation fails to break after some lines because it contains
*really* long words.

>From 924f2097698103e08c2668c987b0fc25d1365918 Mon Sep 17 00:00:00 2001
From: Manuel Giraud <[email-masked]>
Date: Fri, 1 Jul 2022 12:02:42 +0200
Subject: [PATCH] Add more separators to longlines-mode.

* lisp/obsolete/longlines.el (longlines-breakpoint-chars): New
custom to have multiple breakpoint chars.
(longlines-set-breakpoint): Add a target-column parameter and use
`longlines-breakpoint-chars'.
(longlines-find-break-backward, longlines-find-break-foreward):
Use `longlines-breakpoint-chars'.
(longlines-wrap-line): Do not insert space upon merging, just
remove the soft newline. Fix "space before tab" in indent.
(longlines-merge-lines-p): Use the new target-column parameter to
find out if the next line could be merged with the current one.
(longlines-encode-region): Do not replace a soft newline with a
space, just remove it.
* etc/NEWS: New user option 'longlines-breakpoint-chars'
---
 etc/NEWS                   |  5 ++
 lisp/obsolete/longlines.el | 99 ++++++++++++++++++--------------------
 2 files changed, 52 insertions(+), 52 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index e757435ff9..1457f35c50 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -419,6 +419,11 @@ including those typed in response to passwords prompt (this was the
 previous behavior).  The default is nil, which inhibits recording of
 passwords.
 
++++
+** New user option 'longlines-breakpoint-chars'.
+This is a string containing chars that could be used as breakpoint in
+longlines mode.
+
 +++
 ** New function 'command-query'.
 This function makes its argument command prompt the user for
diff --git a/lisp/obsolete/longlines.el b/lisp/obsolete/longlines.el
index 731f47794c..8705172c5e 100644
--- a/lisp/obsolete/longlines.el
+++ b/lisp/obsolete/longlines.el
@@ -73,6 +73,10 @@ longlines-show-effect
 This is used when `longlines-show-hard-newlines' is on."
   :type 'string)
 
+(defcustom longlines-breakpoint-chars " ;,|"
+  "A bag of separator chars for longlines."
+  :type 'string)
+
 ;;; Internal variables
 
 (defvar longlines-wrap-beg nil)
@@ -273,11 +277,8 @@ longlines-wrap-line
   "If the current line needs to be wrapped, wrap it and return nil.
 If wrapping is performed, point remains on the line.  If the line does
 not need to be wrapped, move point to the next line and return t."
-  (if (longlines-set-breakpoint)
+  (if (longlines-set-breakpoint fill-column)
       (progn (insert-before-markers-and-inherit ?\n)
-	     (backward-char 1)
-             (delete-char -1)
-	     (forward-char 1)
              nil)
     (if (longlines-merge-lines-p)
         (progn (end-of-line)
@@ -286,58 +287,59 @@ longlines-wrap-line
      ;; replace these two newlines by a single space.  Unfortunately,
      ;; this breaks the conservation of (spaces + newlines), so we
      ;; have to fiddle with longlines-wrap-point.
-	       (if (or (prog1 (bolp) (forward-char 1)) (eolp))
-		   (progn
-		     (delete-char -1)
-		     (if (> longlines-wrap-point (point))
-			 (setq longlines-wrap-point
-			       (1- longlines-wrap-point))))
-		 (insert-before-markers-and-inherit ?\s)
-		 (backward-char 1)
-		 (delete-char -1)
-		 (forward-char 1))
+               (if (or (prog1 (bolp) (forward-char 1)) (eolp))
+	           (progn
+	             (delete-char -1)
+	             (if (> longlines-wrap-point (point))
+		         (setq longlines-wrap-point
+		               (1- longlines-wrap-point))))
+	         (delete-char -1))
                nil)
       (forward-line 1)
       t)))
 
-(defun longlines-set-breakpoint ()
+(defun longlines-set-breakpoint (target-column)
   "Place point where we should break the current line, and return t.
 If the line should not be broken, return nil; point remains on the
 line."
-  (move-to-column fill-column)
-  (if (and (re-search-forward "[^ ]" (line-end-position) 1)
-           (> (current-column) fill-column))
-      ;; This line is too long.  Can we break it?
-      (or (longlines-find-break-backward)
-          (progn (move-to-column fill-column)
-                 (longlines-find-break-forward)))))
+  (move-to-column target-column)
+  (let ((non-breakpoint-re (format "[^%s]" longlines-breakpoint-chars)))
+    (if (and (re-search-forward non-breakpoint-re (line-end-position) t 1)
+             (> (current-column) target-column))
+        ;; This line is too long.  Can we break it?
+        (or (longlines-find-break-backward)
+            (progn (move-to-column target-column)
+                   (longlines-find-break-forward))))))
 
 (defun longlines-find-break-backward ()
   "Move point backward to the first available breakpoint and return t.
 If no breakpoint is found, return nil."
-  (and (search-backward " " (line-beginning-position) 1)
-       (save-excursion
-         (skip-chars-backward " " (line-beginning-position))
-         (null (bolp)))
-       (progn (forward-char 1)
-              (if (and fill-nobreak-predicate
-                       (run-hook-with-args-until-success
-                        'fill-nobreak-predicate))
-                  (progn (skip-chars-backward " " (line-beginning-position))
-                         (longlines-find-break-backward))
-                t))))
+  (let ((breakpoint-re (format "[%s]" longlines-breakpoint-chars)))
+    (and (re-search-backward breakpoint-re (line-beginning-position) t 1)
+         (save-excursion
+           (skip-chars-backward longlines-breakpoint-chars (line-beginning-position))
+           (null (bolp)))
+         (progn (forward-char 1)
+                (if (and fill-nobreak-predicate
+                         (run-hook-with-args-until-success
+                          'fill-nobreak-predicate))
+                    (progn (skip-chars-backward longlines-breakpoint-chars
+                                                (line-beginning-position))
+                           (longlines-find-break-backward))
+                  t)))))
 
 (defun longlines-find-break-forward ()
   "Move point forward to the first available breakpoint and return t.
 If no break point is found, return nil."
-  (and (search-forward " " (line-end-position) 1)
-       (progn (skip-chars-forward " " (line-end-position))
-              (null (eolp)))
-       (if (and fill-nobreak-predicate
-                (run-hook-with-args-until-success
-                 'fill-nobreak-predicate))
-           (longlines-find-break-forward)
-         t)))
+  (let ((breakpoint-re (format "[%s]" longlines-breakpoint-chars)))
+    (and (search-forward breakpoint-re (line-end-position) t 1)
+         (progn (skip-chars-forward longlines-breakpoint-chars (line-end-position))
+                (null (eolp)))
+         (if (and fill-nobreak-predicate
+                  (run-hook-with-args-until-success
+                   'fill-nobreak-predicate))
+             (longlines-find-break-forward)
+           t))))
 
 (defun longlines-merge-lines-p ()
   "Return t if part of the next line can fit onto the current line.
@@ -348,12 +350,7 @@ longlines-merge-lines-p
          (null (get-text-property (point) 'hard))
          (let ((space (- fill-column (current-column))))
            (forward-line 1)
-           (if (eq (char-after) ? )
-               t ; We can always merge some spaces
-             (<= (if (search-forward " " (line-end-position) 1)
-                     (current-column)
-                   (1+ (current-column)))
-                 space))))))
+           (longlines-set-breakpoint (max 0 (1- space)))))))
 
 (defun longlines-decode-region (&optional beg end)
   "Turn all newlines between BEG and END into hard newlines.
@@ -372,7 +369,7 @@ longlines-decode-buffer
   (longlines-decode-region (point-min) (point-max)))
 
 (defun longlines-encode-region (beg end &optional _buffer)
-  "Replace each soft newline between BEG and END with exactly one space.
+  "Remove each soft newline between BEG and END.
 Hard newlines are left intact.  The optional argument BUFFER exists for
 compatibility with `format-alist', and is ignored."
   (save-excursion
@@ -382,10 +379,8 @@ longlines-encode-region
       (while (search-forward "\n" reg-max t)
 	(let ((pos (match-beginning 0)))
 	  (unless (get-text-property pos 'hard)
-	    (goto-char (1+ pos))
-	    (insert-and-inherit " ")
-	    (delete-region pos (1+ pos))
-            (remove-text-properties pos (1+ pos) '(hard nil)))))
+            (remove-text-properties pos (1+ pos) '(hard nil))
+            (delete-region pos (1+ pos)))))
       (set-buffer-modified-p mod)
       end)))
 
-- 
2.36.1

In GNU Emacs 29.0.50 (build 1, x86_64-unknown-openbsd7.1, X toolkit, cairo version 1.17.6, Xaw scroll bars)
 of 2022-06-30 built on elite.giraud
Repository revision: 77e99dcacb57cae558f833334a8367fbc9b4fd8a
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12101003
System Description: OpenBSD elite.giraud 7.1 GENERIC.MP#579 amd64

Configured using:
 'configure --prefix=/home/manuel/emacs --bindir=/home/manuel/bin
 --with-x-toolkit=athena --without-sound --without-compress-install
 CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib'

Configured features:
CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GSETTINGS HARFBUZZ JPEG JSON
LCMS2 LIBOTF LIBXML2 MODULES NOTIFY KQUEUE PDUMPER PNG RSVG SQLITE3
THREADS TIFF TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM LUCID
ZLIB

Important settings:
  value of $LC_ALL: en_US.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Group

Minor modes in effect:
  global-git-commit-mode: t
  magit-auto-revert-mode: t
  gnus-topic-mode: t
  icomplete-mode: t
  display-time-mode: t
  gnus-undo-mode: t
  shell-dirtrack-mode: t
  global-so-long-mode: t
  repeat-mode: t
  global-eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  buffer-read-only: 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/manuel/.emacs.d/elpa/transient-20220527.2213/transient hides /home/manuel/emacs/share/emacs/29.0.50/lisp/transient

Features:
(shadow emacsbug ispell vc-hg vc-bzr vc-src vc-sccs vc-cvs vc-rcs
log-view vc-dir ewoc emacs-news-mode goto-addr vc-annotate vc ibuf-ext
ibuffer ibuffer-loaddefs whitespace ediff ediff-merg ediff-mult
ediff-wind ediff-diff ediff-help ediff-init ediff-util shortdoc dabbrev
cl-print debug backtrace vc-git bug-reference magit-patch magit-extras
face-remap magit-bookmark magit-submodule magit-obsolete 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 magit-diff git-commit log-edit pcvs-util
add-log magit-core magit-autorevert autorevert magit-margin
magit-transient magit-process with-editor magit-mode transient magit-git
magit-base magit-section dash compat-27 compat-26 compat descr-text
cus-start help-fns radix-tree wdired gnus-dired executable vc-dispatcher
vc-svn smerge-mode diff diff-mode term ehelp pulse tabify imenu man
pcmpl-unix pcmpl-linux sort gnus-cite shr-color mail-extr textsec
uni-scripts idna-mapping ucs-normalize uni-confusable textsec-check
gnus-async gnus-bcklg gnus-ml gnus-topic mm-archive url-http url-gw
url-cache url-auth qp utf-7 imap rfc2104 nnrss mm-url nndoc nndraft nnmh
network-stream nsm nnfolder nnml gnus-agent gnus-srvr gnus-score
score-mode nnvirtual nntp gnus-cache w3m w3m-hist w3m-fb bookmark-w3m
w3m-ems w3m-favicon w3m-image tab-line w3m-proc w3m-util misearch
multi-isearch longlines paredit edmacro icomplete time battery
exwm-randr xcb-randr exwm-config exwm exwm-input xcb-keysyms xcb-xkb
exwm-manage exwm-floating xcb-cursor xcb-render exwm-layout
exwm-workspace exwm-core xcb-ewmh xcb-icccm xcb xcb-xproto xcb-types
xcb-debug kmacro server stimmung-themes modus-operandi-theme
modus-themes osm bookmark mingus libmpdee transmission diary-lib
diary-loaddefs color calc-bin calc-ext calc calc-loaddefs rect calc-macs
w3m-load mu4e mu4e-org mu4e-main mu4e-view mu4e-view-gnus
mu4e-view-common mu4e-headers mu4e-compose mu4e-context mu4e-draft
mu4e-actions ido rfc2368 smtpmail mu4e-mark mu4e-proc mu4e-utils
doc-view filenotify jka-compr image-mode exif mu4e-lists mu4e-message
flow-fill mule-util hl-line mu4e-vars mu4e-meta supercite regi
ebdb-message ebdb-gnus gnus-msg gnus-art mm-uu mml2015 mm-view mml-smime
smime gnutls dig gnus-sum shr pixel-fill kinsoku url-file url-dired svg
dom gnus-group gnus-undo gnus-start gnus-dbus gnus-cloud nnimap nnmail
mail-source utf7 netrc nnoo gnus-spec gnus-int gnus-range message
sendmail yank-media puny rfc822 mml mml-sec epa derived epg rfc6068
epg-config mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047
rfc2045 ietf-drums gmm-utils mailheader gnus-win gnus nnheader gnus-util
mail-utils range mm-util mail-prsvr ebdb-mua ebdb-com crm ebdb-format
ebdb mailabbrev eieio-opt cl-extra help-mode speedbar ezimage dframe
eieio-base pcase timezone org ob ob-tangle ob-ref ob-lob ob-table ob-exp
org-macro org-footnote org-src ob-comint org-pcomplete org-list
org-faces org-entities org-version ob-emacs-lisp ob-core ob-eval
org-table oc-basic bibtex ol rx org-keys oc org-compat org-macs
org-loaddefs find-func cal-menu calendar cal-loaddefs visual-basic-mode
web-mode disp-table erlang-start smart-tabs-mode skeleton cc-mode
cc-fonts cc-guess cc-menus cc-cmds cc-styles cc-align cc-engine cc-vars
cc-defs movitz-slime cl slime-asdf grep slime-tramp tramp tramp-loaddefs
trampver tramp-integration cus-edit cus-load wid-edit files-x
tramp-compat shell pcomplete parse-time iso8601 time-date ls-lisp
format-spec slime-fancy slime-indentation slime-cl-indent cl-indent
slime-trace-dialog slime-fontifying-fu slime-package-fu slime-references
slime-compiler-notes-tree advice slime-scratch slime-presentations
bridge slime-macrostep macrostep slime-mdot-fu slime-enclosing-context
slime-fuzzy slime-fancy-trace slime-fancy-inspector slime-c-p-c
slime-editing-commands slime-autodoc slime-repl slime-parse slime
compile text-property-search etags fileloop generator xref project
arc-mode archive-mode noutline outline pp comint ansi-color ring
hyperspec thingatpt slime-autoloads dired-aux dired-x dired
dired-loaddefs so-long notifications dbus xml repeat easy-mmode tex-site
hyperbole-autoloads magit-autoloads git-commit-autoloads
magit-section-autoloads dash-autoloads rust-mode-autoloads
with-editor-autoloads info compat-autoloads 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 eieio-loaddefs
password-cache json subr-x map byte-opt gv bytecomp byte-compile cconv
url-vars cl-loaddefs cl-lib rmc iso-transl tooltip eldoc paren electric
uniquify ediff-hook vc-hooks lisp-float-type elisp-mode mwheel
term/x-win x-win term/common-win x-dnd 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
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 kqueue lcms2
dynamic-setting system-font-setting font-render-setting cairo x-toolkit
xinput2 x multi-tty make-network-process emacs)

Memory information:
((conses 16 1135909 389038)
 (symbols 48 78353 14)
 (strings 32 379818 23058)
 (string-bytes 1 25245556)
 (vectors 16 195351)
 (vector-slots 8 3611718 203761)
 (floats 8 702 681)
 (intervals 56 23236 3722)
 (buffers 992 53))

-- 
Manuel Giraud