Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 322 lines (286 sloc) 12.299 kb
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
1 ;;; jump.el --- build functions which contextually jump between files
2
3 ;; Copyright (C) 2008 Eric Schulte
4
5 ;; Author: Eric Schulte
6 ;; URL: http://github.com/eschulte/jump.el/tree/master
19a1120 @purcell Change "Version:" header to a neutral placeholder to reduce confusion
purcell authored
7 ;; Version: DEV
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
8 ;; Created: 2008-08-21
9 ;; Keywords: project, convenience, navigation
9677d93 @milkypostman fix jump defines so they are on the same line
milkypostman authored
10 ;; Package-Requires: ((findr "0.7") (inflections "1.0"))
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
11
12 ;; This file is NOT part of GNU Emacs.
13
14 ;;; License:
15
16 ;; This program is free software; you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation; either version 3, or (at your option)
19 ;; any later version.
29ce83d @eschulte initial commit
authored
20 ;;
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
21 ;; This program is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
25 ;;
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs; see the file COPYING. If not, write to the
28 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
29 ;; Boston, MA 02110-1301, USA.
30
31 ;;; Commentary:
29ce83d @eschulte initial commit
authored
32
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
33 ;; This library is intended to aid in the construction of functions
34 ;; for navigating projects. The `defjump' function using a hopefully
35 ;; convenient specification schema which jumps to new file/methods
36 ;; based upon the file/method context of the current buffer/point.
29ce83d @eschulte initial commit
authored
37
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
38 ;; This effort was inspired heavily by find-file-in-project.el by Phil
39 ;; Hagelberg and Doug Alcorn, and toggle.el by Ryan Davis. The
40 ;; initial goal of jump.el was to subsume both of these tools.
29ce83d @eschulte initial commit
authored
41
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
42 ;;; Example: (jumping to the related model in a rails application)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
43
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
44 ;; (defjump
45 ;; 'rinari-find-model
46 ;; '(("app/controllers/\\1_controller.rb#\\2" . "app/models/\\1.rb#\\2")
47 ;; ("app/views/\\1/.*" . "app/models/\\1.rb")
48 ;; ("app/helpers/\\1_helper.rb" . "app/models/\\1.rb")
49 ;; ("db/migrate/.*create_\\1.rb" . "app/models/\\1.rb")
50 ;; ("test/functional/\\1_controller_test.rb" . "app/models/\\1.rb")
51 ;; ("test/unit/\\1_test.rb#test_\\2" . "app/models/\\1.rb#\\2")
52 ;; ("test/unit/\\1_test.rb" . "app/models/\\1.rb")
53 ;; ("test/fixtures/\\1.yml" . "app/models/\\1.rb")
54 ;; (t . "app/models/"))
55 ;; 'rinari-root
56 ;; "Go to the most logical model given the current location."
57 ;; '(lambda (path)
58 ;; (message (shell-command-to-string
59 ;; (format "ruby %sscript/generate model %s"
60 ;; (rinari-root)
61 ;; (and (string-match ".*/\\(.+?\\)\.rb" path)
62 ;; (match-string 1 path))))))
63 ;; 'ruby-add-log-current-method)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
64
29ce83d @eschulte initial commit
authored
65 ;;; Code:
37b0f01 adding which-func.el which is not included by default in Xemacs
Axel Strübing authored
66 (if (featurep 'xemacs)
07c60eb if XEmacs then define ido-mode
Eric Schulte authored
67 (add-to-list 'load-path (file-name-as-directory (or load-file-name buffer-file-name))))
29ce83d @eschulte initial commit
authored
68 (require 'which-func)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
69 (require 'findr)
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
70 (require 'inflections)
29ce83d @eschulte initial commit
authored
71
07c60eb if XEmacs then define ido-mode
Eric Schulte authored
72 ;; ido-mode must be defined (only an issue with Xemacs)
73 (unless (fboundp 'ido-mode) (defvar ido-mode nil))
74
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
75 (defvar jump-ignore-file-regexp ;; TODO actually start using this
4e69e3c @eschulte improve jump-ignore-file-regexp
authored
76 "\\(.*\\.\\(git\\|svn\\|cvs\\).*\\|.*~\\|.*\\#.*\\#\\)"
2bb4b81 @eschulte switching to using the find command
authored
77 "regexp for the find shell command to ignore undesirable files")
78
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
79 (defun jump-completing-read (prompt choices &optional predicate require-match initial-input hist def)
80 "if `ido-mode' is turned on use ido speedups completing the read"
81 (if ido-mode
82 (ido-completing-read prompt choices predicate require-match initial-input hist def)
3368170 now working with Xemacs
Axel Strübing authored
83 (if (featurep 'xemacs)
84 (completing-read prompt (mapcar 'list choices) predicate require-match initial-input hist def)
85 (completing-read prompt choices predicate require-match initial-input hist def))))
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
86
87 (defun jump-find-file-in-dir (dir)
88 "if `ido-mode' is turned on use ido speedups finding the file"
4ffb4b2 @eschulte finer tuned ido-mode integration
authored
89 (if (or (equal ido-mode 'file) (equal ido-mode 'both))
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
90 (ido-find-file-in-dir dir)
8b12ea7 @eschulte graceful degredation in absence of ido-mode
authored
91 (let ((default-directory dir)) (call-interactively 'find-file))))
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
92
29ce83d @eschulte initial commit
authored
93 (defun jump-method ()
2bb4b81 @eschulte switching to using the find command
authored
94 "Return the method defined at the current position in current
95 buffer."
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
96 (let ((func (funcall method-command)))
2bb4b81 @eschulte switching to using the find command
authored
97 (or (and func (string-match "#\\(.+\\)" func) (match-string 1 func))
29ce83d @eschulte initial commit
authored
98 func)))
99
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
100 (defun jump-uniqueify (file-cons)
101 "Set the car of the argument to include the directory name plus the file name."
102 (setcar file-cons
103 (concat (car file-cons) " "
ac0658e @eschulte should be possible to work on windows w/o a \\ seperator
authored
104 (cadr (reverse (split-string (cdr file-cons) "/"))))))
2bb4b81 @eschulte switching to using the find command
authored
105
106 (defun jump-select-and-find-file (files)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
107 "Select a single file from an alist of file names and paths.
108 Return the path selected or nil if files was empty."
2bb4b81 @eschulte switching to using the find command
authored
109 (let ((file (case (length files)
110 (0 nil)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
111 (1 (caar files))
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
112 (t (jump-completing-read "Jump to: "
113 (mapcar 'car files))))))
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
114 (if file (find-file (cdr (assoc file files))))))
2bb4b81 @eschulte switching to using the find command
authored
115
4e69e3c @eschulte improve jump-ignore-file-regexp
authored
116 (defun jump-remove-unwanted-files (files)
820bb7d @eschulte cleanup embarrassing code and increasing robustness
authored
117 "Remove file matching `jump-ignore-file-regexp' from the list
118 of possible jumps."
119 (delete-if nil
120 (mapcar
121 (lambda (file-cons)
122 (unless (string-match jump-ignore-file-regexp (cdr file-cons))
123 file-cons))
124 files)))
4e69e3c @eschulte improve jump-ignore-file-regexp
authored
125
2bb4b81 @eschulte switching to using the find command
authored
126 (defun jump-to-file (&optional file)
ac0658e @eschulte should be possible to work on windows w/o a \\ seperator
authored
127 "Open the file located at file if file ends in a / then look in
128 the related directory, and if file contains regexps then select
129 from all matches."
2bb4b81 @eschulte switching to using the find command
authored
130 (interactive "Mfile: ")
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
131 (let ((file-cons (cons (file-name-nondirectory file) file))
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
132 file-alist)
f8af053 @eschulte now *finally* it stops trying new paths if and only if a file was found
authored
133 (if (and (equal (file-name-directory file) file) (file-exists-p file))
9ad6bfa @eschulte attempting support for windows directory seperators
authored
134 (jump-find-file-in-dir (expand-file-name file root)) ;; open directory
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
135 (if (file-exists-p file)
136 (find-file file) ;; open file
2bb4b81 @eschulte switching to using the find command
authored
137 (jump-select-and-find-file ;; open with regexp
4e69e3c @eschulte improve jump-ignore-file-regexp
authored
138 (jump-remove-unwanted-files
139 (mapcar (lambda (file)
140 (let ((file-cons (cons (file-name-nondirectory file)
141 (expand-file-name file))))
142 (when (assoc (car file-cons) file-alist)
143 (jump-uniqueify (assoc (car file-cons) file-alist))
144 (jump-uniqueify file-cons))
145 (add-to-list 'file-alist file-cons)
146 file-cons))
820bb7d @eschulte cleanup embarrassing code and increasing robustness
authored
147 (let ((dir (expand-file-name
148 (or (file-name-directory (cdr file-cons)) "")
149 root)))
150 (when (and (file-exists-p dir) (file-directory-p dir))
151 (findr (car file-cons)
152 (expand-file-name
153 (or (file-name-directory
154 (cdr file-cons)) "") root)))))))))))
2bb4b81 @eschulte switching to using the find command
authored
155
156 (defun jump-to-method (&optional method)
157 "If `jump-method' returns method in buffer, go to the first
158 line inside of method."
159 (interactive "Mmethod: ")
160 (goto-char (point-min))
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
161 (let (results)
162 (while (not (setf results
163 (or (string-equal (jump-method) method)
164 (and (> (forward-line 1) 0)
165 (goto-char (point-min)))))))
f8af053 @eschulte now *finally* it stops trying new paths if and only if a file was found
authored
166 (when (and (commandp 'recenter-top-bottom) (not (equal results 1))) (recenter-top-bottom))))
9677d93 @milkypostman fix jump defines so they are on the same line
milkypostman authored
167
29ce83d @eschulte initial commit
authored
168 (defun jump-to-path (path)
169 "Jump to the location specified by PATH (regexp allowed in
ac0658e @eschulte should be possible to work on windows w/o a \\ seperator
authored
170 path). If path ends in / then just look in that directory"
2bb4b81 @eschulte switching to using the find command
authored
171 (let ((file path)
172 method)
29ce83d @eschulte initial commit
authored
173 (when (string-match "^\\(.*\\)#\\(.*\\)$" path)
174 (setf method (match-string 2 path))
2bb4b81 @eschulte switching to using the find command
authored
175 (setf file (match-string 1 path)))
f8af053 @eschulte now *finally* it stops trying new paths if and only if a file was found
authored
176 (when (jump-to-file file) ;; returns t as long as a file was found
177 (when method (jump-to-method method))
178 t)))
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
179
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
180 (defun jump-insert-matches (spec matches)
bf66c3e @eschulte defjump takes a root
authored
181 (if matches
182 (let ((count 1) (new-spec spec) (spec nil))
183 (while (not (equal spec new-spec))
184 (setf spec new-spec)
185 (setf new-spec
186 (replace-regexp-in-string (format "\\\\%d" count)
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
187 (or (nth (- count 1) matches) ".*?")
bf66c3e @eschulte defjump takes a root
authored
188 spec))
189 (setf count (+ 1 count)))
190 new-spec) spec))
191
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
192 (defun jump-inflections (terms)
193 "Return all combinations of the singular and pluralizations of TERMS."
194 (let ((terms (mapcar
195 (lambda (term)
196 (delete-dups (list term
197 (singularize-string term)
198 (pluralize-string term))))
199 terms))
200 results interum-results)
201 (dolist (group terms)
202 (dolist (term group)
203 (if results
204 (dolist (combination results)
205 (setf interum-results (cons
206 (cons term combination)
207 interum-results)))
208 (setf interum-results (cons (list term) interum-results))))
209 (setf results interum-results)
210 (setf interum-results nil))
211 (mapcar 'reverse results)))
212
213 (defun jump-to-all-inflections (spec matches)
214 (let (status) ;; TODO maybe try file first and method second
215 (loop for path in (mapcar (lambda (option)
216 (jump-insert-matches spec option))
217 (jump-inflections matches))
218 until (setf status (jump-to-path path)))
219 status))
220
221 (defun jump-to (spec &optional matches make)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
222 "Jump to a spot defined by SPEC. If optional argument MATCHES
223 replace all '\\n' portions of SPEC with the nth (1 indexed)
34b6fb5 @eschulte Now, when the first part of a jump specification, but the second part
authored
224 element of MATCHES. If optional argument MAKE, then create the
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
225 target file if it doesn't exist, if MAKE is a function then use
226 MAKE to create the target file."
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
227 (if (functionp spec) (eval (list spec matches)) ;; custom function in spec
228 (let ((path (jump-insert-matches spec matches)))
f8af053 @eschulte now *finally* it stops trying new paths if and only if a file was found
authored
229 (if (not (or (jump-to-path path)
230 (and matches (jump-to-all-inflections spec matches))))
231 (when make (message (format "making %s" path))
433ae4c @eschulte simplified creation regexp
authored
232 (let ((path (if (or (string-match "^\\(.*?\\)\\.\\*" path)
7a95596 @eschulte more gracefully handles creation of regexp-terminated paths
authored
233 (string-match "^\\(.*/\\)$" path))
234 (read-from-minibuffer "create " (match-string 1 path))
235 path)))
236 (when (functionp make) (eval (list make path)))
237 (find-file (concat root (if (string-match "^\\(.*\\)#" path)
f8af053 @eschulte now *finally* it stops trying new paths if and only if a file was found
authored
238 (match-string 1 path) path)))))
239 t))))
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
240
241 (defun jump-from (spec)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
242 "Match SPEC to the current location returning a list of any matches"
f4f8852 @eschulte accepts functions as specs, and more graceful degradation
authored
243 (cond ((stringp spec)
244 (let* ((file (or (and (buffer-file-name)
245 (expand-file-name (buffer-file-name)))
246 (buffer-name)))
247 (method (jump-method))
248 (path (if (string-match "#.+" spec)
249 (concat file "#" method)
250 file)))
251 (and (string-match spec path)
252 (or (let ((counter 1) mymatch matches)
253 (while (setf mymatch (match-string counter path))
254 (setf matches (cons mymatch matches))
255 (setf counter (+ 1 counter)))
256 (reverse matches)) t))))
257 ((functionp spec) (eval (list spec)))
258 ((equal t spec) t)
7c93fba @purcell Fix invalid error message
purcell authored
259 (t (message (format "unrecognized jump-from specification format %s" spec)))))
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
260
8e6bdd1 @eschulte added headers and autoload comments for submission to ELPA
authored
261 ;;;###autoload
820bb7d @eschulte cleanup embarrassing code and increasing robustness
authored
262 (defmacro defjump (name specs root &optional doc make method-command)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
263 "Define NAME as a function with behavior determined by SPECS.
264 SPECS should be a list of cons cells of the form
265
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
266 (jump-from-spec . jump-to-spec)
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
267
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
268 NAME will then try subsequent jump-from-specs until one succeeds,
269 at which point any resulting match information, along with the
270 related jump-to-spec will be used to jump to the intended buffer.
271 See `jump-to' and `jump-from' for information on spec
272 construction.
273
274 ROOT should specify the root of the project in which all jumps
275 take place, it can be either a string directory path, or a
276 function returning
277
278 Optional argument DOC specifies the documentation of the
279 resulting function.
84d5036 @eschulte fleshed out tests, all functions SEEM to be working
authored
280
281 Optional argument MAKE can be used to specify that missing files
282 should be created. If MAKE is a function then it will be called
283 with the file path as it's only argument. After possibly calling
284 MAKE `find-file' will be used to open the path.
285
dc303f3 @eschulte bug fixes, actually works for rails navigation
authored
286 Optional argument METHOD-COMMAND overrides the function used to
287 find the current method which defaults to `which-function'."
820bb7d @eschulte cleanup embarrassing code and increasing robustness
authored
288 `(defun ,name (&optional create)
289 ,(concat doc "\n\nautomatically created by `defjump'")
290 (interactive "P")
599fb5d @purcell Incorporate @vhallac's fix for when defjump root is a function
purcell authored
291 (let ((root ,(if (functionp root) `(,root) root))
820bb7d @eschulte cleanup embarrassing code and increasing robustness
authored
292 (method-command ,(or method-command 'which-function))
293 matches)
294 (loop ;; try every rule in mappings
295 for spec in (quote ,(mapcar
296 (lambda (spec)
297 (if (stringp (car spec))
298 ;;xemacs did not understand :digit: class
299 (if (featurep 'xemacs)
300 (cons (replace-regexp-in-string
301 "\\\\[0-9]+" "\\\\(.*?\\\\)"
302 (car spec)) (cdr spec))
303 (cons (replace-regexp-in-string
304 "\\\\[[:digit:]]+" "\\\\(.*?\\\\)"
305 (car spec)) (cdr spec)))
306 spec))
307 specs))
308 ;; don't stop until both the front and the back match
309 ;;
310 ;; the back should match if the user is presented with a list
311 ;; of files, or a single file is jumped to
312 until
313 (and (setf matches (jump-from (car spec)))
314 (cond
315 ((equal t matches)
316 (jump-to (cdr spec) nil (when create ,make)))
317 ((consp matches)
318 (jump-to (cdr spec) matches (when create ,make)))))))))
29ce83d @eschulte initial commit
authored
319
320 (provide 'jump)
9677d93 @milkypostman fix jump defines so they are on the same line
milkypostman authored
321 ;;; jump.el ends here
Something went wrong with that request. Please try again.