diff options
| -rw-r--r-- | rust-mode-tests.el | 649 | ||||
| -rw-r--r-- | rust-mode.el | 457 |
2 files changed, 1028 insertions, 78 deletions
diff --git a/rust-mode-tests.el b/rust-mode-tests.el index a846a27..4a6cf20 100644 --- a/rust-mode-tests.el +++ b/rust-mode-tests.el @@ -710,6 +710,7 @@ INIT-POS, FINAL-POS are position symbols found in `rust-test-positions-alist'." (with-temp-buffer (rust-mode) (insert source-code) + (font-lock-fontify-buffer) (goto-char (rust-get-buffer-pos init-pos)) (apply manip-func args) (should (equal (point) (rust-get-buffer-pos final-pos))))) @@ -723,6 +724,7 @@ All positions are position symbols found in `rust-test-positions-alist'." (with-temp-buffer (rust-mode) (insert source-code) + (font-lock-fontify-buffer) (goto-char (rust-get-buffer-pos init-pos)) (apply manip-func args) (should (equal (list (region-beginning) (region-end)) @@ -1303,7 +1305,7 @@ fn indented_already() { \n // The previous line already has its spaces } ") - + (font-lock-fontify-buffer) (goto-line 11) (move-to-column 0) (indent-for-tab-command) @@ -1483,6 +1485,14 @@ la la\"); ;; Needs to leave 1 space before "world" "\"hello \\\n world\"")) +(ert-deftest indent-multi-line-type-param-list () + (test-indent + " +pub fn foo<T, + V>() { + hello(); +}")) + (defun rust-test-matching-parens (content pairs &optional nonparen-positions) "Assert that in rust-mode, given a buffer with the given `content', emacs's paren matching will find all of the pairs of positions @@ -1562,3 +1572,640 @@ la la\"); "r#\"\"\"#;\n'q'" '("r#\"\"\"#" font-lock-string-face "'q'" font-lock-string-face))) + +(ert-deftest rust-test-basic-paren-matching () + (rust-test-matching-parens + " +fn foo() { + let a = [1, 2, 3]; +}" + '((8 9) ;; Parens of foo() + (11 36) ;; Curly braces + (25 33) ;; Square brackets + ))) + +(ert-deftest rust-test-paren-matching-generic-fn () + (rust-test-matching-parens + " +fn foo<A>() { +}" + '((8 10) ;; Angle brackets <A> + (11 12) ;; Parens + (14 16) ;; Curly braces + ))) + +(ert-deftest rust-test-paren-matching-generic-fn-with-return-value () + (rust-test-matching-parens + " +fn foo<A>() -> bool { + false +}" + '((8 10) ;; Angle brackets <A> + (11 12) ;; Parens + (22 34) ;; Curly braces + ) + + '(15 ;; The ">" in "->" is not an angle bracket + ))) + +(ert-deftest rust-test-paren-matching-match-stmt () + (rust-test-matching-parens + " +fn foo() { + something_str(match <Type as Trait>::method() { + Some(_) => \"Got some\", + None => \"Nada\" + }); +}" + '((8 9) ;; parens of fn foo + (11 127) ;; curly braces of foo + (30 124) ;; parens of something_str + (37 51) ;; angle brackets of <Type as Trait> + (60 61) ;; parens of method() + (63 123) ;; curly braces of match + (77 79) ;; parens of Some(_) + ) + + '(82 ;; > in first => + 112 ;; > in second => + ))) + +(ert-deftest rust-test-paren-matching-bitshift-operators () + (rust-test-matching-parens + " +fn foo(z:i32) { + let a:Option<Result<i32,i32>> = Some(Ok(4 >> 1)); + let b = a.map(|x| x.map(|y| y << 3)); + let trick_question = z<<<Type as Trait>::method(); // First two <s are not brackets, third is +}" + '((34 50) ;; angle brackets of Option + (41 49) ;; angle brackets of Result + (142 156) ;; angle brackets of <Type as Trait> + ) + '(64 ;; The >> inside Some(Ok()) are not angle brackets + 65 ;; The >> inside Some(Ok()) are not angle brackets + 106 ;; The << inside map() are not angle brackets + 107 ;; The << inside map() are not angle brackets + 140 ;; The << before <Type as Trait> are not angle brackets + 141 ;; The << before <Type as Trait> are not angle brackets + 183 ;; The < inside the comment + ))) + +(ert-deftest rust-test-paren-matching-angle-bracket-after-colon-ident () + (rust-test-matching-parens + " +struct Bla<T> { + a:Option<(i32,Option<bool>)>, + b:Option<T>, + c:bool +} + +fn f(x:i32,y:Option<i32>) { + let z:Option<i32> = None; + let b:Bla<i8> = Bla{ + a:None, + b:None, + c:x<y.unwrap(); + } +}" + '((12 14) ;; Angle brackets of Bla<T> + (30 49) ;; Outer angle brackets of a:Option<...> + (42 47) ;; Inner angle brackets of Option<bool> + (64 66) ;; Angle brackets of Option<T> + (102 106) ;; Angle brackets of y:Option<i32> + (127 131) ;; Angle brackets of z:Option<i32> + (154 157) ;; Angle brackets of b:Bla<i8> + ) + '(209 ;; less than operator in c:x<y.unwrap... + ))) + +(ert-deftest rust-test-paren-matching-struct-literals () + (rust-test-matching-parens + " +fn foo(x:i32) -> Bar { + Bar { + b:x<3 + } +}" + '() + '(17 ;; the -> is not a brace + 46 ;; x<3 the < is a less than sign + )) + ) + +(ert-deftest rust-test-paren-matching-nested-struct-literals () + (rust-test-matching-parens + " +fn f(x:i32,y:i32) -> Foo<Bar> { + Foo{ + bar:Bar{ + a:3, + b:x<y + } + } +} +" + '((26 30)) ;; Angle brackets of Foo<Bar> + ) + '(92 ;; less than operator x<y + )) + +(ert-deftest rust-test-paren-matching-fn-types-in-type-params () + (rust-test-matching-parens + " +fn foo<T:Fn() -> X<Y>>() -> Z { +} +" + '((8 23) ;; The angle brackets of foo<T...> + (20 22 ;; The angle brackets of X<Y> + )) + '(17 ;; The first -> + 28 ;; The second -> + ) + )) + +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () + (rust-test-matching-parens + " +fn foo(x:i32) { + f(x < 3); +} +" + '() + '(26 ;; The < inside f is a less than operator + ) + )) + +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () + (rust-test-matching-parens + " +fn foo(x:i32) -> bool { + return x < 3; +} +" + '() + '(17 ;; The -> + 39 ;; The < after return is a less than operator + ) + )) + +(ert-deftest rust-test-type-paren-matching-angle-brackets-in-type-items () + (rust-test-matching-parens + " +type Foo = Blah<Z,Y>; +type Bar<X> = (Foo, Bletch<X>); +type ThisThing<Z,A,D,F> = HereYouGo<Z,Y<Fn(A) -> B<C<D>>,E<F>>>;" + '((17 21) ;; Angle brackets of Blah<Z,Y> + (32 34) ;; Angle brackets of Bar<X> + (50 52) ;; Angle brackets of Bletch<X> + (70 78) ;; Angle brackets of ThisThing<Z,A,D,F> + (91 118) ;; Angle brackets of HereYouGo<...> + (95 117) ;; Angle brackets of Y<Fn...> + (106 111) ;; Angle brackets of B<C<D>> + (108 110) ;; Angle brackets of C<D> + (114 116) ;; Angle brackets of E<F> + ))) + +(ert-deftest rust-test-paren-matching-tuple-like-struct () + (rust-test-matching-parens + " +struct A(Option<B>); +struct C<Q>(Result<Q,i32>);" + '((17 19) ;; The angle brackets <B> + (10 20) ;; The parens of A(); + (31 33) ;; The angle brackets of C<Q> + (41 47) ;; The angle brackets of Result<Q,i32> + ) + '())) + +(ert-deftest rust-test-paren-matching-in-enum () + (rust-test-matching-parens + " +enum Boo<A> { + TupleLike(Option<A>), + StructLike{foo: Result<A,i32>} +}" + '((10 12) ;; Angle brackets of Boo<A> + (36 38) ;; Angle brackets of Option<A> + (68 74) ;; Angle brackets of Result<A,i32> + ))) + +(ert-deftest rust-test-paren-matching-assoc-type-bounds () + (rust-test-matching-parens + "impl <A:B<AssocType = C<A> >> Thing<A> {}" + '((6 29) ;; Outer angle brackets of impl + (10 28) ;; Outer angle brackets of B<AssocType = C<A>> + (24 26) ;; Inner angle brackets of C<A> + (36 38) ;; Angle brackets of Thing<A> + ) + )) + +(ert-deftest rust-test-paren-matching-plus-signs-in-expressions-and-bounds () + ;; Note that as I write this, the function "bluh" below does not compile, but + ;; it warns that the equality constraint in a where clause is "not yet + ;; supported." It seems that the compiler will support this eventually, so + ;; the emacs mode needs to support it. + (rust-test-matching-parens + "fn foo<A:Trait1+Trait2<i32>,B>(a:A,b:B) -> bool where B:Trait3<Foo>+Trait4<Bar> { + 2 + a < 3 && 3 + b > 11 +} + +fn bluh<A>() where A:Fn()+MyTrait<i32>, MyTrait<A>::AssocType = Option<bool> { +} + +fn fluh<C>() where C:Fn(i32) -> (i32, i32) + SomeTrait<i32>, C::AssocType = OtherThing<bool> { +}" + '((7 30) ;; Angle brackets of foo<...> + (23 27) ;; Angle brackets of Trait2<i32> + (63 67) ;; Angle brackets of Trait3<Foo> + (75 79) ;; Angle brackets of Trait4<Bar> + + (121 123) ;; Angle brackets of bluh<A> + (147 151) ;; Angle brackets of MyTrait<i32> + + (161 163) ;; Angle brackets of MyTrait<A> + (184 189) ;; Angle brackets of Option<bool> + + (203 205) ;; Angle brackets of <C> + (250 254) ;; Angle brackets of SomeTrait<i32> + (282 287) ;; Angle brackets of Option<bool> + ) + '(93 ;; Less-than sign of a < 3 + 106 ;; Greater than sign of b > 11 + ))) + +(ert-deftest rust-test-paren-matching-generic-type-in-tuple-return-type () + (rust-test-matching-parens + "pub fn take(mut self) -> (EmptyBucket<K, V, M>, K, V) {}" + '((38 46)) + )) + +(ert-deftest rust-test-paren-matching-references-and-logical-and () + (rust-test-matching-parens + " +fn ampersand_check(a: &Option<i32>, b:bool) -> &Option<u32> { + a.map(|x| { + b && x < 32 + }) +}" + '((31 35) ;; Option<i32> + (56 60) ;; Option<u32> + ) + '(95 ;; x < 32 + ) + ) + ) + +(ert-deftest rust-test-paren-matching-lt-sign-in-if-statement () + (rust-test-matching-parens + " +fn if_check(a:i32,b:i32,c:i32) { + if a + b < c { + + } + if a < b { + + } + if (c < a) { + + } +} + +fn while_check(x:i32,y:i32) -> bool { + while x < y { + } + for x in y < x { + } + match y < z { + true => (), _ => () + } + return z < y; +}" + '() + '(48 ;; b < c + 78 ;; a < b + 109 ;; (c < a) + + 184 ;; x < y + 211 ;; y < x + 235 ;; y < z + 288 ;; z < y + ))) + +(ert-deftest rust-test-paren-matching-lt-expr-with-field () + (rust-test-matching-parens + "fn foo() { x.y < 3 }" + '() + '(16 ;; x.y < 3 + ))) + +(ert-deftest rust-test-paren-matching-lt-expr-with-quote () + (rust-test-matching-parens + " +fn quote_check() { + 'x' < y; + \"y\" < x; + r##\"z\"## < q; + a <= 3 && b < '2' +}" + '() + '(29 ;; 'x' < y + 42 ;; "y" < x + 60 ;; r##"z"## < q + 71 ;; a <= '3' + 81 ;; b < '2' + ))) + +(ert-deftest rust-test-paren-matching-keywords-capitalized-are-ok-type-names () + (rust-test-matching-parens + " +fn foo() -> Box<i32> { + let z:If<bool> = If(a < 3); +}" + '((17 21) ;; Box<i32> + (37 42) ;; If<bool> + ) + '(51 ;; If(a < 3) + ))) + +(ert-deftest rust-test-paren-matching-lt-expression-inside-macro () + (rust-test-matching-parens + "fn bla() { assert!(x < y); }" + '() + '(22 ;; x < y + ))) + +(ert-deftest rust-test-paren-matching-array-types-with-generics () + (rust-test-matching-parens + "fn boo () -> [Option<i32>] {}" + '((21 25)))) + +(ert-deftest rust-test-paren-matching-angle-bracket-inner-reference () + (rust-test-matching-parens + "fn x() -> Option<&Node<T>> {}" + '((17 26) ;; Option + (23 25) ;; Node + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-semicolon () + (rust-test-matching-parens + "fn f(x:i32) -> bool { (); x < 3 }" + '() + '(29 + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-comma () + (rust-test-matching-parens + "fn foo() { + (e, a < b) +}" + '((16 25) ;; The parens () + ) + '(22 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-let () + (rust-test-matching-parens + "fn main() { + let x = a < b; +}" + '((11 32) ;; The { } + ) + '(27 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-two-lt-ops-in-a-row () + (rust-test-matching-parens + "fn next(&mut self) -> Option<<I as Iterator>::Item>" + '((29 51) ;; Outer Option<> + (30 44) ;; Inner <I as Iterator> + ) + '(21 + ))) + +(ert-deftest rust-test-paren-matching-lt-after-caret () + (rust-test-matching-parens + "fn foo() { x^2 < 3 }" + '((10 20) ;; The { } + ) + '(16 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-special-type () + (rust-test-matching-parens + "fn foo() { low as uint <= c }" + '((10 29)) + '(24))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-closing-curly-brace () + (rust-test-matching-parens + "fn main() { if true {} a < 3 }" + '((11 30) + ) + '(26))) + +(ert-deftest rust-test-paren-matching-const () + (rust-test-matching-parens + " +const BLA = 1 << 3; +const BLUB = 2 < 4;" + '() + '(16 + 17 ;; Both chars of the << in 1 << 3 + 37 ;; The < in 2 < 4 + ))) + +(ert-deftest rust-test-paren-matching-c-like-enum () + (rust-test-matching-parens + " +enum CLikeEnum { + Two = 1 << 1, + Four = 1 << 2 +}" + '((17 56 ;; The { } of the enum + )) + '(31 + 32 ;; The first << + 50 + 51 ;; The second << + ))) + +(ert-deftest rust-test-paren-matching-no-angle-brackets-in-macros () + (rust-test-matching-parens + " +fn foo<A>(a:A) { + macro_a!( foo::<ignore the bracets> ); + macro_b![ foo as Option<B> ]; +} + +macro_c!{ + struct Boo<D> {} +}" + '((8 10)) + ;; Inside macros, it should not find any angle brackets, even if it normally + ;; would + '(38 ;; macro_a < + 57 ;; macro_a > + 89 ;; macro_b < + 91 ;; macro_b > + 123 ;; macro_c < + 125 ;; macro_d > + ))) + +(ert-deftest rust-test-paren-matching-type-with-module-name () + (rust-test-matching-parens + " +const X: libc::c_int = 1 << 2; +fn main() { + let z: libc::c_uint = 1 << 4; +} +" + '((43 79)) ;; The curly braces + '(27 + 28 ;; The first << + 73 + 74 ;; The second << + ))) + +(ert-deftest rust-test-paren-matching-qualififed-struct-literal () + (rust-test-matching-parens + " +fn foo() -> Fn(asd) -> F<V> { + let z = foo::Struct{ b: 1 << 4, c: 2 < 4 } +}" + '((30 80) ;; Outer curly brackets + ) + '(62 + 63 ;; The shift operator + 73 ;; The less than operator + ))) + +(ert-deftest rust-test-paren-matching-let-mut () + (rust-test-matching-parens + " +fn f() { + let mut b = 1 < 3; + let mut i = 1 << 3; +} +" + '() + '(28 ;; 1 < 3 + 51 + 52 ;; 1 << 3 + ))) + +(ert-deftest rust-test-paren-matching-as-ref-type () + (rust-test-matching-parens + "fn f() { + let a = b as &Foo<Bar>; +}" + '((31 35) ;; Angle brackets Foo<Bar> + ))) + +(ert-deftest rust-test-paren-matching-type-ascription () + (rust-test-matching-parens + " +fn rfc803() { + let z = a < b:FunnkyThing<int>; + let s = Foo { + a: b < 3, + b: d:CrazyStuff<int> < 3, + c: 2 < x:CrazyStuff<uint> + } +}" + '((45 49) ;; FunkyThing<int> + (111 115) ;; CrazyStuff<int> + (149 154) ;; CrazyStuff<uint> + ) + '(30 ;; a < b + 83 ;; b < 3 + 117 ;; d... < 3 + 135 ;; 2 < x + ))) + +(ert-deftest rust-test-paren-matching-angle-brackets-in-enum-with-where-claause () + (rust-test-matching-parens + " +enum MyEnum<T> where T:std::fmt::Debug { + Thing(Option<T>) +}" + '((13 15) ;; MyEnum<T> + (59 61) ;; Option<T> + ))) + +(ert-deftest rust-test-paren-matching-where-clauses-with-closure-types () + (rust-test-matching-parens + " +enum Boo<'a,T> where T:Fn() -> Option<&'a str> + 'a { + Thingy(Option<&'a T>) +} + +fn foo<'a>() -> Result<B,C> where C::X: D<A>, B:FnMut() -> Option<Q>+'a { + Foo(a < b) +} + +type Foo<T> where T: Copy = Box<T>; +" + '((10 15) ;; Boo<'a,T> + (39 47) ;; Option<&'a str> + (72 78) ;; Option<&'a T> + + (106 110) ;; Result<B,C> + (125 127) ;; D<A> + (149 151) ;; Option<Q> + (184 186) ;; Foo<T> + (207 209) ;; Box<T> + ) + + '(168 ;; Foo(a < b) + ) + )) + +(ert-deftest rust-test-angle-bracket-matching-turned-off () + (let ((rust-match-angle-brackets nil)) + (rust-test-matching-parens + "fn foo<a>() {}" + '((10 11)) + '(7 9)))) + +;; If electric-pair-mode is available, load it and run the tests that use it. If not, +;; no error--the tests will be skipped. +(require 'elec-pair nil t) + +;; The emacs 23 version of ERT does not have test skipping functionality. So +;; don't even define these tests if elec-pair is not available. +(when (featurep 'elec-pair) + (defun test-electric-pair-insert (original point-pos char closer) + (let ((old-electric-pair-mode electric-pair-mode)) + (electric-pair-mode 1) + (unwind-protect + (with-temp-buffer + (rust-mode) + (insert original) + (font-lock-fontify-buffer) + + (goto-char point-pos) + (deactivate-mark) + (let ((last-command-event char)) (self-insert-command 1)) + (should (equal (char-after) + (or closer (aref original point-pos))))) + (electric-pair-mode (or old-electric-pair-mode 1))))) + + (ert-deftest rust-test-electric-pair-generic-fn () + (test-electric-pair-insert "fn foo() { }" 7 ?< ?>)) + + (ert-deftest rust-test-electric-pair-impl-param () + (test-electric-pair-insert "impl Foo<T> for Bar<T>" 5 ?< ?>)) + + (ert-deftest rust-test-electric-pair-impl-for-type-param () + (test-electric-pair-insert "impl<T> Foo<T> for Bar" 22 ?< ?>)) + + (ert-deftest rust-test-electric-pair-lt-expression () + (test-electric-pair-insert "fn foo(bar:i32) -> bool { bar }" 30 ?< nil)) + + (ert-deftest rust-test-electric-pair-lt-expression-in-struct-literal () + (test-electric-pair-insert "fn foo(x:i32) -> Bar { Bar { a:(bleh() + whatever::<X>()), b:x } }" 63 ?< nil)) + + (ert-deftest rust-test-electric-pair-lt-expression-capitalized-keyword () + (test-electric-pair-insert "fn foo() -> Box" 16 ?< ?>)) + ) diff --git a/rust-mode.el b/rust-mode.el index 05d8528..d890299 100644 --- a/rust-mode.el +++ b/rust-mode.el @@ -41,6 +41,10 @@ (let ((beg-of-symbol (save-excursion (forward-thing 'symbol -1) (point)))) (looking-back rust-re-ident beg-of-symbol))) +(defun rust-looking-back-macro () + "Non-nil if looking back at an ident followed by a !" + (save-excursion (backward-char) (and (= ?! (char-after)) (rust-looking-back-ident)))) + ;; Syntax definitions and helpers (defvar rust-mode-syntax-table (let ((table (make-syntax-table))) @@ -53,6 +57,11 @@ (modify-syntax-entry ?\" "\"" table) (modify-syntax-entry ?\\ "\\" table) + ;; Angle brackets. We suppress this with syntactic fontification when + ;; needed + (modify-syntax-entry ?< "(>" table) + (modify-syntax-entry ?> ")<" table) + ;; Comments (modify-syntax-entry ?/ ". 124b" table) (modify-syntax-entry ?* ". 23" table) @@ -92,6 +101,13 @@ :type 'string :group 'rust-mode) +(defcustom rust-match-angle-brackets t + "Enable angle bracket matching. Attempt to match `<' and `>' where + appropriate." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + (defun rust-paren-level () (nth 0 (syntax-ppss))) (defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss))) (defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss)))) @@ -103,6 +119,15 @@ (rust-rewind-past-str-cmnt)) (if (/= starting (point)) (rust-rewind-irrelevant)))) +(defun rust-in-macro () + (save-excursion + (when (> (rust-paren-level) 0) + (backward-up-list) + (rust-rewind-irrelevant) + (or (rust-looking-back-macro) + (and (rust-looking-back-ident) (save-excursion (backward-sexp) (rust-rewind-irrelevant) (rust-looking-back-str "macro_rules!"))) + (rust-in-macro)) + ))) (defun rust-align-to-expr-after-brace () (save-excursion @@ -342,6 +367,7 @@ "str" "char")) (defconst rust-re-CamelCase "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*") +(defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]") (defun rust-re-word (inner) (concat "\\<" inner "\\>")) (defun rust-re-grab (inner) (concat "\\(" inner "\\)")) (defun rust-re-grabword (inner) (rust-re-grab (rust-re-word inner))) @@ -545,10 +571,362 @@ (and (nth 3 pstate) (wholenump (nth 8 pstate)) (< (nth 8 pstate) (match-beginning 0))) ;; Skip if in a string that isn't starting here ))))))) +(defun rust-syntax-class-before-point () + (when (> (point) 1) + (syntax-class (syntax-after (1- (point)))))) + +(defun rust-rewind-qualified-ident () + (while (rust-looking-back-ident) + (backward-sexp) + (when (save-excursion (rust-rewind-irrelevant) (rust-looking-back-str "::")) + (rust-rewind-irrelevant) + (backward-char 2) + (rust-rewind-irrelevant)))) + +(defun rust-rewind-type-param-list () + (cond + ((and (rust-looking-back-str ">") (equal 5 (rust-syntax-class-before-point))) + (backward-sexp) + (rust-rewind-irrelevant)) + + ;; We need to be able to back up past the Fn(args) -> RT form as well. If + ;; we're looking back at this, we want to end up just after "Fn". + ((member (char-before) '(?\] ?\) )) + (let* ((is-paren (rust-looking-back-str ")")) + (dest (save-excursion + (backward-sexp) + (rust-rewind-irrelevant) + (or + (when (rust-looking-back-str "->") + (backward-char 2) + (rust-rewind-irrelevant) + (when (rust-looking-back-str ")") + (backward-sexp) + (point))) + (and is-paren (point)))))) + (when dest + (goto-char dest)))))) + +(defun rust-rewind-to-decl-name () + "If we are before an ident that is part of a declaration that + can have a where clause, rewind back to just before the name of + the subject of that where clause and return the new point. + Otherwise return nil" + + (let* ((ident-pos (point)) + (newpos (save-excursion + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (cond + ((rust-looking-back-symbols '("fn" "trait" "enum" "struct" "impl" "type")) ident-pos) + + ((equal 5 (rust-syntax-class-before-point)) + (backward-sexp) + (rust-rewind-to-decl-name)) + + ((looking-back "[:,'+=]" (1- (point))) + (backward-char) + (rust-rewind-to-decl-name)) + + ((rust-looking-back-str "->") + (backward-char 2) + (rust-rewind-to-decl-name)) + + ((rust-looking-back-ident) + (rust-rewind-qualified-ident) + (rust-rewind-to-decl-name)))))) + (when newpos (goto-char newpos)) + newpos)) + +(defun rust-is-in-expression-context (token) + "Return t if what comes right after the point is part of an + expression (as opposed to starting a type) by looking at what + comes before. Takes a symbol that roughly indicates what is + after the point. + + This function is used as part of `rust-is-lt-char-operator' as + part of angle bracket matching, and is not intended to be used + outside of this context." + + (save-excursion + (let ((postchar (char-after))) + (rust-rewind-irrelevant) + + ;; A type alias or ascription could have a type param list. Skip backwards past it. + (when (member token '(ambiguous-operator open-brace)) + (rust-rewind-type-param-list)) + + (cond + + ;; Certain keywords always introduce expressions + ((rust-looking-back-symbols '("if" "while" "match" "return" "box" "in")) t) + + ;; "as" introduces a type + ((rust-looking-back-symbols '("as")) nil) + + ;; An open angle bracket never introduces expression context WITHIN the angle brackets + ((and (equal token 'open-brace) (equal postchar ?<)) nil) + + ;; An ident! followed by an open brace is a macro invocation. Consider + ;; it to be an expression. + ((and (equal token 'open-brace) (rust-looking-back-macro)) t) + + ;; An identifier is right after an ending paren, bracket, angle bracket + ;; or curly brace. It's a type if the last sexp was a type. + ((and (equal token 'ident) (equal 5 (rust-syntax-class-before-point))) + (backward-sexp) + (rust-is-in-expression-context 'open-brace)) + + ;; If a "for" appears without a ; or { before it, it's part of an + ;; "impl X for y", so the y is a type. Otherwise it's + ;; introducing a loop, so the y is an expression + ((and (equal token 'ident) (rust-looking-back-symbols '("for"))) + (backward-sexp) + (rust-rewind-irrelevant) + (looking-back "[{;]" (1- (point)))) + + ((rust-looking-back-ident) + (rust-rewind-qualified-ident) + (rust-rewind-irrelevant) + (cond + ((equal token 'open-brace) + ;; We now know we have: + ;; ident <maybe type params> [{([] + ;; where [{([] denotes either a {, ( or [. This character is bound as postchar. + (cond + ;; If postchar is a paren or square bracket, then if the brace is a type if the identifier is one + ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) + + ;; If postchar is a curly brace, the brace can only be a type if + ;; ident2 is the name of an enum, struct or trait being declared. + ;; Note that if there is a -> before the ident then the ident would + ;; be a type but the { is not. + ((equal ?{ postchar) + (not (and (rust-rewind-to-decl-name) + (progn + (rust-rewind-irrelevant) + (rust-looking-back-symbols '("enum" "struct" "trait" "type")))))) + )) + + ((equal token 'ambiguous-operator) + (cond + ;; An ampersand after an ident has to be an operator rather than a & at the beginning of a ref type + ((equal postchar ?&) t) + + ;; A : followed by a type then an = introduces an expression (unless it is part of a where clause of a "type" declaration) + ((and (equal postchar ?=) + (looking-back "[^:]:" (- (point) 2)) + (not (save-excursion (and (rust-rewind-to-decl-name) (progn (rust-rewind-irrelevant) (rust-looking-back-symbols '("type")))))))) + + ;; "let ident =" introduces an expression--and so does "const" and "mut" + ((and (equal postchar ?=) (rust-looking-back-symbols '("let" "const" "mut"))) t) + + ;; As a specific special case, see if this is the = in this situation: + ;; enum EnumName<type params> { Ident = + ;; In this case, this is a c-like enum and despite Ident + ;; representing a type, what comes after the = is an expression + ((and + (> (rust-paren-level) 0) + (save-excursion + (backward-up-list) + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (and + (rust-looking-back-ident) + (progn + (rust-rewind-qualified-ident) + (rust-rewind-irrelevant) + (rust-looking-back-str "enum"))))) + t) + + ;; Otherwise the ambiguous operator is a type if the identifier is a type + ((rust-is-in-expression-context 'ident) t))) + + ((equal token 'colon) + (cond + ;; If we see a ident: not inside any braces/parens, we're at top level. + ;; There are no allowed expressions after colons there, just types. + ((<= (rust-paren-level) 0) nil) + + ;; We see ident: inside a list + ((looking-back "[{,]" (1- (point))) + (backward-up-list) + + ;; If a : appears whose surrounding paren/brackets/braces are + ;; anything other than curly braces, it can't be a field + ;; initializer and must be denoting a type. + (when (looking-at "{") + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (when (rust-looking-back-ident) + ;; We have a context that looks like this: + ;; ident2 <maybe type params> { [maybe paren-balanced code ending in comma] ident1: + ;; the point is sitting just after ident2, and we trying to + ;; figure out if the colon introduces an expression or a type. + ;; The answer is that ident1 is a field name, and what comes + ;; after the colon is an expression, if ident2 is an + ;; expression. + (rust-rewind-qualified-ident) + (rust-is-in-expression-context 'ident)))) + + + ;; Otherwise, if the ident: appeared with anything other than , or { + ;; before it, it can't be part of a struct initializer and therefore + ;; must be denoting a type. + nil + )) + )) + + ;; An operator-like character after a string is indeed an operator + ((and (equal token 'ambiguous-operator) + (member (rust-syntax-class-before-point) '(5 7 15))) t) + + ;; A colon that has something other than an identifier before it is a + ;; type ascription + ((equal token 'colon) nil) + + ;; A :: introduces a type (or module, but not an expression in any case) + ((rust-looking-back-str "::") nil) + + ((rust-looking-back-str ":") + (backward-char) + (rust-is-in-expression-context 'colon)) + + ;; A -> introduces a type + ((rust-looking-back-str "->") nil) + + ;; If we are up against the beginning of a list, or after a comma inside + ;; of one, back up out of it and check what the list itself is + ((or + (equal 4 (rust-syntax-class-before-point)) + (rust-looking-back-str ",")) + (backward-up-list) + (rust-is-in-expression-context 'open-brace)) + + ;; A => introduces an expression + ((rust-looking-back-str "=>") t) + + ;; A == introduces an expression + ((rust-looking-back-str "==") t) + + ;; These operators can introduce expressions or types + ((looking-back "[-+=!?&*]" (1- (point))) + (backward-char) + (rust-is-in-expression-context 'ambiguous-operator)) + + ;; These operators always introduce expressions. (Note that if this + ;; regexp finds a < it must not be an angle bracket, or it'd + ;; have been caught in the syntax-class check above instead of this.) + ((looking-back rust-re-pre-expression-operators (1- (point))) t) + )))) + +(defun rust-is-lt-char-operator () + "Return t if the < sign just after point is an operator rather + than an opening angle bracket, otherwise nil." + + (let ((case-fold-search nil)) + (save-excursion + (rust-rewind-irrelevant) + ;; We are now just after the character syntactically before the <. + (cond + + ;; If we are looking back at a < that is not an angle bracket (but not + ;; two of them) then this is the second < in a bit shift operator + ((and (rust-looking-back-str "<") + (not (equal 4 (rust-syntax-class-before-point))) + (not (rust-looking-back-str "<<")))) + + ;; On the other hand, if we are after a closing paren/brace/bracket it + ;; can only be an operator, not an angle bracket. Likewise, if we are + ;; after a string it's an operator. (The string case could actually be + ;; valid in rust for character literals.) + ((member (rust-syntax-class-before-point) '(5 7 15)) t) + + ;; If we are looking back at an operator, we know that we are at + ;; the beginning of an expression, and thus it has to be an angle + ;; bracket (starting a "<Type as Trait>::" construct.) + ((looking-back rust-re-pre-expression-operators (1- (point))) nil) + + ;; If we are looking back at a keyword, it's an angle bracket + ;; unless that keyword is "self", "true" or "false" + ((rust-looking-back-symbols rust-mode-keywords) + (rust-looking-back-symbols '("self" "true" "false"))) + + ;; If we're looking back at an identifier, this depends on whether + ;; the identifier is part of an expression or a type + ((rust-looking-back-ident) + (backward-sexp) + (or + ;; The special types can't take type param lists, so a < after one is + ;; always an operator + (looking-at (regexp-opt rust-special-types 'symbols)) + + (rust-is-in-expression-context 'ident))) + + ;; Otherwise, assume it's an angle bracket + )))) + +(defun rust-electric-pair-inhibit-predicate-wrap (char) + "Wraps the default `electric-pair-inhibit-predicate' to prevent + inserting a \"matching\" > after a < that would be treated as a + less than sign rather than as an opening angle bracket." + (or + (when (= ?< char) + (save-excursion + (backward-char) + (rust-is-lt-char-operator))) + (funcall (default-value 'electric-pair-inhibit-predicate) char))) + +(defun rust-look-for-non-angle-bracket-lt-gt (bound) + "Find an angle bracket (\"<\" or \">\") that should be part of + a matched pair Relies on the fact that when it finds a < or >, + we have already decided which previous ones are angle brackets + and which ones are not. So this only really works as a + font-lock-syntactic-keywords matcher--it won't work at + arbitrary positions without the earlier parts of the buffer + having already been covered." + + (rust-conditional-re-search-forward + "[<>]" bound + (lambda () + (goto-char (match-beginning 0)) + (cond + ;; If matching is turned off suppress all of them + ((not rust-match-angle-brackets) t) + + ;; We don't take < or > in strings or comments to be angle brackets + ((rust-in-str-or-cmnt) t) + + ;; Inside a macro we don't really know the syntax. Any < or > may be an + ;; angle bracket or it may not. But we know that the other braces have + ;; to balance regardless of the < and >, so if we don't treat any < or > + ;; as angle brackets it won't mess up any paren balancing. + ((rust-in-macro) t) + + ((looking-at "<") + (rust-is-lt-char-operator)) + + ((looking-at ">") + (cond + ;; Don't treat the > in -> or => as an angle bracket + ((member (char-before (point)) '(?- ?=)) t) + + ;; If we are at top level and not in any list, it can't be a closing + ;; angle bracket + ((>= 0 (rust-paren-level)) t) + + ;; Otherwise, treat the > as a closing angle bracket if it would + ;; match an opening one + ((save-excursion + (backward-up-list) + (not (looking-at "<")))))))))) + (defvar rust-mode-font-lock-syntactic-keywords (append ;; Handle raw strings and character literals: - `((rust-look-for-non-standard-string (1 "|" nil t) (4 "_" nil t) (5 "|" nil t) (6 "|" nil t) (7 "\"" nil t) (8 "\"" nil t))))) + `((rust-look-for-non-standard-string (1 "|" nil t) (4 "_" nil t) (5 "|" nil t) (6 "|" nil t) (7 "\"" nil t) (8 "\"" nil t))) + ;; Find where < and > characters represent operators rather than angle brackets: + '((rust-look-for-non-angle-bracket-lt-gt (0 "." t))))) (defun rust-mode-syntactic-face-function (state) "Syntactic face function to distinguish doc comments from other comments." @@ -730,81 +1108,6 @@ 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 - (or (not blink-matching-paren-distance) - (< (- 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 ">" nil)) - (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)) @@ -847,7 +1150,7 @@ This is written mainly to be used as `end-of-defun-function' for Rust." (setq-local beginning-of-defun-function 'rust-beginning-of-defun) (setq-local end-of-defun-function 'rust-end-of-defun) (setq-local parse-sexp-lookup-properties t) - (add-hook 'post-self-insert-hook 'rust-match-angle-bracket-hook)) + (setq-local electric-pair-inhibit-predicate 'rust-electric-pair-inhibit-predicate-wrap)) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) |
