Skip to content

Fix aligning of method chains (more-or-less), add various unit tests, and add matching angle brackets. #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions rust-mode-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -919,3 +919,89 @@ list of substrings of `STR' each followed by its face."
"main" font-lock-function-name-face
"let" font-lock-keyword-face
"'\\''" font-lock-string-face)))

(ert-deftest indent-method-chains-no-align ()
(let ((rust-indent-method-chain nil)) (test-indent
"
fn main() {
let x = thing.do_it()
.aligned()
.more_alignment();
}
"
)))

(ert-deftest indent-method-chains-with-align ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
let x = thing.do_it()
.aligned()
.more_alignment();
}
"
)))

(ert-deftest indent-method-chains-with-align-and-second-stmt ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
let x = thing.do_it()
.aligned()
.more_alignment();
foo.bar();
}
"
)))

(ert-deftest indent-method-chains-field ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
let x = thing.do_it
.aligned
.more_alignment();
}
"
)))

(ert-deftest indent-method-chains-double-field-on-first-line ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
let x = thing.a.do_it
.aligned
.more_alignment();
}
"
)))

(ert-deftest indent-method-chains-no-let ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
thing.a.do_it
.aligned
.more_alignment();
}
"
)))

(ert-deftest indent-method-chains-comment ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
// thing.do_it()
// .aligned()
}
"
)))

(ert-deftest indent-method-chains-close-block ()
(let ((rust-indent-method-chain t)) (test-indent
"
fn main() {
foo.bar()
}
"
)))
151 changes: 127 additions & 24 deletions rust-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,61 @@
(backward-word 1))
(current-column))))

(defun rust-align-to-method-chain ()
(save-excursion
(previous-line)
(end-of-line)
(backward-word 1)
(backward-char)
(when (looking-at "\\..+\(.*\)\n")
(- (current-column) rust-indent-offset))))

(defun rust-rewind-to-beginning-of-current-level-expr ()
(let ((current-level (rust-paren-level)))
(back-to-indentation)
(while (> (rust-paren-level) current-level)
(backward-up-list)
(back-to-indentation))))

(defun rust-align-to-method-chain ()
(save-excursion
;; for method-chain alignment to apply, we must be looking at
;; another method call or field access or something like
;; that. This avoids rather "eager" jumps in situations like:
;;
;; {
;; something.foo()
;; <indent>
;;
;; Without this check, we would wind up with the cursor under the
;; `.`. In an older version, I had the inverse of the current
;; check, where we checked for situations that should NOT indent,
;; vs checking for the one situation where we SHOULD. It should be
;; clear that this is more robust, but also I find it mildly less
;; annoying to have to press tab again to align to a method chain
;; than to have an over-eager indent in all other cases which must
;; be undone via tab.

(when (looking-at (concat "\s*\." rust-re-ident))
(previous-line)
(end-of-line)

(let
;; skip-dot-identifier is used to position the point at the
;; `.` when looking at something like
;;
;; foo.bar
;; ^ ^
;; | |
;; | position of point
;; returned offset
;;
((skip-dot-identifier
(lambda ()
(when (looking-back (concat "\." rust-re-ident))
(backward-word 1)
(backward-char)
(- (current-column) rust-indent-offset)))))
(cond
;; foo.bar(...)
((looking-back ")")
(backward-list 1)
(funcall skip-dot-identifier))

;; foo.bar
(t (funcall skip-dot-identifier)))))))

(defun rust-mode-indent-line ()
(interactive)
(let ((indent
Expand All @@ -123,10 +162,10 @@
(or
(when rust-indent-method-chain
(rust-align-to-method-chain))
(save-excursion
(backward-up-list)
(rust-rewind-to-beginning-of-current-level-expr)
(+ (current-column) rust-indent-offset))))))
(save-excursion
(backward-up-list)
(rust-rewind-to-beginning-of-current-level-expr)
(+ (current-column) rust-indent-offset))))))
(cond
;; A function return type is indented to the corresponding function arguments
((looking-at "->")
Expand All @@ -137,16 +176,6 @@

;; A closing brace is 1 level unindended
((looking-at "}") (- baseline rust-indent-offset))

;;Line up method chains by their .'s
((when (and rust-indent-method-chain
(looking-at "\..+\(.*\);?\n"))
(or
(let ((method-indent (rust-align-to-method-chain)))
(when method-indent
(+ method-indent rust-indent-offset)))
(+ baseline rust-indent-offset))))


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

;; Angle-bracket matching. This is kind of a hack designed to deal
;; with the fact that we can't add angle-brackets to the list of
;; matching characters unconditionally. Basically we just have some
;; special-case code such that whenever `>` is typed, we look
;; backwards to find a matching `<` and highlight it, whether or not
;; this is *actually* appropriate. This could be annoying so it is
;; configurable (but on by default because it's awesome).

(defcustom rust-blink-matching-angle-brackets t
"Blink matching `<` (if any) when `>` is typed"
:type 'boolean
:group 'rust-mode)

(defvar rust-point-before-matching-angle-bracket 0)

(defvar rust-matching-angle-bracker-timer nil)

(defun rust-find-matching-angle-bracket ()
(save-excursion
(let ((angle-brackets 1)
(start-point (point))
(invalid nil))
(while (and
;; didn't find a match
(> angle-brackets 0)
;; we have no guarantee of a match, so give up eventually
(< (- start-point (point)) blink-matching-paren-distance)
;; didn't hit the top of the buffer
(> (point) (point-min))
;; didn't hit something else weird like a `;`
(not invalid))
(backward-char 1)
(cond
((looking-at ">")
(setq angle-brackets (+ angle-brackets 1)))
((looking-at "<")
(setq angle-brackets (- angle-brackets 1)))
((looking-at "[;{]")
(setq invalid t))))
(cond
((= angle-brackets 0) (point))
(t nil)))))

(defun rust-restore-point-after-angle-bracket ()
(goto-char rust-point-before-matching-angle-bracket)
(when rust-matching-angle-bracker-timer
(cancel-timer rust-matching-angle-bracker-timer))
(setq rust-matching-angle-bracker-timer nil)
(remove-hook 'pre-command-hook 'rust-restore-point-after-angle-bracket))

(defun rust-match-angle-bracket-hook ()
"If the most recently inserted character is a `>`, briefly moves point to matching `<` (if any)."
(interactive)
(when (and rust-blink-matching-angle-brackets
(looking-back ">"))
(let ((matching-angle-bracket-point (save-excursion
(backward-char 1)
(rust-find-matching-angle-bracket))))
(when matching-angle-bracket-point
(progn
(setq rust-point-before-matching-angle-bracket (point))
(goto-char matching-angle-bracket-point)
(add-hook 'pre-command-hook 'rust-restore-point-after-angle-bracket)
(setq rust-matching-angle-bracker-timer
(run-at-time blink-matching-delay nil 'rust-restore-point-after-angle-bracket)))))))

(defun rust-match-angle-bracket ()
"The point should be placed on a `>`. Finds the matching `<` and moves point there."
(interactive)
(let ((matching-angle-bracket-point (rust-find-matching-angle-bracket)))
(if matching-angle-bracket-point
(goto-char matching-angle-bracket-point)
(message "no matching angle bracket found"))))

;; For compatibility with Emacs < 24, derive conditionally
(defalias 'rust-parent-mode
(if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))


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

(defun rust-syntax-propertize-extend-region (start end)
Expand Down