summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rust-mode-tests.el649
-rw-r--r--rust-mode.el457
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))