5

Currently I have variables defined with setq-local, this is useful for modes, so I can define variables only for a particular mode.

How can this be done for functions?

I'd like to have a function, eg:
generic-lookup-reference-at-point, bind this to a key, then define it for each mode which has the ability to lookup an item under the cursor.

ideasman42
  • 8,375
  • 1
  • 28
  • 105

3 Answers3

8
(defvar my-local-variable nil
"Variable used on a bufer-local basis to indicate ...")

(make-variable-buffer-local 'my-local-variable)

USAGE:

(setq my-local-variable (lambda () (message "major-mode:  %s" major-mode)))

(define-key name-of-major-mode-keymap [f5]
  (lambda () (interactive)
             (funcall my-local-variable)))
lawlist
  • 18,826
  • 5
  • 37
  • 118
  • 1
    Is there a reason to use `defvar` instead of `defvar-local`? – ideasman42 Jun 25 '19 at 03:42
  • 1
    It is possible that `defvar-local` came into being after my introduction to Emacs ... If it has the same effect as `defvar` followed by `make-variable-buffer-local`, then by all means consolidate the two into one ... :) I still have several libraries that use the `defvar` / `make-variable-buffer-local` combination and I've never tried `defvar-local`. – lawlist Jun 25 '19 at 03:46
  • That is indeed precisely what it does. Introduced (along with `setq-local`) in 24.3, so both macros are pretty safe to make use of in general at this point. – phils Jun 25 '19 at 06:08
  • Just `nil` as default looks wrong. The old way is to replace `(funcall my-local-variable)` by something like `(funcall (or my-local-variable #'my-default-function))`. The new way is to use `#'my-default-function` as default value for `my-local-variable`. – Tobias Jun 25 '19 at 13:04
  • 1
    Agree `nil` as default isn't great, wrote a macro that sets a placeholder function, here: https://emacs.stackexchange.com/a/51244/2418 – ideasman42 Jun 26 '19 at 01:29
2

If a library developer is responsible for definining the mode dependent behavior and you do not plan to allow the user to change it per defcustom you can use mode dependent overloads.

The code below is for playing around with mode local overrides. In buffers derived from text-mode [f9] prints "action in text-mode." in other buffers it prints "default action."

(require 'mode-local)

(define-overload lookup-reference-at-point (point) ;
  "Default lookup for reference at POINT."
  (interactive "d")
  (:override
   (message "This is the")
   (message "default action.")))

(define-mode-local-override lookup-reference-at-point text-mode (point)
  "Lookup for reference at POINT in `text-mode'."
  (message "This is the")
  (message "action in text-mode."))

(global-set-key [f9] #'lookup-reference-at-point)
Tobias
  • 32,569
  • 1
  • 34
  • 75
1

Posting answer to own question, including an example.

As @lawlist points out this can be done using defvar-local, this example uses macros to avoid writing boiler plate code and prints a message if the function isn't defined, runs a fallback function.

(defmacro my-generic-fn-with-fallback (var description &rest body)
  `
  (progn
    (defun ,var ()
      ,description
      (interactive)
      (call-interactively ,var))
    (defvar-local ,var ,@body "Value storing a function, called by a function of the same name.")))

(defmacro my-generic-fn (var description)
  `
  (my-generic-fn-with-fallback ,var ,description
    (lambda ()
      (interactive)
      (message "Mode <%s> doesn't define <%s>, doing nothing!" major-mode ',var))))

(my-generic-fn my-generic-run "Run the current buffer")
(my-generic-fn my-generic-goto-thing-at-point "Go to symbol at the point")
(my-generic-fn my-generic-usage-of-thing-at-point "References to symbol point")

(my-generic-fn-with-fallback my-generic-jump-next "Jump forward"
                             'evil-jump-forward)
(my-generic-fn-with-fallback my-generic-jump-prev "Jump backward"
                             'evil-jump-backward)

(my-generic-fn-with-fallback my-generic-doc-jump-section "Jump to sections"
                             'imenu)

Then each mode can set functions:

(add-hook
 'diff-mode-hook
 (lambda ()

   ;; --- snip

   ;; Generic functions
   ;; 'my-generic-run (no need)
   (setq my-generic-goto-thing-at-point 'diff-goto-source-and-close)
   (setq my-generic-jump-next 'diff-goto-source-and-close)
   (setq my-generic-jump-prev 'kill-buffer-and-window)
   ;; Lambda's work too.
   (setq my-generic-doc-jump-section (lambda () (interactive) (doc-jump-section-diff)))

   ;; --- snip

   )
 )

Bind the keys:


;; global key example.
(global-set-key (kbd "<f5>") (lambda () (interactive) (funcall my-generic-run)))

;; using evil-mode.
(define-key evil-normal-state-map (kbd "M-l") 'my-generic-jump-next)
(define-key evil-normal-state-map (kbd "M-h") 'my-generic-jump-prev)
(define-key evil-normal-state-map (kbd "M-RET") 'my-generic-goto-thing-at-point)
(define-key evil-normal-state-map (kbd "C-S-u") 'my-generic-usage-of-thing-at-point)
ideasman42
  • 8,375
  • 1
  • 28
  • 105