Recursively adding .org files in a top-level directory for org-agenda-files takes a long time - emacs

I'm trying to find a way to quickly recurse through every subdirectory searching for org files. I've found several solutions (Elisp Cookbook, and several solutions on github), but they don't handle my real world usage (hundreds of directories (and subdirectories) and hundreds of org files). They seem to run forever on my system (Windows 7, with max-lisp-eval-depth = 10000). My work around is to add each directory manually to my org-agenda-list, but it's annoying and I know I've probably forgotten some. Any suggestions would be appreciated.

Haven't seen other people post this, so I will do.
Have you tried load "find-list" library and use its "find-lisp-find-files" function?
I added these lines in my org config and it works, but it may not fit your performance requirement:
(load-library "find-lisp")
(setq org-agenda-files
(find-lisp-find-files "FOLDERNAME" "\.org$"))
source: http://emacs-orgmode.gnu.narkive.com/n5bQRs5t/o-multiple-recursive-directories-with-org-agenda-files

The following code works well in emacs 24.3+:
;; Collect all .org from my Org directory and subdirs
(setq org-agenda-file-regexp "\\`[^.].*\\.org\\'") ; default value
(defun load-org-agenda-files-recursively (dir) "Find all directories in DIR."
(unless (file-directory-p dir) (error "Not a directory `%s'" dir))
(unless (equal (directory-files dir nil org-agenda-file-regexp t) nil)
(add-to-list 'org-agenda-files dir)
)
(dolist (file (directory-files dir nil nil t))
(unless (member file '("." ".."))
(let ((file (concat dir file "/")))
(when (file-directory-p file)
(load-org-agenda-files-recursively file)
)
)
)
)
)
(load-org-agenda-files-recursively "/path/to/your/org/dir/" ) ; trailing slash required
It does not require intermediate files creation and you can put it on a shortcut as well.
To be able to refile to any file found add this:
(setq org-refile-targets
'((nil :maxlevel . 3)
(org-agenda-files :maxlevel . 1)))

How about storing the list of org-agenda directories in a file that you (automatically) update every once in a while, when you know the direcory structure has changed and you have some time.
You could for example use something like this:
;; From http://www.emacswiki.org/emacs/ElispCookbook#toc58
(defun directory-dirs (dir)
"Find all directories in DIR."
(unless (file-directory-p dir)
(error "Not a directory `%s'" dir))
(let ((dir (directory-file-name dir))
(dirs '())
(files (directory-files dir nil nil t)))
(dolist (file files)
(unless (member file '("." ".."))
(let ((file (concat dir "/" file)))
(when (file-directory-p file)
(setq dirs (append (cons file
(directory-dirs file))
dirs))))))
dirs))
(setq my-org-agenda-root "~/org")
(setq my-org-agenda-files-list "~/.emacs.d/org-agenda-list.el")
(defun my-update-org-agenda-files ()
"Create or update the `my-org-agenda-files-list' file.
This file contains elisp code to set `org-agenda-files' to a
recursive list of all children under `my-org-agenda-root'. "
(interactive)
(with-temp-buffer
(insert
";; Warning: this file has been automatically generated\n"
";; by `my-update-org-agenda-files'\n")
(let ((dir-list (directory-dirs my-org-agenda-root))
(print-level nil)
(print-length nil))
(cl-prettyprint `(setq org-agenda-files (quote ,dir-list))))
(write-file my-org-agenda-files-list)))
(load my-org-agenda-files-list)
Every once in a while, run M-xmy-update-org-agenda-files to update the list.

As of Emacs 25, you can use directory-files-recursively which returns all files matching a regex from a root directory.
(defun org-get-agenda-files-recursively (dir)
"Get org agenda files from root DIR."
(directory-files-recursively dir "\.org$"))
(defun org-set-agenda-files-recursively (dir)
"Set org-agenda files from root DIR."
(setq org-agenda-files
(org-get-agenda-files-recursively dir)))
(defun org-add-agenda-files-recursively (dir)
"Add org-agenda files from root DIR."
(nconc org-agenda-files
(org-get-agenda-files-recursively dir)))
(setq org-agenda-files nil) ; zero out for testing
(org-set-agenda-files-recursively "~/Github") ; test set
(org-add-agenda-files-recursively "~/Dropbox") ; test add
You can view the contents of org-agenda-files by typing C-hv org-agenda-files.

Little late to the party, and probably not the answer you were hoping for, but the only way I found to speed up my agenda load times was by not including some directories which had thousands of org files.
(setq org-directory "~/org/"
my-agenda-dirs '("personal" "projects" "todos" "work")
org-agenda-files (mapcan (lambda (x) (directory-files-recursively
(expand-file-name x org-directory)
"\.org$"))
my-agenda-dirs))
Essentially rather than recursing over my entire org dir I only recurse through a select few of it's subdirs.

I also tried "recursive directory listing" at Emacs startup. It simply is way to loooong to be usable. So, try to stick with a limited number of "root" directories where you put your agenda files.
Even better is sticking with "~/org", and possibly a few subdirs like "work" and "personal", and that's it. You can put there your agenda files, and have links Inside them to your real project root dirs.
I know, this is not optimal, but I don't have anything better right now.

Related

Concatenating multiple files in emacs

Is there a fast (automatic) way to create one long file from all of the files in a directory using emacs? IE
>Text_1.txt
>{contents of Text_1}
>Text_2.txt
>{contents of text2}
>FinalResult.txt
>{contents of Text_1
>contents of Text2}
How about this:
(defun insert-my-files ()
(interactive)
(let ((dir (read-directory-name "Directory to insert: ")))
(mapc #'(lambda (file)
(let ((file-full (concat dir file)))
(insert-file-contents file-full)))
(cddr (directory-files dir)))))
Call it with M-x insert-my-files, and it will insert the contents of the directory you supply.
I don't know if you'd call it a fast way, but insert-file can be used to insert a file into an existing buffer.
For the specific case you're talking about though, the fastest way is probably from the command line: cat * > FinalResult.txt

How to have emacs-helm list offer files in fixed directories as options?

This is related to this question:
how-to-have-emacs-helm-list-offer-files-in-current-directory-as-options
but rather than add files from the current directory, I'd like to be able to have a fixed list of directories that helm-mini would always offer the files from. Ideally, I would like to be able to just have the files with a particular extension, and I'd like this to be done recursively (only one layer deep, in fact).
Here is a slightly different take that prefilters for the org extension.
(require 'helm-cmd-t)
(defvar my-org-folders (list "~/org")
"my permanent folders for helm-mini")
(defun helm-my-org (&optional arg)
"Use C-u arg to work with repos."
(interactive "P")
(if (consp arg)
(call-interactively 'helm-cmd-t-repos)
(let ((helm-ff-transformer-show-only-basename nil))
(helm :sources (mapcar (lambda (dir)
(helm-cmd-t-get-create-source-dir dir))
my-org-folders)
:candidate-number-limit 20
:buffer "*helm-my-org:*"
:input "org$ "))))
You can better solve it by leveraging the helm-cmd-t library. It packages
a directory recursively as repositories that you can use as a source.
It understands how to read lots of DVCS repos very fast.
The default functionality is not exactly what you are after here, but you can
easily leverage the machinery to fill all your requirements.
For example, here I define a command that adds two repos to the default
helm-mini sources.
(require 'helm-cmd-t)
(defvar my-mini-folders (list "~/src/ember/data" "~/src/ember/ember.js")
"my permanent folders for helm-mini")
(defun helm-my-mini (&optional arg)
"my helm-mini. Use C-u arg to work with repos."
(interactive "P")
(if (consp arg)
(call-interactively 'helm-cmd-t-repos)
(let ((helm-ff-transformer-show-only-basename nil))
(helm :sources (nconc (list
helm-c-source-buffers-list
helm-c-source-recentf
helm-c-source-buffer-not-found)
(mapcar (lambda (dir)
(helm-cmd-t-get-create-source-dir dir))
my-mini-folders))
:candidate-number-limit 20
:buffer "*helm-my-mini:*"))))
Here you go. I am using this code to list all org files in a particular directory. If you want to list all the files, just remove the candidate-transformer line in the source, and remove the emagician/helm-ct-is-org-file.
You'll likely want to rename the source/variable/function too. ;)
edit: Fixed, thanks to peeking at helm-cmd-t
note: This is my first real crack at making a helm source, and this implementation likely sucks. It also specifically solves my problem (finding all org files in one directly only) rather then the more generalized problem (building a helm source based on files from a particular directory).
(defvar emagician/helm-c-source-files
`((name . "Find Emagician Files")
(header-name . (lambda (_)))
(candidates . ,(lambda ()
(when (file-accessible-directory-p emagician-dir)
(directory-files emagician-dir t))))
(match helm-c-match-on-file-name helm-c-match-on-directory-name)
(keymap . ,helm-generic-files-map)
(candidate-transformer . emagician/helm-ct-is-org-file)
(help-message . helm-generic-file-help-message)
(mode-line . ,helm-generic-file-mode-line-string)
(type . file)))
(defun emagician/helm-ct-is-org-file (candidates)
(remove-if-not (lambda (c)
(and (string= (substring c -4) ".org")
(not (string= (substring (file-name-nondirectory c) 0 2) ".#"))))
candidates))
(defun emagician/helm-emagician-dir ()
"List all the org files in the Emagician dir"
(interactive)
(helm :sources emagician/helm-c-source-files
:candidate-number-limit 40
:buffer "*emagician-|-+-|-files*"))
(global-set-key (kbd "S-<f3>") 'emagician/helm-emagician-dir)

How to find the files in TAGS file in emacs

I have generated the TAGS file using ctags for the *.h and *.cpp file in a directory.
How to find the files in TAGS file.
Assuming i have generated the TAGS file for the files one.h two.h three.h. What is the command to find the file one.h, two.h, three.h not the tags in those files.
Assuming that you simply want to know how to use the TAGS file...
Load the TAGS file with:
M-x visit-tags-table RET TAGS file or parent directory RET
Then you can use it with:
M-. (i.e. find-tag)
M-x tags-search RET pattern RET
(with M-, to move to each successive match)
M-x tags-apropos RET pattern RET
M-x tags-query-replace RET pattern RET replacement RET
Those are the defaults. Naturally there are enhancements available:
http://www.emacswiki.org/emacs/EmacsTags
Personally I use etags-select (which you can obtain via ELPA), and I have M-. bound to etags-select-find-tag.
I wrote this a couple of years ago, I haven't gotten around to releasing it yet, though... Enjoy!
The function tags-extra-find-file will let you visit a file in the current tags table, complete with file-name completion. This is perfect if you have many source files spread out over a large number of directories. (Honestly, I use this at least one hundred times every day...)
(defun tags-extra-get-all-tags-files ()
"Return all, fully qualified, file names."
(save-excursion
(let ((first-time t))
(while (visit-tags-table-buffer (not first-time))
(setq first-time nil)
(setq res
(append res (mapcar 'expand-file-name (tags-table-files)))))))
res))
(defun tags-extra-find-file (name)
"Edit file named NAME that is part of the current tags table.
The file name should not include parts of the path."
(interactive
(list
(completing-read "Name of file: "
;; Make an a-list of all files without path.
(mapcar
(lambda (file)
(cons (file-name-nondirectory file) nil))
(tags-extra-get-all-tags-files)))))
(let ((files (tags-extra-get-all-tags-files))
(done nil)
(name-re (concat "^" (regexp-quote name) "$")))
(while (and (not done)
files)
(let ((case-fold-search t))
(if (string-match name-re (file-name-nondirectory (car files)))
(setq done t)
(setq files (cdr files)))))
(if files
(find-file (car files))
(error "File not found in the tags table."))))
This correction works in emacs 26.3. With an old etags, M-. would accept a file name, such as Setup.cpp, and visit the file wherever etags found it. Very handy with lots of files in many directories. No need to remember what directory the file was in to visit it. I'm surprised that's not an out-of-the-box feature!
(defun tags-extra-get-all-tags-files ()
"Return all, fully qualified, file names."
(setq res nil)
(save-excursion
(let ((first-time t))
(while (visit-tags-table-buffer (not first-time))
(setq first-time nil)
(setq res
(append res (mapcar 'expand-file-name (tags-table-files)))))))
res)
Something like this? It might not be entirely robust.
(defun visit-tags-table-and-files (file)
"Run `visit-tags-table FILE', then visit all the referenced files."
(interactive "fTags file: ")
(visit-tags-table file)
(save-excursion
(set-buffer (get-file-buffer tags-file-name))
(mapc #'find-file (tags-table-files)) ) )
Thanks #Lindydancer's answer and emacswiki ido. The emacswiki version only support one tag file. Combining them which allows me jumping to any file in all TAG files. Petty close to the sublime text's goto anything.
Here is the code.
;;; using ido find file in tag files
(defun tags-extra-get-all-tags-files ()
"Return all, fully qualified, file names."
(save-excursion
(let ((first-time t)
(res nil))
(while (visit-tags-table-buffer (not first-time))
(setq first-time nil)
(setq res
(append res (mapcar 'expand-file-name (tags-table-files)))))
res)))
(defun ido-find-file-in-tag-files ()
(interactive)
(find-file
(expand-file-name
(ido-completing-read
"Files: " (tags-extra-get-all-tags-files) nil t))))

problem in `delete-directory` with enabled `delete-by-removing-to-trash`

There is a strange behavior of delete-directory function with enabled flag delete-by-removing-to-trash. It deletes files one by one instead of applying move-file-to-trash to the directory. As a result emacs deletes big directories slowly and there
are many files in the trash after deleting, so it is impossible to restore the directory.
Example:
Directory structure:
ddd/
ccc/
1.txt
There are three files in the trash after deleting ddd:
trash/
ddd/
ccc/
1.txt
instead of one:
trash/
ddd/
It is very slow, because emacs traverse directory recursively.
I can't restore deleted directory.
What i need is exactly the same behavior as of move-file-to-trash. But it should be transparent (i.e. 'D x' in dired mode). How to solve the problem? As a temporary solution i see the making advice function for `delete-directory'.
As a temporary workaround i use:
(setq delete-by-moving-to-trash t)
(defadvice dired-delete-file (around
activate-move-to-trash
activate
compile)
(if delete-by-moving-to-trash
(move-file-to-trash (ad-get-arg 0))
ad-do-it))
Here's my whole system. If you look at the bottom you'll see a nearly identical defadvice to Andreo's.
;;; trash-settings.el - Intelligent integration with system trash
;; Use the system trash, except for temp files and stuff
(require 'cl)
(defcustom system-trash-exclude-names
nil
"List of file names to exclude from system trash.
The names in this variable are matched only against the basename
of the file to be deleted."
:type '(repeat string)
:group 'trash)
(defcustom system-trash-exclude-paths
nil
"List of absolute paths to exclude from system trash.
If a path to a directory is excluded, then all the contents of that directory are also excluded."
:type '(repeat string)
:group 'trash)
(defcustom system-trash-exclude-matches
nil
"List of regexps or functions matching file names to exclude from system trash.
The matches are only applied against the file name, not the path."
:type '(repeat (choice regexp function))
:group 'trash)
(defcustom system-trash-exclude-path-matches
nil
"List of regexps or functions matching paths to exclude from system trash.
The matches are applied against the full path."
:type '(repeat (choice regexp function))
:group 'trash)
(defun call-process-discard-output (program &rest args)
"Execute program with args without saving any output.
In particular, no temp files are created."
(eval (append `(call-process ,program nil nil nil) args)))
(defun string-begins-with-p (string beginning)
"Return t if and only if string begins with beginning"
(string-match-p (concat "^" (regexp-quote beginning)) string))
(defun file-excluded-from-system-trash-p (path)
"Returns non-nil if file name is excluded from trash."
(let ((basename (file-name-nondirectory path)))
(or
(some (apply-partially 'string= basename)
system-trash-exclude-names)
(some (apply-partially 'string-begins-with-p path)
system-trash-exclude-paths)
(some (lambda (match)
(funcall
(cond ((stringp match) 'string-match-p)
((functionp protected-match) 'funcall)
(t 'ignore))
match
basename))
system-trash-exclude-matches)
(some (lambda (match)
(funcall
(cond ((stringp match) 'string-match-p)
((functionp protected-match) 'funcall)
(t 'ignore))
match
path))
system-trash-exclude-path-matches))))
(defun trash-or-rm (filename)
"Attempt to move a file to the trash. If this fails, simply delete it.
This guarantees that any deletable file will either be trashed or deleted.
If the file is excluded from the trash, it is simply deleted."
(unless (file-excluded-from-system-trash-p filename)
(ignore-errors
(call-process-discard-output "gvfs-trash" filename)))
(when (file-exists-p filename)
(call-process-discard-output "rm" "-rf" filename)))
(defalias 'system-move-file-to-trash 'trash-or-rm)
(defadvice delete-directory (around no-recursive-trash activate)
"When trashing a directory, there's no need to trash its contents first."
(if delete-by-moving-to-trash
(move-file-to-trash directory)
ad-do-it))
(defadvice dired-delete-file (around no-recursive-trash activate)
"When trashing a directory, there's no need to trash its contents first.
There's also no need to ask, because it's undoable."
(if delete-by-moving-to-trash
(move-file-to-trash file)
ad-do-it))
Edit: I should mention that my solution above requires the gvfs-trash command-line program, which is associated with GNOME. You can replace this with trash from the trash-cli package if you don't use GNOME.
My "answer" would be: M-x report-emacs-bug. Bug reports help everyone. Emacs Dev will determine whether there is actually a bug, and everyone involved will learn from the report.

How to format all files under a dir in emacs?

In emacs, I format a file as:
1) C-x h (or M-x mark-whole-buffer)
2) C-M-\ (or M-x indent-region)
I need help show me how to format all files under a dir?
Here's another way to go about it:
First, evaluate this function definition in your *scratch* buffer:
(defun indent-marked-files ()
(interactive)
(dolist (file (dired-get-marked-files))
(find-file file)
(indent-region (point-min) (point-max))
(save-buffer)
(kill-buffer nil)))
Next, open a Dired buffer at the top level of the directory under which you want to change all of the files. Give the dired command a numeric prefix so that it will ask for the switches to give to the ls command, and add the R (recurse) switch: C-u C-x d R RET your-directory RET.
Next, mark all of the regular files in the recursive directory listing: first * / to mark all the directories, then * t to toggle the selection.
Finally, run the above command: M-x indent-marked-files.
Be aware that if you already have any buffers visiting any of the target files, they'll be killed by indent-marked-files. Also be aware that none of the file changes will be undoable; use with caution! I tested it in a simple case and it seems to work as described, but I make no guarantees.
Create a macro to do it. Open the directory in dired (C-x d), and then:
Put point on the first file.
Press F3 to start recording the macro.
Hit RET to open the file.
Format it with C-x h, C-M-\.
Bury the buffer with M-x bury-buffer. You'll be back in the dired buffer.
Go down one line.
Hit F4 to stop recording the macro.
So now you have a macro that opens the file on the current line, formats it, drops back to dired, and puts point to the next line. Run it with F4 as many times as needed.
I am late in answering this question, but this is still the first result on Google.
I made an improvement to #Sean's answer to remove the need for the complicated Dired interaction.
(defun my/indent-files (directory extension)
(interactive (list (read-directory-name "Directory: ")
(read-string "File extension: ")))
(dolist (file (directory-files-recursively directory extension))
(find-file file)
(indent-region (point-min) (point-max))
(save-buffer)
(kill-buffer nil)))
Sample use: M-x my/indent-files then ~/Dropbox then .org.
This will run indent-region on the all .org files, save the buffer then kill it.
You can try this:
(defun format-all-files (regexp)
"Format multiple files in one command."
(interactive "sFind files matching regexp (default all): ")
(when (string= "" regexp) (setq regexp ""))
(let ((dir (file-name-directory regexp))
(nodir (file-name-nondirectory regexp)))
(when dir (cd dir))
(when (string= "" nodir) (setq nodir "."))
(let ((files (directory-files "." t nodir nil t))
(errors 0))
(while (not (null files))
(let ((filename (car files)))
(if (file-readable-p filename)
(progn
(set-buffer (find-file-noselect filename))
(mark-whole-buffer)
(indent-region-or-balanced-expression)
(save-buffer)
(kill-buffer (current-buffer)))
(incf errors))
(setq files (cdr files))))
(when (> errors 0)
(message (format "%d files were unreadable." errors))))))
But note that this must load the file-specific mode over and over again, which may involve syntax highlighting or whatever initialization happens on a load of that type. For really big formatting jobs, a batch program such as indent which only indents will be much faster.