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.