As @db48x wrote, append-to-file
uses Emacs' default completion facilities, which means calling the function completion-read
. In turn, the variable completing-read-function
determines what backend to use for completion. By controlling completion-read-function
you decide what completion interface you want. Reading file names is a bit different, because it's done by read-file-name
which is controlled by read-file-name-function
. However, the default behavior is that read-file-name
calls completing-read
.
When helm-mode
is enabled, it sets completing-read-function
to helm--completing-read-default
and read-file-name-function
to helm--generic-read-file-name
. In addition, in newer Emacs version (24.4+) Helm uses add-function
to override these variables instead of setq
. I assume that's because with setq
it needs to know the original value when disabling helm-mode
, but with add-function
it doesn't.
The completion you get when invoking append-to-file
is actually a generic helm file name completion, which isn't as heavy and feature-rich as the command helm-find-files
. In fact, most Helm commands don't use interactive
to read arguments, because they want more features than the generic Helm completion. Instead, they have a minimal interactive
declaration and call helm
function for reading arguments and performing actions, thus utilizing Helm's features. Notably, this includes helm-find-files
and helm-projectile-find-file
.
To change the default completion backend, you can just disable helm-mode
and set completing-read-function
and read-file-name-function
to whatever you wish. Helm commands will still function through Helm as usual, and won't be affected by this change.
Alternatively, if you want append-to-file
to use different completion, but still use Helm as the general completion backend, you will need to wrap append-to-file
in another command. That command should read the arguments and then call append-to-file
(non-interactively). With Helm, you could start with the following and modify according to your needs and how much of Helm's features you want to use:
(defun helm-append-to-file (start end)
(interactive "r")
(let ((filename
(helm :sources some-helm-sources
:buffer "*helm append to file*"
:prompt "File to append to: ")))
(append-to-file start end filename)))
Note that if (helm ...)
changes the current buffer or region then append-to-file
won't append the region you expect it to, so you'll want to wrap (helm ...)
in a save-excursion
or similar. You could use :candidates
instead of :sources
, but at that point you won't be much better off than the generic Helm completion. You might be lucky enough to find suitable pre-existing sources, so you don't need to write your own, but otherwise you will have to learn how to write a Helm source. You can look at helm-projectile.el
and the macro helm-projectile-command
to see how helm-projectile commands are defined.
For more info on Emacs completion you can visit section 19.6 of the elisp manual or read the relevant functions' documentation.
EDIT: Using find and Helm
The solution below uses the shell command find
to get a list of filenames. I use split-string
so the files show up in the Helm prompt without the common prefix (instead I include the common prefix in the source's name). Because of that, I need to use f-join
to so append-to-file
gets the correct filename. Note that Helm has a transformers feature to alter how candidates are displayed, but I didn't use them here since I'm less familiar with them.
Using f-join
brings a dependency on f.el
, which you will need to install (in Spacemacs it's already installed). Using seq-map
brings a dependency on seq.el
, which requires Emacs 25.1 or newer. Both of these can be replaced by functions available in Emacs 24.5 and older (cl-map
instead of seq-map
, several string and filename functions instead of f-join
).
(require 'f)
(require 'helm)
(require 'seq)
(defun helm-append-to-file (start end)
(interactive "r")
(let* ((path "/path/to/files")
(relative-filename
(helm :sources (helm-build-sync-source "append"
:candidates (get-candidates path))
:buffer "*helm append to file*"
:prompt "File to append to: "))
(filename (f-join path relative-filename)))
(append-to-file start end filename)))
(defun get-candidates (path)
"Get names of capture files.
Return a list of filenames. Each filename is relative to PATH."
;; use -print0 just in case a filename contains a newline
(let ((find-command (format "find -L '%s' -wholename '*/capture.md' -print0" path)))
;; remove common path prefix so candidates are easier to read in helm prompt
(seq-map (lambda (s) (string-remove-prefix path s))
;; "\0" matches -print0
(split-string (shell-command-to-string find-command) "\0" t))))
helm-completing-read-handlers-alist
, but I am not completely sure how to set it correctly to your needs, probably something like(add-to-list 'helm-completing-read-handlers-alist '((ido-find-file . helm-projectile-find-file)))
or(add-to-list 'helm-completing-read-handlers-alist '((append-to-file . helm-projectile-find-file)))
will do.helm-completing-read-handlers-alist
? Does it containappend-to-file
?append-to-file
.