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

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

[nongnu] elpa/rust-mode 07943f0 178/486: Perform syntactic angle bracket


From: ELPA Syncer
Subject: [nongnu] elpa/rust-mode 07943f0 178/486: Perform syntactic angle bracket matching
Date: Sat, 7 Aug 2021 09:25:14 -0400 (EDT)

branch: elpa/rust-mode
commit 07943f0080ebd7cf3ab021f7203e3a424eccd2ce
Author: Micah Chalmer <micah@micahchalmer.net>
Commit: Micah Chalmer <micah@micahchalmer.net>

    Perform syntactic angle bracket matching
    
    Add angle brackets to the list of emacs matching characters.  Use
    syntactic fontification to suppress this where it is not appropriate (in
    less than operators, arrows, etc.)  Remove the non-syntactic version
    that was there previously.
    
    For electric-pair-mode, suppress the pairing for < and > characters that
    are not angle brackets.
    
    Because syntax is used for indentation, this fixes some problems with
    it, particularly when attempting to stretch type parameter lists over
    multiple lines.
---
 rust-mode-tests.el | 649 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 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))



reply via email to

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