Subject: Re: bug#60110: 29.0.60; ruby-mode method parameter alignment

From: Dmitry Gutov <[email-masked]>
Date: Mon, 19 Dec 2022 00:32:01 +0200

Hi Aaron,

Thanks for the report.

On 16/12/2022 07:04, Aaron Jensen wrote:
> 
> Currently, ruby-mode smie indents method parameters like this:
> 
> def some_method(
>      some_arg,
>      some_other_arg
>    )
> end
> 
> def self.some_method(
>        some_arg,
>        some_other_arg
>      )
> end

I'm assuming the difference in indentation between these posted cases is 
just an email/bug tracker bug. If they are sometimes different, please 
let me know. That would be another bug.

> I would prefer to be able to indent them like this:
> 
> def some_method(
>    some_arg,
>    some_other_arg
> )
> end
> 
> def self.some_method(
>    some_arg,
>    some_other_arg
> )
> end

See the attached patch. (setq ruby-method-params-indent 0) should do it.

> Rubocop has two rules that govern this behavior:
> 
> Layout/FirstParameterIndentation: consistent
> https://docs.rubocop.org/rubocop/1.40/cops_layout.html#layoutfirstparameterindentation
> 
> Layout/ParameterAlignment: with_first_parameter and
> with_fixed_indentation would both work. I don't have a strong opinion on
> this, because I usually break after a ( if I am going to put parameters
> on their own line. Leaving the first one on the same line as the method
> name is not as scannable.
> https://docs.rubocop.org/rubocop/1.40/cops_layout.html#layoutparameteralignment

All right. It doesn't seem to support many styles, only indenting the 
first param against the opening paren, and against the beginning of 
"def". But it confirmed my idea for the option name (a generic one).

diff --git a/etc/NEWS b/etc/NEWS
index cded60cca63..fdf01592e10 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2761,6 +2761,9 @@ project-dedicated or global) is specified by the new
 ---
 *** Support for endless methods.
 
+---
+*** New user option 'ruby-method-params-indent'.
+
 ** Eshell
 
 +++
diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el
index ed6044280ea..d4e6a25e5b3 100644
--- a/lisp/progmodes/ruby-mode.el
+++ b/lisp/progmodes/ruby-mode.el
@@ -267,6 +267,24 @@ ruby-align-chained-calls
   :safe 'booleanp
   :version "24.4")
 
+(defcustom ruby-method-params-indent t
+  "Indentation style of multiline method parameters.
+
+When t, the whole list with parentheses is indented against the
+method name.
+
+When a number, indent the parameters this many columns against
+the beginning of the method (the \"def\" keyword).
+
+The value nil means the same as 0.
+
+Only has effect when `ruby-use-smie' is t."
+  :type '(choice (const :tag "Indent against method name" t)
+                 (number :tag "Indent specified number of columns against def")
+                 (const :tag "Indent to def" nil))
+  :safe (lambda (val) (or (memq val '(t nil)) (numberp val)))
+  :version 29.1)
+
 (defcustom ruby-deep-arglist t
   "Deep indent lists in parenthesis when non-nil.
 Also ignores spaces after parenthesis when `space'.
@@ -659,9 +677,12 @@ ruby-smie-rules
        (unless (or (eolp) (forward-comment 1))
          (cons 'column (current-column)))))
     ('(:before . " @ ")
-     (save-excursion
-       (skip-chars-forward " \t")
-       (cons 'column (current-column))))
+     (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))
     ('(:before . ".")
      (if (smie-rule-sibling-p)
diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby-method-params-indent.rb b/test/lisp/progmodes/ruby-mode-resources/ruby-method-params-indent.rb
new file mode 100644
index 00000000000..2b665797397
--- /dev/null
+++ b/test/lisp/progmodes/ruby-mode-resources/ruby-method-params-indent.rb
@@ -0,0 +1,18 @@
+class C
+  def self.foo(
+    baz,
+    bar
+  ) =
+    what
+
+  def foo=(
+    baz,
+    bar
+  )
+    hello
+  end
+end
+
+# Local Variables:
+# ruby-method-params-indent: 0
+# End:
diff --git a/test/lisp/progmodes/ruby-mode-resources/ruby.rb b/test/lisp/progmodes/ruby-mode-resources/ruby.rb
index 5636a4fc0e2..61b75c0c7f2 100644
--- a/test/lisp/progmodes/ruby-mode-resources/ruby.rb
+++ b/test/lisp/progmodes/ruby-mode-resources/ruby.rb
@@ -536,3 +536,7 @@ def foo=(
     hello
   end
 end
+
+# Local Variables:
+# ruby-method-params-indent: t
+# End:
diff --git a/test/lisp/progmodes/ruby-mode-tests.el b/test/lisp/progmodes/ruby-mode-tests.el
index 9be01dc78f9..ad9fc3dad4d 100644
--- a/test/lisp/progmodes/ruby-mode-tests.el
+++ b/test/lisp/progmodes/ruby-mode-tests.el
@@ -943,7 +943,7 @@ ruby-imenu-with-private-modifier
                      "Blub#bye"
                      "Blub#hiding")))))
 
-(ert-deftest ruby--indent/converted-from-manual-test ()
+(ert-deftest ruby--indent/run-manual-test ()
   :tags '(:expensive-test)
   ;; Converted from manual test.
   (let ((buf (find-file-noselect (ert-resource-file "ruby.rb"))))
@@ -954,6 +954,17 @@ ruby--indent/converted-from-manual-test
             (should (equal (buffer-string) orig))))
       (kill-buffer buf))))
 
+(ert-deftest ruby--indent/run-manual-test-method-params-indent ()
+  :tags '(:expensive-test)
+  ;; Converted from manual test.
+  (let ((buf (find-file-noselect (ert-resource-file "ruby-method-params-indent.rb"))))
+    (unwind-protect
+        (with-current-buffer buf
+          (let ((orig (buffer-string)))
+            (indent-region (point-min) (point-max))
+            (should (equal (buffer-string) orig))))
+      (kill-buffer buf))))
+
 (ert-deftest ruby--test-chained-indentation ()
   (with-temp-buffer
     (ruby-mode)