reddit / emacs
Finding file with similar filename to current file
augustinus-jp · 2022-05-20 07:56
I'm rather new to Emacs, but I've found it and especially `org-capture` very useful. I know how to make an elisp macro to find a specific file or to find one from a variable, but I was wondering if there's a way to incorporate patterns and interactive prompts like `%^{PROMPT}` into `find-file`.
For my work I have to look at a variety of a given documents in multiple languages/editions, which are broken down into sub-books and chapters. For example, reading the *Iliad* in both Greek and English. However, this results in directories with hundreds of files each, sometimes more, and trying to find the same chapter in a different edition is a pain. For sake of example, let's use the Bible.
All files have been systematically assigned a file name that follows the same exact pattern. **Note:** `Edition` **is used as both the subdirectory and the first part of the filename**.
./(Edition)/(Edition)_(Book_ID)_(Chapter)_read.txt
Let's say I'm reading Genesis Chapter 1 in Greek. It's broken down by chapter at the following directory:
./grcbrent/grcbrent_002_GEN_01_read.txt
./grcbrent/grcbrent_002_GEN_02_read.txt
./grcbrent/grcbrent_003_EXO_01_read.txt
But, now I want to compare it with Genesis Chapter 1 in Latin:
./latVUC/latVUC_002_GEN_01_read.txt
So, is there an easy way to program a function that, while viewing `./grcbrent/grcbrent_002_GEN_01_read.txt`, I don't know, reads `buffer-file-name`, replaces both the directory name and the first part of the filename until the first underscore with "`%^{Edition}`"("`./%^{Edition}/%^{Edition}_002_GEN_01_read.txt`") to produce a minibuffer like the following:
Edition:
and after typing `latVUC` `[RET]` does `(find-file "./latVUC/latVUC_002_GEN_01_read.txt")` ?
**TLDR**
I want to be able to jump from version `a` of a file to version `b`, i.e. (`./a/a_x_y.txt` to `./b/b_x_y.txt`) where `b` is typed in the minibuffer.
I appreciate any help in figuring this out. Thanks!
[deleted] · 2022-05-20 08:59
[deleted]
[deleted] · 2022-05-20 09:02
[deleted]
redguardtoo · 2022-05-20 12:34
Since emacs 27, there is `string-distance` API.
tjgrinn · 2022-05-20 14:49
What you probably need to do is split the buffer-file-name into tokens and separators, use cl-substitute to replace each token with the minibuffer text, recombine the tokens and separators, and check if that file exists. This is a pretty rough implementation: https://gist.github.com/tylergrinn/cad98cc625056b0fd05774fa5790cc9e
00-11 · 2022-05-20 15:37
One thing you could do is just use `M-%` (`query-replace`) in the minibuffer to replace `grcbrent` with `latVUC` in both places. (You can move the cursor to the beginning of the default file name, with `C-a`, before using `M-%`.)
Remember that you can use most editing commands in the minibuffer to edit text there. This applies to `M-%` as one editing command.
But `M-%` itself prompts you to enter text in the minibuffer, so to use it when already in the minibuffer and prompted by some other command (e.g. `find-file`) you'll need to have option `enable-recursive-minibuffers` be non-`nil` at that time.
You can set that option temporarily (`M-x set-var RET enable-rec RET t`), customize it (`M-x customize-o RET enable-rec RET`), or define your own command that just binds it for that command's duration. Then use your command instead of, say, `find-file`:
(defun my-find-file (filename &optional wildcards)
"..."
(interactive
(let ((enable-recursive-minibuffers t)) ; <==============
(list (read-file-name "Find file: " nil default-directory t)
t)))
(find-file filename wildcards))
augustinus-jp · 2022-05-20 21:18
Thank you! I'd already set `recursive-minibuffers` to `t`, but I keep forgetting to use commands like `query-replace` in the minibuffer. That's an extremely helpful tip!
augustinus-jp · 2022-05-20 21:20
Good to know! I'd been using Emacs 26 until recently, so I didn't know about the `string-distance` API. Cool!
00-11 · 2022-05-20 21:59
It's easy for people to forget this. Unfortunately, one result of forgetting, or not valuing, this feature is that people can tend to rebind regular editing keys in a minibuffer keymap to non-editing actions specific to minibuffer interaction.
People writing libraries that use the minibuffer (and this includes development of Emacs itself) would be well advised to avoid "losing" handy editing keys this way.
Things like Isearch in the minibuffer are useful. When the minibuffer is active, Isearch there should, well, act on the content of the minibuffer...
It's misguided to assume that minibuffer input will be single-line or limited in other ways. (It's OK for any given use of the minibuffer to expect that, but it's misguided to assume that generally.)
augustinus-jp · 2022-05-21 05:33
Thanks so much for writing up a quick function so quickly! Pointing me to the use of tokens, separators, and split-string I think will be really helpful.
I was able to fix up a `split-string: Wrong type argument: stringp, nil` error by deleting what I think was a stray t, but it's still giving me a `Wrong number of arguments: mapconcat, 2` but I'll take a closer look when I get the time.
tjgrinn · 2022-05-21 13:04
No problem, I love little elisp projects like this. The problem is probably the line \`(buffer-file-name (nth 1 (buffer-list)))\`. Try it with \`emacs -Q\` to make sure. It's supposed to select the last buffer used before switching to the minibuffer but some completion engines may create additional buffers, so you may need to replace it with \`(nth 2 (buffer-list))\` or something.
tjgrinn · 2022-05-21 16:12
I've updated it to store the current file name as a variable before switching to the minibuffer, which hopefully will work for all completion engines.
Phil-Hudson · 2022-05-21 18:43
I would use `dired` and its multiple filename-matching methods applicable to multiple selected and expanded directories. I would recommend installing and configuring package `dired-subtree` for your use case.