diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el index a4aa61905e4..021dda1a3e1 100644 --- a/lisp/progmodes/ruby-mode.el +++ b/lisp/progmodes/ruby-mode.el @@ -285,6 +285,92 @@ ruby-method-params-indent :safe (lambda (val) (or (memq val '(t nil)) (numberp val))) :version "29.1") +(defcustom ruby-block-indent t + "Non-nil to align the body of a block to the statement's start. + +The body and the closer will be aligned to the column where the +statement containing the block starts. Example: + + foo.bar + .each do + baz + end + +If nil, it will be aligned instead to the beginning of the line +containing the block's opener: + + foo.bar + .each do + baz + end + +Only has effect when `ruby-use-smie' is t." + :type 'boolean + :safe 'booleanp) + +(defcustom ruby-after-operator-indent t + "Non-nil to use structural indentation after binary operators. + +The code will be aligned to the implicit parent expression, +according to the operator precedence: + + qux = 4 + 5 * + 6 + + 7 + +Set it to nil to align to the beginning of the statement: + + qux = 4 + 5 * + 6 + + 7 + +Only has effect when `ruby-use-smie' is t." + :type 'boolean + :safe 'booleanp) + +(defcustom ruby-method-call-indent t + "Non-nil to use the structural indentation algorithm. + +The method call will be aligned to the implicit parent +expression, according to the operator precedence: + + foo = subject + .update( + 1 + ) + +Set it to nil to align to the beginning of the statement: + + foo = subject + .update( + 1 + ) + +Only has effect when `ruby-use-smie' is t." + :type 'boolean + :safe 'booleanp) + +(defcustom ruby-parenless-call-arguments-indent t + "Non-nil to align arguments in a parenless call vertically. + +Example: + + qux :+, + bar, + :[]=, + bar + +Set it to nil to align to the beginning of the statement: + + qux :+, + bar, + :[]=, + bar + +Only has effect when `ruby-use-smie' is t." + :type 'boolean + :safe 'booleanp) + (defcustom ruby-deep-arglist t "Deep indent lists in parenthesis when non-nil. Also ignores spaces after parenthesis when `space'. @@ -416,6 +502,7 @@ ruby-smie-grammar '((right "=") (right "+=" "-=" "*=" "/=" "%=" "**=" "&=" "|=" "^=" "<<=" ">>=" "&&=" "||=") + (right "?") (nonassoc ".." "...") (left "&&" "||") (nonassoc "<=>") @@ -608,10 +695,10 @@ ruby-smie--backward-token "def=") (t tok))))))) -(defun ruby-smie--indent-to-stmt () +(defun ruby-smie--indent-to-stmt (&optional offset) (save-excursion (smie-backward-sexp ";") - (cons 'column (smie-indent-virtual)))) + (cons 'column (+ (smie-indent-virtual) (or offset 0))))) (defun ruby-smie--indent-to-stmt-p (keyword) (or (eq t ruby-align-to-stmt-keywords) @@ -642,7 +729,9 @@ ruby-smie-rules (forward-comment -1) (not (eq (preceding-char) ?:)))) ;; Curly block opener. - (ruby-smie--indent-to-stmt)) + (if ruby-block-indent + (ruby-smie--indent-to-stmt) + (cons 'column (current-indentation)))) ((smie-rule-hanging-p) ;; Treat purely syntactic block-constructs as being part of their parent, ;; when the opening token is hanging and the parent is not an @@ -677,13 +766,20 @@ ruby-smie-rules (unless (or (eolp) (forward-comment 1)) (cons 'column (current-column))))) ('(:before . " @ ") - (if (or (eq ruby-method-params-indent t) - (not (smie-rule-parent-p "def" "def="))) - (save-excursion - (skip-chars-forward " \t") - (cons 'column (current-column))) - (smie-rule-parent (or ruby-method-params-indent 0)))) - ('(:before . "do") (ruby-smie--indent-to-stmt)) + (cond + ((and (not ruby-parenless-call-arguments-indent) + (not (smie-rule-parent-p "def" "def="))) + (ruby-smie--indent-to-stmt ruby-indent-level)) + ((or (eq ruby-method-params-indent t) + (not (smie-rule-parent-p "def" "def="))) + (save-excursion + (skip-chars-forward " \t") + (cons 'column (current-column)))) + (t (smie-rule-parent (or ruby-method-params-indent 0))))) + ('(:before . "do") + (if ruby-block-indent + (ruby-smie--indent-to-stmt) + (cons 'column (current-indentation)))) ('(:before . ".") (if (smie-rule-sibling-p) (when ruby-align-chained-calls @@ -696,8 +792,10 @@ ruby-smie-rules (not (smie-rule-bolp))))) (cons 'column (current-column))) (smie-backward-sexp ".") - (cons 'column (+ (current-column) - ruby-indent-level)))) + (if ruby-method-call-indent + (cons 'column (+ (current-column) + ruby-indent-level)) + (ruby-smie--indent-to-stmt ruby-indent-level)))) (`(:before . ,(or "else" "then" "elsif" "rescue" "ensure")) (smie-rule-parent)) (`(:before . ,(or "when" "in")) @@ -708,16 +806,22 @@ ruby-smie-rules "<=>" ">" "<" ">=" "<=" "==" "===" "!=" "<<" ">>" "+=" "-=" "*=" "/=" "%=" "**=" "&=" "|=" "^=" "|" "<<=" ">>=" "&&=" "||=" "and" "or")) - (and (smie-rule-parent-p ";" nil) - (smie-indent--hanging-p) - ruby-indent-level)) + (cond + ((not ruby-after-operator-indent) + (ruby-smie--indent-to-stmt ruby-indent-level)) + ((and (smie-rule-parent-p ";" nil) + (smie-indent--hanging-p)) + ruby-indent-level))) (`(:before . "=") (save-excursion (and (smie-rule-parent-p " @ ") (goto-char (nth 1 (smie-indent--parent))) (smie-rule-prev-p "def=") (cons 'column (+ (current-column) ruby-indent-level -3))))) - (`(:after . ,(or "?" ":")) ruby-indent-level) + (`(:after . ,(or "?" ":")) + (if ruby-after-operator-indent + ruby-indent-level + (ruby-smie--indent-to-stmt ruby-indent-level))) (`(:before . ,(guard (memq (intern-soft token) ruby-alignable-keywords))) (when (not (ruby--at-indentation-p)) (if (ruby-smie--indent-to-stmt-p token) @@ -725,7 +829,10 @@ ruby-smie-rules (cons 'column (current-column))))) ('(:before . "iuwu-mod") (smie-rule-parent ruby-indent-level)) - )) + (`(:before . ",") + (and (not ruby-parenless-call-arguments-indent) + (smie-rule-parent-p " @ ") + (ruby-smie--indent-to-stmt ruby-indent-level))))) (defun ruby--at-indentation-p (&optional point) (save-excursion diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby-after-operator-indent.rb b/test/lisp/progmodes/ruby-mode-resources/ruby-after-operator-indent.rb new file mode 100644 index 00000000000..25cd8736f97 --- /dev/null +++ b/test/lisp/progmodes/ruby-mode-resources/ruby-after-operator-indent.rb @@ -0,0 +1,29 @@ +4 + + 5 + + 6 + + 7 + +qux = 4 + 5 * + 6 + + 7 + +foo = obj.bar { |m| tee(m) } + + obj.qux { |m| hum(m) } + +foo. + bar + .baz + +qux = foo.fee ? + bar : + tee + +# Endless methods. +class Bar + def foo(abc) = bar + + baz +end + +# Local Variables: +# ruby-after-operator-indent: nil +# End: diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby-block-indent.rb b/test/lisp/progmodes/ruby-mode-resources/ruby-block-indent.rb new file mode 100644 index 00000000000..32882814b7e --- /dev/null +++ b/test/lisp/progmodes/ruby-mode-resources/ruby-block-indent.rb @@ -0,0 +1,33 @@ +foo + .asdasd + .proc do |**args| + p(**args) + end + +foo + .asdasd + .proc { |**args| + p(**args) + } + +bar.foo do + bar +end + +bar.foo(tee) do + bar +end + +bar.foo(tee) { + bar +} + +x.foo do + foo +end.bar do + bar +end + +# Local Variables: +# ruby-block-indent: nil +# End: diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby-method-call-indent.rb b/test/lisp/progmodes/ruby-mode-resources/ruby-method-call-indent.rb new file mode 100644 index 00000000000..1a8285ee919 --- /dev/null +++ b/test/lisp/progmodes/ruby-mode-resources/ruby-method-call-indent.rb @@ -0,0 +1,15 @@ +foo2 = + subject. + update( + 2 + ) + +foo3 = + subject + .update( + 2 + ) + +# Local Variables: +# ruby-method-call-indent: nil +# End: diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby-parenless-call-arguments-indent.rb b/test/lisp/progmodes/ruby-mode-resources/ruby-parenless-call-arguments-indent.rb new file mode 100644 index 00000000000..58e08810c4c --- /dev/null +++ b/test/lisp/progmodes/ruby-mode-resources/ruby-parenless-call-arguments-indent.rb @@ -0,0 +1,23 @@ +method arg1, + method2 arg2, + arg3, [ + arg4, + arg5 + ] + +zzz = method (a + b), + c, :d => :e, + f: g + +return render json: { + errors: { base: [message] }, + copying: copying + }, + status: 400 + +foo(a, + b) + +# Local Variables: +# ruby-parenless-call-arguments-indent: nil +# End: diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby.rb b/test/lisp/progmodes/ruby-mode-resources/ruby.rb index 6a69d9db78a..bfae948b259 100644 --- a/test/lisp/progmodes/ruby-mode-resources/ruby.rb +++ b/test/lisp/progmodes/ruby-mode-resources/ruby.rb @@ -226,6 +226,7 @@ def begin foo. bar + .baz # https://github.com/rails/rails/blob/17f5d8e062909f1fcae25351834d8e89967b645e/activesupport/lib/active_support/time_with_zone.rb#L206 foo # comment intended to confuse the tokenizer @@ -380,6 +381,18 @@ def bar i + 1 end +m1 = foo + .asdasd + .proc do |**args| + p(**args) +end + +m2 = foo + .asdasd + .proc { |**args| + p(**args) +} + bar.foo do bar end @@ -398,6 +411,12 @@ def bar end end +x.foo do + foo +end.bar do + bar +end + foo | bar @@ -540,5 +559,9 @@ def baz.full_name = "#{bar} 3" end # Local Variables: +# ruby-after-operator-indent: t +# ruby-block-indent: t +# ruby-method-call-indent: t # ruby-method-params-indent: t +# ruby-parenless-call-arguments-indent: t # End: diff --git a/test/lisp/progmodes/ruby-mode-tests.el b/test/lisp/progmodes/ruby-mode-tests.el index 560f780285a..5c81cc31cc1 100644 --- a/test/lisp/progmodes/ruby-mode-tests.el +++ b/test/lisp/progmodes/ruby-mode-tests.el @@ -956,7 +956,11 @@ ruby-deftest-indent (kill-buffer buf))))) (ruby-deftest-indent "ruby.rb") +(ruby-deftest-indent "ruby-after-operator-indent.rb") +(ruby-deftest-indent "ruby-block-indent.rb") +(ruby-deftest-indent "ruby-method-call-indent.rb") (ruby-deftest-indent "ruby-method-params-indent.rb") +(ruby-deftest-indent "ruby-parenless-call-arguments-indent.rb") (ert-deftest ruby--test-chained-indentation () (with-temp-buffer