emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/go-mode 592bd4b 440/495: indent: fix various cases with da


From: ELPA Syncer
Subject: [nongnu] elpa/go-mode 592bd4b 440/495: indent: fix various cases with dangling operators
Date: Sat, 7 Aug 2021 09:06:04 -0400 (EDT)

branch: elpa/go-mode
commit 592bd4b50c65861c73c72ae9d883421009eac291
Author: Muir Manders <muir@mnd.rs>
Commit: Peter Sanford <psanford@sanford.io>

    indent: fix various cases with dangling operators
    
    There were many uncommon cases of multiline expressions that we
    weren't indenting correctly. In general, sub-expressions always get
    indented after dangling operators. Sub-expressions can be explicit
    with parens, or implicit if they use operators that bind tighter than
    the dangling operator. And, similarly, when indented sub-expressions
    end, the indent must be reduced for subsequent dangling lines.
    
    Some examples that are now fixed:
    
    good:         bad:
    
    1 |           1 |
      1&            1&
        1           1
    
    good:          bad:
    
    (1 |           (1 |
      (2 |           (2 |
        3)) |          3)) |
      5                5
    
    Cases like these are uncommon, but do occur in the standard library.
    
    The fix involved a significant rewrite of go-indentation-at-point. The
    main conceptual additions are logic to handle closing parens better,
    and logic to indent tightly binding operators on a continuation
    line. Performance was not a goal, but things seem to be faster with
    the changes.
    
    Fixes #234
---
 go-mode.el                                         | 331 +++++++++++++--------
 .../indentation_tests/composite_literal.go         |  21 ++
 .../indentation_tests/dangling_operator.go         | 218 ++++++++++++++
 test/testdata/indentation_tests/function_call.go   |  64 ++++
 test/testdata/indentation_tests/multiline_if.go    |  88 ++++++
 .../testdata/indentation_tests/multiline_struct.go |  22 ++
 .../return_function_call_struct.go                 |   4 +-
 test/testdata/indentation_tests/switch.go          |   5 +-
 8 files changed, 623 insertions(+), 130 deletions(-)

diff --git a/go-mode.el b/go-mode.el
index de361e0..348d986 100644
--- a/go-mode.el
+++ b/go-mode.el
@@ -538,105 +538,30 @@ STOP-AT-STRING is not true, over strings."
 
 (defun go-previous-line-has-dangling-op-p ()
   "Return non-nil if the current line is a continuation line.
-
-The returned value is the beginning of the line with the dangling operator."
+The return value is cached based on the current `line-beginning-position'."
   (let* ((line-begin (line-beginning-position))
          (val (gethash line-begin go-dangling-cache 'nope)))
     (when (or (go--buffer-narrowed-p) (equal val 'nope))
       (save-excursion
-        (beginning-of-line)
-        (go--backward-irrelevant t)
-        (if (or
-             (looking-back go-dangling-operators-regexp
-                           (- (point) go--max-dangling-operator-length))
-             ;; treat comma as dangling operator in certain cases
-             (and (eq (char-before) ?,) (go--commas-indent-p)))
-            (setq val (line-beginning-position))
+        (go--forward-line -1)
+        (if (go--current-line-has-dangling-op-p)
+            (setq val (line-end-position))
           (setq val nil))
 
         (if (not (go--buffer-narrowed-p))
             (puthash line-begin val go-dangling-cache))))
     val))
 
-(defun go--at-function-definition ()
-  "Return non-nil if point is on the opening curly brace of a
-function definition.
+(defun go--current-line-has-dangling-op-p ()
+  "Return non-nil if current line ends in a dangling operator.
+The return value is not cached."
+  (or
+   (go--line-suffix-p go-dangling-operators-regexp)
+   ;; treat comma as dangling operator in certain cases
+   (and
+    (go--line-suffix-p ",")
+    (save-excursion (end-of-line) (go--commas-indent-p)))))
 
-We do this by first calling (beginning-of-defun), which will take
-us to the start of *some* function.  We then look for the opening
-curly brace of that function and compare its position against the
-curly brace we are checking.  If they match, we return non-nil."
-  (if (= (char-after) ?\{)
-      (save-excursion
-        (let ((old-point (point))
-              start-nesting)
-          (beginning-of-defun)
-          (when (looking-at "func ")
-            (setq start-nesting (go-paren-level))
-            (skip-chars-forward "^{")
-            (while (> (go-paren-level) start-nesting)
-              (forward-char)
-              (skip-chars-forward "^{") 0)
-            (if (and (= (go-paren-level) start-nesting) (= old-point (point)))
-                t))))))
-
-(defun go--indentation-for-opening-parenthesis ()
-  "Return the semantic indentation for the current opening parenthesis.
-
-If point is on an opening curly brace and said curly brace
-belongs to a function declaration, the indentation of the func
-keyword will be returned.  Otherwise the indentation of the
-current line will be returned."
-  (save-excursion
-    (if (go--at-function-definition)
-        (progn
-          (beginning-of-defun)
-          (current-indentation))
-      (current-indentation))))
-
-(defun go--dangling-line-opens-indent-p ()
-  "Return non-nil if current dangling line indents the following line."
-  (save-excursion
-    (let ((line-begin (line-beginning-position))
-          (open-paren (go--open-paren-position)))
-
-          (if (not (and open-paren (>= open-paren line-begin)))
-            ;; if line doesn't open a paren, check if we are a dangling line 
under
-            ;; a dangling assignment with nothing on RHS of "="
-            ;;
-            ;; foo :=
-            ;;   bar ||   // check if we are this line (need to open indent)
-            ;;     baz ||
-            ;;     qux
-            (progn
-              (let ((prev-line (go-previous-line-has-dangling-op-p)))
-                (goto-char line-begin)
-                (when (and
-                       prev-line
-                       (not (looking-at ".*,[[:space:]]*$"))) ;; doesn't apply 
to dangling commas
-                  (goto-char prev-line)
-                  (and
-                   (not (go-previous-line-has-dangling-op-p))
-                   (looking-at ".*=[[:space:]]*$")))))
-
-            (goto-char open-paren)
-
-            (or
-             ;; previous line is dangling and opens indent
-             (let ((prev-line (go-previous-line-has-dangling-op-p)))
-               (when prev-line
-                 (save-excursion
-                   (goto-char prev-line)
-                   (end-of-line)
-                   (go--dangling-line-opens-indent-p))))
-
-             ;; or paren is only preceded by identifier or other parens
-             (string-match-p "^[[:space:]]*[[:word:][:multibyte:]]*(*$" 
(buffer-substring line-begin (point)))
-
-             ;; or a preceding paren on this line opens an indent
-             (and
-              (> (point) line-begin)
-              (progn (backward-char) (go--dangling-line-opens-indent-p))))))))
 
 (defun go--commas-indent-p ()
   "Return non-nil if in a context where dangling commas indent next line."
@@ -719,42 +644,198 @@ The return value is the position of the opening paren."
        (point)))))
 
 (defun go-indentation-at-point ()
+  "Return the appropriate indentation for the current line.
+
+This function works by walking a line's characters backwards. When it
+encounters a closing paren or brace it bounces to the corresponding
+opener. If it arrives at the beginning of the line you are indenting,
+it moves to the end of the previous line if the current line is a
+continuation line, else it moves to the containing opening paren or
+brace. If it arrives at the beginning of a line other than the
+starting line, it is done."
   (save-excursion
-    (let (start-nesting dangling-line)
-      (back-to-indentation)
-      (setq start-nesting (go-paren-level))
+    (beginning-of-line)
+
+    (let ((start-line (point))
+          (first t)
+          (indent 0))
+
+      ;; Skip leading whitespace.
+      (skip-syntax-forward " ")
+
+      ;; Move after following char if it is a closer. This is so below code
+      ;; sees it and jumps to the corresponding opener.
+      (skip-syntax-forward ")" (1+ (point)))
+
+      (while (or
+              ;; Always run the first iteration so we process empty lines.
+              first
+
+              ;; Otherwise stop if we are at the start of a line.
+              (not (bolp)))
+        (setq first nil)
+
+        (cl-case (char-before)
+
+          ;; We have found a closer (paren or brace).
+          ((?\) ?})
+           (backward-char)
+           (let ((bol (line-beginning-position)))
+
+             ;; Jump back to corresponding opener.
+             (go-goto-opening-parenthesis)
+
+             ;; Here we decrement the indent if we are closing an indented
+             ;; expression. In other words, the closer's line was indented
+             ;; relative to the opener's line, and that indent should not
+             ;; be inherited by our starting line.
+             (when (and
+                    ;; Opener wasn't on our starting line.
+                    (< bol start-line)
+
+                    ;; Opener and closer aren't on same line.
+                    (< (point) bol)
+
+                    (go-previous-line-has-dangling-op-p)
+
+                    ;; Opener is at same paren level as start of line (ignore 
sub-expressions).
+                    (eq (go-paren-level) (save-excursion (beginning-of-line) 
(go-paren-level)))
+
+                    ;; This dangling line opened indent relative to previous 
dangling line.
+                    (go--continuation-line-indents-p))
+               (cl-decf indent tab-width))))
+
+          ;; Brackets don't affect indentation, so just skip them.
+          ((?\])
+           (backward-char)))
+
+        ;; Skip non-closers since we are only interested in closing 
parens/braces.
+        (skip-syntax-backward "^)" (line-beginning-position))
+
+        (when (go-in-string-or-comment-p)
+          (go-goto-beginning-of-string-or-comment))
+
+        ;; At the beginning of the starting line.
+        (when (= start-line (point))
+
+          ;; We are a continuation line.
+          (if (go-previous-line-has-dangling-op-p)
+              (progn
+                ;; Presume a continuation line always gets an extra indent.
+                ;; We reduce the indent after the loop, if necessary.
+                (cl-incf indent tab-width)
+
+                ;; Go to the end of the dangling line.
+                (goto-char (go-previous-line-has-dangling-op-p)))
+
+            ;; If we aren't a continuation line and we have an enclosing paren
+            ;; or brace, jump to opener and increment our indent.
+            (when (go-goto-opening-parenthesis)
+              (cl-incf indent tab-width)))))
+
+      ;; If our ending line is a continuation line but doesn't open
+      ;; an extra indent, reduce indent. We tentatively gave indents to all
+      ;; dangling lines and all lines inside open parens, so here we take that
+      ;; indent back.
+      ;;
+      ;;                1 +                      1 +
+      ;; ending line      1 + foo(                 1 + foo(
+      ;; starting line      1,        becomes      1,
+      ;;                  )                      )
+      ;;
+      ;;
+      ;;                1 +                     1 +
+      ;; ending line      1 +         becomes     1 +
+      ;; starting line      1                     1
+      (when (and
+             (go-previous-line-has-dangling-op-p)
+             (not (go--continuation-line-indents-p)))
+        (cl-decf indent tab-width))
+
+      ;; Apply our computed indent relative to the indent of the ending line.
+      (+ indent (current-indentation)))))
+
+(defconst go--operator-chars "*/%<>&\\^+\\-|=!,"
+  "Individual characters that appear in operators.
+Comma is included because it is sometimes a dangling operator, so
+needs to be considered by `go--continuation-line-indents-p'")
+
+(defun go--operator-precedence (op)
+  "Go operator precedence (higher binds tighter).
+
+Comma gets the default 0 precedence which is appropriate because commas
+are loose binding expression separators."
+  (cl-case (intern op)
+    (! 6)
+    ((* / % << >> & &^) 5)
+    ((+ - | ^) 4)
+    ((== != < <= > >=) 3)
+    (&& 2)
+    (|| 1)
+    (t 0)))
+
+(defun go--continuation-line-indents-p ()
+  "Return non-nil if the current continuation line opens an additional indent.
+
+This function works by looking at the Go operators used on the current
+line. If all the operators bind tighter than the previous line's
+dangling operator and the current line ends in a dangling operator or
+open paren, the next line will have an additional indent.
+
+For example:
+foo ||
+  foo && // this continuation line opens another indent
+    foo
+"
+  (save-excursion
+    (let (prev-op (all-tighter t))
+
+      ;; Record the dangling operator from previous line.
+      (save-excursion
+        (goto-char (go-previous-line-has-dangling-op-p))
+        (go--end-of-line)
+        (skip-syntax-backward " ")
+        (let ((end (point)))
+          (skip-chars-backward go--operator-chars)
+          (setq prev-op (buffer-substring-no-properties (point) end))))
+
+      (beginning-of-line)
+
+      (when (or
+             ;; We can only open indent if we have a dangling operator, or
+             (go--current-line-has-dangling-op-p)
+             ;; we end in an opening paren/brace or comma.
+             (go--line-suffix-p "[(,]\\|[^[:space:]]{"))
+
+        (let ((prev-precedence (go--operator-precedence prev-op))
+              (start-depth (go-paren-level))
+              (line-start (line-beginning-position)))
+
+          (end-of-line)
+
+          ;; While we haven't found a looser operator and are on the starting 
line...
+          (while (and all-tighter (> (point) line-start))
+
+            ;; Skip over non-operator characters.
+            (skip-chars-backward (concat "^" go--operator-chars) line-start)
+
+            (let ((end (point)))
+              (cond
+               ;; Ignore sub-expressions at different paren levels.
+               ((/= (go-paren-level) start-depth)
+                (skip-syntax-backward "^()"))
+
+               ((go-in-string-or-comment-p)
+                (go-goto-beginning-of-string-or-comment))
+
+               ;; We found an operator. Check if it has lower precedence.
+               ((/= (skip-chars-backward go--operator-chars) 0)
+                (when (>=
+                       prev-precedence
+                       (go--operator-precedence (buffer-substring (point) 
end)))
+                  (setq all-tighter nil)))))))
+        all-tighter))))
 
-      (cond
-       ((go-in-string-p)
-        (current-indentation))
-       ((looking-at "[])}]")
-        (go-goto-opening-parenthesis)
-        (if (and
-             (not (eq (char-after) ?\())
-             (go-previous-line-has-dangling-op-p))
-            (go--non-dangling-indent)
-          (go--indentation-for-opening-parenthesis)))
-       ((setq dangling-line (go-previous-line-has-dangling-op-p))
-        (goto-char dangling-line)
-        (end-of-line)
-
-        ;; only one nesting for all dangling operators in one operation
-        (if (and
-             (not (go--dangling-line-opens-indent-p))
-             (go-previous-line-has-dangling-op-p))
-            (current-indentation)
-          (+ (current-indentation) tab-width)))
-       ((zerop (go-paren-level))
-        0)
-       ((progn (go-goto-opening-parenthesis) (< (go-paren-level) 
start-nesting))
-        (if (and
-             (not (eq (char-after) ?\())
-             (go-previous-line-has-dangling-op-p))
-            (+ (go--non-dangling-indent) tab-width)
-          (+ (go--indentation-for-opening-parenthesis) tab-width))
-        )
-       (t
-        (current-indentation))))))
 (defun go--end-of-line ()
   "Move to the end of the code on the current line.
 Point will be left before any trailing comments. Point will be left
@@ -860,12 +941,6 @@ INDENT is the normal indent of this line, i.e. that of the 
case body."
         ;; aligned with "case", leave it that way
         (= (current-indentation) (- indent tab-width)))))))
 
-(defun go--non-dangling-indent ()
-  (save-excursion
-    (while (go-previous-line-has-dangling-op-p)
-      (forward-line -1))
-    (current-indentation)))
-
 (defconst go--case-regexp "\\([[:space:]]*case\\([[:space:]]\\|$\\)\\)")
 (defconst go--case-or-default-regexp (concat "\\(" go--case-regexp "\\|"  
"[[:space:]]*default:\\)"))
 
diff --git a/test/testdata/indentation_tests/composite_literal.go 
b/test/testdata/indentation_tests/composite_literal.go
new file mode 100644
index 0000000..b20d832
--- /dev/null
+++ b/test/testdata/indentation_tests/composite_literal.go
@@ -0,0 +1,21 @@
+package indentation_tests
+
+func _() {
+       w := struct {
+               foo int
+       }{
+               nil,
+               foo(func() {
+                       foo++
+               }),
+       }
+
+       map[string]func(f Foo, b *Bar){
+               "foo": func(f Foo, b *Bar) {
+                       println("hi")
+               },
+               "bar": func(f Foo, b *Bar) {
+                       println("there")
+               },
+       }
+}
diff --git a/test/testdata/indentation_tests/dangling_operator.go 
b/test/testdata/indentation_tests/dangling_operator.go
index 78c0bb2..55fe125 100644
--- a/test/testdata/indentation_tests/dangling_operator.go
+++ b/test/testdata/indentation_tests/dangling_operator.go
@@ -25,11 +25,42 @@ func init() {
                456,
        )
 
+       1 ||
+               1 || print(1,
+               2,
+       )
+
+       1 ||
+               1 && print(1,
+                       2,
+               )
+
        f :=
                print(1,
                        2,
                )
 
+       1 +
+               foo(
+                       1) +
+               foo
+
+       1 +
+               (1 +
+                       1) + (1 +
+               1)
+
+       1 +
+               1 + foo(
+               1,
+       )
+
+       foo(
+               1 && foo(
+                       1,
+               ),
+       )
+
        g :=
                int64(4 *
                        3 *
@@ -47,6 +78,193 @@ func init() {
        a,
                b := 1, 2
 
+       1 + foo(
+               3,
+       )
+
+       foo &&
+               foo && (foo &&
+               foo)
+
+       foo(1 +
+               3 +
+               4,
+       )
+
+       1 +
+               1
+
+       1 +
+               (1 +
+                       1)
+
+       1 + (1 +
+               1)
+
+       1 + (1 +
+               1) +
+               1
+
+       1 + (1 +
+               1) + (1 +
+               1)
+
+       1 +
+               ((1 +
+                       1) + 1) + (1 +
+               1)
+
+       1 +
+               ((1 +
+                       1) +
+                       1) + (1 +
+               1)
+
+       1 +
+               ((1 +
+                       1) +
+                       1) + (1 +
+               1)
+
+       1 +
+               (1 + (1 +
+                       1) + (1 +
+                       1))
+
+       1 +
+               ((1 +
+                       1) +
+                       1)
+
+       1 + (1 + 1) +
+               (1 +
+                       1)
+
+       1 +
+               1 + (1 +
+               1)
+
+       1 +
+               1 +
+               1 + (1 +
+               1)
+
+       1 +
+               (1 + foo(1+
+                       1))
+
+       (1 &&
+               (2 &&
+                       (3 &&
+                               4))) &&
+               5
+
+       Foo{1 +
+               2,
+               3,
+       }
+
+       1 + (1 +
+               (1 + (1 +
+                       1)))
+
+       1 + (1 + (1 + (1 +
+               1) +
+               1) +
+               1)
+
+       1 +
+               1 + Foo{1 +
+               1}
+
+       1 +
+               FOo{
+                       1,
+               }
+
+       // foo ends the dangle, -indent
+       1 +
+               1 + foo(
+               1,
+       )
+
+       1 +
+               foo(
+                       1,
+               )
+
+       1 +
+               (1 +
+                       1)
+
+       1 +
+               1 + (1 +
+               1)
+
+       1 +
+               1 + 1 +
+               1
+
+       1 +
+               (2 +
+                       (3 + 4)) + foo(
+               1,
+       )
+
+       1 +
+               (1 + foo(
+                       1,
+               ))
+
+       1 +
+               (2 +
+                       (3 + 4)) +
+               1
+
+       1 +
+               (2 +
+                       3) + foo(
+               1,
+               2) +
+               foo
+
+       foo &&
+               (foo && (bar && baz) &&
+                       qux) &&
+               hi
+
+       foo(1,
+               bar(
+                       1,
+                       foo(2,
+                               1))) +
+               foo
+
+       foo &&
+               f(bar && (foo &&
+                       baz)) &&
+               qux
+
+       foo(1+`,
+lol`+
+               123,
+               456)
+
+       "hi" + `,
+lol` +
+               "there"
+
+       foo /* hi */
+       bar
+
+       1 + // hi
+               2 +
+               3
+
+       1 + /* hi */
+               2 +
+               3
+
        return 123,
                456
 }
diff --git a/test/testdata/indentation_tests/function_call.go 
b/test/testdata/indentation_tests/function_call.go
new file mode 100644
index 0000000..3222bd7
--- /dev/null
+++ b/test/testdata/indentation_tests/function_call.go
@@ -0,0 +1,64 @@
+package indentation_tests
+
+func _() {
+       foo(bar(
+               baz(func() {
+                       qux.hi = "there"
+               }),
+               baz(func() {
+                       qux.hi = "there"
+               }),
+       ))
+
+       switch {
+       case foo:
+       }
+
+       unrelated(t)
+       foo([]int{
+               123,
+       }, func() {
+               return
+       })
+
+
+       foo(
+               func() {
+                       func() {
+                       }
+               })
+
+       foo(
+               foo(
+                       1,
+               ))
+
+
+       foo(
+               1,
+       )
+
+       foo(
+               foo(
+                       1,
+               ))
+
+       foo(
+               foo(
+                       1,
+               ),
+       )
+
+       foo(foo(
+               1,
+       ))
+
+       foo(1 +
+               2)
+
+
+       foo(foo(
+               1,
+       ),
+       )
+}
diff --git a/test/testdata/indentation_tests/multiline_if.go 
b/test/testdata/indentation_tests/multiline_if.go
index d73ae74..e3ae385 100644
--- a/test/testdata/indentation_tests/multiline_if.go
+++ b/test/testdata/indentation_tests/multiline_if.go
@@ -42,9 +42,97 @@ func _() {
                true
        }
 
+       if (true &&
+               true) &&
+               true {
+               true
+       }
+
        if bytes.Contains(out, []byte("-fsanitize")) &&
                (bytes.Contains(out, []byte("unrecognized")) ||
                        bytes.Contains(out, []byte("unsupported"))) {
                return true, errors.New(string(out))
        }
+
+       if true ==
+               false {
+               return
+       }
+
+       if true !=
+               false {
+               return
+       }
+
+       if foo(1, // hi
+               // hi
+
+               2) { // hi
+               return
+       }
+
+       if foo(
+               func() {
+               }) {
+               return
+       }
+
+       if foo == 0 ||
+               !foo.Bar(
+                       "some",
+                       "args") {
+               return
+       }
+
+       if true {
+               break
+       } else if true {
+               if true {
+                       break
+               }
+       }
+
+       if true {
+       } else if true ||
+               true {
+               return
+       }
+
+       if 1 +
+               1 {
+               X
+       }
+
+       if 1 +
+               (1 +
+                       1) {
+               X
+       }
+
+       if 1 +
+               (1 +
+                       1) +
+               1 {
+               X
+       }
+
+       if 1 +
+               1 +
+               1 + (1 +
+               1) {
+               X
+       }
+
+       if 1 +
+               (1 +
+                       1) + (1 +
+               1) {
+               X
+       }
+
+       if (Foo{1,
+               1}).Bar {
+               return
+       }
+
 }
diff --git a/test/testdata/indentation_tests/multiline_struct.go 
b/test/testdata/indentation_tests/multiline_struct.go
index 87eb884..de20f85 100644
--- a/test/testdata/indentation_tests/multiline_struct.go
+++ b/test/testdata/indentation_tests/multiline_struct.go
@@ -38,4 +38,26 @@ func _() {
                bar, // important
                baz int //comments
        }
+
+       Cool(Foo{
+               Bar: Cool(Baz{
+                       Blah: 123,
+               }),
+       })
+
+       Foo{{
+               1,
+       }, {
+               2,
+       }}
+
+       var Foo = Bar{
+               Baz: (&Blah{
+                       One: 1,
+               }).Banana,
+       }
+
+       Foo{
+               1}.Bar(
+               1)
 }
diff --git a/test/testdata/indentation_tests/return_function_call_struct.go 
b/test/testdata/indentation_tests/return_function_call_struct.go
index c0c2007..f936aef 100644
--- a/test/testdata/indentation_tests/return_function_call_struct.go
+++ b/test/testdata/indentation_tests/return_function_call_struct.go
@@ -1,9 +1,11 @@
+package main
+
 func main() {
        return F(
                S{
                        1,
                        2,
                        3,
-               }
+               },
        )
 }
diff --git a/test/testdata/indentation_tests/switch.go 
b/test/testdata/indentation_tests/switch.go
index d0b99e9..71a58ed 100644
--- a/test/testdata/indentation_tests/switch.go
+++ b/test/testdata/indentation_tests/switch.go
@@ -6,7 +6,10 @@ func main() {
        label:
                code()
        case "bar":
-       case "baz":
+       case "baz": // important comma,
+               if true {
+                       return
+               }
        case "meow": // some documentation
        default:
                code()



reply via email to

[Prev in Thread] Current Thread [Next in Thread]