Increasingly for Java projects I've been relying on IntelliJ IDEA, an amazing IDE. There are still times, however, when I need the sharp tool capabilities of Emacs. I've never been completely satisfied with the "find file in project" capabilities I have been able to configure in Emacs (especially after using IntelliJ IDEA) so I set out to fix that. Building on the ideas found in Stuart Halloway's What you can learn from ido.el and the find file in project snippet given on the emacs wiki page for ido.el I can up with the following improved find file in project approach - well actually two approaches as I have created approaches that gather their file lists from either 1) a TAG file or 2) by issuing a shell find command.
The main tweaks provided are:
- I am limiting the search to file name rather than full path. This makes the flex finding much more effective and much faster when working with large projects
- I turn on case sensitive searching making capital letter camel case matching work very well
- Name conflicts are handled by a second disambiguation prompt.
I'm sure there is still plenty of room for improvement here so you can check for updates in my .emacs file. The following indicates the present state of things with hopefully enough comments so that you can follow along:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(require 'ido) | |
(ido-mode t) | |
(setq ido-enable-flex-matching t) | |
;; effectively turn off its auto searches | |
(setq ido-auto-merge-delay-time 99999) | |
(defvar my-project-file-regex "\\(.*?\\)\\([^/]+?\\)$") | |
(defvar my-project-file-replace "\\2") | |
(defvar my-project-find | |
(concat "find -H %s" | |
" \\( -name \"*.svn\" -o -name \"*.git\" \\) -prune" | |
" -o -type f -name \"*.java\" -print")) | |
(defvar my-project-root "/home/dburger/src/mondo_project") | |
(defun my-project-root (dir) | |
(interactive "DDirectory: ") | |
(setq my-project-root dir)) | |
(defun file-names-to-completion-list (file-names) | |
"Given a list of FILE-NAMES produces a completion list. | |
The list returned has a list of unique file names in position 0 and a | |
hash table from file name to a list of full paths for that file name in | |
position 1." | |
(let (display | |
key | |
value | |
(table (make-hash-table :test 'equal))) | |
(mapc (lambda (path) | |
(setq key (replace-regexp-in-string my-project-file-regex | |
my-project-file-replace path)) | |
(setq value (or (gethash key table) (list))) | |
(push path value) | |
(puthash key value table)) | |
file-names) | |
(maphash (lambda (key value) (push key display)) table) | |
(list display table))) | |
(defun file-name-from-completion-list (clist) | |
"Produces a file name from a completion list using ido. | |
CLIST is a completion list produced by 'file-names-to-completion-list'." | |
(let* ((key (ido-completing-read "Project file: " (car clist) nil t)) | |
(value (gethash key (nth 1 clist))) | |
(value (if (> (length value) 1) | |
(ido-completing-read "Disambiguate: " value nil t) | |
(car value)))) | |
value)) | |
(defun ido-find-file-in-tag-files () | |
"Uses ido to open a file from among those listed in a tag file." | |
(interactive) | |
(save-excursion | |
(let ((enable-recursive-minibuffers t)) | |
(visit-tags-table-buffer)) | |
(let* ((ido-case-fold nil) | |
(clist (file-names-to-completion-list (tags-table-files)))) | |
(find-file (expand-file-name (file-name-from-completion-list clist)))))) | |
(defun ido-find-file-in-project () | |
"Uses ido to select a file from the project. | |
The files in the project are defined by the value of 'my-project-root' | |
and 'my-project-find' as a shell find command is used to produce the | |
list of files." | |
(interactive) | |
(let* ((ido-case-fold nil) | |
(cmd (format my-project-find my-project-root)) | |
(project-files (split-string (shell-command-to-string cmd) "\n")) | |
(clist (file-names-to-completion-list project-files))) | |
(find-file (expand-file-name (file-name-from-completion-list clist))))) | |
;; choose your flavor | |
(global-set-key "\C-cp" 'ido-find-file-in-tag-files) | |
;; (global-set-key "\C-cp" 'ido-find-file-in-project) |