1
;;; rails-navigation.el --- emacs-rails navigation functions
2
3
;; Copyright (C) 2006 Dmitry Galinsky <dima dot exe at gmail dot com>
4
5
;; Authors: Dmitry Galinsky <dima dot exe at gmail dot com>,
6
;;          Rezikov Peter <crazypit13 (at) gmail.com>
7
8
;; Keywords: ruby rails languages oop
9
;; $URL$
10
;; $Id$
11
12
;;; License
13
14
;; This program is free software; you can redistribute it and/or
15
;; modify it under the terms of the GNU General Public License
16
;; as published by the Free Software Foundation; either version 2
17
;; of the License, or (at your option) any later version.
18
19
;; This program is distributed in the hope that it will be useful,
20
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
;; GNU General Public License for more details.
23
24
;; You should have received a copy of the GNU General Public License
25
;; along with this program; if not, write to the Free Software
26
;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27
28
(defun rails-nav:create-goto-menu (items title &optional append-to-menu)
29
  (when append-to-menu
30
    (dolist (l append-to-menu items)
31
      (add-to-list 'items l t)))
32
  (let ((selected
33
         (when items
34
           (rails-core:menu
35
            (list title (cons title items))))))
36
    (if selected selected (message "No files found"))))
37
38
(defun rails-nav:goto-file-with-menu (dir title &optional ext no-inflector append-to-menu)
39
  "Make a menu to choose files from and find-file it."
40
  (let* (file
41
         files
42
         (ext (if ext ext "rb"))
43
         (ext (concat "\\." ext "$"))
44
         (dir (rails-core:file dir)))
45
    (setq files (directory-files-recursive dir nil ext))
46
    (setq files (sort files 'string<))
47
    (setq files (mapcar
48
                 #'(lambda(f)
49
                     (list
50
                      (if no-inflector f (rails-core:class-by-file f))
51
                      f))
52
                 files))
53
    (when-bind
54
     (selected (rails-nav:create-goto-menu files title append-to-menu))
55
     (if (symbolp selected)
56
         (apply selected (list))
57
       (rails-core:find-file-if-exist (concat dir selected))))))
58
59
(defun rails-nav:goto-file-with-menu-from-list (items title func &optional append-to-menu)
60
  (when-bind
61
   (selected (rails-nav:create-goto-menu (list->alist items) title append-to-menu))
62
   (when-bind
63
    (file (apply func (list selected)))
64
    (rails-core:find-file-if-exist file))))
65
66
(defun rails-nav:goto-controllers ()
67
  "Go to controllers."
68
  (interactive)
69
  (rails-nav:goto-file-with-menu-from-list
70
   (rails-core:controllers t)
71
   "Go to controller.."
72
   'rails-core:controller-file))
73
74
(defun rails-nav:goto-models ()
75
  "Go to models."
76
  (interactive)
77
  (rails-nav:goto-file-with-menu-from-list
78
   (rails-core:models)
79
   "Go to model.."
80
   'rails-core:model-file))
81
82
(defun rails-nav:goto-functional-tests ()
83
  "Go to functional tests."
84
  (interactive)
85
  (rails-nav:goto-file-with-menu-from-list
86
   (rails-core:functional-tests)
87
   "Go to functional test.."
88
   'rails-core:functional-test-file))
89
90
(defun rails-nav:goto-unit-tests ()
91
  "Go to functional tests."
92
  (interactive)
93
  (rails-nav:goto-file-with-menu-from-list
94
   (rails-core:unit-tests)
95
   "Go to unit test.."
96
   'rails-core:unit-test-file))
97
98
(defun rails-nav:goto-observers ()
99
  "Go to observers."
100
  (interactive)
101
  (rails-nav:goto-file-with-menu-from-list
102
   (rails-core:observers)
103
   "Go to observer.."
104
   'rails-core:observer-file))
105
106
(defun rails-nav:goto-mailers ()
107
  "Go to mailers."
108
  (interactive)
109
  (rails-nav:goto-file-with-menu-from-list
110
   (rails-core:mailers)
111
   "Go to mailers.."
112
   'rails-core:mailer-file))
113
114
(defun rails-nav:goto-migrate ()
115
  "Go to migrations."
116
  (interactive)
117
  (rails-nav:goto-file-with-menu-from-list
118
   (rails-core:migrations)
119
   "Go to migrate.."
120
   'rails-core:migration-file))
121
122
(defun rails-nav:goto-helpers ()
123
  "Go to helpers."
124
  (interactive)
125
  (rails-nav:goto-file-with-menu-from-list
126
   (rails-core:helpers)
127
   "Go to helper.."
128
   'rails-core:helper-file))
129
130
(defun rails-nav:goto-plugins ()
131
  "Go to plugins."
132
  (interactive)
133
  (rails-nav:goto-file-with-menu-from-list
134
   (rails-core:plugins)
135
   "Go to plugin.."
136
   (lambda (plugin)
137
     (concat "vendor/plugins/" plugin "/init.rb"))))
138
139
(defun rails-nav:goto-rspec-controllers ()
140
  "Go to controller specs."
141
  (interactive)
142
  (rails-nav:goto-file-with-menu-from-list
143
   (rails-core:rspec-controllers)
144
   "Go to controller spec.."
145
   'rails-core:rspec-controller-file))
146
147
(defun rails-nav:goto-rspec-lib ()
148
  "Go to lib specs."
149
  (interactive)
150
  (rails-nav:goto-file-with-menu-from-list
151
   (rails-core:rspec-lib)
152
   "Go to lib spec.."
153
   'rails-core:rspec-lib-file))
154
155
(defun rails-nav:goto-rspec-models ()
156
  "Go to model specs."
157
  (interactive)
158
  (rails-nav:goto-file-with-menu-from-list
159
   (rails-core:rspec-models)
160
   "Go to model spec.."
161
   'rails-core:rspec-model-file))
162
163
(defun rails-nav:goto-rspec-views ()
164
  "Go to view specs."
165
  (interactive)
166
  (rails-nav:goto-file-with-menu-from-list
167
   (rails-core:rspec-views)
168
   "Go to view spec.."
169
   'rails-core:rspec-view-file))
170
171
(defun rails-nav:goto-rspec-fixtures ()
172
  "Go to rspec fuxtures."
173
  (interactive)
174
  (rails-nav:goto-file-with-menu-from-list
175
   (rails-core:rspec-fixtures)
176
   "Go to rspec fixtures.."
177
   'rails-core:rspec-fixture-file))
178
179
(defun rails-nav:create-new-layout (&optional name)
180
  "Create a new layout."
181
  (let ((name (or name (read-string "Layout name? "))))
182
    (when name
183
      (let* ((default-type (or rails-controller-layout:recent-template-type (car rails-templates-list)))
184
             (type (completing-read (format "Create %s.[%s]? " name default-type)
185
                                    rails-templates-list nil nil default-type)))
186
        (rails-core:find-file (rails-core:file (format "app/views/layouts/%s.%s" name type)))
187
        (if (y-or-n-p "Insert initial template? ")
188
          (insert rails-layout-template))))))
189
190
(defun rails-nav:goto-layouts ()
191
  "Go to layouts."
192
  (interactive)
193
  (let ((items (list (rails-core:menu-separator)
194
                     (cons "Create new layout" 'rails-nav:create-new-layout))))
195
    (rails-nav:goto-file-with-menu-from-list
196
     (rails-core:layouts)
197
     "Go to layout.."
198
     (lambda (l)
199
       (if (stringp l)
200
           (rails-core:layout-file l)
201
         (apply l (list))))
202
     items)))
203
204
(defun rails-nav:goto-fixtures ()
205
  "Go to fixtures."
206
  (interactive)
207
  (rails-nav:goto-file-with-menu-from-list
208
   (rails-core:fixtures)
209
   "Go to fixture.."
210
   'rails-core:fixture-file))
211
212
(defun rails-nav:goto-stylesheets ()
213
  "Go to stylesheets."
214
  (interactive)
215
  (rails-nav:goto-file-with-menu "public/stylesheets/" "Go to stylesheet.." "css" t))
216
217
(defun rails-nav:goto-javascripts ()
218
  "Go to JavaScripts."
219
  (interactive)
220
  (rails-nav:goto-file-with-menu "public/javascripts/" "Go to stylesheet.." "js" t))
221
222
;;;;;;;;;; Goto file on current line ;;;;;;;;;;
223
224
(defmacro* def-goto-line (name (&rest conditions) &rest body)
225
  "Go to the file specified by the current line. Parses the
226
current line for a series of patterns."
227
  (let ((line (gensym))
228
        (field (gensym))
229
        (prefix (gensym)))
230
    `(progn
231
       (defun ,name (,line ,prefix)
232
         (block ,name
233
           ,@(loop for (sexpr . map) in conditions
234
                   collect
235
                   `(let ((case-fold-search nil))
236
                      (when (string-match ,sexpr ,line)
237
                        (let ,(loop for var-acc in map collect
238
                                    (if (listp var-acc)
239
                                      `(,(first var-acc) (match-string ,(second var-acc) ,line))
240
                                      var-acc))
241
                          (return-from ,name (progn ,@body)))))))))))
242
243
(defun rails-goto-file-on-current-line (prefix)
244
  "Analyze a string (or ERb block) and open some file related with it.
245
For example, on a line with \"render :partial\" runing this
246
function will open the partial file.  The function works with
247
\"layout 'name'\", \"render/redirect-to [:action => 'name',] [controller => 'n']\",
248
stylesheet_link_tag and other common
249
patterns.
250
251
Rules for actions/controllers:
252
 If you are in a controller, the cursor will be placed on the controller action.
253
 If you in view, the view file related to the action will be opened.
254
 Use prefix before the command to change this navigation direction."
255
  (interactive "P")
256
  (rails-project:with-root
257
   (root)
258
   (save-match-data
259
     (unless
260
         (when-bind
261
          (line (save-excursion
262
                  (if (rails-core:rhtml-buffer-p)
263
                      (rails-core:erb-block-string)
264
                    (current-line-string))))
265
          (loop for func in rails-on-current-line-gotos
266
                until (when (funcall func line prefix) (return t))))
267
       (message "Can't switch to some file from this line.")))))
268
269
(defvar rails-on-current-line-gotos
270
  '(rails-line-->partial
271
    rails-line-->action
272
    rails-line-->controller+action
273
    rails-line-->layout
274
    rails-line-->stylesheet
275
    rails-line-->js
276
    rails-line-->association-model
277
    rails-line-->single-association-model
278
    rails-line-->multi-association-model
279
    rails-line-->class)
280
  "Functions that will be called to analyze the line when
281
rails-goto-file-on-current-line is run.")
282
283
(def-goto-line rails-line-->stylesheet (("[ ]*stylesheet_link_tag[ ][\"']\\([^\"']*\\)[\"']"
284
                                         (name 1)))
285
  (rails-core:find-or-ask-to-create
286
   (format "Stylesheet \"%s\" does not exist do you whant to create it? " name)
287
   (rails-core:stylesheet-name name)))
288
289
(def-goto-line rails-line-->partial (("\\([ ]*render\\|replace_html\\|insert_html\\).*:partial[ ]*=>[ ]*[\"']\\([^\"']*\\)[\"']"
290
                                      (name 2)))
291
  (rails-core:find-or-ask-to-create
292
   (format "Partial \"%s\" does not exist do you whant to create it? " name)
293
   (rails-core:partial-name name)))
294
295
(def-goto-line rails-line-->action (("\\([ ]*render\\|replace_html\\|insert_html\\).*:action[ ]*=>[ ]*[\"'\:]\\([^\"']*\\)"
296
                                     (name 2)))
297
  (rails-core:find-or-ask-to-create
298
   (format "View \"%s\" does not exist do you whant to create it? " name)
299
   (rails-core:view-name name)))
300
301
(def-goto-line rails-line-->layout (("^[ ]*layout[ ]*[\"']\\(.*\\)[\"']" (name 1)))
302
  (let ((file-name (rails-core:layout-file name)))
303
    (if (file-exists-p (rails-core:file file-name))
304
        (rails-core:find-file file-name)
305
      (rails-nav:create-new-layout name))))
306
307
(def-goto-line rails-line-->js (("^[ ]*javascript_include_tag[ ]*[\"']\\(.*\\)[\"']"
308
                                 (name  1)))
309
  (rails-core:find-or-ask-to-create
310
   (format "JavaScript file \"%s\" does not exist do you whant to create it? " name)
311
   (rails-core:js-file name)))
312
313
(def-goto-line rails-line-->association-model (("^[ \t]*\\(has_one\\|belongs_to\\|has_many\\|has_and_belongs_to_many\\)[ \t].*:class_name[ \t]*=>[ \t]*[\"']\\([^\"']*\\)[\"']"
314
                                                (name 2)))
315
  (rails-core:find-file (rails-core:model-file name)))
316
317
(def-goto-line rails-line-->single-association-model (("^[ \t]*\\(has_one\\|belongs_to\\)[ \t]*:\\([a-z0-9_]*\\)" (name 2)))
318
  (rails-core:find-file (rails-core:model-file name)))
319
320
(def-goto-line rails-line-->multi-association-model (("^[ \t]*\\(has_many\\|has_and_belongs_to_many\\)[ \t]*:\\([a-z0-9_]*\\)" (name 2)))
321
  (rails-core:find-file (rails-core:model-file (singularize-string name))))
322
323
(def-goto-line rails-line-->class (("\\b\\(\\([A-Z][a-z]*\\)+\\)\\b" (name 1)))
324
  (or (rails-core:find-file-if-exist (rails-core:model-file name))
325
      (rails-core:find-file-if-exist (rails-core:controller-file name))
326
      (rails-core:find-file-if-exist (rails-core:lib-file name))))
327
328
(defvar rails-line-to-controller/action-keywords
329
  '("render" "redirect_to" "link_to" "form_tag" "start_form_tag" "render_component"
330
    "form_remote_tag" "link_to_remote"))
331
332
(defun rails-line-->controller+action (line prefix)
333
  (when (loop for keyword in rails-line-to-controller/action-keywords
334
              when (string-match (format "^[ ]*%s " keyword) line) do (return t))
335
    (let (action controller)
336
      (when (string-match ":action[ ]*=>[ ]*[\"']\\([^\"']*\\)[\"']" line)
337
        (setf action (match-string 1 line)))
338
      (when (string-match ":controller[ ]*=>[ ]*[\"']\\([^\"']*\\)[\"']" line)
339
        (setf controller (match-string 1 line)))
340
      (rails-controller-layout:switch-to-action-in-controller
341
       (if controller controller
342
         (rails-core:current-controller))
343
       action))))
344
345
(provide 'rails-navigation)