4

Suppose a third-party plugin contains the following function:

(defun example-function ()
  (interactive)
  ;; ... do many things ...
  (goto-char (point-max))
  ;; ... do many things ...
  (goto-char (point-max))
  ;; ... do many things ...
  (goto-char (point-max)))

The function uses goto-char in various places in its body.

I want to change the behavior of the function such that every time it calls (goto-char (point-max)), it will also call (recenter -1). To that end, I added this advice to redefine the calls to goto-char:

(advice-add 'example-function
            :around
            (lambda (orig-fun &rest args)
              (cl-labels ((goto-char (pos)
                            (goto-char pos)
                            (recenter -1)))
                (apply orig-fun args))))

However, the cl-labels appears to have no effect; goto-char still retains its old behavior inside example-function. What is wrong with the advice?

(Emacs version: GNU Emacs 25.2)

Flux
  • 603
  • 3
  • 17
  • You can't change the behavior of goto-char just inside example-function using an advice. You have two options: rewrite example-function to call recenter after every goto-char; or add an :after advice to goto-char which calls recenter. In the latter case, the advised goto-char will be called every time, not just from inside example-function. – NickD Jan 17 '21 at 19:27

2 Answers2

3

(Caveat: All from memory; I might have some of this wrong.)

WRT your code:

(lambda (orig-fun &rest args)
              (cl-labels ((goto-char (pos)
                            (goto-char pos)
                            (recenter -1)))
                (apply orig-fun args))))

You never call goto-char in the body of that cl-labels form, so your override wouldn't be used.

It's the orig-fun definition which would be calling goto-char, but that code is not within the lexical scope of the cl-labels code.

Generally, using cl-letf with (symbol-function 'foo) as the PLACE allows dynamically-scoped overrides (i.e. seen by other functions as well as the immediate code), but even then I think this kind of override could only work in uncompiled code, as goto-char has its own byte-code op, meaning that calls to it are compiled away to just that op code when it's byte-compiled (which also inhibits writing advice for goto-char itself).

phils
  • 50,977
  • 3
  • 79
  • 122
1

I am not sure what is wrong with your code, but it seems like it should not work because you are recursively calling goto-char inside the definition. You would have to redefine the goto-char function (which is in the C code I think). I think you need to advise the goto-char function temporarily like this:

(defun example-function ()
  (interactive)
  ;; ... do many things ...
  (goto-char (point-max))
  ;; ... do many things ...
  (goto-char (point-max))
  ;; ... do many things ...
  (goto-char (point-max)))

(defun modified-goto-char (orig-fun &rest args) (apply orig-fun args) (recenter -1))

(advice-add 'example-function :around (lambda (orig-fun &rest args) (advice-add 'goto-char :around #'modified-goto-char) (apply orig-fun args) (advice-remove 'goto-char #'modified-goto-char)))

John Kitchin
  • 11,891
  • 1
  • 20
  • 42