[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/rust-mode c9c7871 179/486: Merge pull request #79 from Mic
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/rust-mode c9c7871 179/486: Merge pull request #79 from MicahChalmer/angle-bracket-madness |
Date: |
Sat, 7 Aug 2021 09:25:14 -0400 (EDT) |
branch: elpa/rust-mode
commit c9c7871f2fc1dde03d8f1d99cfff223fee902377
Merge: f1b6007 07943f0
Author: Niko Matsakis <niko@alum.mit.edu>
Commit: Niko Matsakis <niko@alum.mit.edu>
Merge pull request #79 from MicahChalmer/angle-bracket-madness
Match angle brackets syntactically
---
rust-mode-tests.el | 752 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
rust-mode.el | 721 +++++++++++++++++++++++++++++++++++++-------------
2 files changed, 1298 insertions(+), 175 deletions(-)
diff --git a/rust-mode-tests.el b/rust-mode-tests.el
index 58fcaaf..4a6cf20 100644
--- a/rust-mode-tests.el
+++ b/rust-mode-tests.el
@@ -362,6 +362,29 @@ use foo::bar::baz;
fn foo() { }
"))
+(ert-deftest font-lock-multi-raw-strings-in-a-row ()
+ (rust-test-font-lock
+ "
+r\"foo\\\", \"bar\", r\"bar\";
+r\"foo\\.\", \"bar\", r\"bar\";
+r\"foo\\..\", \"bar\", r\"foo\\..\\bar\";
+r\"\\\", \"foo\", r\"\\foo\";
+not_a_string();
+
+"
+
+ (apply 'append (mapcar (lambda (s) (list s 'font-lock-string-face))
+ '("r\"foo\\\"" "\"bar\"" "r\"bar\""
+ "r\"foo\\.\"" "\"bar\"" "r\"bar\""
+ "r\"foo\\..\"" "\"bar\"" "r\"foo\\..\\bar\""
+ "r\"\\\"" "\"foo\"" "r\"\\foo\"")))
+ ))
+
+(ert-deftest font-lock-raw-string-after-normal-string-ending-in-r ()
+ (rust-test-font-lock
+ "\"bar\" r\"foo\""
+ '("\"bar\"" font-lock-string-face "r\"foo\"" font-lock-string-face)))
+
(ert-deftest indent-params-no-align ()
(test-indent
"
@@ -687,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)))))
@@ -700,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))
@@ -1280,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)
@@ -1459,3 +1484,728 @@ la la\");
(test-indent
;; 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
+ as matching braces. The list of nonparen-positions asserts
+ specific positions that should NOT be considered to be
+ parens/braces of any kind.
+
+ This does not assert that the `pairs' list is
+ comprehensive--there can be additional pairs that don't appear
+ in the list and the test still passes (as long as none of their
+ positions appear in `nonparen-positions'.)"
+ (with-temp-buffer
+ (rust-mode)
+ (insert content)
+ (font-lock-fontify-buffer)
+ (dolist (pair pairs)
+ (let* ((open-pos (nth 0 pair))
+ (close-pos (nth 1 pair)))
+ (should (equal 4 (syntax-class (syntax-after open-pos))))
+ (should (equal 5 (syntax-class (syntax-after close-pos))))
+ (should (equal (scan-sexps open-pos 1) (+ 1 close-pos)))
+ (should (equal (scan-sexps (+ 1 close-pos) -1) open-pos))))
+ (dolist (nonpar-pos nonparen-positions)
+ (let ((nonpar-syntax-class (syntax-class (syntax-after nonpar-pos))))
+ (should (not (equal 4 nonpar-syntax-class)))
+ (should (not (equal 5 nonpar-syntax-class)))))))
+
+(ert-deftest rust-test-unmatched-single-quote-in-comment-paren-matching ()
+ ;; This was a bug from the char quote handling that affected the paren
+ ;; matching. An unmatched quote char in a comment caused the problems.
+ (rust-test-matching-parens
+ "// If this appeared first in the file...
+\"\\
+{\";
+
+// And the { was not the on the first column:
+ {
+ // This then messed up the paren matching: '\\'
+}
+
+"
+ '((97 150) ;; The { and } at the bottom
+ )))
+
+(ert-deftest rust-test-two-character-quotes-in-a-row ()
+ (with-temp-buffer
+ (rust-mode)
+ (font-lock-fontify-buffer)
+ (insert "'\\n','a', fn")
+ (font-lock-after-change-function 1 12 0)
+
+ (should (equal 'font-lock-string-face (get-text-property 3 'face)))
+ (should (equal nil (get-text-property 5 'face)))
+ (should (equal 'font-lock-string-face (get-text-property 7 'face)))
+ (should (equal nil (get-text-property 9 'face)))
+ (should (equal 'font-lock-keyword-face (get-text-property 12 'face)))
+ )
+ )
+
+(ert-deftest single-quote-null-char ()
+ (rust-test-font-lock
+ "'\\0' 'a' fn"
+ '("'\\0'" font-lock-string-face
+ "'a'" font-lock-string-face
+ "fn" font-lock-keyword-face)))
+
+(ert-deftest r-in-string-after-single-quoted-double-quote ()
+ (rust-test-font-lock
+ "'\"';\n\"r\";\n\"oops\";"
+ '("'\"'" font-lock-string-face
+ "\"r\"" font-lock-string-face
+ "\"oops\"" font-lock-string-face
+ )))
+
+(ert-deftest char-literal-after-quote-in-raw-string ()
+ (rust-test-font-lock
+ "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 7fcc09e..d890299 100644
--- a/rust-mode.el
+++ b/rust-mode.el
@@ -20,6 +20,31 @@
"Set variable VAR to value VAL in current buffer."
(list 'set (list 'make-local-variable (list 'quote var)) val))))
+(defun rust-looking-back-str (str)
+ "Like `looking-back' but for fixed strings rather than regexps (so that it's
not so slow)"
+ (let ((len (length str)))
+ (and (> (point) len)
+ (equal str (buffer-substring-no-properties (- (point) len)
(point))))))
+
+(defun rust-looking-back-symbols (SYMS)
+ "Return non-nil if the point is just after a complete symbol that is a
member of the list of strings SYMS"
+ (save-excursion
+ (let* ((pt-orig (point))
+ (beg-of-symbol (progn (forward-thing 'symbol -1) (point)))
+ (end-of-symbol (progn (forward-thing 'symbol 1) (point))))
+ (and
+ (= end-of-symbol pt-orig)
+ (member (buffer-substring-no-properties beg-of-symbol pt-orig) SYMS)))))
+
+(defun rust-looking-back-ident ()
+ "Non-nil if we are looking backwards at a valid rust identifier"
+ (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)))
@@ -32,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)
@@ -71,17 +101,33 @@
: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))))
(defun rust-rewind-irrelevant ()
(let ((starting (point)))
(skip-chars-backward "[:space:]\n")
- (if (looking-back "\\*/" nil) (backward-char))
+ (if (rust-looking-back-str "*/") (backward-char))
(if (rust-in-str-or-cmnt)
(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
@@ -141,13 +187,13 @@
;;
((skip-dot-identifier
(lambda ()
- (when (looking-back (concat "\\." rust-re-ident) nil)
+ (when (and (rust-looking-back-ident) (save-excursion
(forward-thing 'symbol -1) (= ?. (char-before))))
(forward-thing 'symbol -1)
(backward-char)
(- (current-column) rust-indent-offset)))))
(cond
;; foo.bar(...)
- ((looking-back ")" nil)
+ ((rust-looking-back-str ")")
(backward-list 1)
(funcall skip-dot-identifier))
@@ -271,7 +317,7 @@
;; ..or if the previous line ends with any of these:
;; { ? : ( , ; [ }
;; then we are at the beginning of an expression, so
stay on the baseline...
- (looking-back "[(,:;?[{}]\\|[^|]|" nil)
+ (looking-back "[(,:;?[{}]\\|[^|]|" (- (point) 2))
;; or if the previous line is the end of an
attribute, stay at the baseline...
(progn
(rust-rewind-to-beginning-of-current-level-expr) (looking-at "#")))))
baseline
@@ -321,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)))
@@ -374,111 +421,512 @@
("fn" . font-lock-function-name-face)
("static" . font-lock-constant-face)))))
-(defun rust-extend-region-raw-string ()
+(defun rust-font-lock-extend-region ()
"Extend the region given by `font-lock-beg' and `font-lock-end'
- to include the beginning of a string if it includes part of it.
- Adjusts to include the r[#] of a raw string as well."
-
- (let* ((orig-beg font-lock-beg)
- (orig-end font-lock-end)
- (beg-ppss (syntax-ppss font-lock-beg))
- (beg-in-str (nth 3 beg-ppss))
- (end-ppss (syntax-ppss font-lock-end))
- (end-in-str (nth 3 end-ppss)))
-
- (when (and beg-in-str (> font-lock-beg (nth 8 beg-ppss)))
- (setq font-lock-beg str-beg)
- (while (equal ?# (char-before font-lock-beg))
- (setq font-lock-beg (1- font-lock-beg)))
- (when (equal ?r (char-before font-lock-beg))
- (setq font-lock-beg (1- font-lock-beg))))
-
- (when end-in-str
- (save-excursion
- (goto-char (nth 8 end-ppss))
- (ignore-errors (forward-sexp))
- (setq font-lock-end (max font-lock-end (point)))))
-
- ;; If we have the beginning of a raw string in the region, make sure we
have the end of
- ;; it.
- (when (or beg-in-str end-in-str)
- (save-excursion
- (goto-char font-lock-beg)
- (while (and (< (point) font-lock-end) (ignore-errors
(rust-look-for-raw-string (buffer-end 1)))))
- (setq font-lock-end (max font-lock-end (point)))))
+ to include the beginning of a string or comment if it includes
+ part of it. Adjusts to include the r[#] of a raw string as
+ well."
+
+ (let ((orig-beg font-lock-beg)
+ (orig-end font-lock-end))
+ (cond
+ ;; If we are not syntactically fontified yet, we cannot correctly cover
+ ;; anything less than the full buffer. The syntactic fontification
+ ;; modifies the syntax, so until it's done we can't use the syntax to
+ ;; determine what to fontify.
+ ((< (or font-lock-syntactically-fontified 0) font-lock-end)
+ (setq font-lock-beg 1)
+ (setq font-lock-end (buffer-end 1)))
+
+ ((let* ((beg-ppss (syntax-ppss font-lock-beg))
+ (beg-in-cmnt (and (nth 4 beg-ppss) (nth 8 beg-ppss)))
+ (beg-in-str (nth 3 beg-ppss))
+ (end-ppss (syntax-ppss font-lock-end))
+ (end-in-str (nth 3 end-ppss)))
+
+ (when (and beg-in-str (> font-lock-beg (nth 8 beg-ppss)))
+ (setq font-lock-beg (nth 8 beg-ppss))
+ (while (equal ?# (char-before font-lock-beg))
+ (setq font-lock-beg (1- font-lock-beg)))
+ (when (equal ?r (char-before font-lock-beg))
+ (setq font-lock-beg (1- font-lock-beg))))
+
+ (when (and beg-in-cmnt (> font-lock-beg beg-in-cmnt))
+ (setq font-lock-beg beg-in-cmnt))
+
+ (when end-in-str
+ (save-excursion
+ (goto-char (nth 8 end-ppss))
+ (ignore-errors (forward-sexp))
+ (setq font-lock-end (max font-lock-end (point)))))
+
+ ;; If we have the beginning of a raw string in the region, make sure
we have the end of
+ ;; it.
+ (when (or beg-in-str end-in-str)
+ (save-excursion
+ (goto-char font-lock-beg)
+ (while (and (< (point) font-lock-end) (ignore-errors
(rust-look-for-raw-string (buffer-end 1)))))
+ (setq font-lock-end (max font-lock-end (point)))))
+ )))
(or (/= font-lock-beg orig-beg)
(/= font-lock-end orig-end))
))
-(defun rust-look-for-raw-string (bound)
- ;; Find a raw string, but only if it's not in the middle of another string or
- ;; a comment
-
- (let* ((raw-str-regexp
- (rx
- (seq
- ;; The "r" starts the raw string. Capture it as group 1 to mark
it as such syntactically:
- (group "r")
-
- ;; Then either:
- (or
- ;; a sequence at least one "#" (followed by quote). Capture all
- ;; but the last "#" as group 2 for this case.
- (seq (group (* "#")) "#\"")
-
- ;; ...or a quote without any "#". Capture it as group 3. This is
- ;; used later to match the opposite quote only if this capture
- ;; occurred
- (group "\""))
-
- ;; The contents of the string:
- (*? anything)
-
- ;; If there are any backslashes at the end of the string, capture
- ;; them as group 4 so we can suppress the normal escape syntax
- ;; parsing:
- (group (* "\\"))
-
- ;; Then the end of the string--the backreferences ensure that we
- ;; only match the kind of ending that corresponds to the beginning
- ;; we had:
- (or
- ;; There were "#"s - capture the last one as group 5 to mark it as
- ;; the end of the string:
- (seq "\"" (backref 2) (group "#"))
-
- ;; No "#"s - capture the ending quote (using a backref to group 3,
- ;; so that we can't match a quote if we had "#"s) as group 6
- (group (backref 3))))))
- ;; If it matches, it ends up with the starting character of the string
- ;; as group 1, any ending backslashes as group 4, and the ending
- ;; character as either group 5 or group 6.
-
+(defun rust-conditional-re-search-forward (regexp bound condition)
+ ;; Search forward for regexp (with bound). If found, call condition and
return the found
+ ;; match only if it returns true.
+ (let* (found
+ found-ret-list
(ret-list (save-excursion
- (let* ((match-end (re-search-forward raw-str-regexp bound
t))
- (ret-list (and match-end (list match-end
(match-beginning 0) (match-data) (point)))))
- (when (and ret-list
- (save-excursion
- (goto-char (nth 1 ret-list))
- (not (rust-in-str-or-cmnt))))
- ret-list)))))
+ (while (and (not found) (re-search-forward regexp bound
t))
+ (setq
+ found-ret-list (list (point) (match-data))
+ found (save-match-data (save-excursion (ignore-errors
(funcall condition)))))
+ ;; If the condition filters out a match, need to search
+ ;; again just after its beginning. This will allow
+ ;; cases such as:
+ ;; "bar" r"foo"
+ ;; where the filtered out search (r" r") should not
+ ;; prevent finding another one that begins in the middle
+ ;; of it (r"foo")
+ (when (not found)
+ (goto-char (1+ (match-beginning 0))))
+ )
+ (when found found-ret-list))))
(when ret-list
- (goto-char (nth 3 ret-list))
- (set-match-data (nth 2 ret-list))
+ (goto-char (nth 0 ret-list))
+ (set-match-data (nth 1 ret-list))
(nth 0 ret-list))))
+(defun rust-look-for-non-standard-string (bound)
+ ;; Find a raw string or character literal, but only if it's not in the middle
+ ;; of another string or a comment.
+
+ (let* ((non-standard-str-regexp
+ (rx
+ (or
+ ;; Raw string: if it matches, it ends up with the starting
character
+ ;; of the string as group 1, any ending backslashes as group 4, and
+ ;; the ending character as either group 5 or group 6.
+ (seq
+ ;; The "r" starts the raw string. Capture it as group 1 to mark
it as such syntactically:
+ (group "r")
+
+ ;; Then either:
+ (or
+ ;; a sequence at least one "#" (followed by quote). Capture all
+ ;; but the last "#" as group 2 for this case.
+ (seq (group (* "#")) "#\"")
+
+ ;; ...or a quote without any "#". Capture it as group 3. This is
+ ;; used later to match the opposite quote only if this capture
+ ;; occurred
+ (group "\""))
+
+ ;; The contents of the string:
+ (*? anything)
+
+ ;; If there are any backslashes at the end of the string, capture
+ ;; them as group 4 so we can suppress the normal escape syntax
+ ;; parsing:
+ (group (* "\\"))
+
+ ;; Then the end of the string--the backreferences ensure that we
+ ;; only match the kind of ending that corresponds to the beginning
+ ;; we had:
+ (or
+ ;; There were "#"s - capture the last one as group 5 to mark it
as
+ ;; the end of the string:
+ (seq "\"" (backref 2) (group "#"))
+
+ ;; No "#"s - capture the ending quote (using a backref to group
3,
+ ;; so that we can't match a quote if we had "#"s) as group 6
+ (group (backref 3))))
+
+ ;; Character literal: match the beginning ' of a character literal
+ ;; as group 7, and the ending one as group 8
+ (seq
+ (group "'")
+ (or
+ (seq
+ "\\"
+ (or
+ (: "U" (= 8 xdigit))
+ (: "u" (= 4 xdigit))
+ (: "x" (= 2 xdigit))
+ (any "'nrt0\"\\")))
+ (not (any "'\\"))
+ )
+ (group "'"))
+ )
+ )))
+ (rust-conditional-re-search-forward
+ non-standard-str-regexp bound
+ (lambda ()
+ (let ((pstate (syntax-ppss (match-beginning 0))))
+ (not
+ (or
+ (nth 4 pstate) ;; Skip if in a comment
+ (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 single quoted character literals:
- (mapcar (lambda (re) (list re '(1 "\"") '(2 "\"")))
- '("\\('\\)[^']\\('\\)"
- "\\('\\)\\\\['nrt\"\\]\\('\\)"
- "\\('\\)\\\\x[[:xdigit:]]\\{2\\}\\('\\)"
- "\\('\\)\\\\u[[:xdigit:]]\\{4\\}\\('\\)"
- "\\('\\)\\\\U[[:xdigit:]]\\{8\\}\\('\\)"))
- ;; Handle raw strings:
- `((rust-look-for-raw-string (1 "|") (4 "_" nil t) (5 "|" nil t) (6 "|" nil
t)))))
+ ;; 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)))
+ ;; 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."
@@ -660,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))
@@ -749,7 +1122,7 @@ This is written mainly to be used as
`end-of-defun-function' for Rust."
(setq-local indent-line-function 'rust-mode-indent-line)
;; Fonts
- (add-to-list 'font-lock-extend-region-functions
'rust-extend-region-raw-string)
+ (add-to-list 'font-lock-extend-region-functions
'rust-font-lock-extend-region)
(setq-local font-lock-defaults '(rust-mode-font-lock-keywords
nil nil nil nil
(font-lock-syntactic-keywords .
rust-mode-font-lock-syntactic-keywords)
@@ -777,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))
- [nongnu] elpa/rust-mode 7cb68f7 088/486: Add new keywords (particularly `where` & `virtual`) to editor modes., (continued)
- [nongnu] elpa/rust-mode 7cb68f7 088/486: Add new keywords (particularly `where` & `virtual`) to editor modes., ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode efbc810 089/486: Replaced some TODO by FIXME, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 13c7e25 105/486: Ignore byte-compiled files, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 3878cc6 107/486: Allow user to override which emacs is used in run_rust_emacs_tests.sh, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 6b57bbf 117/486: Make features (and their tests) work on emacs 23, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 4d633fc 125/486: Merge pull request #32 from MicahChalmer/raw-string-handling, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 8d99bf8 126/486: Merge pull request #31 from pnkfelix/more-robust-test-driver, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode e16c37c 161/486: Remove Marmalade from the README, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode ee171df 172/486: test $EMACS directly rather than using "which", ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode d1ed015 173/486: Distinguish face for doc-comments, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode c9c7871 179/486: Merge pull request #79 from MicahChalmer/angle-bracket-madness,
ELPA Syncer <=
- [nongnu] elpa/rust-mode ee564d5 185/486: Merge pull request #85 from talchas/master, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 99b128c 187/486: Fix slowness in angle bracket matching, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 5e77aed 191/486: Correcting highlighting of capitals in function names., ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 5e51aaa 194/486: Merge pull request #69 from tromey/safe-local-variables, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 012537b 195/486: remove byte-compiler warnings and prevent future ones, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 866df37 196/486: make rust-mode use lexical binding, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 6252cf1 217/486: Fix indentation of closing delimiters, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode dd6d417 215/486: Fix type annotations incorrectly highlighted as modules., ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode 86d636a 223/486: Remove duplicate keywords, ELPA Syncer, 2021/08/07
- [nongnu] elpa/rust-mode b1cca0f 222/486: Merge pull request #72 from birkenfeld/builtin-face, ELPA Syncer, 2021/08/07