A basic function can be written fairly simply: create a buffer, and append the contents of each file in sequence to the buffer. Here's an implementation of that in three simple functions: my/find-concat-files
creates the buffer, populates it by mapping my/find-append-file
over the given list of files, starts View-mode
on the resulting buffer to make it read-only and sets an exit action so that when you exit View-mode
with q
, the buffer is killed.
In turn, my/find-append-file
passes the file to my/find-file-contents
which returns the contents of the file as a string; my/find-append-file
then inserts those contents at the end of the buffer.
Here's the implementation:
#+begin_src elisp
(defun my/find-file-contents (file)
"Contents of FILE as a string."
(with-current-buffer (find-file-noselect file)
(let ((s (buffer-string)))
(kill-buffer-if-not-modified (current-buffer))
s)))
(defun my/find-append-file (file)
"Append the contents of FILE to the end of the current buffer."
(goto-char (point-max))
(insert (my/find-file-contents file)))
(defun my/find-concat-files (files)
"Create a new buffer and append to it the contents of each file in FILES.
Then go to the beginning of the buffer, enable `view-mode' on it (to
make it read-only and enable all the motion commands that `view-mode'
provides) and set up the `view-exit-action' so that when `view-mode' is
exited (with `q'), the buffer is killed. Finally, pop the buffer up in
some window and make it current, so that the user can interact with it."
(with-current-buffer (generate-new-buffer " *Concat*")
(mapc #'my/find-append-file files)
(goto-char (point-min))
(view-mode-enter nil (lambda (buf) (kill-buffer buf))))
(pop-to-buffer " *Concat*"))
#+end_src
Some comments:
my/find-concat-files
is not interactive, so you cannot bind it to a key. You have to call it with M-: (my/find-concat-files my/files)
where my/files
is a list of the files you want to concatenate.
- If you want to make it
interactive
(i.e. a command that you can bind to a key), then the user-interface aspects of specifying the files are going to come to the fore, but following your comment, I did not worry about that.
- I tested it by creating 100 files,
fooNN.txt
, in a test
subdir and setting the list with (setq my/files (directory-files "test" 'full "^foo[0-9][0-9]\\.txt"))
.
- It is fairly slow: it takes about 2 secs on my machine (and it required three garbage collections - about 0.5 secs). By far, the most expensive part of the code is the
find-file-noselect
: maybe that can be replaced by a lower level function to speed it up.
my/find-file-contents
opens the file, saves and returns its contents and then kills the file buffer (if not modified). If you had the file open already, then that buffer will be gone after this. I don't see an easy way to deal with this, although I suspect that it's not going to be much of a problem, but keep this limitation in mind. OTOH, creating and killing the buffer, multiplied by 100, is probably what caused the garbage collections.