Function: projectile-files-via-ext-command-async
projectile-files-via-ext-command-async is a byte-compiled function
defined in projectile.el.
Signature
(projectile-files-via-ext-command-async ROOT COMMAND CALLBACK &optional PATHSPECS)
Documentation
Asynchronously list relative file names in project ROOT by running COMMAND.
Like projectile-files-via-ext-command, but spawns COMMAND with
make-process so Emacs is not blocked while it runs. The output is
parsed with the very same logic, so the result is identical to the
synchronous command.
CALLBACK is funcalled with two arguments when COMMAND finishes: the list
of files (nil on failure) and an error description string (nil on
success). As in projectile-files-via-ext-command, a non-zero exit
that still produced output is treated as success (the output is passed
to CALLBACK); only a non-zero exit with no output is reported as an
error. Either way the command's stderr is copied into the
*projectile-files-errors* buffer.
PATHSPECS is handled exactly as in projectile-files-via-ext-command.
Returns the process object, or nil when COMMAND is nil or empty (CALLBACK is then invoked with an empty list and no error, so callers don't have to special-case disabled commands) or when a remote file-name handler declines to start the process (CALLBACK is invoked with an error).
Remote ROOTs are handled via TRAMP (make-process is given a non-nil
:file-handler).
Source Code
;; Defined in ~/.emacs.d/elpa/projectile-20260820.1509/projectile.el
(defun projectile-files-via-ext-command-async (root command callback &optional pathspecs)
"Asynchronously list relative file names in project ROOT by running COMMAND.
Like `projectile-files-via-ext-command', but spawns COMMAND with
`make-process' so Emacs is not blocked while it runs. The output is
parsed with the very same logic, so the result is identical to the
synchronous command.
CALLBACK is funcalled with two arguments when COMMAND finishes: the list
of files (nil on failure) and an error description string (nil on
success). As in `projectile-files-via-ext-command', a non-zero exit
that still produced output is treated as success (the output is passed
to CALLBACK); only a non-zero exit with no output is reported as an
error. Either way the command's stderr is copied into the
`*projectile-files-errors*' buffer.
PATHSPECS is handled exactly as in `projectile-files-via-ext-command'.
Returns the process object, or nil when COMMAND is nil or empty (CALLBACK
is then invoked with an empty list and no error, so callers don't have to
special-case disabled commands) or when a remote file-name handler
declines to start the process (CALLBACK is invoked with an error).
Remote ROOTs are handled via TRAMP (`make-process' is given a non-nil
`:file-handler')."
(if (not (and (stringp command) (not (string-empty-p command))))
(progn (funcall callback nil nil) nil)
(let* ((default-directory root)
;; Capture stderr in a temp file on the *same host* as the
;; command (local file locally, remote file over TRAMP) and
;; redirect the whole command group into it, mirroring the
;; synchronous runner's stderr handling without relying on
;; `make-process' :stderr support over TRAMP.
(errors-file (make-nearby-temp-file "projectile-files-errors"))
(errors-localname (or (file-remote-p errors-file 'localname) errors-file))
;; See the synchronous runner: a pipeline can't take the pathspecs,
;; so it runs unrestricted and its output is filtered instead.
(pathspecs-on-command (and (projectile--command-accepts-pathspecs-p command)
pathspecs))
(restrict (unless pathspecs-on-command pathspecs))
(full-command (concat "{ " (projectile--ext-command-line command pathspecs-on-command)
"; } 2>" (shell-quote-argument errors-localname)))
(stdout-buffer (generate-new-buffer " *projectile-async-index*"))
;; A POSIX shell to run the wrapper under (see
;; `projectile--posix-shell' for why we don't use `shell-file-name');
;; nil on Windows without `sh', in which case we decline below and
;; the caller falls back to the synchronous runner (see #2116).
(shell (projectile--posix-shell))
;; The result is delivered by this function rather than
;; straight from the sentinel, so a caller waiting on the
;; process can also deliver it (see
;; `projectile--dir-files-alien-await' and issue #2118). It
;; runs at most once, whoever gets there first.
(finished nil)
(finish
(lambda (proc)
(unless finished
(setq finished t)
(unwind-protect
;; A consumer that gives up on the wait (e.g. a C-g during
;; `projectile--dir-files-alien-await') marks the process
;; aborted and kills it. Killing fires the sentinel with a
;; `signal' status, but we must not then report a bogus
;; failure or clobber the errors buffer - just clean up.
(unless (process-get proc 'projectile-aborted)
(let ((exit-code (process-exit-status proc))
(files (projectile--restrict-to-subdirs
(with-current-buffer stdout-buffer
(projectile--ext-command-output-files))
restrict)))
(cond
;; Clean exit: pass the listing through.
((and (numberp exit-code) (zerop exit-code))
(funcall callback files nil))
;; Non-zero exit but we still got a listing: trust it,
;; mirroring the synchronous runner. Surface stderr
;; and mention it quietly when there's anything to see.
(files
(when (projectile--surface-ext-command-errors errors-file)
(projectile--message
"`%s' exited with code %s but produced output; using it (see *projectile-files-errors*)"
command exit-code))
(funcall callback files nil))
;; Non-zero exit and nothing on stdout: a real failure.
(t
(projectile--surface-ext-command-errors errors-file)
(funcall callback
nil
(format "exit code %s: %s"
exit-code command))))))
(when (buffer-live-p stdout-buffer) (kill-buffer stdout-buffer))
(when (file-exists-p errors-file)
(ignore-errors (delete-file errors-file)))))))
(proc
(when shell
(condition-case nil
(make-process
:name "projectile-index"
:buffer stdout-buffer
:command (list shell "-c" full-command)
:connection-type 'pipe
:noquery t
:file-handler t
:sentinel
(lambda (proc _event)
(when (memq (process-status proc) '(exit signal))
(funcall finish proc))))
;; A failed spawn (e.g. Windows can't exec the shell, #2116)
;; signals `file-error'; trap it and decline so the caller
;; falls back to the synchronous runner, rather than letting
;; it escape from indexing.
(file-error nil)))))
;; Hand the delivery function to whoever waits on the process, so a
;; sentinel that doesn't get run can't strand them (see #2118).
(when (processp proc)
(process-put proc 'projectile-finish finish))
;; No process: either no POSIX shell was found (Windows without `sh',
;; see #2116), a spawn error was trapped above, or a file-name handler
;; declined (e.g. a remote host that doesn't support `make-process').
;; In every case the sentinel never fires, so honour the callback
;; contract and clean up; the caller then falls back to the synchronous
;; runner.
(unless proc
(when (buffer-live-p stdout-buffer) (kill-buffer stdout-buffer))
(when (file-exists-p errors-file) (ignore-errors (delete-file errors-file)))
(funcall callback nil "could not start the indexing process"))
proc)))