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?

  • 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 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 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 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 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 at 21:06
2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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