Skip to content

Commit ec81a95

Browse files
committed
Merge pull request #22 from nikomatsakis/align-method-chain
Fix aligning of method chains (more-or-less), add various unit tests, and add matching angle brackets.
2 parents 351cc91 + 55e7483 commit ec81a95

File tree

2 files changed

+213
-24
lines changed

2 files changed

+213
-24
lines changed

rust-mode-tests.el

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,3 +922,89 @@ list of substrings of `STR' each followed by its face."
922922
"main" font-lock-function-name-face
923923
"let" font-lock-keyword-face
924924
"'\\''" font-lock-string-face)))
925+
926+
(ert-deftest indent-method-chains-no-align ()
927+
(let ((rust-indent-method-chain nil)) (test-indent
928+
"
929+
fn main() {
930+
let x = thing.do_it()
931+
.aligned()
932+
.more_alignment();
933+
}
934+
"
935+
)))
936+
937+
(ert-deftest indent-method-chains-with-align ()
938+
(let ((rust-indent-method-chain t)) (test-indent
939+
"
940+
fn main() {
941+
let x = thing.do_it()
942+
.aligned()
943+
.more_alignment();
944+
}
945+
"
946+
)))
947+
948+
(ert-deftest indent-method-chains-with-align-and-second-stmt ()
949+
(let ((rust-indent-method-chain t)) (test-indent
950+
"
951+
fn main() {
952+
let x = thing.do_it()
953+
.aligned()
954+
.more_alignment();
955+
foo.bar();
956+
}
957+
"
958+
)))
959+
960+
(ert-deftest indent-method-chains-field ()
961+
(let ((rust-indent-method-chain t)) (test-indent
962+
"
963+
fn main() {
964+
let x = thing.do_it
965+
.aligned
966+
.more_alignment();
967+
}
968+
"
969+
)))
970+
971+
(ert-deftest indent-method-chains-double-field-on-first-line ()
972+
(let ((rust-indent-method-chain t)) (test-indent
973+
"
974+
fn main() {
975+
let x = thing.a.do_it
976+
.aligned
977+
.more_alignment();
978+
}
979+
"
980+
)))
981+
982+
(ert-deftest indent-method-chains-no-let ()
983+
(let ((rust-indent-method-chain t)) (test-indent
984+
"
985+
fn main() {
986+
thing.a.do_it
987+
.aligned
988+
.more_alignment();
989+
}
990+
"
991+
)))
992+
993+
(ert-deftest indent-method-chains-comment ()
994+
(let ((rust-indent-method-chain t)) (test-indent
995+
"
996+
fn main() {
997+
// thing.do_it()
998+
// .aligned()
999+
}
1000+
"
1001+
)))
1002+
1003+
(ert-deftest indent-method-chains-close-block ()
1004+
(let ((rust-indent-method-chain t)) (test-indent
1005+
"
1006+
fn main() {
1007+
foo.bar()
1008+
}
1009+
"
1010+
)))

rust-mode.el

Lines changed: 127 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,61 @@
8989
(backward-word 1))
9090
(current-column))))
9191

92-
(defun rust-align-to-method-chain ()
93-
(save-excursion
94-
(previous-line)
95-
(end-of-line)
96-
(backward-word 1)
97-
(backward-char)
98-
(when (looking-at "\\..+\(.*\)\n")
99-
(- (current-column) rust-indent-offset))))
100-
10192
(defun rust-rewind-to-beginning-of-current-level-expr ()
10293
(let ((current-level (rust-paren-level)))
10394
(back-to-indentation)
10495
(while (> (rust-paren-level) current-level)
10596
(backward-up-list)
10697
(back-to-indentation))))
10798

99+
(defun rust-align-to-method-chain ()
100+
(save-excursion
101+
;; for method-chain alignment to apply, we must be looking at
102+
;; another method call or field access or something like
103+
;; that. This avoids rather "eager" jumps in situations like:
104+
;;
105+
;; {
106+
;; something.foo()
107+
;; <indent>
108+
;;
109+
;; Without this check, we would wind up with the cursor under the
110+
;; `.`. In an older version, I had the inverse of the current
111+
;; check, where we checked for situations that should NOT indent,
112+
;; vs checking for the one situation where we SHOULD. It should be
113+
;; clear that this is more robust, but also I find it mildly less
114+
;; annoying to have to press tab again to align to a method chain
115+
;; than to have an over-eager indent in all other cases which must
116+
;; be undone via tab.
117+
118+
(when (looking-at (concat "\s*\." rust-re-ident))
119+
(previous-line)
120+
(end-of-line)
121+
122+
(let
123+
;; skip-dot-identifier is used to position the point at the
124+
;; `.` when looking at something like
125+
;;
126+
;; foo.bar
127+
;; ^ ^
128+
;; | |
129+
;; | position of point
130+
;; returned offset
131+
;;
132+
((skip-dot-identifier
133+
(lambda ()
134+
(when (looking-back (concat "\." rust-re-ident))
135+
(backward-word 1)
136+
(backward-char)
137+
(- (current-column) rust-indent-offset)))))
138+
(cond
139+
;; foo.bar(...)
140+
((looking-back ")")
141+
(backward-list 1)
142+
(funcall skip-dot-identifier))
143+
144+
;; foo.bar
145+
(t (funcall skip-dot-identifier)))))))
146+
108147
(defun rust-mode-indent-line ()
109148
(interactive)
110149
(let ((indent
@@ -123,10 +162,10 @@
123162
(or
124163
(when rust-indent-method-chain
125164
(rust-align-to-method-chain))
126-
(save-excursion
127-
(backward-up-list)
128-
(rust-rewind-to-beginning-of-current-level-expr)
129-
(+ (current-column) rust-indent-offset))))))
165+
(save-excursion
166+
(backward-up-list)
167+
(rust-rewind-to-beginning-of-current-level-expr)
168+
(+ (current-column) rust-indent-offset))))))
130169
(cond
131170
;; A function return type is indented to the corresponding function arguments
132171
((looking-at "->")
@@ -137,16 +176,6 @@
137176

138177
;; A closing brace is 1 level unindended
139178
((looking-at "}") (- baseline rust-indent-offset))
140-
141-
;;Line up method chains by their .'s
142-
((when (and rust-indent-method-chain
143-
(looking-at "\..+\(.*\);?\n"))
144-
(or
145-
(let ((method-indent (rust-align-to-method-chain)))
146-
(when method-indent
147-
(+ method-indent rust-indent-offset)))
148-
(+ baseline rust-indent-offset))))
149-
150179

151180
;; Doc comments in /** style with leading * indent to line up the *s
152181
((and (nth 4 (syntax-ppss)) (looking-at "*"))
@@ -452,11 +481,84 @@ This is written mainly to be used as `end-of-defun-function' for Rust."
452481
;; There is no opening brace, so consider the whole buffer to be one "defun"
453482
(goto-char (point-max))))
454483

484+
;; Angle-bracket matching. This is kind of a hack designed to deal
485+
;; with the fact that we can't add angle-brackets to the list of
486+
;; matching characters unconditionally. Basically we just have some
487+
;; special-case code such that whenever `>` is typed, we look
488+
;; backwards to find a matching `<` and highlight it, whether or not
489+
;; this is *actually* appropriate. This could be annoying so it is
490+
;; configurable (but on by default because it's awesome).
491+
492+
(defcustom rust-blink-matching-angle-brackets t
493+
"Blink matching `<` (if any) when `>` is typed"
494+
:type 'boolean
495+
:group 'rust-mode)
496+
497+
(defvar rust-point-before-matching-angle-bracket 0)
498+
499+
(defvar rust-matching-angle-bracker-timer nil)
500+
501+
(defun rust-find-matching-angle-bracket ()
502+
(save-excursion
503+
(let ((angle-brackets 1)
504+
(start-point (point))
505+
(invalid nil))
506+
(while (and
507+
;; didn't find a match
508+
(> angle-brackets 0)
509+
;; we have no guarantee of a match, so give up eventually
510+
(< (- start-point (point)) blink-matching-paren-distance)
511+
;; didn't hit the top of the buffer
512+
(> (point) (point-min))
513+
;; didn't hit something else weird like a `;`
514+
(not invalid))
515+
(backward-char 1)
516+
(cond
517+
((looking-at ">")
518+
(setq angle-brackets (+ angle-brackets 1)))
519+
((looking-at "<")
520+
(setq angle-brackets (- angle-brackets 1)))
521+
((looking-at "[;{]")
522+
(setq invalid t))))
523+
(cond
524+
((= angle-brackets 0) (point))
525+
(t nil)))))
526+
527+
(defun rust-restore-point-after-angle-bracket ()
528+
(goto-char rust-point-before-matching-angle-bracket)
529+
(when rust-matching-angle-bracker-timer
530+
(cancel-timer rust-matching-angle-bracker-timer))
531+
(setq rust-matching-angle-bracker-timer nil)
532+
(remove-hook 'pre-command-hook 'rust-restore-point-after-angle-bracket))
533+
534+
(defun rust-match-angle-bracket-hook ()
535+
"If the most recently inserted character is a `>`, briefly moves point to matching `<` (if any)."
536+
(interactive)
537+
(when (and rust-blink-matching-angle-brackets
538+
(looking-back ">"))
539+
(let ((matching-angle-bracket-point (save-excursion
540+
(backward-char 1)
541+
(rust-find-matching-angle-bracket))))
542+
(when matching-angle-bracket-point
543+
(progn
544+
(setq rust-point-before-matching-angle-bracket (point))
545+
(goto-char matching-angle-bracket-point)
546+
(add-hook 'pre-command-hook 'rust-restore-point-after-angle-bracket)
547+
(setq rust-matching-angle-bracker-timer
548+
(run-at-time blink-matching-delay nil 'rust-restore-point-after-angle-bracket)))))))
549+
550+
(defun rust-match-angle-bracket ()
551+
"The point should be placed on a `>`. Finds the matching `<` and moves point there."
552+
(interactive)
553+
(let ((matching-angle-bracket-point (rust-find-matching-angle-bracket)))
554+
(if matching-angle-bracket-point
555+
(goto-char matching-angle-bracket-point)
556+
(message "no matching angle bracket found"))))
557+
455558
;; For compatibility with Emacs < 24, derive conditionally
456559
(defalias 'rust-parent-mode
457560
(if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
458561

459-
460562
;;;###autoload
461563
(define-derived-mode rust-mode rust-parent-mode "Rust"
462564
"Major mode for Rust code."
@@ -490,6 +592,7 @@ This is written mainly to be used as `end-of-defun-function' for Rust."
490592
(setq-local end-of-defun-function 'rust-end-of-defun)
491593
(setq-local parse-sexp-lookup-properties t)
492594
(add-hook 'syntax-propertize-extend-region-functions 'rust-syntax-propertize-extend-region)
595+
(add-hook 'post-self-insert-hook 'rust-match-angle-bracket-hook)
493596
(setq-local syntax-propertize-function 'rust-syntax-propertize))
494597

495598
(defun rust-syntax-propertize-extend-region (start end)

0 commit comments

Comments
 (0)