Function: magit-todos-defscanner
magit-todos-defscanner is a macro defined in magit-todos.el.
Signature
(magit-todos-defscanner NAME &key AVAILABLEP COMMAND RESULTS-REGEXP (ALLOW-EXIT-CODES '(0)) (DIRECTORY-FORM '(f-relative directory default-directory)) (CALLBACK #''magit-todos--scan-callback))
Documentation
Define a magit-todos scanner named NAME.
NAME is a string, which may contain spaces. It is only used for descriptive purposes.
AVAILABLEP is a predicate which is used to determine whether the
scanner is usable. In most cases, it should use
executable-find to look for the scanner command.
COMMAND is a sexp which should evaluate to the scanner command,
i.e. a list of strings to be eventually passed to
start-process. Nil elements are removed, numbers are converted
to strings, and nested lists are flattened into a single list.
It is evaluated each time the scanner is run. If COMMAND
evaluates to nil, it is not run.
Within the COMMAND list these variables are available:
depth: When non-nil, an integer, which is the depth that should
be passed to the scanner's max-depth option (i.e. magit-todos-depth).
directory: The directory in which the scan should be run.
extra-args: The value of the customization variable
"magit-todos-NAME-extra-args" (see below).
keywords: List of item keywords defined in
magit-todos-keywords-list.
search-regexp-pcre: PCRE-compatible regular expression to be passed
to the scanner process.
search-regexp-elisp: Emacs regular expression, which may be
used for scanners written as Emacs Lisp functions.
RESULTS-REGEXP is an optional string or unquoted sexp which is
used to match results in the scanner process's output buffer.
Typically this will be a sexp which calls rx-to-string. It is
evaluated each time the scanner is run. If nil, the appropriate
default is used which matches results in the form:
FILENAME:LINE:MATCH
Where MATCH may also match Org outline heading stars when
appropriate. Custom regexps may also match column numbers or
byte offsets in the appropriate numbered groups; see
make-magit-todos-item.
ALLOW-EXIT-CODES is a list of integers corresponding to exit codes which should not be interpreted as errors (e.g. rg uses 1 to indicate no match and no error, so its list should include 0 and 1). Note that TRAMP seems to use code 9 instead of 0, so 9 is added to this list automatically.
DIRECTORY-FORM may be a form within which the symbol directory
is bound to the directory path being searched; it should evaluate
to the directory path that should be passed to the
command. (Since some commands' output differs by the way the
search directory is passed, like "./" or "." vs. a full path,
this may be used to, e.g. ensure that the command does not
include a leading "./" in filenames.)
CALLBACK is called to process the process's output buffer. Normally the default should be used, which inserts items into the Magit status buffer which is passed as an argument to the scanner function.
The macro defines the following:
"magit-todos-NAME-extra-args": A customization setting, a list
of strings to be passed to the scanner as extra arguments.
"magit-todos--scan-with-NAME": The function which runs the
scanner command.
It also adds the scanner to the customization variable
magit-todos-scanner, and to the variable
magit-todos-scanners (which is used to set
magit-todos-scanner by calling magit-todos--choose-scanner).
Source Code
;; Defined in /nix/store/xy37aj9089qvbs5clw0qszpi3hrh3mxx-emacs-packages-deps/share/emacs/site-lisp/elpa/magit-todos-20250928.1611/magit-todos.el
;;;; Scanners
(cl-defmacro magit-todos-defscanner (name &key availablep command results-regexp
(allow-exit-codes '(0))
(directory-form '(f-relative directory default-directory))
(callback (function 'magit-todos--scan-callback)))
"Define a `magit-todos' scanner named NAME.
NAME is a string, which may contain spaces. It is only used for
descriptive purposes.
AVAILABLEP is a predicate which is used to determine whether the
scanner is usable. In most cases, it should use
`executable-find' to look for the scanner command.
COMMAND is a sexp which should evaluate to the scanner command,
i.e. a list of strings to be eventually passed to
`start-process'. Nil elements are removed, numbers are converted
to strings, and nested lists are flattened into a single list.
It is evaluated each time the scanner is run. If COMMAND
evaluates to nil, it is not run.
Within the COMMAND list these variables are available:
`depth': When non-nil, an integer, which is the depth that should
be passed to the scanner's max-depth option (i.e. `magit-todos-depth').
`directory': The directory in which the scan should be run.
`extra-args': The value of the customization variable
\"magit-todos-NAME-extra-args\" (see below).
`keywords': List of item keywords defined in
`magit-todos-keywords-list'.
`search-regexp-pcre': PCRE-compatible regular expression to be passed
to the scanner process.
`search-regexp-elisp': Emacs regular expression, which may be
used for scanners written as Emacs Lisp functions.
RESULTS-REGEXP is an optional string or unquoted sexp which is
used to match results in the scanner process's output buffer.
Typically this will be a sexp which calls `rx-to-string'. It is
evaluated each time the scanner is run. If nil, the appropriate
default is used which matches results in the form:
FILENAME:LINE:MATCH
Where MATCH may also match Org outline heading stars when
appropriate. Custom regexps may also match column numbers or
byte offsets in the appropriate numbered groups; see
`make-magit-todos-item'.
ALLOW-EXIT-CODES is a list of integers corresponding to exit
codes which should not be interpreted as errors (e.g. rg uses 1
to indicate no match and no error, so its list should include 0
and 1). Note that TRAMP seems to use code 9 instead of 0, so 9
is added to this list automatically.
DIRECTORY-FORM may be a form within which the symbol `directory'
is bound to the directory path being searched; it should evaluate
to the directory path that should be passed to the
command. (Since some commands' output differs by the way the
search directory is passed, like \"./\" or \".\" vs. a full path,
this may be used to, e.g. ensure that the command does not
include a leading \"./\" in filenames.)
CALLBACK is called to process the process's output buffer.
Normally the default should be used, which inserts items into the
Magit status buffer which is passed as an argument to the scanner
function.
The macro defines the following:
\"magit-todos-NAME-extra-args\": A customization setting, a list
of strings to be passed to the scanner as extra arguments.
\"magit-todos--scan-with-NAME\": The function which runs the
scanner command.
It also adds the scanner to the customization variable
`magit-todos-scanner', and to the variable
`magit-todos-scanners' (which is used to set
`magit-todos-scanner' by calling `magit-todos--choose-scanner')."
;; TODO: Try to obviate the -scanners variable, let --choose-scanner use the
;; custom-type of -scanner directly. Maybe, anyway--I don't want to ugly up the UI
;; for users.
(declare (indent defun) (debug (stringp [&rest &or [":test" def-form]
[":command" def-form]
[":results-regexp" [&or stringp def-form]]])))
(let* ((name-without-spaces (s-replace " " "-" name))
(scan-fn-name (concat "magit-todos--scan-with-" name-without-spaces))
(scan-fn-symbol (intern scan-fn-name))
(extra-args-var (intern (format "magit-todos-%s-extra-args" name-without-spaces))))
(cl-pushnew 9 allow-exit-codes)
(setf allow-exit-codes (sort allow-exit-codes #'<))
`(progn
(defcustom ,extra-args-var nil
,(format "Extra arguments passed to %s." name)
:type '(repeat string))
;; NOTE: Both the macro and the macro-defined function have `callback' arguments. Pay attention to unquoting.
;; FIXME: That is confusing.
(cl-defun ,scan-fn-symbol (&key magit-status-buffer directory depth heading sync callback)
,(format "Scan for to-dos with %s.
Then calls CALLBACK. MAGIT-STATUS-BUFFER is what it says. DIRECTORY
is the directory in which to run the scan. DEPTH should be an
integer, typically the value of `magit-todos-depth'. HEADING is
passed to CALLBACK.
When SYNC is nil, the scanner process is returned, and CALLBACK
is a function which is called by the process sentinel with one
argument, a list of match items.
When SYNC is non-nil, match items are returned."
name-without-spaces)
(let* ((process-connection-type 'pipe)
(directory ,directory-form)
(extra-args (when ,extra-args-var
(--map (s-split (rx (1+ space)) it 'omit-nulls)
,extra-args-var)))
(keywords magit-todos-keywords-list)
(search-regexp-elisp (rx-to-string
`(or
;; Org item
(seq bol (group (1+ "*"))
(1+ blank)
(group (or ,@keywords))
(1+ space)
(group (1+ not-newline)))
;; Non-Org
(seq (or bol (1+ blank))
(group (or ,@keywords))
(regexp ,magit-todos-keyword-suffix)
(optional (1+ blank)
(group (1+ not-newline)))))))
(search-regexp-pcre (rxt-elisp-to-pcre search-regexp-elisp))
(results-regexp (or ,results-regexp
(rx-to-string
`(seq bol
;; Filename
(group-n 8 (1+ (not (any ":")))) ":"
;; Line
(group-n 2 (1+ digit)) ":"
(or
;; Org item
(seq (group-n 1 (1+ "*"))
(1+ blank)
(group-n 4 (or ,@keywords))
(1+ blank)
(group-n 5 (1+ not-newline)))
;; Non-Org
(seq (optional (1+ not-newline))
(group-n 4 (or ,@keywords))
(optional (group-n 6 (regexp ,magit-todos-keyword-suffix)))
(optional (1+ blank))
(optional (group-n 5 (1+ not-newline)))))))))
(command (-flatten (-non-nil ,command))))
;; Convert any numbers in command to strings (e.g. depth).
(cl-loop for elt in-ref command
when (numberp elt)
do (setf elt (number-to-string elt)))
;; Run command.
(when command
(when magit-todos-nice
(setf command (append (list "nice" "-n5") command)))
(if sync
;; Synchronous: return matching items.
(with-temp-buffer
(unless (member (apply #'call-process (car command) nil (current-buffer) nil
(cdr command))
',allow-exit-codes)
(user-error (concat (car command) " failed")))
(magit-todos--buffer-items results-regexp))
;; Async: return process.
(let ((process (magit-todos--async-start-process ,scan-fn-name
:command command
;; NOTE: This callback chain.
:finish-func (apply-partially ,callback
:callback callback
:magit-status-buffer magit-status-buffer
:results-regexp results-regexp
:search-regexp-elisp search-regexp-elisp
:heading heading
:exclude-globs magit-todos-exclude-globs
:process)))) ; Process is appended to the list.
(setf (process-get process :allow-exit-codes) ',allow-exit-codes)
process)))))
(magit-todos--add-to-custom-type 'magit-todos-scanner
(list 'const :tag ,name #',scan-fn-symbol))
(add-to-list 'magit-todos-scanners
(list (cons 'name ,name)
(cons 'function #',scan-fn-symbol)
(cons 'availablep ,availablep))
'append))))