* lisp/progmodes/ruby-mode.el: First cut at SMIE support.

(ruby-use-smie): New var.
(ruby-smie-grammar): New constant.
(ruby-smie--bosp, ruby-smie--implicit-semi-p)
(ruby-smie--forward-token, ruby-smie--backward-token)
(ruby-smie-rules): New functions.
(ruby-mode-variables): Setup SMIE if applicable.
* test/indent/ruby.rb: Fix indentation after =; add more cases.
This commit is contained in:
Stefan Monnier 2013-05-08 16:25:57 -04:00
parent 060ca4088d
commit a9e4425bc9
4 changed files with 165 additions and 5 deletions

View file

@ -1,3 +1,13 @@
2013-05-08 Stefan Monnier <monnier@iro.umontreal.ca>
* progmodes/ruby-mode.el: First cut at SMIE support.
(ruby-use-smie): New var.
(ruby-smie-grammar): New constant.
(ruby-smie--bosp, ruby-smie--implicit-semi-p)
(ruby-smie--forward-token, ruby-smie--backward-token)
(ruby-smie-rules): New functions.
(ruby-mode-variables): Setup SMIE if applicable.
2013-05-08 Eli Zaretskii <eliz@gnu.org>
* simple.el (line-move-visual): Signal beginning/end of buffer

View file

@ -148,13 +148,16 @@ This should only be called after matching against `ruby-here-doc-beg-re'."
(define-abbrev-table 'ruby-mode-abbrev-table ()
"Abbrev table in use in Ruby mode buffers.")
(defvar ruby-use-smie nil)
(defvar ruby-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-C-b") 'ruby-backward-sexp)
(define-key map (kbd "M-C-f") 'ruby-forward-sexp)
(unless ruby-use-smie
(define-key map (kbd "M-C-b") 'ruby-backward-sexp)
(define-key map (kbd "M-C-f") 'ruby-forward-sexp)
(define-key map (kbd "M-C-q") 'ruby-indent-exp))
(define-key map (kbd "M-C-p") 'ruby-beginning-of-block)
(define-key map (kbd "M-C-n") 'ruby-end-of-block)
(define-key map (kbd "M-C-q") 'ruby-indent-exp)
(define-key map (kbd "C-c {") 'ruby-toggle-block)
map)
"Keymap used in Ruby mode.")
@ -236,6 +239,111 @@ Also ignores spaces after parenthesis when 'space."
(put 'ruby-comment-column 'safe-local-variable 'integerp)
(put 'ruby-deep-arglist 'safe-local-variable 'booleanp)
;;; SMIE support
(require 'smie)
(defconst ruby-smie-grammar
;; FIXME: Add support for Cucumber.
(smie-prec2->grammar
(smie-bnf->prec2
'((id)
(insts (inst) (insts ";" insts))
(inst (exp) (inst "iuwu-mod" exp))
(exp (exp1) (exp "," exp))
(exp1 (exp2) (exp2 "?" exp1 ":" exp1))
(exp2 ("def" insts "end")
("begin" insts-rescue-insts "end")
("do" insts "end")
("class" insts "end") ("module" insts "end")
("for" for-body "end")
("[" expseq "]")
("{" hashvals "}")
("while" insts "end")
("until" insts "end")
("unless" insts "end")
("if" if-body "end")
("case" cases "end"))
(for-body (for-head ";" insts))
(for-head (id "in" exp))
(cases (exp "then" insts) ;; FIXME: Ruby also allows (exp ":" insts).
(cases "when" cases) (insts "else" insts))
(expseq (exp) );;(expseq "," expseq)
(hashvals (id "=>" exp1) (hashvals "," hashvals))
(insts-rescue-insts (insts)
(insts-rescue-insts "rescue" insts-rescue-insts)
(insts-rescue-insts "ensure" insts-rescue-insts))
(itheni (insts) (exp "then" insts))
(ielsei (itheni) (itheni "else" insts))
(if-body (ielsei) (if-body "elsif" if-body)))
'((nonassoc "in") (assoc ";") (assoc ","))
'((assoc "when"))
'((assoc "elsif"))
'((assoc "rescue" "ensure"))
'((assoc ",")))))
(defun ruby-smie--bosp ()
(save-excursion (skip-chars-backward " \t")
(or (bolp) (eq (char-before) ?\;))))
(defun ruby-smie--implicit-semi-p ()
(save-excursion
(skip-chars-backward " \t")
(not (or (bolp)
(memq (char-before) '(?\; ?- ?+ ?* ?/ ?:))
(and (memq (char-before) '(?\? ?=))
(not (memq (char-syntax (char-before (1- (point))))
'(?w ?_))))))))
(defun ruby-smie--forward-token ()
(skip-chars-forward " \t")
(if (and (looking-at "[\n#]")
;; Only add implicit ; when needed.
(ruby-smie--implicit-semi-p))
(progn
(if (eolp) (forward-char 1) (forward-comment 1))
";")
(forward-comment (point-max))
(let ((tok (smie-default-forward-token)))
(cond
((member tok '("unless" "if" "while" "until"))
(if (save-excursion (forward-word -1) (ruby-smie--bosp))
tok "iuwu-mod"))
(t tok)))))
(defun ruby-smie--backward-token ()
(let ((pos (point)))
(forward-comment (- (point)))
(if (and (> pos (line-end-position))
(ruby-smie--implicit-semi-p))
(progn (skip-chars-forward " \t")
";")
(let ((tok (smie-default-backward-token)))
(cond
((member tok '("unless" "if" "while" "until"))
(if (ruby-smie--bosp)
tok "iuwu-mod"))
(t tok))))))
(defun ruby-smie-rules (kind token)
(pcase (cons kind token)
(`(:elem . basic) ruby-indent-level)
(`(:after . ";")
(if (smie-rule-parent-p "def" "begin" "do" "class" "module" "for"
"[" "{" "while" "until" "unless"
"if" "then" "elsif" "else" "when"
"rescue" "ensure")
(smie-rule-parent ruby-indent-level)
;; For (invalid) code between switch and case.
;; (if (smie-parent-p "switch") 4)
0))
(`(:before . ,(or `"else" `"then" `"elsif")) 0)
(`(:before . ,(or `"when"))
(if (not (smie-rule-sibling-p)) 0)) ;; ruby-indent-level
;; Hack attack: Since newlines are separators, don't try to align args that
;; appear on a separate line.
(`(:list-intro . ";") t)))
(defun ruby-imenu-create-index-in-block (prefix beg end)
"Create an imenu index of methods inside a block."
(let ((index-alist '()) (case-fold-search nil)
@ -290,7 +398,11 @@ Also ignores spaces after parenthesis when 'space."
(set-syntax-table ruby-mode-syntax-table)
(setq local-abbrev-table ruby-mode-abbrev-table)
(setq indent-tabs-mode ruby-indent-tabs-mode)
(set (make-local-variable 'indent-line-function) 'ruby-indent-line)
(if ruby-use-smie
(smie-setup ruby-smie-grammar #'ruby-smie-rules
:forward-token #'ruby-smie--forward-token
:backward-token #'ruby-smie--backward-token)
(set (make-local-variable 'indent-line-function) 'ruby-indent-line))
(set (make-local-variable 'require-final-newline) t)
(set (make-local-variable 'comment-start) "# ")
(set (make-local-variable 'comment-end) "")

View file

@ -1,3 +1,7 @@
2013-05-08 Stefan Monnier <monnier@iro.umontreal.ca>
* indent/ruby.rb: Fix indentation after =; add more cases.
2013-05-05 Stefan Monnier <monnier@iro.umontreal.ca>
* indent/pascal.pas: Add test for mis-identified comments.

View file

@ -10,7 +10,7 @@
# Or inside comments.
x = # "tot %q/to"; =
y = 2 / 3
y = 2 / 3
# Regexp after whitelisted method.
"abc".sub /b/, 'd'
@ -21,6 +21,40 @@
# Highlight the regexp after "if".
x = toto / foo if /do bar/ =~ "dobar"
def test1(arg)
puts "hello"
end
def test2 (arg)
a = "apple"
if a == 2
puts "hello"
else
puts "there"
end
if a == 2 then
puts "hello"
elsif a == 3
puts "hello3"
elsif a == 3 then
puts "hello3"
else
puts "there"
end
case a
when "a"
6
# when "b" :
# 7
# when "c" : 2
when "d" then 4
else 5
end
end
# Some Cucumber code:
Given /toto/ do
print "hello"