diff options
| author | Niko Matsakis <niko@alum.mit.edu> | 2015-07-06 15:46:18 -0400 |
|---|---|---|
| committer | Niko Matsakis <niko@alum.mit.edu> | 2015-07-06 15:46:18 -0400 |
| commit | c9c7871f2fc1dde03d8f1d99cfff223fee902377 (patch) | |
| tree | 0a307845df3f08668adf467979195bfc37b06303 | |
| parent | f1b6007ab5c9acb5c2b07452f5f38caf85ec537f (diff) | |
| parent | 07943f0080ebd7cf3ab021f7203e3a424eccd2ce (diff) | |
| download | rust-mode-c9c7871f2fc1dde03d8f1d99cfff223fee902377.tar.gz | |
Merge pull request #79 from MicahChalmer/angle-bracket-madness
Match angle brackets syntactically
| -rw-r--r-- | rust-mode-tests.el | 752 | ||||
| -rw-r--r-- | 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)) |
