0

Situation

I would like to have a function, that when called, creates a new file/buffer in a specific directory, indepent of the current directory. But with the same file name as the current file that the buffer is visiting.

So I created the following:

(defun change-to-foobar-directory ()
    (interactive)
    (setq buffer-temp-name (buffer-name))
    (kill-this-buffer)
    (find-file (concat "C:\\foobar\\" buffer-temp-name)))

This works great.

When I have the buffer foobar.el in directory C:\another-dir, and I call change-to-foobar-directory, then I get the buffer C:\foobar\foobar.el. The difference is that I'm editing a buffer in another directory (C:\foobar).

Problem

This solution works great so far. When I add that function to a hook, for example:

(add-hook 'emacs-lisp-mode-hook 'change-to-foobar-directory)

And when I'm in the file another-file.c, I create a new buffer foobar.el with the Evil command :e foobar.el (which stands for edit the file foobar.el in the same directory of buffer, even when the file doesn't exist yet, then a new buffer will be created).

Because the emacs.lisp-mode gets activated, the function change-to-foobar-directory should be called.

But I get the same file another-file.c instead editing the file/buffer C:\foobar\foorbar.el. So it seems with the hook, change-to-foobar-directory will not work properly. I'm not sure what caused this. When I call that command interactively, it will work properly.

Any idea what caused this?

3 Answers 3

7

Dunno what Evil might change in the picture (try without Evil, to see if there is a difference).

But if you are interested in the buffer name of a buffer visiting a file then what you should use is buffer-file-name, not just buffer-name. Specifically, (file-name-nondirectory (buffer-file-name)), if you want only the non-dir part. (buffer-name is for any buffer; buffer-file-name returns nil if the buffer is not visiting a file. This lets you conditionally not try to, for example, use the name of a non-file buffer as a file name.)

Similarly, do not use concat for file names. Instead, use expand-file-name with the directory you want to use as the second arg: (expand-file-name buffer-temp-name "C:/foobar/") (and no need to use MS Windows directory separators.

Also, use let to bind the relative file name, instead of just setq (unless, for some reason, you have a global variable of that name and you want to change its value).

Finally, if you want to kill the buffer and then find a file to replace it, just use find-alternate-file. This will take care of the problem you are experiencing, and that @nanny and @Stefan have pointed to.

(defun change-to-foobar-directory ()
  (interactive)
  "..."
  (let* ((filebuf               (buffer-file-name))
         (relname               (and filebuf  (file-name-nondirectory filebuf)))
         (emacs-lisp-mode-hook  ()))
    (when filebuf
     (find-alternate-file (expand-file-name relname "C:/foobar/")))))

Suggestion: Spend a little time with the Elisp manual, node File Names and its subnodes. You'll appreciate it.

10
  • Your solution didn't worked, got the message "Note: file is write protected" and the buffer name is a concenation of previous directory path and new path. However your code seems faulty (Sorry, I consider you as the master in Lisp) I would like to learn from it: 1. What is the difference between buffer-file-name and buffer-name? 2. Why prefering the expand-file-name above concat, while the result would be the same? 3. Your project is discussed here.
    – ReneFroger
    Commented Oct 5, 2015 at 18:47
  • buffer-file-name is only for buffers visiting files. If used in another buffer (not associated with a file) it returns nil, which is probably the problem you are encountering. In that case, you would need to specify what behavior you want, for such a buffer. OK, you want to kill it. But do you really want to open a file with the same name as that non-file buffer (e.g. *scratch* or whatever)? Not clear to me what the aim is in this case.
    – Drew
    Commented Oct 5, 2015 at 22:16
  • expand-file-name and the other file-name manipulating functions are designed for, well, manipulating file names. They can be used in multiple contexts, including different platforms and remote hosts. concat sees only strings, not file names. Again, consider reading some of the doc I pointed you to.
    – Drew
    Commented Oct 5, 2015 at 22:17
  • (This Q&A have nothing to do with OneOnOneEmacs, which you referenced.)
    – Drew
    Commented Oct 5, 2015 at 22:19
  • I've edited the post so that the function should now be a no-op if the current buffer is not visiting a file. In the absence of a clear spec of what behavior you want in that case, this seems reasonable.
    – Drew
    Commented Oct 5, 2015 at 22:22
5

In your problematic case, when you're visiting foobar.el, Emacs does the following in this order:

  • fetch foobar.el into a new buffer.
  • initialize the major mode (including running the hooks).
  • display the buffer in the selected window.

So your hook will kill the foobar.el buffer, create a new foobar.el buffer at the new location. At this point, you should get an "infinite-recursion error" here since that find-file will also run the emacs-lisp-mode-hook. If you somehow avoid that error, then you bump into another error which is that right after your hook has finished, the third step ("display the buffer in the selected window") may cause yet more grief (either signalling an error because that buffer has been killed in the mean time, or displaying some other buffer in its stead).

2

Hooks get called as the last step in a mode's initialization.

And when I'm in the file another-file.c, I create a new buffer foobar.el

So you create that buffer, foobar.el, emacs-lisp-mode gets enabled in the buffer, and then its hooks are run. One of its hooks is your function change-to-foobar-directory. Your function calls kill-this-buffer. But what's this-buffer at this time? foobar.el.

The function is working perfectly fine, it simply has a logical error. Instead, try:

(defun change-to-foobar-directory ()
    (interactive)
    (setq buffer-temp-name (buffer-name))
    (kill-buffer (other-buffer))
    (find-file (concat "C:\\foobar\\" buffer-temp-name)))

Or see Drew's answer for a better alternative.

1
  • Thanks for your reply. But unfortunately, wIth your snippet, the changed function didn't worked out. I got the error: Error while checking syntax automatically: (file-error "Opening output file" "Invalid argument" "C:/foobar/foobar.el<foobar>"). While the folder foobar exists. So it worked right there in your situation?
    – ReneFroger
    Commented Oct 5, 2015 at 18:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.