1

I want to slightly modify the behaviour of a function, old-function whose source I cannot modify.

I can do that simply by

(defun my-new-function (old-fn a b c)
  (if (evaluate-my-condition)
      (funcall old-fun (my-transform a) b c)
    (funcall old-fun a b c)))

 (advice-add #'old-function :around #'my-new-function)

This works well. However, I would rather have this functionality as a buffer-local minor-mode since I rarely need this modified functionality.

Since advice-add works globally (as is pointed out here), is there another way to achieve this?

Drew
  • 75,699
  • 9
  • 109
  • 225
Tohiko
  • 1,589
  • 1
  • 10
  • 22
  • Since you evaluate on condition of `(evaluate-my-condition)` anyways, why not simply introduce a buffer-local variabe, which is checked by `(evaluate-my-condition)` as an additonal condition? – jue Feb 22 '20 at 13:41
  • Yes, that's what I am doing at the moment. But this does mean that this advice is there for all buffers even those that don't need them. I was hoping for a more elegant solution. – Tohiko Feb 22 '20 at 14:37
  • I'would say your solution is a good one, because the advice is transparent (based on the condition). And help functions will show, that there is an advice active. – jue Feb 22 '20 at 14:49
  • @Drew, if the minor mode is buffer-local then removing the the advice when the mode is turned off will break other buffers that have the mode turned on. – Tohiko Feb 22 '20 at 16:21
  • Wouldn't it be simpler to just create a new function with the modifications specifically for the minor mode? – Aquaactress Feb 22 '20 at 21:06
  • It's not quite what you asked for here. However, depending on your use-case you may find noflet useful. https://github.com/nicferrier/emacs-noflet/blob/master/README.creole – Qudit Feb 22 '20 at 21:29
  • @Tohiko: Yes, thx. I missed that the mode is buffer-local. – Drew Feb 23 '20 at 02:22

1 Answers1

4

Let us collect the facts:

  • The function cell of a symbol is not buffer-local. Therefore, if you want to change the behavior of the function buffer-locally you need a buffer-local variable or a hook-variable. A hook-variable has local values in buffers where add-hook with non-nil LOCAL argument is applied to them.
  • Since you have no access to old-function whose behavior you want to modify you have only two options:

    • Redefinition
    • Advising

    The promoted method is advising. So we stick to it.

  • There are many variants how you can couple the advice and the buffer-local variable to get the desired effect.

Perhaps the simplest method:

(defun old-fun (a b c)
  (interactive "sa: \nsb: \nsc: ")
  (message "{(old-fun %s %s %s) in buffer %s}"
       a b c
       (current-buffer)))

(defun my-transform (a)
  "Special transformation when `my-minor-mode' is active."
  (message "{(my-transform %s) in buffer %s}" a (current-buffer)))

(define-minor-mode my-minor-mode
  "Replace `old-fun' with `my-special-fun'.")

(defun around-old-fun (fun a b c)
  "Override advice which replaces `old-fun' with `my-new-functions'.
Falls back to `old-fun' when no function is registered at `my-new-functions'."
  (if my-minor-mode
      (funcall fun (my-transform a) b c)
    (funcall fun a b c)))

(advice-add 'old-fun :around #'around-old-fun)

The appearance of the advice in the doc-string of old-fun is a nuisance you have to endure in buffers where my-minor-mode is not active.


Appendix:

The simple implementation of symbol-function does not allow for buffer-local values:

DEFUN ("symbol-function", Fsymbol_function, Ssymbol_function, 1, 1, 0,
       doc: /* Return SYMBOL's function definition, or nil if that is void.  */)
  (Lisp_Object symbol)
{
  CHECK_SYMBOL (symbol);
  return XSYMBOL (symbol)->u.s.function;
}

In contrast there follows the definition of symbol-value:

Lisp_Object
find_symbol_value (Lisp_Object symbol)
{
  struct Lisp_Symbol *sym;

  CHECK_SYMBOL (symbol);
  sym = XSYMBOL (symbol);

 start:
  switch (sym->u.s.redirect)
    {
    case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
    case SYMBOL_PLAINVAL: return SYMBOL_VAL (sym);
    case SYMBOL_LOCALIZED:
      {
    struct Lisp_Buffer_Local_Value *blv = SYMBOL_BLV (sym);
    swap_in_symval_forwarding (sym, blv);
    return (blv->fwd.fwdptr
        ? do_symval_forwarding (blv->fwd)
        : blv_value (blv));
      }
    case SYMBOL_FORWARDED:
      return do_symval_forwarding (SYMBOL_FWD (sym));
    default: emacs_abort ();
    }
}

DEFUN ("symbol-value", Fsymbol_value, Ssymbol_value, 1, 1, 0,
       doc: /* Return SYMBOL's value.  Error if that is void.
Note that if `lexical-binding' is in effect, this returns the
global value outside of any lexical scope.  */)
  (Lisp_Object symbol)
{
  Lisp_Object val;

  val = find_symbol_value (symbol);
  if (!EQ (val, Qunbound))
    return val;

  xsignal1 (Qvoid_variable, symbol);
}

The line

struct Lisp_Buffer_Local_Value *blv = SYMBOL_BLV (sym);

in find_symbol_value retrieves the buffer local value of the symbol.

Tobias
  • 32,569
  • 1
  • 34
  • 75