From a9e4425bc99733b6ecf3898801b6595f35c4ef30 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Wed, 8 May 2013 16:25:57 -0400 Subject: [PATCH] * 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. --- lisp/ChangeLog | 10 +++ lisp/progmodes/ruby-mode.el | 120 ++++++++++++++++++++++++++++++++++-- test/ChangeLog | 4 ++ test/indent/ruby.rb | 36 ++++++++++- 4 files changed, 165 insertions(+), 5 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 1abcc933847..12e2007c126 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,13 @@ +2013-05-08 Stefan Monnier + + * 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 * simple.el (line-move-visual): Signal beginning/end of buffer diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el index 631badac34c..3ea55c4eabf 100644 --- a/lisp/progmodes/ruby-mode.el +++ b/lisp/progmodes/ruby-mode.el @@ -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) "") diff --git a/test/ChangeLog b/test/ChangeLog index 48d499a9fa4..c11d5d26c13 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,7 @@ +2013-05-08 Stefan Monnier + + * indent/ruby.rb: Fix indentation after =; add more cases. + 2013-05-05 Stefan Monnier * indent/pascal.pas: Add test for mis-identified comments. diff --git a/test/indent/ruby.rb b/test/indent/ruby.rb index 4f2e9e63377..90c6dcdc65c 100644 --- a/test/indent/ruby.rb +++ b/test/indent/ruby.rb @@ -10,7 +10,7 @@ d = %(hello (nested) world) # 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 @@ a = asub / aslb + bsub / bslb; # 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" -- 2.39.2