3

I'm trying to prevent fill-paragraph from breaking a certain type of string:

<<dd_display: %9.3f abs(_b[2.rep78])>>

This is an inline Stata dynamic tag. The <<dd_display: and >> are fixed, the rest inside can change and be any length and contain any amount of whitespace.

I found Prevent fill-paragraph from breaking LaTeX citations in Org Mode however it seems to rely on the space being in a fairly predictable place (within a certain number of positions from the start). I'm trying to generalize this to look backwards for <<dd_display without first finding a >>. This is my best shot so far, but it doesn't work.

(defun my-in-dd-display ()
  (< (looking-back "<<dd_display:") (- (match-end 0) (line-beginning-position))
     (looking-back ">>") (- (match-end 0) (line-beginning-position))))
(add-hook 'fill-nobreak-predicate #'my-in-dd-display)

Can anyone help?

2 Answers 2

3
+50

I wanted dalanicolai to fix his answer but he didn't do it (see my first and my second comment). So, I give here my own answer.

One surpresses filling if one finds the tag starter in a backward search without finding the tag ender at first:

(defun my-in-dd-display ()
  "Don't break lines in Inline Stata Dynamic Tags."
  (save-excursion
    (save-match-data
      (and (re-search-backward ">>\\|\\(<<dd_display:\\)" (line-beginning-position) t)
       (match-beginning 1)))))

(add-hook 'fill-nobreak-predicate #'my-in-dd-display)

The re-search-backward does the backward search that matches both, the tag-starter and the tag-end. The search stops if such a match is found. But only the tag-starter is grouped. The form (and ... (match-beginning 1)) makes sure that the function returns non-nil if the tag-starter-group is matched and nil if the tag-end is matched.

Since the function is only a predicate it should not move point. Avoiding a modification of the match-data is not required by the doc string of fill-nobreak-predicate but it is added as a precaution.

It is not clear from the question in which major mode you want to set up my-in-dd-display as fill predicate.

The standard way to add my-in-dd-display locally in certain major mode my-major-mode would be:

(defun my-local-setup-in-dd-display ()
  "Activate `my-in-dd-display' locally in major modes except Orgmode."
  (add-hook 'fill-nobreak-predicate 'my-in-dd-display nil t))

(add-hook 'my-major-mode #'my-local-setup-in-dd-display)

Alternatively one could globally add my-in-dd-display to fill-nobreak-predicate for all modes except Orgmode as it is done in the first code snippet:

(add-hook 'fill-nobreak-predicate 'my-in-dd-display)

Orgmode has a non-standard way setting up fill-nobreak-predicate in org-setup-filling. It uses setq-local instead of add-hook.

So, to activate my-in-dd-display in Orgmode you need to set it with setq or setq-local in org-mode-hook:

(defun my-org-setup-in-dd-display ()
  "Activate `my-in-dd-display' by `setq-local' as it is required for Orgmode."
  (unless (memq 'my-in-dd-display fill-nobreak-predicate)
    (setq-local fill-nobreak-predicate (cons 'my-in-dd-display fill-nobreak-predicate))))

(add-hook 'org-mode-hook #'my-org-setup-in-dd-display)
5
  • Thanks - I think I was messing up something while trying your version from the comment - this works terrific! Would you mind explaining how it works? (The actual function, not the bit about activating it.)
    – Josh
    Aug 22, 2023 at 9:46
  • 1
    @Josh I thought that the sentence "One surpresses filling if one finds the tag starter in a backward search without finding the tag ender at first" would describe it all. The re-search-backward does the backward search that matches both, the tag-starter and the tag-ender -- and the search stops if such a match is found. But only the tag-starter is grouped the the form (and ... (match-beginning 1)) makes sure that the function only returns non-nil if that group is matched/filled/non-nil. (If that additional explanation helps I will add it to the answer below the code.)
    – Tobias
    Aug 22, 2023 at 10:23
  • @Josh Note, that this form of my-in-dd-display is even appropriate for auto-fill-mode since it works for incomplete input, i.e., tag-starters without tag-enders.
    – Tobias
    Aug 22, 2023 at 10:26
  • @Tobias I was waiting for Josh to provide an example for what he expected (also, before commenting on your comment). I tested your version, but I was not sure if that provided the desired behavior (it broke the line before the tag instead of after, and also Josh commented that it did not work). Anyway, thanks for your comments, I do appreciate them. Also, your answer is great, and very good that you decided to add it here. With your answer and the help of edebug, I finally understand what is going on :) Aug 22, 2023 at 11:18
  • @Tobias Thanks for the explanation. I gathered what it was generally trying to do, but you explanation helped me how to the code was actually accomplishing it.
    – Josh
    Aug 22, 2023 at 12:26
2

EDIT

This answer provides an incorrect solution (see @Tobias his comments and answer), but I leave it here as an example of a faulty solution, and for the explanation why (via Tobias his comments and answer).

END EDIT

I don't really understand how the fill-paragraph algorithm works, for that you would probably have to study the source code. But after some trial and error, it seems like the following predicate function achieves what you want

(defun fillp ()
  (prog1 (and (search-forward ">>" (line-end-position) t)
           (save-excursion
         (search-backward "<<dd_display:" (line-beginning-position) t))
           (forward-char))))

(add-hook 'fill-nobreak-predicate #'fillp)

The fill-nobreak-predicate docstring does mention that the function is called with the 'point at the place to be tested'. It seems that after executing the predicate, the point jumps back to the beginning of the word on which point ends (or the word before that place), after executing the predicate. Therefore, it seems that the 'save-excursion' and the 'forward-char' are also required.

5
  • 1
    Consider: (defun fillp () "Don't break lines in Inline Stata Dynamic Tags." (save-excursion (save-match-data (and (re-search-backward ">>\\|\\(<<dd_display:\\)" (line-beginning-position) t) (match-beginning 1)))))
    – Tobias
    Aug 20, 2023 at 6:20
  • These are great - but they fail if I have multiple <<dd_display: ... >> in a single paragraph. Can this be modified to search forward for >> before <<dd_display and search backwards for <<dd_display: before >>? E.g., if I have this string: dfksfksf fskdfdklsfkdskf akfksalk akd akldka sjdkj kljdaksjk dla <<dd_display: abc def>> fdsjkfljds fjdksljflsd <<dd_display: fdskfds>> fsdjkflsd with fill-column set to 80.
    – Josh
    Aug 20, 2023 at 18:39
  • @Josh I am not sure what you mean, because to me, it looks like my code works fine on your example. Maybe you can add the example to your question, including the expected result? Thanks! Aug 21, 2023 at 7:31
  • 1
    @dalanicolai Don't mess up current point though the first search for >>. The version in my comment above solves two problems: 1st it accepts partial input, i.e., it does not insert line breaks if the user has already typed in <<dd_display: before point without closing the tag yet. 2nd: Your code also avoids filling when there is a complete tag behind point, i.e., xPoint here <<dd_display: complete tag here>>.
    – Tobias
    Aug 21, 2023 at 11:04
  • @Josh Please try your test with my-in-dd-display from my answer. I did it and it worked for me.
    – Tobias
    Aug 22, 2023 at 6:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.