| 1 |
;;; rails.el --- minor mode for editing RubyOnRails code |
| 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 |
;;; Code: |
| 29 |
|
| 30 |
(unless (<= 22 emacs-major-version) |
| 31 |
(error |
| 32 |
(format "emacs-rails require CVS version of Emacs (future Emacs 22), and not be running on your Emacs %s.%s" |
| 33 |
emacs-major-version |
| 34 |
emacs-minor-version))) |
| 35 |
|
| 36 |
(eval-when-compile |
| 37 |
(require 'speedbar) |
| 38 |
(require 'inf-ruby) |
| 39 |
(require 'ruby-mode) |
| 40 |
(require 'ruby-electric)) |
| 41 |
|
| 42 |
(require 'grep) |
| 43 |
(require 'sql) |
| 44 |
(require 'ansi-color) |
| 45 |
(require 'etags) |
| 46 |
|
| 47 |
(require 'predictive-prog-mode) |
| 48 |
|
| 49 |
(require 'inflections) |
| 50 |
|
| 51 |
(require 'rails-compat) |
| 52 |
(require 'rails-project) |
| 53 |
|
| 54 |
|
| 55 |
;;;;;;;;; Defining custom group before loading other file |
| 56 |
|
| 57 |
(defgroup rails nil |
| 58 |
"Edit Rails projet with Emacs." |
| 59 |
:group 'programming |
| 60 |
:prefix "rails-") |
| 61 |
|
| 62 |
;;;;;;;;; Loading most of the mode |
| 63 |
|
| 64 |
(require 'rails-core) |
| 65 |
(require 'rails-ruby) |
| 66 |
(require 'rails-lib) |
| 67 |
|
| 68 |
(require 'rails-cmd-proxy) |
| 69 |
(require 'rails-navigation) |
| 70 |
(require 'rails-find) |
| 71 |
(require 'rails-scripts) |
| 72 |
(require 'rails-rake) |
| 73 |
(require 'rails-test) |
| 74 |
(require 'rails-ws) |
| 75 |
(require 'rails-log) |
| 76 |
(require 'rails-ui) |
| 77 |
(require 'rails-model-layout) |
| 78 |
(require 'rails-controller-layout) |
| 79 |
(require 'rails-features) |
| 80 |
(require 'rails-lib-layout) |
| 81 |
(require 'rails-spec) |
| 82 |
(require 'rails-shoulda) |
| 83 |
(require 'rails-refactoring) |
| 84 |
|
| 85 |
;;;;;;;;;; Variable definition ;;;;;;;;;; |
| 86 |
|
| 87 |
(defcustom rails-api-root "" |
| 88 |
"*Root of Rails API html documentation. Must be a local directory." |
| 89 |
:group 'rails |
| 90 |
:type 'string) |
| 91 |
|
| 92 |
(defcustom rails-use-alternative-browse-url nil |
| 93 |
"Indicates an alternative way of loading URLs on Windows. |
| 94 |
Try using the normal method before. If URLs invoked by the |
| 95 |
program don't end up in the right place, set this option to |
| 96 |
true." |
| 97 |
:group 'rails |
| 98 |
:type 'boolean) |
| 99 |
|
| 100 |
(defcustom rails-browse-api-with-w3m nil |
| 101 |
"Indicates that the user wants to browse the Rails API using |
| 102 |
Emacs w3m browser." |
| 103 |
:group 'rails |
| 104 |
:type 'boolean) |
| 105 |
|
| 106 |
(defcustom rails-tags-command "ctags -e -a --Ruby-kinds=-f -o %s -R %s" |
| 107 |
"Command used to generate TAGS in Rails root" |
| 108 |
:group 'rails |
| 109 |
:type 'string) |
| 110 |
|
| 111 |
(defcustom rails-ri-command "ri" |
| 112 |
"Command used to invoke the ri utility." |
| 113 |
:group 'rails |
| 114 |
:type 'string) |
| 115 |
|
| 116 |
(defcustom rails-always-use-text-menus nil |
| 117 |
"Force the use of text menus by default." |
| 118 |
:group 'rails |
| 119 |
:type 'boolean) |
| 120 |
|
| 121 |
(defcustom rails-text-menu-function nil |
| 122 |
"Which function to use to create text menus. nil means #'rails-core:ttm-menu" |
| 123 |
:group 'rails |
| 124 |
:type 'string) |
| 125 |
|
| 126 |
(defcustom rails-ask-when-reload-tags nil |
| 127 |
"Indicates whether the user should confirm reload a TAGS table or not." |
| 128 |
:group 'rails |
| 129 |
:type 'boolean) |
| 130 |
|
| 131 |
(defcustom rails-chm-file nil |
| 132 |
"Path to CHM documentation file on Windows, or nil." |
| 133 |
:group 'rails |
| 134 |
:type 'string) |
| 135 |
|
| 136 |
(defcustom rails-ruby-command "ruby" |
| 137 |
"Ruby preferred command line invocation." |
| 138 |
:group 'rails |
| 139 |
:type 'string) |
| 140 |
|
| 141 |
(defcustom rails-layout-template |
| 142 |
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" |
| 143 |
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> |
| 144 |
<html xmlns=\"http://www.w3.org/1999/xhtml\" |
| 145 |
xml:lang=\"en\" lang=\"en\"> |
| 146 |
<head> |
| 147 |
<title></title> |
| 148 |
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> |
| 149 |
<%= stylesheet_link_tag \"default\" %> |
| 150 |
</head> |
| 151 |
|
| 152 |
<body> |
| 153 |
<%= yield %> |
| 154 |
</body> |
| 155 |
</html>" |
| 156 |
"Default html template for new rails layout" |
| 157 |
:group 'rails |
| 158 |
:type 'string) |
| 159 |
|
| 160 |
(defcustom rails-enable-ruby-electric t |
| 161 |
"Indicates whether ruby electric minor mode should be enabled by default for ruby files" |
| 162 |
:group 'rails |
| 163 |
:type 'boolean) |
| 164 |
|
| 165 |
(defcustom rails-number-of-lines-shown-when-opening-log-file 130 |
| 166 |
"Specifies how many lines to show initially when opening a log file" |
| 167 |
:group 'rails |
| 168 |
:type 'integer) |
| 169 |
|
| 170 |
(defvar rails-version "0.5.99.6") |
| 171 |
(defvar rails-use-another-define-key nil) |
| 172 |
(defvar rails-primary-switch-func nil) |
| 173 |
(defvar rails-secondary-switch-func nil) |
| 174 |
(defvar rails-required-lisp-eval-depth 1000) ; Specifies the minimum required value of max-lisp-eval-depth for rails mode to work |
| 175 |
|
| 176 |
(defcustom rails-indent-and-complete t |
| 177 |
"Key to indent and complete." |
| 178 |
:group 'rails |
| 179 |
:type 'boolean) |
| 180 |
|
| 181 |
(defvar rails-directory<-->types |
| 182 |
'((:controller "app/controllers/") |
| 183 |
(:layout "app/views/layouts/") |
| 184 |
(:view "app/views/") |
| 185 |
(:observer "app/models/" (lambda (file) (rails-core:observer-p file))) |
| 186 |
(:mailer "app/models/" (lambda (file) (rails-core:mailer-p file))) |
| 187 |
(:model "app/models/" (lambda (file) (and (not (rails-core:mailer-p file)) |
| 188 |
(not (rails-core:observer-p file))))) |
| 189 |
(:helper "app/helpers/") |
| 190 |
(:unit-test "vendor/plugins/.*/test/") ; needs to appear before more-general :plugin |
| 191 |
(:model "vendor/plugins/.*/lib/") ; needs to appear before more-general :plugin |
| 192 |
(:plugin "vendor/plugins/") |
| 193 |
(:unit-test "test/unit/") |
| 194 |
(:functional-test "test/functional/") |
| 195 |
(:integration-test "test/integration/") |
| 196 |
(:fixture "test/fixtures/") |
| 197 |
(:lib "lib") |
| 198 |
(:rspec-controller "spec/controllers") |
| 199 |
(:rspec-controller "spec/requests") |
| 200 |
(:rspec-fixture "spec/fixtures") |
| 201 |
(:rspec-lib "spec/lib") |
| 202 |
(:rspec-model "spec/models") |
| 203 |
(:migration "db/migrate")) |
| 204 |
"Rails file types -- rails directories map") |
| 205 |
|
| 206 |
(defcustom rails-environments '("development" "production" "test") |
| 207 |
"rails environments" |
| 208 |
:group 'rails |
| 209 |
:type '(repeat string)) |
| 210 |
|
| 211 |
(defcustom rails-default-environment (first rails-environments) |
| 212 |
"rails environment used by default" |
| 213 |
:group 'rails |
| 214 |
:type 'string) |
| 215 |
|
| 216 |
(defvar rails-adapters-alist |
| 217 |
'(("mysql" . sql-mysql) |
| 218 |
("postgresql" . sql-postgres) |
| 219 |
("sqlite3" . sql-sqlite)) |
| 220 |
"Sets emacs sql function for rails adapter names.") |
| 221 |
|
| 222 |
(defcustom rails-tags-dirs '("app" "lib" "test" "db") |
| 223 |
"List of directories from RAILS_ROOT where ctags works." |
| 224 |
:group 'rails |
| 225 |
:type '(repeat string)) |
| 226 |
|
| 227 |
(defcustom rails-grep-extensions '("builder" "erb" "haml" "liquid" "mab" "rake" "rb" "rhtml" "rjs" "rxml" "yml" "feature" "js" "html" "rtex" "prawn") |
| 228 |
"List of file extensions which grep searches." |
| 229 |
:group 'rails |
| 230 |
:type '(repeat string)) |
| 231 |
|
| 232 |
(defcustom rails-templates-list |
| 233 |
'("html.erb" "erb" "js.rjs" "rjs" "xml.builder" "builder" "rhtml" "rxml" "html.haml" "haml" "html.liquid" "liquid" "html.mad" "mab" "pdf.rtex" "rtex" "pdf.prawn" "prawn") |
| 234 |
"List of view templates. This first template is the default template." |
| 235 |
:group 'rails |
| 236 |
:type '(repeat string)) |
| 237 |
|
| 238 |
(defvar rails-error-regexp-alist |
| 239 |
'( |
| 240 |
(" /?\\(app/[a-z0-9._/]*\\):\\([0-9]+\\)" 1 2) |
| 241 |
(" /?\\(lib/[a-z0-9._/]*\\):\\([0-9]+\\)" 1 2) |
| 242 |
(" /?\\(test/[a-z0-9._/]*\\):\\([0-9]+\\)" 1 2) |
| 243 |
(" /?\\(db/[a-z0-9._/]*\\):\\([0-9]+\\)" 1 2) |
| 244 |
(" /?\\(vendor/[a-z0-9._/]*\\):\\([0-9]+\\)" 1 2) |
| 245 |
(" /?\\(app/[a-z0-9._/]*\\)" 1) |
| 246 |
(" /?\\(lib/[a-z0-9._/]*\\)" 1) |
| 247 |
(" /?\\(test/[a-z0-9._/]*\\)" 1) |
| 248 |
(" /?\\(db/[a-z0-9._/]*\\)" 1) |
| 249 |
(" /?\\(vendor/[a-z0-9._/]*\\)" 1) |
| 250 |
) |
| 251 |
"Rails specific compilation-error-regexp-alist.") |
| 252 |
|
| 253 |
(defun rails-use-text-menu () |
| 254 |
"If t use text menu, popup menu otherwise" |
| 255 |
(or (null window-system) rails-always-use-text-menus)) |
| 256 |
|
| 257 |
;;;;;;;; hack ;;;; |
| 258 |
(defun rails-svn-status-into-root () |
| 259 |
(interactive) |
| 260 |
(rails-project:with-root (root) |
| 261 |
(svn-status root))) |
| 262 |
|
| 263 |
(defun rails-git-status-into-root () |
| 264 |
(interactive) |
| 265 |
(rails-project:with-root (root) |
| 266 |
(git-status root))) |
| 267 |
|
| 268 |
(defun rails-scm-status-into-root () |
| 269 |
(interactive) |
| 270 |
(rails-project:with-root (root) |
| 271 |
(cond ((file-directory-p (expand-file-name ".git" root)) |
| 272 |
(rails-git-status-into-root)) |
| 273 |
((file-directory-p (expand-file-name ".svn" root)) |
| 274 |
(rails-svn-status-into-root))))) |
| 275 |
|
| 276 |
;; helper functions/macros |
| 277 |
|
| 278 |
(defun backward-ruby-object () |
| 279 |
(if (looking-back "[-a-zA-Z_#:*]+" (line-beginning-position) t) |
| 280 |
(goto-char (match-beginning 0)))) |
| 281 |
|
| 282 |
(defun forward-ruby-object (n) |
| 283 |
(if (> 0 n) |
| 284 |
(when (search-backward-regexp "[^-a-zA-Z_#:*][-a-zA-Z_#:*]+" nil t (- n)) |
| 285 |
(forward-char) |
| 286 |
(point)) |
| 287 |
(when (search-forward-regexp "[-a-zA-Z_#:*]+" nil t n) |
| 288 |
(goto-char (match-end 0))))) |
| 289 |
|
| 290 |
(defun rails-search-doc (&optional item) |
| 291 |
(interactive) |
| 292 |
(setq item (if item item (thing-at-point 'ruby-object))) |
| 293 |
(unless item |
| 294 |
(setq item (read-string "Search symbol: "))) |
| 295 |
(if item |
| 296 |
(if (and rails-chm-file |
| 297 |
(file-exists-p rails-chm-file)) |
| 298 |
(start-process "keyhh" "*keyhh*" "keyhh.exe" "-#klink" |
| 299 |
(format "'%s'" item) rails-chm-file) |
| 300 |
(with-current-buffer (get-buffer-create "*ri*") |
| 301 |
(setq buffer-read-only nil) |
| 302 |
(erase-buffer) |
| 303 |
(message (concat "Please wait...")) |
| 304 |
(call-process rails-ri-command nil "*ri*" t "-T" "-f" "ansi" item) |
| 305 |
(ansi-color-apply-on-region (point-min) (point-max)) |
| 306 |
(setq buffer-read-only t) |
| 307 |
(goto-char (point-min)) |
| 308 |
(local-set-key "q" 'quit-window) |
| 309 |
(local-set-key [f1] 'rails-search-doc) |
| 310 |
(display-buffer (current-buffer)))))) |
| 311 |
|
| 312 |
(defun rails-create-tags() |
| 313 |
"Create tags file" |
| 314 |
(interactive) |
| 315 |
(rails-project:in-root |
| 316 |
(message "Creating TAGS, please wait...") |
| 317 |
(let ((tags-file-name (rails-core:file "TAGS"))) |
| 318 |
(shell-command |
| 319 |
(format rails-tags-command tags-file-name |
| 320 |
(strings-join " " (mapcar #'rails-core:file rails-tags-dirs)))) |
| 321 |
(flet ((yes-or-no-p (p) (if rails-ask-when-reload-tags |
| 322 |
(y-or-n-p p) |
| 323 |
t))) |
| 324 |
(visit-tags-table tags-file-name))))) |
| 325 |
|
| 326 |
(defun rails-apply-for-buffer-type () |
| 327 |
(let* ((type (rails-core:buffer-type)) |
| 328 |
(name (substring (symbol-name type) 1)) |
| 329 |
(minor-mode-name (format "rails-%s-minor-mode" name)) |
| 330 |
(minor-mode-abbrev (concat minor-mode-name "-abbrev-table"))) |
| 331 |
(when (require (intern minor-mode-name) nil t) ;; load new style minor mode rails-*-minor-mode |
| 332 |
(when (fboundp (intern minor-mode-name)) |
| 333 |
(apply (intern minor-mode-name) (list t)) |
| 334 |
(when (boundp (intern minor-mode-abbrev)) |
| 335 |
(merge-abbrev-tables |
| 336 |
(symbol-value (intern minor-mode-abbrev)) |
| 337 |
local-abbrev-table)))))) |
| 338 |
|
| 339 |
(defun rails-grep-project (regexp) |
| 340 |
"Find regexp in project." |
| 341 |
(interactive (progn (grep-compute-defaults) |
| 342 |
(list (grep-read-regexp)))) |
| 343 |
(rgrep regexp (mapconcat (lambda (ext) (format "*.%s" ext)) rails-grep-extensions " ") (rails-project:root))) |
| 344 |
|
| 345 |
(defun rails-shell () |
| 346 |
"Switch to the project inferior shell buffer." |
| 347 |
(interactive) |
| 348 |
(let ((default-directory (rails-project:root))) |
| 349 |
(shell (get-buffer-create (concat "*rails-" (rails-project:name) "-shell*"))))) |
| 350 |
|
| 351 |
;;;;;;;;;; Database integration ;;;;;;;;;; |
| 352 |
|
| 353 |
(defstruct rails-db-conf adapter host database username password) |
| 354 |
|
| 355 |
(defun rails-db-parameters (env) |
| 356 |
"Return database parameters for enviroment ENV" |
| 357 |
(with-temp-buffer |
| 358 |
(shell-command |
| 359 |
(format "ruby -r yaml -r erb -e 'YAML.load(ERB.new(ARGF.read).result)[\"%s\"].to_yaml.display' %s" |
| 360 |
env |
| 361 |
(rails-core:file "config/database.yml")) |
| 362 |
(current-buffer)) |
| 363 |
(let ((answer |
| 364 |
(make-rails-db-conf |
| 365 |
:adapter (yml-value "adapter") |
| 366 |
:host (yml-value "host") |
| 367 |
:database (yml-value "database") |
| 368 |
:username (yml-value "username") |
| 369 |
:password (yml-value "password")))) |
| 370 |
answer))) |
| 371 |
|
| 372 |
(defun rails-database-emacs-func (adapter) |
| 373 |
"Return the Emacs function for ADAPTER that, when run, will |
| 374 |
+invoke the appropriate database server console." |
| 375 |
(cdr (assoc adapter rails-adapters-alist))) |
| 376 |
|
| 377 |
(defun rails-read-enviroment-name (&optional default) |
| 378 |
"Read Rails enviroment with auto-completion." |
| 379 |
(completing-read "Environment name: " (list->alist rails-environments) nil nil default)) |
| 380 |
|
| 381 |
(defun* rails-run-sql (&optional env) |
| 382 |
"Run a SQL process for the current Rails project." |
| 383 |
(interactive (list (rails-read-enviroment-name "development"))) |
| 384 |
(rails-project:with-root (root) |
| 385 |
(cd root) |
| 386 |
(if (bufferp (sql-find-sqli-buffer)) |
| 387 |
(switch-to-buffer-other-window (sql-find-sqli-buffer)) |
| 388 |
(let ((conf (rails-db-parameters env))) |
| 389 |
(let ((sql-database (rails-db-conf-database conf)) |
| 390 |
(default-process-coding-system '(utf-8 . utf-8)) |
| 391 |
(sql-server (rails-db-conf-host conf)) |
| 392 |
(sql-user (rails-db-conf-username conf)) |
| 393 |
(sql-password (rails-db-conf-password conf))) |
| 394 |
;; Reload localy sql-get-login to avoid asking of confirmation of DB login parameters |
| 395 |
(flet ((sql-get-login (&rest pars) () t)) |
| 396 |
(funcall (rails-database-emacs-func (rails-db-conf-adapter conf))))))))) |
| 397 |
|
| 398 |
(defun rails-has-api-root () |
| 399 |
"Test whether `rails-api-root' is configured or not, and offer to configure |
| 400 |
it in case it's still empty for the project." |
| 401 |
(rails-project:with-root |
| 402 |
(root) |
| 403 |
(if (file-exists-p (rails-core:file (concat rails-api-root "/index.html"))) |
| 404 |
t |
| 405 |
(unless (or (file-exists-p (rails-core:file "doc/api/index.html")) |
| 406 |
(not (yes-or-no-p (concat "This project has no API documentation. " |
| 407 |
"Would you like to configure it now? ")))) |
| 408 |
(let (clobber-gems) |
| 409 |
(message "This may take a while. Please wait...") |
| 410 |
(unless (file-exists-p (rails-core:file "vendor/rails")) |
| 411 |
(setq clobber-gems t) |
| 412 |
(message "Freezing gems...") |
| 413 |
(shell-command-to-string "rake rails:freeze:gems")) |
| 414 |
;; Hack to allow generation of the documentation for Rails 1.0 and 1.1 |
| 415 |
;; See http://dev.rubyonrails.org/ticket/4459 |
| 416 |
(unless (file-exists-p (rails-core:file "vendor/rails/activesupport/README")) |
| 417 |
(write-string-to-file (rails-core:file "vendor/rails/activesupport/README") |
| 418 |
"Placeholder")) |
| 419 |
(message "Generating documentation...") |
| 420 |
(shell-command-to-string "rake doc:rails") |
| 421 |
(if clobber-gems |
| 422 |
(progn |
| 423 |
(message "Unfreezing gems...") |
| 424 |
(shell-command-to-string "rake rails:unfreeze"))) |
| 425 |
(message "Done..."))) |
| 426 |
(if (file-exists-p (rails-core:file "doc/api/index.html")) |
| 427 |
(setq rails-api-root (rails-core:file "doc/api")))))) |
| 428 |
|
| 429 |
(defun rails-browse-api () |
| 430 |
"Browse Rails API on RAILS-API-ROOT." |
| 431 |
(interactive) |
| 432 |
(if (rails-has-api-root) |
| 433 |
(rails-browse-api-url (concat rails-api-root "/index.html")) |
| 434 |
(message "Please configure variable rails-api-root."))) |
| 435 |
|
| 436 |
(defun rails-get-api-entries (name file sexp get-file-func) |
| 437 |
"Return all API entries named NAME in file FILE using SEXP to |
| 438 |
find matches, and GET-FILE-FUNC to process the matches found." |
| 439 |
(if (file-exists-p (concat rails-api-root "/" file)) |
| 440 |
(save-current-buffer |
| 441 |
(save-match-data |
| 442 |
(find-file (concat rails-api-root "/" file)) |
| 443 |
(let* ((result |
| 444 |
(loop for line in (split-string (buffer-string) "\n") |
| 445 |
when (string-match (format sexp (regexp-quote name)) line) |
| 446 |
collect (cons (match-string-no-properties 2 line) |
| 447 |
(match-string-no-properties 1 line))))) |
| 448 |
(kill-buffer (current-buffer)) |
| 449 |
(when-bind (api-file (funcall get-file-func result)) |
| 450 |
(rails-browse-api-url (concat "file://" rails-api-root "/" api-file)))))) |
| 451 |
(message "There are no API docs."))) |
| 452 |
|
| 453 |
(defun rails-browse-api-class (class) |
| 454 |
"Browse the Rails API documentation for CLASS." |
| 455 |
(rails-get-api-entries |
| 456 |
class "fr_class_index.html" "<a href=\"\\(.*\\)\">%s<" |
| 457 |
(lambda (entries) |
| 458 |
(cond ((= 0 (length entries)) (progn (message "No API Rails doc for class %s." class) nil)) |
| 459 |
((= 1 (length entries)) (cdar entries)))))) |
| 460 |
|
| 461 |
(defun rails-browse-api-method (method) |
| 462 |
"Browse the Rails API documentation for METHOD." |
| 463 |
(rails-get-api-entries |
| 464 |
method "fr_method_index.html" "<a href=\"\\(.*\\)\">%s[ ]+(\\(.*\\))" |
| 465 |
(lambda (entries) |
| 466 |
(cond ((= 0 (length entries)) (progn (message "No API Rails doc for %s" method) nil)) |
| 467 |
((= 1 (length entries)) (cdar entries)) |
| 468 |
(t (cdr (assoc (completing-read (format "Method %s from what class? " method) entries) |
| 469 |
entries))))))) |
| 470 |
|
| 471 |
(defun rails-browse-api-at-point () |
| 472 |
"Open the Rails API documentation on the class or method at the current point. |
| 473 |
The variable `rails-api-root' must be pointing to a local path |
| 474 |
either in your project or elsewhere in the filesystem. The |
| 475 |
function will also offer to build the documentation locally if |
| 476 |
necessary." |
| 477 |
(interactive) |
| 478 |
(if (rails-has-api-root) |
| 479 |
(let ((current-symbol (prog2 |
| 480 |
(modify-syntax-entry ?: "w") |
| 481 |
(thing-at-point 'sexp) |
| 482 |
(modify-syntax-entry ?: ".")))) |
| 483 |
(if current-symbol |
| 484 |
(if (capital-word-p current-symbol) |
| 485 |
(rails-browse-api-class current-symbol) |
| 486 |
(rails-browse-api-method current-symbol)))) |
| 487 |
(message "Please configure \"rails-api-root\"."))) |
| 488 |
|
| 489 |
;;; Rails minor mode |
| 490 |
|
| 491 |
(define-minor-mode rails-minor-mode |
| 492 |
"RubyOnRails" |
| 493 |
nil |
| 494 |
" RoR" |
| 495 |
rails-minor-mode-map |
| 496 |
(abbrev-mode -1) |
| 497 |
(make-local-variable 'tags-file-name) |
| 498 |
(make-local-variable 'rails-primary-switch-func) |
| 499 |
(make-local-variable 'rails-secondary-switch-func) |
| 500 |
(set (make-local-variable 'compile-command) "rake") |
| 501 |
(set (make-local-variable 'ffip-project-root) (rails-project:root)) |
| 502 |
(set (make-local-variable 'ffip-regexp) |
| 503 |
(concat ".*\\(" (rails-core:regex-for-match-view) |
| 504 |
"\\|" (mapconcat #'car rails-auto-mode-alist "\\|") |
| 505 |
"\\|" (mapconcat (lambda (ext) (concat "\\." ext "$")) rails-refactoring-source-extensions "\\|") |
| 506 |
"\\|" (mapconcat (lambda (ext) (concat "\\." ext "$")) rails-grep-extensions "\\|") |
| 507 |
"\\)")) |
| 508 |
(set (make-local-variable 'ffip-patterns) |
| 509 |
(mapcar (lambda (ext) (concat "*." ext)) rails-grep-extensions)) |
| 510 |
(rails-features:install)) |
| 511 |
|
| 512 |
;; hooks |
| 513 |
|
| 514 |
(add-hook 'ruby-mode-hook |
| 515 |
(lambda () |
| 516 |
(when (rails-project:root) |
| 517 |
(require 'rails-ruby) |
| 518 |
(require 'ruby-electric) |
| 519 |
(ruby-electric-mode (or rails-enable-ruby-electric -1)) |
| 520 |
(ruby-hs-minor-mode t) |
| 521 |
(imenu-add-to-menubar "IMENU") |
| 522 |
(if rails-indent-and-complete |
| 523 |
(local-set-key (if rails-use-another-define-key |
| 524 |
(kbd "TAB") (kbd "<tab>")) |
| 525 |
'indent-and-complete)) |
| 526 |
(local-set-key (rails-key "f") '(lambda() |
| 527 |
(interactive) |
| 528 |
(mouse-major-mode-menu (rails-core:menu-position)))) |
| 529 |
(local-set-key (kbd "C-:") 'ruby-toggle-string<>simbol) |
| 530 |
(local-set-key (if rails-use-another-define-key |
| 531 |
(kbd "RET") (kbd "<return>")) |
| 532 |
'ruby-newline-and-indent)))) |
| 533 |
|
| 534 |
(add-hook 'speedbar-mode-hook |
| 535 |
(lambda() |
| 536 |
(speedbar-add-supported-extension "\\.rb"))) |
| 537 |
|
| 538 |
(add-hook 'find-file-hooks |
| 539 |
(lambda() |
| 540 |
(rails-project:with-root |
| 541 |
(root) |
| 542 |
(progn |
| 543 |
(if rails-indent-and-complete |
| 544 |
(local-set-key (if rails-use-another-define-key |
| 545 |
(kbd "TAB") (kbd "<tab>")) |
| 546 |
'indent-and-complete)) |
| 547 |
(rails-minor-mode t) |
| 548 |
(rails-apply-for-buffer-type))))) |
| 549 |
|
| 550 |
;; Run rails-minor-mode in dired |
| 551 |
|
| 552 |
(add-hook 'dired-mode-hook |
| 553 |
(lambda () |
| 554 |
(if (rails-project:root) |
| 555 |
(rails-minor-mode t)))) |
| 556 |
|
| 557 |
(defvar rails-auto-mode-alist '(("\\.rb$" . ruby-mode) |
| 558 |
("\\.rake$" . ruby-mode) |
| 559 |
("\\.mab$" . ruby-mode) |
| 560 |
("Rakefile$" . ruby-mode) |
| 561 |
("Capfile$" . ruby-mode) |
| 562 |
("\\.rxml$" . ruby-mode) |
| 563 |
("\\.builder$" . ruby-mode) |
| 564 |
("\\.rjs$" . ruby-mode) |
| 565 |
("\\.prawn$" . ruby-mode) |
| 566 |
("\\.rhtml$" . rhtml-mode) |
| 567 |
("\\.erb$" . rhtml-mode))) |
| 568 |
|
| 569 |
(dolist (pair rails-auto-mode-alist) |
| 570 |
(add-to-list 'auto-mode-alist pair) |
| 571 |
(modify-coding-system-alist 'file (car pair) 'utf-8)) |
| 572 |
|
| 573 |
(modify-coding-system-alist 'file (rails-core:regex-for-match-view) 'utf-8) |
| 574 |
|
| 575 |
;; Some navigation breaks if max-lisp-eval-depth is not high enough, up it if too low |
| 576 |
(setq max-lisp-eval-depth (max max-lisp-eval-depth rails-required-lisp-eval-depth)) |
| 577 |
|
| 578 |
(provide 'rails) |