0

I would like to implement a sensible strategy for handling optional arguments. In this instance, I want the function to be as for a function like abbrev-mode (although not in the context of a minor mode):

If called interactively, toggle the ‘Abbrev mode’ mode. If the prefix argument is positive, enable the mode, and if it is zero or negative, disable the mode.

If called from Lisp, toggle the mode if ARG is ‘toggle’. Enable the mode if ARG is nil, omitted, or is a positive number. Disable the mode if ARG is a negative number.

Have started with this simple implementation.

(defun roto-abbrev (&optional n)
  "Enables words to be expanded as one types."
  (interactive)
  (unless n (setq n 1))
  (abbrev-mode n))

How do I make it behave similar to abbrev-mode function with respect to its argument handling?

Dilna
  • 1
  • 3
  • 11
  • 1
    The question is unclear: How do you want the function to behave? What would the user see and do? IOW, what is its specification? – NickD Aug 06 '22 at 00:01
  • 1
    IOW, you want to know how exactly abbrev-mode (or really any minor mode function`, since they all implement the behavior you describe) implements its argument handling. Is that correct? – NickD Aug 06 '22 at 02:15
  • Yes, want to replicate that for my function. – Dilna Aug 06 '22 at 02:18
  • See if my edit reflects your goal - please revert it if it does not. – NickD Aug 06 '22 at 02:45
  • I am not focusing specifically on making minor modes, but on functions with optional argument. – Dilna Aug 06 '22 at 02:55
  • 1
    OK - then you still need to explain what the function's action should be when the corresponding action of a minor mode is "enable the mode" or "disable the mode". What should happen if I do M-x roto-abbrev or I do (roto-abbrev 'toggle) from lisp? If roto-abbrev were a minor-mode function then the minor-mode would be toggled: what's the behavior you expect in this case? Similarly for all the other cases. – NickD Aug 06 '22 at 03:06
  • 1
    "How do I make it behave similar to abbrev-mode function with respect to its argument handling?" -- you would begin by looking at how abbrev-mode handles its argument. I trust that there is an unwritten portion of this question in which you did that, found it wasn't satisfactory, but decided to omitted that code and explanation from your text. I suggest you improve your question by restoring those things. – phils Aug 06 '22 at 03:38
  • @ephram If you would like to discover these things yourself, it might be handy to know about pp-macroexpand-last-sexp. Go to the definition of abbrev-mode (e.g. using xref-find-definitions). Then place your cursor after the definition and do M-x pp-macroexpand-last-sexp. Now you can study yourself how the functionality has been implemented. – dalanicolai Aug 09 '22 at 08:44

2 Answers2

1

It is unclear what is your final goal, but this replicates the behaviour described in the quoted part of your answer. Indeed, it entails the large part of the call-type/prefix combinations, so it could be useful in different contexts.

(defvar my-minor-mode-active nil)

(defun my-minor-mode-set (activate) "Activate my-minor-mode if ACTIVATE is non-nil, else deactivate it." (cond (activate (setq my-minor-mode-active t) ;; More initialising code here ) ((not activate) (setq my-minor-mode-active nil) ;; More deinitialising code here )))

(defun f (&optional prefix) (interactive "P")

;; Manage user interactive calls (when (called-interactively-p 'interactive) (cond ((null prefix) (my-minor-mode-set (not my-minor-mode-active)) (message (concat "Inter. call with nil prefix, hence the mode is toggled to " (if my-minor-mode-active "active." "inactive."))))

 ((> (prefix-numeric-value prefix) 0)
  (my-minor-mode-set t)
  (message "Inter. call with positive prefix, hence the mode is activated."))
 ((<= (prefix-numeric-value prefix) 0)
  (my-minor-mode-set nil)
  (message "Inter. call with non-positive prefix, hence the mode is deactivated."))))

;; Manage Lisp calls (including keyboard macros) (when (not (called-interactively-p 'interactive)) (cond ((equal prefix 'toggle) (my-minor-mode-set (not my-minor-mode-active)) (message (concat "Lisp call with 'toggle prefix, hence the mode is toggled to " (if (not my-minor-mode-active) "active." "inactive.")))) ((or (null prefix) (> (prefix-numeric-value prefix) 0)) (my-minor-mode-set t) (message "Lisp call with nil/positive prefix, hence the mode is activated.")) ((<= (prefix-numeric-value prefix) 0) (my-minor-mode-set nil) (message "Lisp. call with non-positive prefix, hence the mode is deactivated.")))))

Test is with the lisp calls:

(f 'toggle)
(f)
(f 0)
(f 1)
(f -1)

And with the interactive calls:

M-f
C-u M-f
C-u 0 M-f
C-u 1 M-f
C-u -1 M-f

The requested behaviour of (f 0) is not immediately clear. I am assuming that non-positive prefixes trigger deactivation.

NickD
  • 29,717
  • 3
  • 27
  • 44
antonio
  • 1,822
  • 13
  • 27
  • Please heed the warnings in the doc-string of call-interactively-p: it would be better to emulate what define-minor-mode does as exactly as possible, where it only uses call-interactively-p for notifying the user: The only known proper use of ‘interactive’ for KIND is in deciding whether to display a helpful message, or how to display it. If you’re thinking of using it for any other purpose, it is quite likely that you’re making a mistake. Think: what do you want to do when the command is called from a keyboard macro? – NickD Aug 06 '22 at 15:41
  • @NickD: Perhaps I misunderstood the OP, but he wrote "I want the function ... not in the context of a minor mode". As for called-interactively-p, do you mean replacing it with something like: (defun foo (&optional print-message) (interactive "p") (when print-message (message "foo")))? How to distinguish here C-u 1 M-x foo from (foo 1)? They appear to be two distinct required cases in the question. – antonio Aug 06 '22 at 22:27
  • It is not clear what the OP wants, so we are all trying to figure that out. As for called-interactively-p (sorry I screwed up the name in my first comment), I have never used it (never needed it really), so I have no idea if there is or not a way to distinguish the two cases you mention. But the warning in the doc seemed important enough to flag. – NickD Aug 07 '22 at 00:13
0

At some point I thought I had understood what the OP wanted and edited the question to reflect that understanding, but the OP demurred and changed it back a bit, so I don't know if this will answer his current question. It is really an answer to the previous form of the question after my edit, but I still think there is a chance that this is really what the OP is after.

The basic idea is that we want a function that handles its argument exactly the way a minor mode does, so we figure out what a "real" minor mode function does and strip way all the minor mode paraphernalia, leaving only the argument handling. But the argument is supposed to enable or disable something, so we emulate this enabling/disabling by defining a variable and set it to t to "enable" or to nil to "disable" whatever it is we are enabling or disabling. For a "real" minor mode, the variable is a buffer-local variable with the same name as the mode function (e.g. abbrev-mode is the name of the mode function and also the name of the mode variable), so we follow that convention.

With that as explanation, here's a minimal function that handles its argument the way the quoted paragraph in the question describes and "enables"/"disables" whatever it is we are trying to enable/disable by setting the corresponding variable. Here's the minimal code:

(defvar-local foo-bar nil)

(defun foo-bar (&optional arg) (interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg) 'toggle)))

(setq foo-bar
        (cond
         ((eq arg 'toggle) (not foo-bar))
         ((and (numberp arg) (&lt; arg 1)) nil)
         (t t)))
foo-bar)

I assume that the variable should be buffer-local here, but it could be made global: just change the defvar-local to defvar. Initially, foo-bar is nil, so it is "disabled".

The tests that @antonio describes in his answer can be used to verify that everything is as it should be: after every test, you can evaluate the foo-bar variable in the appropriate buffer to make sure its value is as expected.

BTW, I did this by macroexpanding a fictitious (define-minor-mode foo-bar "foo-bar docstring") and ruthlessly eliminating everything but the argument handling and the state variable.

Although I hope this will answer the OP's "real" question, I am not holding my breath. Depending on how it goes, I might keep it, amend it or delete it.

NickD
  • 29,717
  • 3
  • 27
  • 44