Emacs dired - using predefined variable - emacs

In emacs dired I want to do something I do quite often in Microsoft PowerShell.
In PowerShell, I have a set of folders that I always use, and I assign their full path to global variables in my profile script (similar to init.el in the emacs world) e.g:
$standardTemp = "C:\Long\Path\To\Folder"
If I am in another folder and I want to copy something to the above folder, I do:
copy myFile $standardTemp
Even more useful as a feature, is if I put a backslash after $standardTemp, it will expand it out, so I can go into subfolders if I need to. This is a very awesome feature and saves me a lot of time.
With the dired copy command can I do something similar, if I define variables with e.g. setq in my init.el file?

How about something like this?
;; Use ido
(require 'ido)
(ido-mode t)
;; Make a hash table to hold the paths
(setq my-target-dirs (make-hash-table :test 'equal))
;; Put some paths in the hash (sorry for Unix pathnames)
(puthash "home" "/home/jhrr/" my-target-dirs)
(puthash "target" "/home/jhrr/target/" my-target-dirs)
;; A function to return all the keys from a hash.
(defun get-keys-from-hash (hash)
(let ((keys ()))
(maphash (lambda (k v) (push k keys)) hash)
keys))
;; And the function to prompt for a directory by keyword that is looked
;; up in the hash-table and used to build the target path from the
;; value of the lookup.
(defun my-dired-expand-copy ()
(interactive)
(let* ((my-hash my-target-dirs)
(files (dired-get-marked-files))
(keys (get-keys-from-hash my-hash)))
(mapc (lambda (file)
(copy-file file
(concat
(gethash
(ido-completing-read
(concat "copy " file " to: ") keys) my-hash)
(file-name-nondirectory file))))
files)))
It's not exhaustively tested as I just whipped it up in 10 minutes, but it does the job and it can handle multiple files.
You will need to open the dired buffer in the directory the files are in and mark each file you want to copy with 'm', then invoke my-dired-expand-copy and it will prompt you for a target destination (in the form of a keyword from the hash-table we set-up) for the file before, finally, copying the file over to the directory that maps to the target keyword.
It doesn't quite cover the sub-directories use-case you mention, but it shouldn't be too hard to get there given a bit more hacking.
UPDATE:
This should now prompt you to be able to descend into subdirectories from an original target; maybe not the most mind-shatteringly wonderful UX on the whole, but, it works:
(defun my-dired-expand-copy-2 ()
(interactive)
(let* ((my-hash my-target-dirs)
(files (dired-get-marked-files))
(keys (get-keys-from-hash my-hash)))
(mapc (lambda (file)
(let ((target (gethash
(ido-completing-read
(concat "copy " file " to: ") keys) my-hash)))
(if (y-or-n-p "Descend?")
;; Descend into subdirectories relative to target dir
(let ((new-target (ido-read-directory-name "new dir: " target)))
(copy-file file (concat new-target
(file-name-nondirectory file)))
(message (concat "File: " file " was copied to " new-target)))
;; Else copy to root of originally selected directory
(copy-file file (concat target (file-name-nondirectory file)))
(message (concat "File: " file " was copied to " target)))))
files)))

When I need to use dired to get to frequently-used directories, I use the standard emacs bookmarking capabilities.
I manually navigate to the directory, and press
C-x r m
to execute the command
bookmark-set
You'll be prompted for a name for the bookmark. Enter a shortcut that you can remember.
At this point, anytime you want to open that directory within dired, simply execute the command
bookmark-jump
with the keys
C-x r b
Enter your shortcut to the directory, and dired will open to that location.
To copy from one directory to another, ensure you have the following set in your init file
(setq dired-dwim-target t)
Then you can open a dired window for the source directory, and another window for the target directory within in the same frame, and dired will automatically assign the source and target location to the appropriate directories.
Note this is just a subset of what emacs bookmarks can do for you!
Chris

In addition to using bookmarks, consider using directory-name aliases (e.g. symlinks) or directory-abbrev-alist. See the Emacs manual, node File Aliases.

If you want to insert the value of an environment variable into the minibuffer, you can do it this way:
C-u M-: (getenv "THE-VARIABLE")
where THE-VARIABLE is the variable name. Using C-u inserts the value of evaluating the sexp into the current buffer (in this case the minibuffer).
So you would, say, use C to copy the marked files in Dired, and then use C-u with a getenv sexp for the existing variable you have, to insert its value into the minibuffer when prompted for the directory to copy to.
(Depending on your Emacs setup, you might need to set enable-recursive-minibuffers to non-nil, to be able to use M-: from the minibuffer.)

Related

How can I create a file in a sub-directory in Emacs Dired?

I use dired to browse a directory and its sub-directories with i (dired-maybe-insert-subdir)
When the cursor is in a sub-directory, and I do C-x C-f, the default directory is the root of my buffer, is there a way to change that?
So if I visit /foo/bar and then I insert in bar's buffer the content of /foo/bar/baz and have my cursor there, get a mini-buffer with /foo/bar/baz/ when I ask to visit a file (with the default keybinding or another, that doesn't matter to me).
The following is my own solution.
(defun dired-subdir-aware (orig-fun &rest args)
(if (eq major-mode 'dired-mode)
(let ((default-directory (dired-current-directory)))
(apply orig-fun args))
(apply orig-fun args)))
(advice-add 'find-file-read-args :around 'dired-subdir-aware)
I just learned how to use the advice function to augment existing functions (and macros). See (elisp) Advising Functions.
Now if you go to a subdir under dired and run C-x C-f, you will be prompted with a correct path.
Update:
Recently, I began to play with (ido) Top. With dired-subdir-aware defined, I easily extend ido-find-file to recognize subdir with the following code.
(dolist (fun '(find-file-read-args ido-expand-directory))
(advice-add fun :around 'dired-subdir-aware))
No, default-directory is local to a buffer, not just part of a buffer. This is by design, and there is no way to change it.
But you could of course define a command that picks up the subdir directory and then binds default-directory to that while it reads a file name to visit, etc.).
For example, this command will read a file name with the default directory being what you want, and then visit that file:
(defun foo (file)
"..."
(interactive
(let ((default-directory (dired-current-directory)))
(list (read-file-name "File: "))))
(find-file file))
And, out of the box, you can certainly visit the subdir in another Dired buffer, where default-directory is what you want. Commands such as dired-do-find-marked-files (F) and dired-display-file (C-o) do that.
But why do you want default-directory to reflect the subdir whose listing you are in? What's your use case?
I refined Drew's answer to a function that does exactly what I wanted:
(defun dired-find-file ()
"Find a file in the current dired directory"
(interactive)
(let ((default-directory (dired-current-directory)))
(find-file (read-file-name "Find file:"))))
I then bound this function to a convenient key combination:
(add-hook 'dired-mode-hook
(lambda ()
(local-set-key (kbd "C-x M-f") 'dired-find-file)))

Can I stop Emacs from resetting default-directory every time I open a file?

I've already asked the same question on Emacs. If it's not permitted, I'm sorry and I will delete the question.
If I:
Start Emacs in my home directory (~)
Find a file in the ~/Projects/ruby-play directory with C-x C-f
Try to find another file with C-x C-f
The default directory in the file finder will be ~/Projects/ruby-play.
What I'd like to see is the default directory being ~ still.
Is there a package/hook/Elisp function I should use to make this happen?
Thank you very much!
One way would be to set initial-default-directory to nil, but then you won't have a default directory at all, not even your home-directory:
(setq insert-default-directory nil)
If you want your home directory to show up as the default, this cannot be done so easily. When interactively calling find-file, this results in a call to read-file-name which has a rather complicated default behavior.
Using setq-default on default-directory doesn't help, as it's value is set when the file of the buffer you're currently seeing is visited.
The only option I see is using your own version of find-file like this:
(defun my-find-file-read-args (prompt mustmatch)
(list (read-file-name prompt "~/" nil mustmatch)
t))
(defun my-find-file (filename &optional wildcards)
"Edit file FILENAME.
Like `find-file` but always uses ~ as the default directory"
(interactive
(my-find-file-read-args "Find file: "
(confirm-nonexistent-file-or-buffer)))
(let ((value (find-file-noselect filename nil nil wildcards)))
(if (listp value)
(mapcar 'switch-to-buffer (nreverse value))
(switch-to-buffer value))))
(global-set-key (kbd "C-x C-f") 'my-find-file)

in Emacs, how to maintain a list of recent directories?

In Emacs, I use recentf extensively. Rather than calling find-files, I usually call a custom function xsteve-ido-choose-from-recentf instead which allows me to choose from my recentf files.
How do I create and maintain a separate list of recent directories, separate from the list of recent files? So that instead of calling dired, I could call something like ido-choose-from-recent-directories?
You ask, in your comment replying to #Stefan's answer: And how do I get from the above to viewing a list of recent directories? -
The answer is that you use the little-known fact that if the DIRNAME argument to dired is a list of (a) the new Dired buffer name followed by (b) file (or directory) names, then Dired is opened for just those files/dirs. IOW:
M-: (dired (cons DIRED-BUFNAME YOUR-LIST-OF-RECENT-DIRECTORIES))
For example:
M-: (dired '("My Dired Buffer" "/a/recent/dir/" "/another/recent1/" "/another/"))
If you use library Dired+ then you can provide such a list interactively, by using a non-positive prefix arg with dired.
But in this case you want to write a command that first gathers the list of recent directories and then opens Dired for them. This should do it:
(defun dired-recent (buffer)
"Open Dired in BUFFER, showing the recently used directories."
(interactive "BDired buffer name: ")
(let ((dirs (delete-dups
(mapcar (lambda (f/d)
(if (file-directory-p f/d)
f/d
(file-name-directory f/d)))
recentf-list))))
(dired (cons (generate-new-buffer-name buffer) dirs))))
That works for me. However, vanilla Emacs does not let you use i to insert the listing for any directory that is not in the same directory tree as the default-directory of the Dired buffer. That means that the above code will work OK, but you will not be able to insert any of the listed directories.
To be able to do that, load library dired+.el. Dired+ also fixes a couple of other inadequacies wrt the vanilla handling of a cons arg to dired.
The above code, together with Dired+ should give you what you want.
UPDATE
I just added this to Dired+. These are the commands added: diredp-dired-recent-dirs and diredp-dired-recent-dirs-other-window.
UPDATE 2
I made it simple to choose which of the recently used directories to include or exclude. Use a prefix arg to initiate such choosing. With no prefix arg you get all recent dirs. I also made it possible to use a prefix arg to be prompted for the ls switches. Here is the doc s tring of diredp-dired-recent-dirs:
Open Dired in BUFFER, showing recently used directories.
You are prompted for BUFFER.
No prefix arg or a plain prefix arg (`C-u', `C-u C-u', etc.) means
list all of the recently used directories.
With a prefix arg:
* If 0, `-', or plain (`C-u') then you are prompted for the `ls'
switches to use.
* If not plain (`C-u') then:
* If >= 0 then the directories to include are read, one by one.
* If < 0 then the directories to exclude are read, one by one.
When entering directories to include or exclude, use `C-g' to end.
Finally, I added bindings for the commands: C-x R (same window) and C-x 4 R (other window), where R is Shift + r.
You don't need to maintain a separate list (which would be a lot of work). Instead, you can extract that list from the recentf list. E.g.
(delete-dups
(mapcar (lambda (file)
(if (file-directory-p file) file (file-name-directory file)))
recentf-list))
Pragmatic Emacs found the solution.
Here is a function to give you a list of recent directories, using ivy
(part of swiper) to narrow it dynamically, and then open the selected
one in dired.
;; open recent directory, requires ivy (part of swiper)
;; borrows from http://stackoverflow.com/questions/23328037/in-emacs-how-to-maintain-a-list-of-recent-directories
(defun bjm/ivy-dired-recent-dirs ()
"Present a list of recently used directories and open the selected one in dired"
(interactive)
(let ((recent-dirs
(delete-dups
(mapcar (lambda (file)
(if (file-directory-p file) file (file-name-directory file)))
recentf-list))))
(let ((dir (ivy-read "Directory: "
recent-dirs
:re-builder #'ivy--regex
:sort nil
:initial-input nil)))
(dired dir))))
(global-set-key (kbd "C-x C-d") 'bjm/ivy-dired-recent-dirs)
Source:
Open a recent directory in dired: revisited | Pragmatic Emacs

How do I get the path from which init.el was loaded?

I am looking to create a custom config for emacs to use for Erlang work and I want to refer to my custom EDTS repo as being under the directory from which init.el was loaded. Right now I have this:
(add-to-list 'load-path "~/.emacs-edts/edts/")
But I would rather not hardcode it and refer to it by variable.
Suggestions?
Strictly speaking the answer is (file-name-directory user-init-file), but instead see C-hv user-emacs-directory
I have the following snippet in my init.el:
(setq my-init-dir
(file-name-directory
(or load-file-name (buffer-file-name))))
This has the advantage of working whether init.el is in your emacs.d directory or not.
I have the following in my init file:
(defun my-file-name-basename (s)
"The directory name, without the final part.
For example:
(my-file-name-basename \"alpha/beta/gamma\") => \"alpha/beta\""
(substring (file-name-directory s) 0 -1))
;; Note: Normally, it's not possible to find out the file a specific
;; function is defined in. However, it's possible to save the file
;; name at the time this file was loaded.
(defvar my-load-file-name load-file-name
"The file name of this file.")
(defun my-start-directory (&optional path)
"The root directory that contains this module.
When PATH is specified, return the start directory concatenated with PATH.
Otherwise return the directory with a trailing slash."
;; Note: Try to figure out where we are, so that we can add the
;; subdirectories. `load-file-name' only works when the file is
;; loaded. Picking up the file from the symbol works when this is
;; evaluated later.
(let ((file-name (or my-load-file-name
(symbol-file 'my-start-directory)
;; Default value. (This is used, for example,
;; when using `eval-buffer' or `eval-region'.)
"~/emacs")))
(let ((start (concat (my-file-name-basename
(my-file-name-basename file-name))
"/")))
(if path
(concat start path)
start))))
In addition to finding out where the file containing the above above code is located (which does not have to be the init file), it provides a convenient way to create paths based on it. For example:
(setq custom-file (my-start-directory "init/custom.el"))

How to make all org-files under a folder added in agenda-list automatically?

I am using org-mode to write notes and org-agenda to organize all notes, especially to search some info. by keyword or tag.
C-c a m can search some files by tag inputed, C-c a s by keyword ,those functions from org-agenda are well to utilize, however, I need to add org-file into the agenda-list by hand.
I added some codes into .emacs, such as
(setq org-agenda-files (list "path/folder/*.org"))
or
(setq org-agenda-files (file-expand-wildcards "path/folder/*.org"))
but, both failed to add files under the folder specified into agenda-list automatically, so I can't search keyword or tag among those org-files, unless that I open a org-file and type C-c [ to add it into agenda-list.
How can I make all org-files under a folder automatically added in agenda?
Just naming the directory should be enough. For example this works for me very well:
(setq org-agenda-files '("~/org"))
Also take a look at org-agenda-text-search-extra-files; it lets you
add extra files included only in text searches. A typical value might
be,
(setq org-agenda-text-search-extra-files
'(agenda-archives
"~/org/subdir/textfile1.txt"
"~/org/subdir/textfile1.txt"))
Caveat: If you add a file to the directory after you have started
Emacs, it will not be included.
Edit: (2018) To include all files with a certain extension in the extra files list you can try the following function I wrote sometime back (a more recent version might be available here).
;; recursively find .org files in provided directory
;; modified from an Emacs Lisp Intro example
(defun sa-find-org-file-recursively (&optional directory filext)
"Return .org and .org_archive files recursively from DIRECTORY.
If FILEXT is provided, return files with extension FILEXT instead."
(interactive "DDirectory: ")
(let* (org-file-list
(case-fold-search t) ; filesystems are case sensitive
(file-name-regex "^[^.#].*") ; exclude dot, autosave, and backupfiles
(filext (or filext "org$\\\|org_archive"))
(fileregex (format "%s\\.\\(%s$\\)" file-name-regex filext))
(cur-dir-list (directory-files directory t file-name-regex)))
;; loop over directory listing
(dolist (file-or-dir cur-dir-list org-file-list) ; returns org-file-list
(cond
((file-regular-p file-or-dir) ; regular files
(if (string-match fileregex file-or-dir) ; org files
(add-to-list 'org-file-list file-or-dir)))
((file-directory-p file-or-dir)
(dolist (org-file (sa-find-org-file-recursively file-or-dir filext)
org-file-list) ; add files found to result
(add-to-list 'org-file-list org-file)))))))
You can use it like this:
(setq org-agenda-text-search-extra-files
(append (sa-find-org-file-recursively "~/org/dir1/" "txt")
(sa-find-org-file-recursively "~/org/dir2/" "tex")))
Edit: (2019) As mentioned in the answer by #mingwei-zhang and the comment by #xiaobing, find-lisp-find-files from find-lisp and directory-files-recursively also provides this functionality. However, please note in these cases the file name argument is a (greedy) regex. So something like (directory-files-recursively "~/my-dir" "org") will give you all Org files including backup files (*.org~). To include only *.org files, you may use (directory-files-recursively "~/my-dir" "org$").
There is a simpler way of doing recursive search of org files (courtesy #xiaobing):
(setq org-agenda-files (directory-files-recursively "~/org/" "\\.org$"))
EDIT: You can also filter out certain directory from lookup by adding a array filter. Example, filtering out all org files in xxxx/xxx/daily/ directory:
(setq org-agenda-files
(seq-filter (lambda(x) (not (string-match "/daily/"(file-name-directory x))))
(directory-files-recursively "~/Notes/roam" "\\.org$")
))
For Emacs <25, you can use find-lisp-find-files:
(load-library "find-lisp")
(setq org-agenda-files
(find-lisp-find-files "FOLDERNAME" "\.org$"))