summary
Elisp newbie has been given interactive code (aka commands, called with M-x whatever
) that "does stuff" on the currently-selected region in the current buffer. What is an/the "elispish way" to scale that code up to handle all of the following (and all interactive) usecases:
- (current) If called with (no argument
&&
region selected in the current buffer):- do something on the text in the region
- If called with (no argument
&&
no region selected in the current buffer):- do something on all the text in the buffer
- If called with (
C-u filepath M-x whatever
) (more on this below)- evaluate the argument as a filepath
- do something on all the text in the file
?
example
I have code in this repo that currently "does stuff" (usecase detailed in thread beginning here) on a region in a buffer. The relevant code is part of this file (adapted from original code by Pascal Bourguignon):
(defun bulk-replace-region-with-tuples-from-file (start end)
(interactive "r") ; need "r" to get region start and end
(save-excursion
(narrow-to-region start end)
(goto-char (point-min))
(replace-multiple-strings ; the payload
(with-file BULK-REPLACE-TUPLES-FILEPATH ; defined earlier in the test code
(goto-char (point-min))
(read (current-buffer))
)
)
(widen)
)
)
That works iff ∃region selected in the current buffer, i.e., usecase#=1 above. How to scale to handle the others?
answer-in-progress
Following wvxvw's guidance (below), I have
0.
later discovered that a prefix argument cannot be a string! Dunno why I thought this: I've been using Emacs forever. Instead, if given a non-nil
prefix argument, I'll hafta prompt for a filepath.
- Simply refactored
bulk-replace-region-with-tuples-from-file
above into a non-interactive helperbulk-replace-region-non-interactive
, merely by removing the(interactive "r")
. - Created a
bulk-replace-buffer-non-interactive
that simply wrapsbulk-replace-region-non-interactive
:
(defun bulk-replace-buffer-non-interactive ()
(bulk-replace-region-non-interactive (point-min) (point-max))
)
- Created a
bulk-replace-file-non-interactive
that simply wrapsbulk-replace-buffer-non-interactive
:
(defun bulk-replace-file-non-interactive (filepath)
(set-buffer (find-file-noselect filepath))
(bulk-replace-buffer-non-interactive)
(save-buffer)
(kill-buffer)
)
- Created a hardcoded but interactive solution to usecase#=1 by simply wrapping its non-interactive helper. It's mostly for testing the helper, but it's a useful API as well as a "step along the path":
(defun bulk-replace-region (begin end)
(interactive "r") ; need "r" to get region begin and end
(bulk-replace-region-non-interactive begin end)
)
- Created a hardcoded but interactive solution to usecase#=2 very similar to previous step:
(defun bulk-replace-buffer ()
(interactive) ; purely to enable `M-x`
(bulk-replace-buffer-non-interactive)
)
- Sorta/kinda handled usecase#=3 in a manner similar to the previous 2 steps: a hardcoded but interactive solution which
- takes the filepath from a prompt (without a prefix argument). (Note
BULK-REPLACE-FILEPATH-PROMPT
is defined above in the code.) - calls the helper on the filepath
- does not try to use a parameterized prompt with
(interactive)
!
- takes the filepath from a prompt (without a prefix argument). (Note
; Don't try this! see https://emacs.stackexchange.com/q/19082/5444
; (defun bulk-replace-file (filepath)
; (interactive (concat "F" BULK-REPLACE-FILEPATH-PROMPT))
(defun bulk-replace-file ()
(interactive) ; purely to enable `M-x`
(bulk-replace-file-non-interactive
; `(read-file-name)` does the prompting
(expand-file-name (read-file-name BULK-REPLACE-FILEPATH-PROMPT))
)
)
- Handled all the usecases (hopefully--pending more complete testing) with an über-wrapper that hides {API, separate code paths} from the user:
(defun bulk-replace (prefix-argument)
(interactive "P") ; to catch prefix argument (if given)
(cond
(prefix-argument ; got one, so prompt user for filepath
(bulk-replace-file)
)
((use-region-p) ; we have a region, so bulk-replace-region
(bulk-replace-region-non-interactive (region-beginning) (region-end))
)
(t ; we always have a current-buffer, so bulk-replace-buffer
(bulk-replace-buffer-non-interactive)
)
)
)
It works! but needs more, better, automated} testing, docstrings, error-handling, etc: to be continued ...