5

I want a "default directory" which is constant across all contexts (buffers, packages, etc). That is, when working on multiple files, opening a new terminal, doing anything which requires a starting directory, I want that starting directory to always be the same.

How can I set a global default directory without having to write an arbitrary number of wrapper functions?


To illustrate the problem, suppose my project has the following structure:

.
└───my-project
    │   bar.py
    │   foo.py
    │
    └───tests
            test_bar.py
            test_foo.py

If I'm working on test_foo.py and I do find-file (C-x C-f) to open foo.py, the prompt will start at my-project/tests. I have to go up a level. Now suppose I'm editing foo.py and I want to look at test_bar.py. When I do find-file within the foo.py buffer, the prompt will be at my-project. I need to navigate to tests. Similar things happen when using eshell and other applications which have a default directory.

According to this, this, and this, the starting directory is handled by default-directory. They all indicate that to change the starting directory, you need to do something like,

(setq default-directory "/my/default/path")

or

(cd "/my/default/path")

The trouble is, default-directory is buffer local. You need to constantly re-assign default-directory.

In the case of find-file, I overcome this by creating a wrapper which changes the directory to the default before calling find-file. I then reassign the keybinding:

(defun my-set-global-default-directory (new-default-directory)
  "Set my-global-default-directory to NEW-DEFAULT-DIRECTORY."
  (interactive "DNew global default directory: ")
  (setq my-global-default-directory new-default-directory))

(defun my-find-file ()
  (interactive)
  (cd my-global-default-directory)
  (call-interactively 'find-file))

(global-set-key (kbd "C-x C-f") 'my-find-file)

This works for find-file, but for this to work universally, I would need to manually wrap any function that uses default-directory!

Lorem Ipsum
  • 4,327
  • 2
  • 14
  • 35
  • I am not very clear about your question, for example, what do your mean by `default-buffer`? Besides, you really should let-bind `default-directory`, such as, `(let ((default-directory "~/.emacs.d/")) (call-interactively #'find-file))`, instead of modifying `default-directory` via `setq` or `cd`. In my opinion, `default-directory` should not be changed by user. – xuchunyang Sep 15 '18 at 19:40
  • Whoa! `default-buffer` was a typo. Was supposed to be `default-directory`. I have corrected the question. – Lorem Ipsum Sep 17 '18 at 12:16
  • 1
    `default-directory` isn't "reset" according to the buffer, but rather it is a *buffer-local* value, so it has an independent value in every buffer. FWIW I am awfully doubtful that what you're asking for is a good idea -- it goes against the expectations for the variable, and as such I strongly *suspect* (but am not certain) that you would encounter undesirable side-effects. Can you elaborate on the *specific* things which you find problematic with the default behaviour? – phils Sep 17 '18 at 14:33
  • 1
    Maybe explaining the context will help. Say I'm working on a project and am utilizing three buffers: my program, the test for the program, and a terminal. The program and test files live in different directories. I find that if I do something related to directories, like finding a file or opening a new terminal instance, the directory prompt depends on where I'm calling the function from. This means that I'm constantly having to "cd ..". I want to set a global default directory so that whenever I do some directory related operation, I start out there. – Lorem Ipsum Sep 17 '18 at 16:00
  • How many different functions do you need? It sounds like `my-find-file`, `my-write-file`, and maybe a few others might be all you need? If that's the case, your approach might be safer than trying to truly change the value of `default-directory` globally – Tyler Sep 17 '18 at 18:38
  • It seems like I was missing something in my understanding of `default-directory`. I rephrased my question and provided an explicit example. – Lorem Ipsum Sep 17 '18 at 20:11
  • If you want this for "projects", to be able to find files, etc., from the root directory of the project, then `projectile` will probably suit your needs. I don't use it so I don't know what it considers to be a project (it might require VCS, for example), but it certainly has functions to do stuff starting at the project root. – Omar Nov 08 '18 at 14:17

2 Answers2

2

The code below uses a new feature in 26.1 called variable-watcher. It allows a function to observe changes in some variable's value. But more or less the same could be achieved by using hooks and/or advices covering all standard functions which are changing the directory (e.g. find-file-hook).

There may be unforeseen consequences to this.

;; -*- lexical-binding: t -*-

(defun run-with-hook (hook fn &optional append local)
  "Like `add-hook', but runs only once."
  (declare (indent 1))
  (let* ((fname (make-symbol "run-with-hook"))
         (fn `(lambda (&rest args)
                (unwind-protect
                    (apply (function ,fn) args)
                  (remove-hook ',hook ',fname ,local)))))

    (fset fname fn)
    (add-hook hook fname append local)))

(defvar global-default-directory (expand-file-name "~"))

(defun global-default-directory-watcher (_symbol _newval op where)
  (when (and (eq op 'set)
             (buffer-live-p where))
    (run-with-hook 'post-command-hook
      (lambda ()
        (when (buffer-live-p where)
          (with-current-buffer where
            (cd global-default-directory)))))))

(define-minor-mode global-default-directory-mode
  "Use a global `default-directory' value in every buffer."
  :global t
  (cond
   (global-default-directory-mode
    (setq-default default-directory global-default-directory)
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (local-variable-p 'default-directory)
          (cd global-default-directory))))
    (add-variable-watcher 'default-directory
                          #'global-default-directory-watcher))
   (t
    (remove-variable-watcher 'default-directory
                             #'global-default-directory-watcher))))
politza
  • 3,316
  • 14
  • 16
1

I am using this setting in my ~/.emacs.d/init.el file to set $FOO as my default directory:

(add-hook 'find-file-hook
          (lambda() (setq default-directory (substitute-in-file-name "$FOO/"))))
klopps
  • 11
  • 1
  • Thank you, I appreciate your contribution. Unfortunately, it doesn't answer the question. It also has several interesting...side-effects(?). If this is what you're currently using, you might try the `my-find-file` I describe in the question. – Lorem Ipsum Nov 14 '18 at 22:29