Emacs recursive project search
Asked Answered
Z

8

20

I am switching to Emacs from TextMate. One feature of TextMate that I would really like to have in Emacs is the "Find in Project" search box that uses fuzzy matching. Emacs sort of has this with ido, but ido does not search recursively through child directories. It searches only within one directory.

Is there a way to give ido a root directory and to search everything under it?

Update:

The questions below pertain to find-file-in-project.el from Michał Marczyk's answer.

If anything in this message sounds obvious it's because I have used Emacs for less than one week. :-)

As I understand it, project-local-variables lets me define things in a .emacs-project file that I keep in my project root.

How do I point find-file-in-project to my project root?

I am not familiar with regex syntax in Emacs Lisp. The default value for ffip-regexp is:

".*\\.\\(rb\\|js\\|css\\|yml\\|yaml\\|rhtml\\|erb\\|html\\|el\\)"

I presume that I can just switch the extensions to the ones appropriate for my project.

Could you explain the ffip-find-options? From the file:

(defvar ffip-find-options "" "Extra options to pass to `find' when using find-file-in-project.

Use this to exclude portions of your project: \"-not -regex \\".vendor.\\"\"")

What does this mean exactly and how do I use it to exclude files/directories?

Could you share an example .emacs-project file?

Zellner answered 3/3, 2010 at 23:11 Comment(1)
What about textmate.el: github.com/defunkt/textmate.elCarnify
I
11

(Updated primarily in order to include actual setup instructions for use with the below mentioned find-file-in-project.el from the RINARI distribution. Original answer left intact; the new bits come after the second horizontal rule.)


Have a look at the TextMate page of the EmacsWiki. The most promising thing they mention is probably this Emacs Lisp script, which provides recursive search under a "project directory" guided by some variables. That file begins with an extensive comments section describing how to use it.

What makes it particularly promising is the following bit:

;; If `ido-mode' is enabled, the menu will use `ido-completing-read'
;; instead of `completing-read'.

Note I haven't used it myself... Though I may very well give it a try now that I've found it! :-)

HTH.

(BTW, that script is part of -- to quote the description from GitHub -- "Rinari Is Not A Rails IDE (it is an Emacs minor mode for Rails)". If you're doing any Rails development, you might want to check out the whole thing.)


Before proceeding any further, configure ido.el. Seriously, it's a must-have on its own and it will improve your experience with find-file-in-project. See this screencast by Stuart Halloway (which I've already mentioned in a comment on this answer) to learn why you need to use it. Also, Stu demonstrates how flexible ido is by emulating TextMate's project-scoped file-finding facility in his own way; if his function suits your needs, read no further.

Ok, so here's how to set up RINARI's find-file-in-project.el:

  1. Obtain find-file-in-project.el and project-local-variables.el from the RINARI distribution and put someplace where Emacs can find them (which means in one of the directories in the load-path variable; you can use (add-to-list 'load-path "/path/to/some/directory") to add new directories to it).

  2. Add (require 'find-file-in-project) to your .emacs file. Also add the following to have the C-x C-M-f sequence bring up the find-file-in-project prompt: (global-set-key (kbd "C-x C-M-f") 'find-file-in-project).

  3. Create a file called .emacs-project in your projects root directory. At a minimum it should contain something like this: (setl ffip-regexp ".*\\.\\(clj\\|py\\)$"). This will make it so that only files whose names and in clj or py will be searched for; please adjust the regex to match your needs. (Note that this regular expression will be passed to the Unix find utility and should use find's preferred regular expression syntax. You still have to double every backslash in regexes as is usual in Emacs; whether you also have to put backslashes before parens / pipes (|) if you want to use their 'magic' regex meaning depends on your find's expectations. The example given above works for me on an Ubuntu box. Look up additional info on regexes in case of doubt.) (Note: this paragraph has been revised in the last edit to fix some confusion w.r.t. regular expression syntax.)

  4. C-x C-M-f away.

There's a number of possible customisations; in particular, you can use (setl ffip-find-options "...") to pass additional options to the Unix find command, which is what find-file-in-project.el calls out to under the hood.

If things appear not to work, please check and double check your spelling -- I did something like (setl ffip-regex ...) once (note the lack of the final 'p' in the variable name) and were initially quite puzzled to discover that no files were being found.

Istria answered 3/3, 2010 at 23:31 Comment(15)
Looks like that mode just finds files, as in generates a list of all files, and allows you to open one for editing.Stopover
You're wrong actually. It's not a list of "all files" -- it uses a regex, which the user can configure, to determine what is searched for . Also, it groks the concept of a "project root" to search under and it's got fuzzy completion when used in conjunction with ido. Which, BTW, seems to be exactly what the OP is asking for and what your personal project claims to be doing.Bhang
Thanks for the answer Michał. I have a few more questions on how to use find-file-in-project. I put them into the original question.Zellner
Ok. I'll look into it when I have a spare moment. In the meantime, you might want to make it clear in the Q which script those added questions pertain to, if only to give newcomers to the Q more of a fighting chance at determining if they might be able to help! (Without reading all existing answers and comments, I mean.) Also, check out this video: vimeo.com/1013263. It contains an example function to accomplish something very similar to what you want (although it uses a tag file, I think) and a great discussion of TextMate's find in project vs. Emacs's ido.Bhang
I've edited in some basic setup instructions -- let me know if this helps. I'll make another edit if it doesn't once I finally cook up a more interesting .emacs-project -- probably tomorrow. (Apparently I've not yet found much need for fancier options.) Also, please consider describing an actual scenario you'd be interested in (meaning a project directory layout, file extensions etc.) and maybe I or somebody else will be able provide a matching config.Bhang
Thanks Michał. The updated answer was exactly what I needed.Zellner
One more question: I tried using this: (setl ffip-regexp ".*\\.\(py\\|html\)$"), but this doesn't work (I just get no match). This one works: (setl ffip-regexp ".*\\.html$"), but allows me to match only one extension. Any ideas?Zellner
Ouch, I've just realised that the regular expression in ffip-regexp should actually use whatever RE syntax find expects, since it's passed to find; it's got nothing to do with Emacs's REs. So, you still need to double the backslashes, but perhaps your find treats (|) as magic without escaping and so would prefer (py|html). Let me know if this works and I'll edit it into the answer.Bhang
Ok, so I've clarified the relevant portion of the answer. Hopefully this is it; I don't see what else could be wrong, I'm using the same kind of expression myself. If you need to debug it, try using find in a terminal with the same options as those find-file-in-project uses (-type f -regex + your regex + any further options to be put in ffip-find-options).Bhang
The reason is that find-file-in-project.el assumes you are using GNU find. I'm on OS X and thus I am using BSD find. Thus I must either install GNU find or patch the program.Zellner
I have installed GNU find. Is there a way for me to tell Emacs that when it uses the shell command "find" that it should run /opt/local/bin/gfind instead of /usr/bin/find?Zellner
I'm basically the Emacs equivalent of alias find="/opt/local/bin/gfind".Zellner
Well, if BSD find doesn't support the -type f -regex ... options at all or if you simply prefer GNU find, then you should probably go ahead and patch find-file-in-project.el -- replace (concat "find " ... with (concat "gfind " ... on line 99. Or if BSD find does support -type f -regex ... and you don't mind using it, you can just write regexes in whatever syntax it expects.Bhang
Is there no way to alias the executable from Emacs' perspective? The reason I am asking is because I am interested in sharing my config with a Linux-using colleague.Zellner
Oh, I'd see. In that case I'd suggest replacing the hard-coded "find " with a custom variable -- defined with, say, (defvar ffip-find-command "find"): (concat ffip-find-command " " ...). (Note the " " -- you need a space between find / gfind and its arguments and it seems ugly to include it in ffip-find-command.) Then you can reset that to something more appropriate with a setq in your ~/.emacs (or even with setl in .emacs-project, should that be useful to use for some reason). There might be other solutions, but this seems the cleanest to me.Bhang
D
15

I use M-x rgrep for this. It automatically skips a lot of things you don't want, like .svn directories.

Despite answered 4/3, 2010 at 3:1 Comment(2)
Almost... but you still have to manually specify where the search should begin.Stopover
rgrep is great, but like jrockaway says, it would be great to be able to bypass some of the interactive prompts altogetherImaimage
I
11

(Updated primarily in order to include actual setup instructions for use with the below mentioned find-file-in-project.el from the RINARI distribution. Original answer left intact; the new bits come after the second horizontal rule.)


Have a look at the TextMate page of the EmacsWiki. The most promising thing they mention is probably this Emacs Lisp script, which provides recursive search under a "project directory" guided by some variables. That file begins with an extensive comments section describing how to use it.

What makes it particularly promising is the following bit:

;; If `ido-mode' is enabled, the menu will use `ido-completing-read'
;; instead of `completing-read'.

Note I haven't used it myself... Though I may very well give it a try now that I've found it! :-)

HTH.

(BTW, that script is part of -- to quote the description from GitHub -- "Rinari Is Not A Rails IDE (it is an Emacs minor mode for Rails)". If you're doing any Rails development, you might want to check out the whole thing.)


Before proceeding any further, configure ido.el. Seriously, it's a must-have on its own and it will improve your experience with find-file-in-project. See this screencast by Stuart Halloway (which I've already mentioned in a comment on this answer) to learn why you need to use it. Also, Stu demonstrates how flexible ido is by emulating TextMate's project-scoped file-finding facility in his own way; if his function suits your needs, read no further.

Ok, so here's how to set up RINARI's find-file-in-project.el:

  1. Obtain find-file-in-project.el and project-local-variables.el from the RINARI distribution and put someplace where Emacs can find them (which means in one of the directories in the load-path variable; you can use (add-to-list 'load-path "/path/to/some/directory") to add new directories to it).

  2. Add (require 'find-file-in-project) to your .emacs file. Also add the following to have the C-x C-M-f sequence bring up the find-file-in-project prompt: (global-set-key (kbd "C-x C-M-f") 'find-file-in-project).

  3. Create a file called .emacs-project in your projects root directory. At a minimum it should contain something like this: (setl ffip-regexp ".*\\.\\(clj\\|py\\)$"). This will make it so that only files whose names and in clj or py will be searched for; please adjust the regex to match your needs. (Note that this regular expression will be passed to the Unix find utility and should use find's preferred regular expression syntax. You still have to double every backslash in regexes as is usual in Emacs; whether you also have to put backslashes before parens / pipes (|) if you want to use their 'magic' regex meaning depends on your find's expectations. The example given above works for me on an Ubuntu box. Look up additional info on regexes in case of doubt.) (Note: this paragraph has been revised in the last edit to fix some confusion w.r.t. regular expression syntax.)

  4. C-x C-M-f away.

There's a number of possible customisations; in particular, you can use (setl ffip-find-options "...") to pass additional options to the Unix find command, which is what find-file-in-project.el calls out to under the hood.

If things appear not to work, please check and double check your spelling -- I did something like (setl ffip-regex ...) once (note the lack of the final 'p' in the variable name) and were initially quite puzzled to discover that no files were being found.

Istria answered 3/3, 2010 at 23:31 Comment(15)
Looks like that mode just finds files, as in generates a list of all files, and allows you to open one for editing.Stopover
You're wrong actually. It's not a list of "all files" -- it uses a regex, which the user can configure, to determine what is searched for . Also, it groks the concept of a "project root" to search under and it's got fuzzy completion when used in conjunction with ido. Which, BTW, seems to be exactly what the OP is asking for and what your personal project claims to be doing.Bhang
Thanks for the answer Michał. I have a few more questions on how to use find-file-in-project. I put them into the original question.Zellner
Ok. I'll look into it when I have a spare moment. In the meantime, you might want to make it clear in the Q which script those added questions pertain to, if only to give newcomers to the Q more of a fighting chance at determining if they might be able to help! (Without reading all existing answers and comments, I mean.) Also, check out this video: vimeo.com/1013263. It contains an example function to accomplish something very similar to what you want (although it uses a tag file, I think) and a great discussion of TextMate's find in project vs. Emacs's ido.Bhang
I've edited in some basic setup instructions -- let me know if this helps. I'll make another edit if it doesn't once I finally cook up a more interesting .emacs-project -- probably tomorrow. (Apparently I've not yet found much need for fancier options.) Also, please consider describing an actual scenario you'd be interested in (meaning a project directory layout, file extensions etc.) and maybe I or somebody else will be able provide a matching config.Bhang
Thanks Michał. The updated answer was exactly what I needed.Zellner
One more question: I tried using this: (setl ffip-regexp ".*\\.\(py\\|html\)$"), but this doesn't work (I just get no match). This one works: (setl ffip-regexp ".*\\.html$"), but allows me to match only one extension. Any ideas?Zellner
Ouch, I've just realised that the regular expression in ffip-regexp should actually use whatever RE syntax find expects, since it's passed to find; it's got nothing to do with Emacs's REs. So, you still need to double the backslashes, but perhaps your find treats (|) as magic without escaping and so would prefer (py|html). Let me know if this works and I'll edit it into the answer.Bhang
Ok, so I've clarified the relevant portion of the answer. Hopefully this is it; I don't see what else could be wrong, I'm using the same kind of expression myself. If you need to debug it, try using find in a terminal with the same options as those find-file-in-project uses (-type f -regex + your regex + any further options to be put in ffip-find-options).Bhang
The reason is that find-file-in-project.el assumes you are using GNU find. I'm on OS X and thus I am using BSD find. Thus I must either install GNU find or patch the program.Zellner
I have installed GNU find. Is there a way for me to tell Emacs that when it uses the shell command "find" that it should run /opt/local/bin/gfind instead of /usr/bin/find?Zellner
I'm basically the Emacs equivalent of alias find="/opt/local/bin/gfind".Zellner
Well, if BSD find doesn't support the -type f -regex ... options at all or if you simply prefer GNU find, then you should probably go ahead and patch find-file-in-project.el -- replace (concat "find " ... with (concat "gfind " ... on line 99. Or if BSD find does support -type f -regex ... and you don't mind using it, you can just write regexes in whatever syntax it expects.Bhang
Is there no way to alias the executable from Emacs' perspective? The reason I am asking is because I am interested in sharing my config with a Linux-using colleague.Zellner
Oh, I'd see. In that case I'd suggest replacing the hard-coded "find " with a custom variable -- defined with, say, (defvar ffip-find-command "find"): (concat ffip-find-command " " ...). (Note the " " -- you need a space between find / gfind and its arguments and it seems ugly to include it in ffip-find-command.) Then you can reset that to something more appropriate with a setq in your ~/.emacs (or even with setl in .emacs-project, should that be useful to use for some reason). There might be other solutions, but this seems the cleanest to me.Bhang
E
4

Surprised nobody mentioned https://github.com/defunkt/textmate.el (now gotta make it work on Windows...)

Epenthesis answered 14/3, 2012 at 21:45 Comment(1)
That is ssooooo much close to the CtrlP functionality ... I have been looking to something like this for like ... forever :D ... (now, just isntead of "Ctrl+P" I'm using "Alt+T" :) ...)Carnify
S
3

eproject has eproject-grep, which does exactly what you want.

With the right project definition, it will only search project files; it will ignore version control, build artifacts, generated files, whatever. The only downside is that it requires a grep command on your system; this dependency will be eliminated soon.

Stopover answered 4/3, 2010 at 22:36 Comment(3)
Have you just downvoted several other answers, none of which is actually wrong and including one which should provide very much the same functionality as this project of yours you're linking to...? I wonder why'd you do that?Bhang
Installed 23.1 (required for eproject). eproject works! with the caveat that you have to configure every language / project file that you want eproject to search. In the end eproject runs a grep comment after searching for all the files it thinks are in your project.Fluctuation
Contribute the languages you use, and then nobody else will have to configure them. I have wanted to assemble a bunch of these for a while. (The wiki has Scala, Haskell, and Perl.)Stopover
B
2

You can get the effect you want by using GNU Global or IDUtils. They are not Emacs specific, but they has Emacs scripts that integrate that effect. (I don't know too much about them myself.)

You could also opt to use CEDET and the EDE project system. EDE is probably a bit heavy weight, but it has a way to just mark the top of a project. If you also keep GNU Global or IDUtils index files with your project, EDE can use it to find a file by name anywhere, or you can use `semantic-symref' to find references to symbols in your source files. CEDET is at http://cedet.sf.net

Bananas answered 4/3, 2010 at 1:37 Comment(0)
C
2

For pure, unadulterated speed, I highly recommend a combination of the command-line tool The Silver Searcher (a.k.a. 'ag') with ag.el. The ag-project interactive function will make an educated guess of your project root if you are using git, hg or svn and search the entire project.

Cesura answered 16/7, 2013 at 3:9 Comment(0)
N
0

FileCache may also be an option. However you would need to add your project directory manually with file-cache-add-directory-recursively.

Nejd answered 4/3, 2010 at 7:26 Comment(0)
R
0

See these links for info about how Icicles can help here:

Icicles completion matching can be substring, regexp, fuzzy (various kinds), or combinations of these. You can also combine simple patterns, intersecting the matches or complementing (subtracting) a subset of them

Rebozo answered 21/8, 2011 at 20:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.