Improving greader-mode reading by adding a single space after every dot and question mark

I noticed that greader-mode chops up paragraphs if sentences end with two spaces, so I wrote the following function - useful with eww, and maybe also Info-mode and nov.el.

It copies the current buffer into a new buffer, adds a space after each dot and after each question mark, and makes sure lines are not too long. Not-too-long-linesification is my personal preference and can be avoided by removing the call to fill-region.

Oh, and it sets the major mode of the new buffer to text-mode, which seems sensible, unless I mistook it for something that it isn’t, as I did not read the documentation for this mode.

(defun kakafarm/paste-text-into-new-text-buffer ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (mark-end-of-buffer)
    (kill-ring-save (point-min)
                    (point-max)))
  (pop-to-buffer-same-window (generate-new-buffer "kakafarm-text-reading-buffer"))
  (text-mode)
  (yank)
  (save-excursion
    (goto-char (point-min))
    (search-forward ". ")
    (replace-match ".  ")
    (goto-char (point-min))
    (search-forward "? ")
    (replace-match "?  ")
    (fill-region (point-min)
                 (point-max)))
  (goto-char (point-min)))
1 Like

I’m on mobile and cannot check, but that might be due to sentence-end-double-space being t by default:

If you want to use just one space between sentences, you can set the variable sentence-end-double-space to nil to make the sentence commands stop for single spaces. […] The variable sentence-end-double-space also affects filling

greader-mode might use that variable for its logic too.

Oh dear. M-x set-variable RET sentence-end-double-space RET nil RET worked.

Now that this is done, I want this variable to only be set in certain specific non-editable modes, e.g. eww, nov.el, so I guess that’ll be somewhere in (use-package greader …), but how do you set it to run something like (setq-local sentence-end-double-space nil) each time you load greader and only in a certain set of the non-editable major modes?

You can check in a hook whether buffer-read-only is t:

(defun forum/cow/adjust-sentence-end ()
  "Set `sentence-end-double-space' in read-only buffer to `nil'."
  (when buffer-read-only
    (setq-local sentence-end-double-space nil)))

(use-package greader
  :commands (greader-mode)
  :config
  (add-hook 'greader-mode-hook #'forum/cow/adjust-sentence-end))

Note that you cannot use :hook (grader-mode . forum/cow/adjust-sentence-end), because use-package assumes that the hook’s target is within the configured package (i.e. greader).

I get this error message:

⛔ Error (use-package): greader/:config: Attempt to set a constant symbol: nil

For this piece of code:

(defun kakafarm/sentence-end-double-nilify-for-read-only-buffers ()
  "Set `sentence-end-double-space' in read-only buffer to `nil'."
  (when buffer-read-only
    (setq-local sentence-end-double-space
                nil)))

(use-package greader
  :commands (greader-mode)
  :config
  (add-hook greader-mode-hook
            #'kakafarm/sentence-end-double-nilify-for-read-only-buffers)
  :hook (Info-mode
         eww-after-render
         fundamental-mode
         lisp-mode
         nov-mode
         text-mode
         w3m-mode))

when loading some page in a new eww buffer. (M-x eww RET gnu.org RET)

That last edit with the quoted 'greader-mode-hook worked!

Now, I have to ask, why can’t we use a lambda instead of a defun function in that (add-hook 'greader-mode-hook …)? Why not this?

  (add-hook 'greader-mode-hook
            (lambda ()
              (when buffer-read-only
                (setq-local sentence-end-double-space
                            nil))))

You can of course use a (lambda ...), but there’s a catch: re-running that part of your configuration may add that hook again, especially if you use some closure by accident. It may also be slower, see add-hook’s documentation:

FUNCTION may be any valid function, but it’s recommended to use a function symbol and not a lambda form. Using a symbol will ensure that the function is not re-added if the function is edited, and using lambda forms may also have a negative performance impact when running ‘add-hook’ and ‘remove-hook’.

It’s also easier to change the definition of your function if you only add the symbol to the hook and not an immediate lambda. For example, let’s say you find out that you need to adjust some other variable too. With a defun, you can just adjust your function and re-evaluate the defun. With a lambda you would have to remove the old definition first and then add it again.

See also (info "(elisp) Setting Hooks"):

24.1.2 Setting Hooks

Here’s an example that adds a function to a mode hook to turn on Auto Fill mode when in Lisp Interaction mode:

(add-hook 'lisp-interaction-mode-hook 'auto-fill-mode)

The value of a hook variable should be a list of functions. You can manipulate that list using the normal Lisp facilities, but the modular way is to use the functions ‘add-hook’ and ‘remove-hook’, defined below. They take care to handle some unusual situations and avoid problems.

It works to put a ‘lambda’-expression function on a hook, but we recommend avoiding this because it can lead to confusion. If you add the same ‘lambda’-expression a second time but write it slightly differently, you will get two equivalent but distinct functions on the hook. If you then remove one of them, the other will still be on it.

1 Like