summaryrefslogtreecommitdiff
path: root/sample/ruby-mode.el
blob: 96915bdc5a76fef004ba1a4209f1ba13e4aba504 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
;;;
;;;  ruby-mode.el -
;;;
;;;  $Author$
;;;  $Revision$
;;;  $Date$
;;;  created at: Fri Feb  4 14:49:13 JST 1994
;;;

(defconst ruby-block-beg-re
  "class\\|module\\|def\\|if\\|case\\|while\\|do\\|for\\|protect"
  )

(defconst ruby-block-mid-re
  "else\\|elsif\\|when\\|using\\|resque\\|ensure"
  )

(defconst ruby-block-end-re
  (concat "\\(end\\([ \t]+\\(" ruby-block-beg-re "\\)\\)?\\)")
  )

(defconst ruby-delimiter
  (concat "(\\|)\\|\\{\\|\\}\\|\"\\|\'\\|\\b\\(" ruby-block-beg-re "\\|" ruby-block-end-re "\\)\\b\\|#")
  )
(defconst ruby-negative
  (concat "^[ \t]*\\b\\(\\(" ruby-block-mid-re "\\)\\|\\(" ruby-block-end-re "\\)\\)\\b")
  )

(defvar ruby-mode-abbrev-table nil
  "Abbrev table in use in ruby-mode buffers.")

(define-abbrev-table 'ruby-mode-abbrev-table ())

(defvar ruby-mode-map nil "Keymap used in ruby mode.")

(if ruby-mode-map
    nil
  (setq ruby-mode-map (make-sparse-keymap))
  (define-key ruby-mode-map "\e\C-a" 'ruby-beginning-of-defun)
  (define-key ruby-mode-map "\e\C-e" 'ruby-end-of-defun)
  (define-key ruby-mode-map "\t" 'ruby-indent-command)
  (define-key ruby-mode-map "\t" 'ruby-indent-command)
  (define-key ruby-mode-map "\C-m" 'ruby-reindent-then-newline-and-indent)
  (define-key ruby-mode-map "\C-j" 'newline))

(defvar ruby-mode-syntax-table nil
  "Syntax table in use in ruby-mode buffers.")

(if ruby-mode-syntax-table
    ()
  (setq ruby-mode-syntax-table (make-syntax-table))
  (modify-syntax-entry ?\' "\"" ruby-mode-syntax-table)
  (modify-syntax-entry ?\" "\"" ruby-mode-syntax-table)
  (modify-syntax-entry ?\n ">   " ruby-mode-syntax-table)
  (modify-syntax-entry ?\f ">   " ruby-mode-syntax-table)
  (modify-syntax-entry ?# "<   " ruby-mode-syntax-table)
  (modify-syntax-entry ?_ "w" ruby-mode-syntax-table)
  (modify-syntax-entry ?< "." ruby-mode-syntax-table)
  (modify-syntax-entry ?> "." ruby-mode-syntax-table)
  (modify-syntax-entry ?& "." ruby-mode-syntax-table)
  (modify-syntax-entry ?| "." ruby-mode-syntax-table)
  (modify-syntax-entry ?$ "." ruby-mode-syntax-table)
  (modify-syntax-entry ?% "." ruby-mode-syntax-table)
  (modify-syntax-entry ?= "." ruby-mode-syntax-table)
  (modify-syntax-entry ?/ "." ruby-mode-syntax-table)
  (modify-syntax-entry ?+ "." ruby-mode-syntax-table)
  (modify-syntax-entry ?* "." ruby-mode-syntax-table)
  (modify-syntax-entry ?- "." ruby-mode-syntax-table)
  (modify-syntax-entry ?\; "." ruby-mode-syntax-table)
  (modify-syntax-entry ?\( "()" ruby-mode-syntax-table)
  (modify-syntax-entry ?) ")(" ruby-mode-syntax-table)
  (modify-syntax-entry ?{ "(}" ruby-mode-syntax-table)
  (modify-syntax-entry ?} "){" ruby-mode-syntax-table)
  (modify-syntax-entry ?\[ "(]" ruby-mode-syntax-table)
  (modify-syntax-entry ?\] ")[" ruby-mode-syntax-table)
  )

(defvar ruby-indent-level 2
  "*Indentation of ruby statements.")

(defun ruby-mode-variables ()
  (setq local-abbrev-table ruby-mode-abbrev-table)
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'ruby-indent-line)
  (make-local-variable 'require-final-newline)
  (setq require-final-newline t)
  (make-variable-buffer-local 'comment-start)
  (setq comment-start "# ")
  (make-variable-buffer-local 'comment-end)
  (setq comment-end "")
  (make-variable-buffer-local 'comment-column)
  (setq comment-column 32)
  (make-variable-buffer-local 'comment-start-skip)
  (setq comment-start-skip "#+ *")
  (make-local-variable 'parse-sexp-ignore-comments)
  (setq parse-sexp-ignore-comments t))

(defun ruby-mode ()
  "Major mode for editing ruby scripts.
\\[ruby-indent-command] properly indents subexpressions of multi-line
class, module, def, if, while, for, do, and case statements, taking
nesting into account.

The variable ruby-indent-level controls the amount of indentation.
\\{ruby-mode-map}"
  (interactive)
  (kill-all-local-variables)
  (use-local-map ruby-mode-map)
  (setq mode-name "ruby")
  (setq major-mode 'ruby-mode)
  (set-syntax-table ruby-mode-syntax-table)
  (ruby-mode-variables)
  (run-hooks 'ruby-mode-hook))

(defun ruby-current-indentation ()
  (save-excursion
    (beginning-of-line)
    (back-to-indentation)
    (current-column)))

(defun ruby-delete-indentation ()
  (let
      ((b nil)
       (m nil))
    (save-excursion
      (beginning-of-line)
      (setq b (point))
      (back-to-indentation)
      (setq m (point)))
    (delete-region b m)))

(defun ruby-indent-line (&optional flag)
  "Correct indentation of the current ruby line."
  (let
      ((x (ruby-calculate-indent)))
    (ruby-indent-to x)))
  
(defun ruby-indent-command ()
  (interactive)
  (ruby-indent-line t))

(defun ruby-indent-to (x)
  (let ((p nil) beg end)
    (if (null x)
	nil
      (setq p (- (current-column) (ruby-current-indentation)))
      (ruby-delete-indentation)
      (beginning-of-line)
      (save-excursion
	(setq beg (point))
	(forward-line 1)
	(setq end (point)))
      (indent-to x)
      (if (> p 0) (forward-char p)))))

(defun ruby-parse-region (start end)
  (let ((indent-point end)
	(indent 0)
	(in-string nil)
	(in-paren nil)
	(depth 0)
	(nest nil))
    (save-excursion
      (if start
	  (goto-char start)
	(ruby-beginning-of-defun))
      (while (and (> indent-point (point))
		  (re-search-forward ruby-delimiter indent-point t))
	(let ((w (buffer-substring (match-beginning 0) (match-end 0))))
	  (cond
	   ((or (string= "\"" w)	;skip string
		(string= "\'" w))
	    (if (search-forward w indent-point t)
		nil
	      (goto-char indent-point)
	      (setq in-string t)))
	   ((string= "#" w)		;skip comment
	    (forward-line 1))
	   ((string= "(" w)		;skip to matching paren
	    (let ((orig depth))
	      (setq nest (cons (point) nest))
	      (setq depth (1+ depth))
	      (while (and (/= depth orig)
			  (re-search-forward "[()]" indent-point t))
		(cond
		 ((= (char-after (match-beginning 0)) ?\( )
		  (setq nest (cons (point) nest))
		  (setq depth (1+ depth)))
		 (t
		  (setq nest (cdr nest))
		  (setq depth (1- depth)))))
	      (if (> depth orig) (setq in-paren t))))
	   ((string= "{" w)		;skip to matching paren
	    (let ((orig depth))
	      (setq nest (cons (point) nest))
	      (setq depth (1+ depth))
	      (while (and (/= depth orig)
			  (re-search-forward "[{}]" indent-point t))
		(cond
		 ((= (char-after (match-beginning 0)) ?\{ )
		  (setq nest (cons (point) nest))
		  (setq depth (1+ depth)))
		 (t
		  (setq nest (cdr nest))
		  (setq depth (1- depth)))))
	      (if (> depth orig) (setq in-paren t))))
	   ((string-match "^end" w)
	    (setq nest (cdr nest))
	    (setq depth (1- depth)))
	   ((string-match ruby-block-beg-re w)
	    (setq nest (cons (point) nest))
	    (setq depth (1+ depth)))
	   (t
	    (error (format "bad string %s" w)))))))
    (if in-paren (message "in-paren"))
    (list in-string in-paren (car nest) depth)))

(defun ruby-calculate-indent (&optional parse-start)
  (save-excursion
    (beginning-of-line)
    (let ((indent-point (point))
	  (case-fold-search nil)
	  state eol
	  (indent 0))
      (if parse-start
	  (goto-char parse-start)
	(beginning-of-defun)
	(setq parse-start (point)))
      (setq state (ruby-parse-region parse-start indent-point))
      (cond
       ((nth 0 state)			; within string
	(setq indent nil))		;  do nothing

       ((nth 1 state)			; in paren
	(goto-char (nth 2 state))
	(setq indent
	      (if (looking-at "$")
		  (+ (current-indentation) ruby-indent-level)
		(current-column))))

       ((> (nth 3 state) 0)		; in nest
	(goto-char (nth 2 state))
	(forward-word -1)		; skip back a keyword
	(setq indent (+ (current-column) ruby-indent-level)))

       (t				; toplevel
	(setq indent 0)))
      (goto-char indent-point)
      (end-of-line)
      (setq eol (point))
      (beginning-of-line)
      (if (re-search-forward ruby-negative eol t)
	  (setq indent (- indent ruby-indent-level)))
      indent)))

(defun ruby-beginning-of-defun (&optional arg)
    "Move backward to next beginning-of-defun.
With argument, do this that many times.
Returns t unless search stops due to end of buffer."
  (interactive "p")
  (and (re-search-backward (concat "^\\(" ruby-block-beg-re "\\)")
			   nil 'move (or arg 1))
       (progn (beginning-of-line) t)))

(defun ruby-end-of-defun (&optional arg)
  "Move forward to next end of defun.
An end of a defun is found by moving forward from the beginning of one."
  (interactive "p")
  (and (re-search-forward (concat "^\\(" ruby-block-end-re "\\)")
			  nil 'move (or arg 1))
       (progn (beginning-of-line) t))
  (forward-line 1))

(defun ruby-reindent-then-newline-and-indent ()
  (interactive "*")
  (save-excursion
    (delete-region (point) (progn (skip-chars-backward " \t") (point))))
  (insert ?\n)
  (save-excursion
    (forward-line -1)
    (indent-according-to-mode))
  (indent-according-to-mode))

(defun ruby-encomment-region (beg end)
  (interactive "r")
  (save-excursion
    (goto-char beg)
    (while (re-search-forward "^" end t)
      (replace-match "#" nil nil))))

(defun ruby-decomment-region (beg end)
  (interactive "r")
  (save-excursion
    (goto-char beg)
    (while (re-search-forward "^\\([ \t]*\\)#" end t)
      (replace-match "\\1" nil nil))))