pax_global_header00006660000000000000000000000064133145351410014512gustar00rootroot0000000000000052 comment=106aeab800fb3404baf231845d3e3549ec235afa rust-mode-0.4.0/000077500000000000000000000000001331453514100134325ustar00rootroot00000000000000rust-mode-0.4.0/.gitignore000066400000000000000000000000061331453514100154160ustar00rootroot00000000000000*.elc rust-mode-0.4.0/.travis.yml000066400000000000000000000012331331453514100155420ustar00rootroot00000000000000language: generic env: matrix: - EMACS=emacs24 - EMACS=emacs-snapshot install: - if [ "$EMACS" = 'emacs24' ]; then sudo add-apt-repository -y ppa:cassou/emacs && sudo apt-get -qq update && sudo apt-get -qq -f install && sudo apt-get -qq install emacs24 emacs24-el; fi - if [ "$EMACS" = 'emacs-snapshot' ]; then sudo add-apt-repository -y ppa:ubuntu-elisp/ppa && sudo apt-get -qq update && sudo apt-get -qq -f install && sudo apt-get -qq install emacs-snapshot && sudo apt-get -qq install emacs-snapshot-el; fi script: - ./run_rust_emacs_tests.sh notifications: email: false rust-mode-0.4.0/LICENSE-APACHE000066400000000000000000000251371331453514100153660ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rust-mode-0.4.0/LICENSE-MIT000066400000000000000000000020571331453514100150720ustar00rootroot00000000000000Copyright (c) 2015 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rust-mode-0.4.0/README.md000066400000000000000000000060461331453514100147170ustar00rootroot00000000000000`rust-mode`: A major Emacs mode for editing Rust source code ============================================================ `rust-mode` makes editing [Rust](http://rust-lang.org) code with Emacs enjoyable. `rust-mode` requires Emacs 24 or later. ## Installation ### Manual Installation To install manually, check out this repository and add this to your `.emacs` file: ```lisp (add-to-list 'load-path "/path/to/rust-mode/") (autoload 'rust-mode "rust-mode" nil t) (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) ``` This associates `rust-mode` with `.rs` files. To enable it explicitly, do M-x rust-mode. ### `package.el` installation via MELPA It can be more convenient to use Emacs's package manager to handle installation for you if you use many elisp libraries. If you have `package.el` but haven't added MELPA, the community package source, yet, add this to `~/.emacs.d/init.el`: ```lisp (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) ``` Then do this to load the package listing: * M-x eval-buffer * M-x package-refresh-contents #### MELPA stable MELPA stable only updates when a new version tag is created. There isn't a specified frequency for this project to release versions; if you are a MELPA stable user and feel that the last release tag is too far out of date, open a pull request that updates the version header in `rust-mode.el` and note that a new version tag should be added when the request is merged. See [Pull Request #178](https://github.com/rust-lang/rust-mode/pull/178) (the first such pull request)) for an example of this. #### Install `rust-mode` One you have `package.el`, you can install `rust-mode` or any other modes by choosing them from a list: * M-x package-list-packages Now, to install packages, move your cursor to them and press i. This will mark the packages for installation. When you're done with marking, press x, and ELPA will install the packages for you (under `~/.emacs.d/elpa/`). * or using M-x package-install rust-mode ### Package installation on Debian testing or unstable ```bash apt install elpa-rust-mode ``` ### Tests via ERT The file `rust-mode-tests.el` contains tests that can be run via [ERT](http://www.gnu.org/software/emacs/manual/html_node/ert/index.html). You can use `run_rust_emacs_tests.sh` to run them in batch mode, if you set the environment variable EMACS to a program that runs emacs. ## Features ### Formatting with [rustfmt][rfmt] The `rust-format-buffer` function will format your code with [rustfmt][rfmt] if installed. By default, this is bound to `C-c C-f`. Placing `(setq rust-format-on-save t)` in your `~/.emacs` will enable automatic running of `rust-format-buffer` when you save a buffer. [rfmt]: https://crates.io/crates/rustfmt/ ## License `rust-mode` is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE) for details. rust-mode-0.4.0/run_rust_emacs_tests.sh000077500000000000000000000024311331453514100202440ustar00rootroot00000000000000#!/bin/sh # Copyright 2014 The Rust Project Developers. See the COPYRIGHT # file at the top-level directory of this distribution and at # http://rust-lang.org/COPYRIGHT. # # Licensed under the Apache License, Version 2.0 or the MIT license # , at your # option. This file may not be copied, modified, or distributed # except according to those terms. # # This runs the test for emacs rust-mode. # Either $EMACS must be set, or it must be possible to find emacs via PATH. if [ -z "$EMACS" ]; then EMACS=emacs fi $EMACS --batch || { echo "You must set EMACS to a program that runs emacs." exit 1 } $( $EMACS -batch > /dev/null 2>&1 ) || { echo "Your emacs command ($EMACS) does not run properly." exit 2 }; $( $EMACS -batch --eval "(require 'ert)" > /dev/null 2>&1 ) || { echo 'You must install the `ert` dependency; see README.md' exit 3 }; warnings="$( $EMACS -Q -batch -f batch-byte-compile rust-mode.el 2>&1 | grep -v '^Wrote ' )" if [ -n "$warnings" ]; then echo "Byte-compilation failed:" echo "$warnings" exit 4 else echo "Byte-compilation passed." fi $EMACS -batch -l rust-mode.el -l rust-mode-tests.el -f ert-run-tests-batch-and-exit rust-mode-0.4.0/rust-mode-tests.el000066400000000000000000002442451331453514100170460ustar00rootroot00000000000000;;; rust-mode-tests.el --- ERT tests for rust-mode.el (require 'rust-mode) (require 'ert) (require 'cl) (require 'imenu) (setq rust-test-fill-column 32) (defun rust-compare-code-after-manip (original point-pos manip-func expected got) (equal expected got)) (defun rust-test-explain-bad-manip (original point-pos manip-func expected got) (if (equal expected got) nil (list ;; The (goto-char) and (insert) business here is just for ;; convenience--after an error, you can copy-paste that into emacs eval to ;; insert the bare strings into a buffer "Rust code was manipulated wrong after:" `(insert ,original) `(goto-char ,point-pos) 'expected `(insert ,expected) 'got `(insert ,got) (loop for i from 0 to (max (length original) (length expected)) for oi = (if (< i (length got)) (elt got i)) for ei = (if (< i (length expected)) (elt expected i)) while (equal oi ei) finally return `(first-difference-at (goto-char ,(+ 1 i)) expected ,(char-to-string ei) got ,(char-to-string oi)))))) (put 'rust-compare-code-after-manip 'ert-explainer 'rust-test-explain-bad-manip) (defun rust-test-manip-code (original point-pos manip-func expected) (with-temp-buffer (rust-mode) (insert original) (goto-char point-pos) (funcall manip-func) (should (rust-compare-code-after-manip original point-pos manip-func expected (buffer-string))))) (defun test-fill-paragraph (unfilled expected &optional start-pos end-pos) "We're going to run through many scenarios here--the point should be able to be anywhere from the start-pos (defaults to 1) through end-pos (defaults to the length of what was passed in) and (fill-paragraph) should return the same result. It should also work with fill-region from start-pos to end-pos. Also, the result should be the same regardless of whether the code is at the beginning or end of the file. (If you're not careful, that can make a difference.) So we test each position given above with the passed code at the beginning, the end, neither and both. So we do this a total of 1 + (end-pos - start-pos)*4 times. Oy." (let* ((start-pos (or start-pos 1)) (end-pos (or end-pos (length unfilled))) (padding "\n \n") (padding-len (length padding))) (loop for pad-at-beginning from 0 to 1 do (loop for pad-at-end from 0 to 1 with padding-beginning = (if (= 0 pad-at-beginning) "" padding) with padding-end = (if (= 0 pad-at-end) "" padding) with padding-adjust = (* padding-len pad-at-beginning) with padding-beginning = (if (= 0 pad-at-beginning) "" padding) with padding-end = (if (= 0 pad-at-end) "" padding) ;; If we're adding space to the beginning, and our start position ;; is at the very beginning, we want to test within the added space. ;; Otherwise adjust the start and end for the beginning padding. with start-pos = (if (= 1 start-pos) 1 (+ padding-adjust start-pos)) with end-pos = (+ end-pos padding-adjust) do (loop for pos from start-pos to end-pos do (rust-test-manip-code (concat padding-beginning unfilled padding-end) pos (lambda () (let ((fill-column rust-test-fill-column)) (fill-paragraph))) (concat padding-beginning expected padding-end))))) ;; In addition to all the fill-paragraph tests, check that it works using fill-region (rust-test-manip-code unfilled start-pos (lambda () (let ((fill-column rust-test-fill-column)) (fill-region start-pos end-pos))) expected) )) (ert-deftest fill-paragraph-top-level-multi-line-style-doc-comment-second-line () (test-fill-paragraph "/** * This is a very very very very very very very long string */" "/** * This is a very very very very * very very very long string */")) (ert-deftest fill-paragraph-top-level-multi-line-style-doc-comment-first-line () (test-fill-paragraph "/** This is a very very very very very very very long string */" "/** This is a very very very * very very very very long * string */")) (ert-deftest fill-paragraph-multi-paragraph-multi-line-style-doc-comment () (let ((multi-paragraph-unfilled "/** * This is the first really really really really really really really long paragraph * * This is the second really really really really really really long paragraph */")) (test-fill-paragraph multi-paragraph-unfilled "/** * This is the first really * really really really really * really really long paragraph * * This is the second really really really really really really long paragraph */" 1 89) (test-fill-paragraph multi-paragraph-unfilled "/** * This is the first really really really really really really really long paragraph * * This is the second really * really really really really * really long paragraph */" 90))) (ert-deftest fill-paragraph-multi-paragraph-single-line-style-doc-comment () (let ((multi-paragraph-unfilled "/// This is the first really really really really really really really long paragraph /// /// This is the second really really really really really really long paragraph")) (test-fill-paragraph multi-paragraph-unfilled "/// This is the first really /// really really really really /// really really long paragraph /// /// This is the second really really really really really really long paragraph" 1 86) (test-fill-paragraph multi-paragraph-unfilled "/// This is the first really really really really really really really long paragraph /// /// This is the second really /// really really really really /// really long paragraph" 87))) (ert-deftest fill-paragraph-multi-paragraph-single-line-style-indented () (test-fill-paragraph " // This is the first really really really really really really really long paragraph // // This is the second really really really really really really long paragraph" " // This is the first really // really really really // really really really // long paragraph // // This is the second really really really really really really long paragraph" 1 89)) (ert-deftest fill-paragraph-multi-line-style-comment () (test-fill-paragraph "/* This is a very very very very very very very very long string */" "/* This is a very very very very * very very very very long * string */")) (ert-deftest fill-paragraph-multi-line-style-inner-doc-comment () (test-fill-paragraph "/*! This is a very very very very very very very long string */" "/*! This is a very very very * very very very very long * string */")) (ert-deftest fill-paragraph-single-line-style-inner-doc-comment () (test-fill-paragraph "//! This is a very very very very very very very long string" "//! This is a very very very //! very very very very long //! string")) (ert-deftest fill-paragraph-prefixless-multi-line-doc-comment () (test-fill-paragraph "/** This is my summary. Blah blah blah blah blah. Dilly dally dilly dally dilly dally doo. This is some more text. Fee fie fo fum. Humpty dumpty sat on a wall. */" "/** This is my summary. Blah blah blah blah blah. Dilly dally dilly dally dilly dally doo. This is some more text. Fee fie fo fum. Humpty dumpty sat on a wall. */" 4 90)) (ert-deftest fill-paragraph-with-no-space-after-star-prefix () (test-fill-paragraph "/** *This is a very very very very very very very long string */" "/** *This is a very very very very *very very very long string */")) (ert-deftest fill-paragraph-single-line-style-with-code-before () (test-fill-paragraph "fn foo() { } /// This is my comment. This is more of my comment. This is even more." "fn foo() { } /// This is my comment. This is /// more of my comment. This is /// even more." 14)) (ert-deftest fill-paragraph-single-line-style-with-code-after () (test-fill-paragraph "/// This is my comment. This is more of my comment. This is even more. fn foo() { }" "/// This is my comment. This is /// more of my comment. This is /// even more. fn foo() { }" 1 73)) (ert-deftest fill-paragraph-single-line-style-code-before-and-after () (test-fill-paragraph "fn foo() { } /// This is my comment. This is more of my comment. This is even more. fn bar() { }" "fn foo() { } /// This is my comment. This is /// more of my comment. This is /// even more. fn bar() { }" 14 85)) (defun test-auto-fill (initial position inserted expected) (rust-test-manip-code initial position (lambda () (unwind-protect (progn (let ((fill-column rust-test-fill-column)) (auto-fill-mode) (goto-char position) (insert inserted) (syntax-ppss-flush-cache 1) (funcall auto-fill-function))) (auto-fill-mode t))) expected)) (ert-deftest auto-fill-multi-line-doc-comment () (test-auto-fill "/** * */" 7 " This is a very very very very very very very long string" "/** * This is a very very very very * very very very long string */")) (ert-deftest auto-fill-single-line-doc-comment () (test-auto-fill "/// This is the first really /// really really really really /// really really long paragraph /// /// " 103 "This is the second really really really really really really long paragraph" "/// This is the first really /// really really really really /// really really long paragraph /// /// This is the second really /// really really really really /// really long paragraph" )) (ert-deftest auto-fill-multi-line-prefixless () (test-auto-fill "/* */" 4 "This is a very very very very very very very long string" "/* This is a very very very very very very very long string */" )) (defun test-indent (indented &optional deindented) (let ((deindented (or deindented (replace-regexp-in-string "^[[:blank:]]*" " " indented)))) (rust-test-manip-code deindented 1 (lambda () (indent-region 1 (+ 1 (buffer-size)))) indented))) (ert-deftest indent-struct-fields-aligned () (test-indent " struct Foo { bar: i32, baz: i32 } struct Blah {x:i32, y:i32, z:String")) (ert-deftest indent-doc-comments () (test-indent " /** * This is a doc comment * */ /// So is this fn foo() { /*! * this is a nested doc comment */ \n //! And so is this }")) (ert-deftest indent-inside-braces () (test-indent " // struct fields out one level: struct foo { a:i32, // comments too b:char } fn bar(x:Box) { // comment here should not affect the next indent bla(); bla(); }")) (ert-deftest indent-top-level () (test-indent " // Everything here is at the top level and should not be indented #[attrib] mod foo; pub static bar = Quux{a: b()} 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 " // Indent out one level because no params appear on the first line fn xyzzy( a:i32, b:char) { } fn abcdef( a:i32, b:char) -> char { }")) (ert-deftest indent-params-align () (test-indent " // Align the second line of params to the first fn foo(a:i32, b:char) { } fn bar( a:i32, b:char) -> i32 { } fn baz( a:i32, // should work with a comment here b:char) -> i32 { } ")) (ert-deftest indent-open-after-arrow () (test-indent " // Indent function body only one level after `-> {` fn foo1(a:i32, b:char) -> i32 { let body; } fn foo2(a:i32, b:char) -> i32 { let body; } fn foo3(a:i32, b:char) -> i32 { let body; } fn foo4(a:i32, b:char) -> i32 where i32:A { let body; } ")) (ert-deftest indent-body-after-where () (let ((rust-indent-where-clause t)) (test-indent " fn foo1(a: A, b: B) -> A where A: Clone + Default, B: Eq { let body; Foo { bar: 3 } } fn foo2(a: A, b: B) -> A where A: Clone + Default, B: Eq { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-clauses-style1a () (let ((rust-indent-where-clause t)) (test-indent " fn foo1a(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-clauses-style1b () (let ((rust-indent-where-clause t)) (test-indent " fn foo1b(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-clauses-style2a () (test-indent " fn foo2a(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } ")) (ert-deftest indent-align-where-clauses-style2b () (test-indent " fn foo2b(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } ")) (ert-deftest indent-align-where-clauses-style3a () (test-indent " fn foo3a(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } ")) (ert-deftest indent-align-where-clauses-style3b () (test-indent " fn foo3b(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } ")) (ert-deftest indent-align-where-clauses-style4a () (let ((rust-indent-where-clause nil)) (test-indent " fn foo4a(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-clauses-style4b () (let ((rust-indent-where-clause nil)) (test-indent " fn foo4b(a: A, b: B, c: C) -> D where A: Clone + Default, B: Eq, C: PartialEq, D: PartialEq { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-clauses-impl-example () (let ((rust-indent-where-clause t)) (test-indent " impl<'a, K, Q: ?Sized, V, S> Index<&'a Q> for HashMap where K: Eq + Hash + Borrow, Q: Eq + Hash, S: HashState, { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-clauses-first-line () (let ((rust-indent-where-clause t)) (test-indent "fn foo1(a: A, b: B) -> A where A: Clone + Default, B: Eq { let body; Foo { bar: 3 } } "))) (ert-deftest indent-align-where-in-comment1 () (test-indent "/// - there must not exist an edge U->V in the graph where: #[derive(Clone, PartialEq, Eq)] pub struct Region { // <-- this should be flush with left margin! entry: BasicBlockIndex, leaves: BTreeMap, } ")) (ert-deftest indent-align-where-in-comment2 () (let ((rust-indent-where-clause t)) (test-indent "fn foo(f:F, g:G) where F:Send, // where G:Sized { let body; } "))) (ert-deftest indent-align-where-in-comment3 () (let ((rust-indent-where-clause t)) (test-indent "fn foo(f:F, g:G) where F:Send, // where F:ThisIsNotActualCode, G:Sized { let body; } "))) (ert-deftest indent-square-bracket-alignment () (test-indent " fn args_on_the_next_line( // with a comment a:i32, b:String) { let aaaaaa = [ 1, 2, 3]; let bbbbbbb = [1, 2, 3, 4, 5, 6]; let ccc = [ 10, 9, 8, 7, 6, 5]; } ")) (ert-deftest indent-closing-square-bracket () (test-indent "fn blergh() { let list = vec![ 1, 2, 3, ]; }")) (ert-deftest indent-closing-paren () (test-indent "fn blergh() { call( a, function ); }")) (ert-deftest indent-nested-fns () (test-indent " fn nexted_fns(a: fn(b:i32, c:char) -> i32, d: i32) -> u128 { 0 } " )) (ert-deftest indent-multi-line-expr () (test-indent " fn foo() { x(); let a = b(); } " )) (ert-deftest indent-match () (test-indent " fn foo() { match blah { Pattern => stuff(), _ => whatever } } " )) (ert-deftest indent-match-multiline-pattern () (test-indent " fn foo() { match blah { Pattern | Pattern2 => { hello() }, _ => whatever } } " )) (ert-deftest indent-indented-match () (test-indent " fn foo() { let x = match blah { Pattern | Pattern2 => { hello() }, _ => whatever }; y(); } " )) (ert-deftest indent-curly-braces-within-parens () (test-indent " fn foo() { let x = foo(bar(|x| { only_one_indent_here(); })); y(); } " )) (ert-deftest indent-weirdly-indented-block () (rust-test-manip-code " fn foo() { { this_block_is_over_to_the_left_for_some_reason(); } } " 16 #'indent-for-tab-command " fn foo() { { this_block_is_over_to_the_left_for_some_reason(); } } " )) (ert-deftest indent-multi-line-attrib () (test-indent " #[attrib( this, that, theotherthing)] fn function_with_multiline_attribute() {} " )) ;; Make sure that in effort to cover match patterns we don't mistreat || or expressions (ert-deftest indent-nonmatch-or-expression () (test-indent " fn foo() { let x = foo() || bar(); } " )) ;; Closing braces in single char literals and strings should not confuse the indentation (ert-deftest indent-closing-braces-in-char-literals () (test-indent " fn foo() { { bar('}'); } { bar(']'); } { bar(')'); } } " )) ;; This is a test for #103: a comment after the last struct member that does ;; not have a trailing comma. The comment used to be indented one stop too ;; far. (ert-deftest indent-comment-after-last-struct-member () (test-indent " struct A { x: u8 // comment } struct A { x: u8 /* comment */ } " )) (setq rust-test-motion-string " fn fn1(arg: i32) -> bool { let x = 5; let y = b(); true } fn fn2(arg: i32) -> bool { let x = 5; let y = b(); true } pub fn fn3(arg: i32) -> bool { let x = 5; let y = b(); true } struct Foo { x: i32 } " rust-test-region-string rust-test-motion-string rust-test-indent-motion-string " fn blank_line(arg:i32) -> bool { } fn indenting_closing_brace() { if(true) { } } fn indenting_middle_of_line() { if(true) { push_me_out(); } else { pull_me_back_in(); } } fn indented_already() { // The previous line already has its spaces } " ;; Symbol -> (line column) rust-test-positions-alist '((start-of-fn1 (2 0)) (start-of-fn1-middle-of-line (2 15)) (middle-of-fn1 (3 7)) (end-of-fn1 (6 0)) (between-fn1-fn2 (7 0)) (start-of-fn2 (8 0)) (middle-of-fn2 (10 4)) (before-start-of-fn1 (1 0)) (after-end-of-fn2 (13 0)) (beginning-of-fn3 (14 0)) (middle-of-fn3 (16 4)) (middle-of-struct (21 10)) (before-start-of-struct (19 0)) (after-end-of-struct (23 0)) (blank-line-indent-start (3 0)) (blank-line-indent-target (3 4)) (closing-brace-indent-start (8 1)) (closing-brace-indent-target (8 5)) (middle-push-indent-start (13 2)) (middle-push-indent-target (13 9)) (after-whitespace-indent-start (13 1)) (after-whitespace-indent-target (13 8)) (middle-pull-indent-start (15 19)) (middle-pull-indent-target (15 12)) (blank-line-indented-already-bol-start (20 0)) (blank-line-indented-already-bol-target (20 4)) (blank-line-indented-already-middle-start (20 2)) (blank-line-indented-already-middle-target (20 4)) (nonblank-line-indented-already-bol-start (21 0)) (nonblank-line-indented-already-bol-target (21 4)) (nonblank-line-indented-already-middle-start (21 2)) (nonblank-line-indented-already-middle-target (21 4)))) (defun rust-get-buffer-pos (pos-symbol) "Get buffer position from POS-SYMBOL. POS-SYMBOL is a symbol found in `rust-test-positions-alist'. Convert the line-column information from that list into a buffer position value." (interactive "P") (let* ( (line-and-column (cadr (assoc pos-symbol rust-test-positions-alist))) (line (nth 0 line-and-column)) (column (nth 1 line-and-column))) (save-excursion (goto-line line) (move-to-column column) (point)))) ;;; FIXME: Maybe add an ERT explainer function (something that shows the ;;; surrounding code of the final point, not just the position). (defun rust-test-motion (source-code init-pos final-pos manip-func &rest args) "Test that MANIP-FUNC moves point from INIT-POS to FINAL-POS. If ARGS are provided, send them to MANIP-FUNC. INIT-POS, FINAL-POS are position symbols found in `rust-test-positions-alist'." (with-temp-buffer (rust-mode) (insert source-code) (goto-char (rust-get-buffer-pos init-pos)) (apply manip-func args) (should (equal (point) (rust-get-buffer-pos final-pos))))) (defun rust-test-region (source-code init-pos reg-beg reg-end manip-func &rest args) "Test that MANIP-FUNC marks region from REG-BEG to REG-END. INIT-POS is the initial position of point. If ARGS are provided, send them to MANIP-FUNC. All positions are position symbols found in `rust-test-positions-alist'." (with-temp-buffer (rust-mode) (insert source-code) (goto-char (rust-get-buffer-pos init-pos)) (apply manip-func args) (should (equal (list (region-beginning) (region-end)) (list (rust-get-buffer-pos reg-beg) (rust-get-buffer-pos reg-end)))))) (ert-deftest rust-beginning-of-defun-from-middle-of-fn () (rust-test-motion rust-test-motion-string 'middle-of-fn1 'start-of-fn1 #'beginning-of-defun)) (ert-deftest rust-beginning-of-defun-from-end () (rust-test-motion rust-test-motion-string 'end-of-fn1 'start-of-fn1 #'beginning-of-defun)) (ert-deftest rust-beginning-of-defun-before-open-brace () (rust-test-motion rust-test-motion-string 'start-of-fn1-middle-of-line 'start-of-fn1 #'beginning-of-defun)) (ert-deftest rust-beginning-of-defun-between-fns () (rust-test-motion rust-test-motion-string 'between-fn1-fn2 'start-of-fn1 #'beginning-of-defun)) (ert-deftest rust-beginning-of-defun-with-arg () (rust-test-motion rust-test-motion-string 'middle-of-fn2 'start-of-fn1 #'beginning-of-defun 2)) (ert-deftest rust-beginning-of-defun-with-negative-arg () (rust-test-motion rust-test-motion-string 'middle-of-fn1 'beginning-of-fn3 #'beginning-of-defun -2)) (ert-deftest rust-beginning-of-defun-pub-fn () (rust-test-motion rust-test-motion-string 'middle-of-fn3 'beginning-of-fn3 #'beginning-of-defun)) (ert-deftest rust-beginning-of-defun-string-comment () (let (fn-1 fn-2 p-1 p-2) (with-temp-buffer (rust-mode) (insert "fn test1() { let s=r#\" fn test2(); \"#;") (setq p-1 (point)) (setq fn-1 (1+ p-1)) (insert " fn test3() { /* fn test4();") (setq p-2 (point)) (insert "\n*/\n}\n") (setq fn-2 (point)) (insert "fn test5() { }") (goto-char p-1) (beginning-of-defun) (should (eq (point) (point-min))) (beginning-of-defun -2) (should (eq (point) fn-2)) (goto-char p-2) (beginning-of-defun) (should (eq (point) fn-1)) (beginning-of-defun -1) (should (eq (point) fn-2)) (goto-char (point-max)) (beginning-of-defun 2) (should (eq (point) fn-1))))) (ert-deftest rust-end-of-defun-from-middle-of-fn () (rust-test-motion rust-test-motion-string 'middle-of-fn1 'between-fn1-fn2 #'end-of-defun)) (ert-deftest rust-end-of-defun-from-beg () (rust-test-motion rust-test-motion-string 'start-of-fn1 'between-fn1-fn2 #'end-of-defun)) (ert-deftest rust-end-of-defun-before-open-brace () (rust-test-motion rust-test-motion-string 'start-of-fn1-middle-of-line 'between-fn1-fn2 #'end-of-defun)) (ert-deftest rust-end-of-defun-between-fns () (rust-test-motion rust-test-motion-string 'between-fn1-fn2 'after-end-of-fn2 #'end-of-defun)) (ert-deftest rust-end-of-defun-with-arg () (rust-test-motion rust-test-motion-string 'middle-of-fn1 'after-end-of-fn2 #'end-of-defun 2)) (ert-deftest rust-end-of-defun-with-negative-arg () (rust-test-motion rust-test-motion-string 'middle-of-fn3 'between-fn1-fn2 #'end-of-defun -2)) (ert-deftest rust-mark-defun-from-middle-of-fn () (rust-test-region rust-test-region-string 'middle-of-fn2 'between-fn1-fn2 'after-end-of-fn2 #'mark-defun)) (ert-deftest rust-mark-defun-from-end () (rust-test-region rust-test-region-string 'end-of-fn1 'before-start-of-fn1 'between-fn1-fn2 #'mark-defun)) (ert-deftest rust-mark-defun-start-of-defun () (rust-test-region rust-test-region-string 'start-of-fn2 'between-fn1-fn2 'after-end-of-fn2 #'mark-defun)) (ert-deftest rust-mark-defun-from-middle-of-struct () (rust-test-region rust-test-region-string 'middle-of-struct 'before-start-of-struct 'after-end-of-struct #'mark-defun)) (ert-deftest indent-line-blank-line-motion () (rust-test-motion rust-test-indent-motion-string 'blank-line-indent-start 'blank-line-indent-target #'indent-for-tab-command)) (ert-deftest indent-line-closing-brace-motion () (rust-test-motion rust-test-indent-motion-string 'closing-brace-indent-start 'closing-brace-indent-target #'indent-for-tab-command)) (ert-deftest indent-line-middle-push-motion () (rust-test-motion rust-test-indent-motion-string 'middle-push-indent-start 'middle-push-indent-target #'indent-for-tab-command)) (ert-deftest indent-line-after-whitespace-motion () (rust-test-motion rust-test-indent-motion-string 'after-whitespace-indent-start 'after-whitespace-indent-target #'indent-for-tab-command)) (ert-deftest indent-line-middle-pull-motion () (rust-test-motion rust-test-indent-motion-string 'middle-pull-indent-start 'middle-pull-indent-target #'indent-for-tab-command)) (ert-deftest indent-line-blank-line-indented-already-bol () (rust-test-motion rust-test-indent-motion-string 'blank-line-indented-already-bol-start 'blank-line-indented-already-bol-target #'indent-for-tab-command)) (ert-deftest indent-line-blank-line-indented-already-middle () (rust-test-motion rust-test-indent-motion-string 'blank-line-indented-already-middle-start 'blank-line-indented-already-middle-target #'indent-for-tab-command)) (ert-deftest indent-line-nonblank-line-indented-already-bol () (rust-test-motion rust-test-indent-motion-string 'nonblank-line-indented-already-bol-start 'nonblank-line-indented-already-bol-target #'indent-for-tab-command)) (ert-deftest indent-line-nonblank-line-indented-already-middle () (rust-test-motion rust-test-indent-motion-string 'nonblank-line-indented-already-middle-start 'nonblank-line-indented-already-middle-target #'indent-for-tab-command)) (ert-deftest no-stack-overflow-in-rust-rewind-irrelevant () (with-temp-buffer (rust-mode) (insert "fn main() {\n let x = 1;") ;; Insert 150 separate comments on the same line (dotimes (i 150) (insert "/* foo */ ")) ;; Rewinding from the last commment to the end of the let needs at least ;; 150 iterations, but if we limit the stack depth to 100 (this appears to ;; be some minimum), a recursive function would overflow, throwing an ;; error. (let ((max-lisp-eval-depth 100)) (rust-rewind-irrelevant) ;; Only a non-stack overflowing function would make it this far. Also ;; check that we rewound till after the ; (should (= (char-before) ?\;))))) (defun rust-test-fontify-string (str) (with-temp-buffer (rust-mode) (insert str) (font-lock-fontify-buffer) (buffer-string))) (defun rust-test-group-str-by-face (str) "Fontify `STR' in rust-mode and group it by face, returning a list of substrings of `STR' each followed by its face." (loop with fontified = (rust-test-fontify-string str) for start = 0 then end while start for end = (next-single-property-change start 'face fontified) for prop = (get-text-property start 'face fontified) for text = (substring-no-properties fontified start end) if prop append (list text prop))) (defun rust-test-font-lock (source face-groups) "Test that `SOURCE' fontifies to the expected `FACE-GROUPS'" (should (equal (rust-test-group-str-by-face source) face-groups))) (ert-deftest font-lock-attribute-simple () (rust-test-font-lock "#[foo]" '("#[foo]" font-lock-preprocessor-face))) (ert-deftest font-lock-attribute-inner () (rust-test-font-lock "#![foo]" '("#![foo]" font-lock-preprocessor-face))) (ert-deftest font-lock-attribute-key-value () (rust-test-font-lock "#[foo = \"bar\"]" '("#[foo = " font-lock-preprocessor-face "\"bar\"" font-lock-string-face "]" font-lock-preprocessor-face))) (ert-deftest font-lock-attribute-around-comment () (rust-test-font-lock "#[foo /* bar */]" '("#[foo " font-lock-preprocessor-face "/* " font-lock-comment-delimiter-face "bar */" font-lock-comment-face "]" font-lock-preprocessor-face))) (ert-deftest font-lock-attribute-inside-string () (rust-test-font-lock "\"#[foo]\"" '("\"#[foo]\"" font-lock-string-face))) (ert-deftest font-lock-attribute-inside-comment () (rust-test-font-lock "/* #[foo] */" '("/* " font-lock-comment-delimiter-face "#[foo] */" font-lock-comment-face))) (ert-deftest font-lock-double-quote-character-literal () (rust-test-font-lock "'\"'; let" '("'\"'" font-lock-string-face "let" font-lock-keyword-face))) (ert-deftest font-lock-fn-contains-capital () (rust-test-font-lock "fn foo_Bar() {}" '("fn" font-lock-keyword-face "foo_Bar" font-lock-function-name-face))) (ert-deftest font-lock-let-bindings () (rust-test-font-lock "let foo;" '("let" font-lock-keyword-face "foo" font-lock-variable-name-face)) (rust-test-font-lock "let ref foo;" '("let" font-lock-keyword-face "ref" font-lock-keyword-face "foo" font-lock-variable-name-face)) (rust-test-font-lock "let mut foo;" '("let" font-lock-keyword-face "mut" font-lock-keyword-face "foo" font-lock-variable-name-face)) (rust-test-font-lock "let foo = 1;" '("let" font-lock-keyword-face "foo" font-lock-variable-name-face)) (rust-test-font-lock "let mut foo = 1;" '("let" font-lock-keyword-face "mut" font-lock-keyword-face "foo" font-lock-variable-name-face)) (rust-test-font-lock "fn foo() { let bar = 1; }" '("fn" font-lock-keyword-face "foo" font-lock-function-name-face "let" font-lock-keyword-face "bar" font-lock-variable-name-face)) (rust-test-font-lock "fn foo() { let mut bar = 1; }" '("fn" font-lock-keyword-face "foo" font-lock-function-name-face "let" font-lock-keyword-face "mut" font-lock-keyword-face "bar" font-lock-variable-name-face))) (ert-deftest font-lock-if-let-binding () (rust-test-font-lock "if let Some(var) = some_var { /* no-op */ }" '("if" font-lock-keyword-face "let" font-lock-keyword-face "Some" font-lock-type-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face))) (ert-deftest font-lock-single-quote-character-literal () (rust-test-font-lock "fn main() { let ch = '\\''; }" '("fn" font-lock-keyword-face "main" font-lock-function-name-face "let" font-lock-keyword-face "ch" font-lock-variable-name-face "'\\''" font-lock-string-face))) (ert-deftest font-lock-escaped-double-quote-character-literal () (rust-test-font-lock "fn main() { let ch = '\\\"'; }" '("fn" font-lock-keyword-face "main" font-lock-function-name-face "let" font-lock-keyword-face "ch" font-lock-variable-name-face "'\\\"'" font-lock-string-face))) (ert-deftest font-lock-escaped-backslash-character-literal () (rust-test-font-lock "fn main() { let ch = '\\\\'; }" '("fn" font-lock-keyword-face "main" font-lock-function-name-face "let" font-lock-keyword-face "ch" font-lock-variable-name-face "'\\\\'" font-lock-string-face))) (ert-deftest font-lock-hex-escape-character-literal () (rust-test-font-lock "let ch = '\\x1f';" '("let" font-lock-keyword-face "ch" font-lock-variable-name-face "'\\x1f'" font-lock-string-face))) (ert-deftest font-lock-unicode-escape-character-literal () (rust-test-font-lock "let ch = '\\u{1ffff}';" '("let" font-lock-keyword-face "ch" font-lock-variable-name-face "'\\u{1ffff}'" font-lock-string-face))) (ert-deftest font-lock-raw-strings-no-hashes () (rust-test-font-lock "r\"No hashes\";" '("r\"No hashes\"" font-lock-string-face))) (ert-deftest font-lock-raw-strings-double-quote () (rust-test-font-lock "fn main() { r#\"With a double quote (\")\"#; } " '("fn" font-lock-keyword-face "main" font-lock-function-name-face "r#\"With a double quote (\")\"#" font-lock-string-face))) (ert-deftest font-lock-raw-strings-two-hashes () (rust-test-font-lock "r##\"With two hashes\"##;" '("r##\"With two hashes\"##" font-lock-string-face))) (ert-deftest font-lock-raw-strings-backslash-at-end () (rust-test-font-lock "r\"With a backslash at the end\\\";" '("r\"With a backslash at the end\\\"" font-lock-string-face))) (ert-deftest font-lock-two-raw-strings () (rust-test-font-lock "fn main() { r\"With a backslash at the end\\\"; r##\"With two hashes\"##; }" '("fn" font-lock-keyword-face "main" font-lock-function-name-face "r\"With a backslash at the end\\\"" font-lock-string-face "r##\"With two hashes\"##" font-lock-string-face))) (ert-deftest font-lock-raw-string-with-inner-hash () (rust-test-font-lock "r##\"I've got an octothorpe (#)\"##; foo()" '("r##\"I've got an octothorpe (#)\"##" font-lock-string-face))) (ert-deftest font-lock-raw-string-with-inner-quote-and-hash () (rust-test-font-lock "not_the_string(); r##\"string \"# still same string\"##; not_the_string()" '("r##\"string \"# still same string\"##" font-lock-string-face))) (ert-deftest font-lock-string-ending-with-r-not-raw-string () (rust-test-font-lock "fn f() { \"Er\"; } fn g() { \"xs\"; }" '("fn" font-lock-keyword-face "f" font-lock-function-name-face "\"Er\"" font-lock-string-face "fn" font-lock-keyword-face "g" font-lock-function-name-face "\"xs\"" font-lock-string-face))) (ert-deftest font-lock-raw-string-trick-ending-followed-by-string-with-quote () (rust-test-font-lock "r\"With what looks like the start of a raw string at the end r#\"; not_a_string(); r##\"With \"embedded\" quote \"##;" '("r\"With what looks like the start of a raw string at the end r#\"" font-lock-string-face "r##\"With \"embedded\" quote \"##" font-lock-string-face))) (ert-deftest font-lock-raw-string-starter-inside-raw-string () ;; Check that it won't look for a raw string beginning inside another raw string. (rust-test-font-lock "r#\"In the first string r\" in the first string \"#; not_in_a_string(); r##\"In the second string\"##;" '("r#\"In the first string r\" in the first string \"#" font-lock-string-face "r##\"In the second string\"##" font-lock-string-face))) (ert-deftest font-lock-raw-string-starter-inside-comment () ;; Check that it won't look for a raw string beginning inside another raw string. (rust-test-font-lock "// r\" this is a comment \"this is a string\"; this_is_not_a_string();)" '("// " font-lock-comment-delimiter-face "r\" this is a comment\n" font-lock-comment-face "\"this is a string\"" font-lock-string-face))) (ert-deftest font-lock-runaway-raw-string () (rust-test-font-lock "const Z = r#\"my raw string\";\n// oops this is still in the string" '("const" font-lock-keyword-face "Z" font-lock-type-face "r#\"my raw string\";\n// oops this is still in the string" font-lock-string-face)) ) (ert-deftest font-lock-recognize-closing-raw-string () (with-temp-buffer (rust-mode) (insert "const foo = r##\" 1...............................................50 1...............................................50 1...............................................50 1...............195-->\"; let ...................50 1...............................................50 1...............................................50 1...............................................50 1...............................................50 1...............................................50 1......................500......................50 \"#; ") (font-lock-fontify-buffer) (goto-char 530) (insert "#") ;; We have now closed the raw string. Check that the whole string is ;; recognized after the change (font-lock-after-change-function (1- (point)) (point) 0) (should (equal 'font-lock-string-face (get-text-property 195 'face))) ;; The "let" (should (equal 'font-lock-string-face (get-text-property 500 'face))) ;; The "500" (should (equal nil (get-text-property 531 'face))) ;; The second ";" )) ;;; Documentation comments (ert-deftest font-lock-doc-line-comment-parent () (rust-test-font-lock "//! doc" '("//! doc" font-lock-doc-face))) (ert-deftest font-lock-doc-line-comment-item () (rust-test-font-lock "/// doc" '("/// doc" font-lock-doc-face))) (ert-deftest font-lock-nondoc-line () (rust-test-font-lock "////// doc" '("////// " font-lock-comment-delimiter-face "doc" font-lock-comment-face))) (ert-deftest font-lock-doc-line-in-string () (rust-test-font-lock "\"/// doc\"" '("\"/// doc\"" font-lock-string-face)) (rust-test-font-lock "\"//! doc\"" '("\"//! doc\"" font-lock-string-face))) (ert-deftest font-lock-doc-line-in-nested-comment () (rust-test-font-lock "/* /// doc */" '("/* " font-lock-comment-delimiter-face "/// doc */" font-lock-comment-face)) (rust-test-font-lock "/* //! doc */" '("/* " font-lock-comment-delimiter-face "//! doc */" font-lock-comment-face))) (ert-deftest font-lock-doc-block-comment-parent () (rust-test-font-lock "/*! doc */" '("/*! doc */" font-lock-doc-face))) (ert-deftest font-lock-doc-block-comment-item () (rust-test-font-lock "/** doc */" '("/** doc */" font-lock-doc-face))) (ert-deftest font-lock-nondoc-block-comment-item () (rust-test-font-lock "/***** doc */" '("/**" font-lock-comment-delimiter-face "*** doc */" font-lock-comment-face))) (ert-deftest font-lock-doc-block-in-string () (rust-test-font-lock "\"/** doc */\"" '("\"/** doc */\"" font-lock-string-face)) (rust-test-font-lock "\"/*! doc */\"" '("\"/*! doc */\"" font-lock-string-face))) (ert-deftest font-lock-module-def () (rust-test-font-lock "mod foo;" '("mod" font-lock-keyword-face "foo" font-lock-constant-face))) (ert-deftest font-lock-module-use () (rust-test-font-lock "use foo;" '("use" font-lock-keyword-face "foo" font-lock-constant-face))) (ert-deftest font-lock-module-path () (rust-test-font-lock "foo::bar" '("foo" font-lock-constant-face))) (ert-deftest font-lock-submodule-path () (rust-test-font-lock "foo::bar::baz" '("foo" font-lock-constant-face "bar" font-lock-constant-face))) (ert-deftest font-lock-type () (rust-test-font-lock "foo::Bar::baz" '("foo" font-lock-constant-face "Bar" font-lock-type-face))) (ert-deftest font-lock-type-annotation () "Ensure type annotations are not confused with modules." (rust-test-font-lock "parse::();" ;; Only the i32 should have been highlighted. '("i32" font-lock-type-face)) (rust-test-font-lock "foo:: " ;; Only the i32 should have been highlighted. '("i32" font-lock-type-face))) (ert-deftest font-lock-question-mark () "Ensure question mark operator is highlighted." (rust-test-font-lock "?" '("?" rust-question-mark-face)) (rust-test-font-lock "foo\(\)?;" '("?" rust-question-mark-face)) (rust-test-font-lock "foo\(bar\(\)?\);" '("?" rust-question-mark-face)) (rust-test-font-lock "\"?\"" '("\"?\"" font-lock-string-face)) (rust-test-font-lock "foo\(\"?\"\);" '("\"?\"" font-lock-string-face)) (rust-test-font-lock "// ?" '("// " font-lock-comment-delimiter-face "?" font-lock-comment-face)) (rust-test-font-lock "/// ?" '("/// ?" font-lock-doc-face)) (rust-test-font-lock "foo\(\"?\"\);" '("\"?\"" font-lock-string-face)) (rust-test-font-lock "foo\(\"?\"\)?;" '("\"?\"" font-lock-string-face "?" rust-question-mark-face))) (ert-deftest rust-test-default-context-sensitive () (rust-test-font-lock "let default = 7; impl foo { default fn f() { } }" '("let" font-lock-keyword-face "default" font-lock-variable-name-face "impl" font-lock-keyword-face "default" font-lock-keyword-face "fn" font-lock-keyword-face "f" font-lock-function-name-face))) (ert-deftest rust-test-union-context-sensitive () (rust-test-font-lock "let union = 7; union foo { x: &'union bar }" '("let" font-lock-keyword-face ;; The first union is a variable name. "union" font-lock-variable-name-face ;; The second union is a contextual keyword. "union" font-lock-keyword-face "foo" font-lock-type-face "x" font-lock-variable-name-face ;; This union is the name of a lifetime. "union" font-lock-variable-name-face "bar" font-lock-type-face))) (ert-deftest indent-method-chains-no-align () (let ((rust-indent-method-chain nil)) (test-indent " fn main() { let x = thing.do_it() .aligned() .more_alignment(); } " ))) (ert-deftest indent-method-chains-no-align-with-question-mark-operator () (let ((rust-indent-method-chain nil)) (test-indent " fn main() { let x = thing.do_it() .aligned() .more_alignment()? .more_alignment(); } " ))) (ert-deftest indent-method-chains-with-align () (let ((rust-indent-method-chain t)) (test-indent " fn main() { let x = thing.do_it() .aligned() .more_alignment(); } " ))) (ert-deftest indent-method-chains-with-align-with-question-mark-operator () (let ((rust-indent-method-chain t)) (test-indent " fn main() { let x = thing.do_it() .aligned() .more_alignment()? .more_alignment(); } " ))) (ert-deftest indent-method-chains-with-align-and-second-stmt () (let ((rust-indent-method-chain t)) (test-indent " fn main() { let x = thing.do_it() .aligned() .more_alignment(); foo.bar(); } " ))) (ert-deftest indent-method-chains-field () (let ((rust-indent-method-chain t)) (test-indent " fn main() { let x = thing.do_it .aligned .more_alignment(); } " ))) (ert-deftest indent-method-chains-double-field-on-first-line () (let ((rust-indent-method-chain t)) (test-indent " fn main() { let x = thing.a.do_it .aligned .more_alignment(); } " ))) (ert-deftest indent-method-chains-no-let () (let ((rust-indent-method-chain t)) (test-indent " fn main() { thing.a.do_it .aligned .more_alignment(); } " ))) (ert-deftest indent-method-chains-look-over-comment () (let ((rust-indent-method-chain t)) (test-indent " fn main() { thing.a.do_it // A comment .aligned // Another comment .more_alignment(); } " ))) (ert-deftest indent-method-chains-comment () (let ((rust-indent-method-chain t)) (test-indent " fn main() { // thing.do_it() // .aligned() } " ))) (ert-deftest indent-method-chains-close-block () (let ((rust-indent-method-chain t)) (test-indent " fn main() { foo.bar() } " ))) (ert-deftest indent-method-chains-after-comment () (let ((rust-indent-method-chain t)) (test-indent " fn main() { // comment here should not push next line out foo.bar() } " ))) (ert-deftest indent-method-chains-after-comment2 () (let ((rust-indent-method-chain t)) (test-indent " fn main() { // Lorem ipsum lorem ipsum lorem ipsum lorem.ipsum foo.bar() } " ))) (ert-deftest indent-function-after-where () (let ((rust-indent-method-chain t)) (test-indent " fn each_split_within<'a, F>(ss: &'a str, lim: usize, mut it: F) -> bool where F: FnMut(&'a str) -> bool { } #[test] fn test_split_within() { } " ))) (ert-deftest indent-function-after-where-nested () (let ((rust-indent-method-chain t)) (test-indent " fn outer() { fn each_split_within<'a, F>(ss: &'a str, lim: usize, mut it: F) -> bool where F: FnMut(&'a str) -> bool { } #[test] fn test_split_within() { } fn bar() { } } " ))) (ert-deftest test-for-issue-36-syntax-corrupted-state () "This is a test for a issue #36, which involved emacs's internal state getting corrupted when actions were done in a specific sequence. The test seems arbitrary, and is, but it was not clear how to narrow it down further. The cause of the bug was code that used to set `syntax-begin-function' to `beginning-of-defun', which doesn't actually fulfill the expectations--`syntax-begin-function' is supposed to back out of all parens, but `beginning-of-defun' could leave it inside parens if a fn appears inside them. Having said that, as I write this I don't understand fully what internal state was corrupted and how. There wasn't an obvious pattern to what did and did not trip it." ;; When bug #36 was present, the following test would pass, but running it ;; caused some unknown emacs state to be corrupted such that the following ;; test failed. Both the "blank_line" and "indented_closing_brace" functions ;; were needed to expose the error, for instance--deleting either of them ;; would make the failure go away. (with-temp-buffer (rust-mode) (insert "fn blank_line(arg:i32) -> bool { } fn indenting_closing_brace() { if(true) { } } 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) (should (equal (current-column) 4)) ) ;; This is the test that would fail only after running the previous one. The ;; code is extracted from src/libstd/collections/table.rs in the rust tree. ;; It was not clear how to reduce it further--removing various bits of it ;; would make it no longer fail. In particular, changing only the comment at ;; the top of the "next" function was sufficient to make it no longer fail. (test-indent " impl Foo for Bar { /// Modifies the bucket pointer in place to make it point to the next slot. pub fn next(&mut self) { // Branchless bucket // As we reach the end of the table... // We take the current idx: 0111111b // Xor it by its increment: ^ 1000000b // ------------ // 1111111b // Then AND with the capacity: & 1000000b // ------------ // to get the backwards offset: 1000000b let maybe_wraparound_dist = (self.idx ^ (self.idx + 1)) & self.table.capacity(); // Finally, we obtain the offset 1 or the offset -cap + 1. let dist = 1 - (maybe_wraparound_dist as isize); self.idx += 1; unsafe { self.raw = self.raw.offset(dist); } } /// Reads a bucket at a given index, returning an enum indicating whether /// the appropriate types to call most of the other functions in /// this module. pub fn peek(self) { match foo { EMPTY_BUCKET => Empty(EmptyBucket { raw: self.raw, idx: self.idx, table: self.table }), _ => Full(FullBucket { raw: self.raw, idx: self.idx, table: self.table }) } } } " )) (ert-deftest test-indent-string-with-eol-backslash () (test-indent " pub fn foo() { format!(\"abc \\ def\") } " )) (ert-deftest test-indent-string-with-eol-backslash-at-start () (test-indent " pub fn foo() { format!(\"\\ abc \\ def\") } " )) (ert-deftest test-indent-string-without-eol-backslash-indent-is-not-touched () (test-indent " pub fn foo() { format!(\" abc def\"); } pub fn foo() { format!(\"la la la la la la\"); } " ;; Should still indent the code parts but leave the string internals alone: " pub fn foo() { format!(\" abc def\"); } pub fn foo() { format!(\"la la la la la la\"); } " )) (ert-deftest test-indent-string-eol-backslash-mixed-with-literal-eol () (test-indent " fn foo() { println!(\" Here is the beginning of the string and here is a line that is arbitrarily indented \\ and a continuation of that indented line and another arbitrary indentation still another yet another \\ with a line continuing it And another line not indented \") } " " fn foo() { println!(\" Here is the beginning of the string and here is a line that is arbitrarily indented \\ and a continuation of that indented line and another arbitrary indentation still another yet another \\ with a line continuing it And another line not indented \") } ")) (ert-deftest test-indent-string-eol-backslash-dont-touch-raw-strings () (test-indent " pub fn foo() { format!(r\"\ abc\ def\"); } pub fn foo() { format!(r\"la la la la\ la la\"); } " ;; Should still indent the code parts but leave the string internals alone: " pub fn foo() { format!(r\"\ abc\ def\"); } pub fn foo() { format!(r\"la la la la\ la la\"); } " )) (ert-deftest indent-inside-string-first-line () (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() { hello(); }")) (ert-deftest indent-open-paren-in-column0 () ;; Just pass the same text for the "deindented" argument. This ;; avoids the extra spaces normally inserted, which would mess up ;; the test because string contents aren't touched by reindentation. (let ((text " const a: &'static str = r#\" {}\"#; fn main() { let b = \"//\"; let c = \"\"; } ")) (test-indent text text))) (ert-deftest indent-question-mark-operator () (test-indent "fn foo() { if bar()? < 1 { } baz(); }")) ;; Regression test for #212. (ert-deftest indent-left-shift () (test-indent " fn main() { let a = [[0u32, 0u32]; 1]; let i = 0; let x = a[i][(1 < i)]; let x = a[i][(1 << i)]; } ")) (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-macro-font-lock () (rust-test-font-lock "foo!\(\);" '("foo!" font-lock-preprocessor-face)) (rust-test-font-lock "foo!{};" '("foo!" font-lock-preprocessor-face)) (rust-test-font-lock "foo![];" '("foo!" font-lock-preprocessor-face))) (ert-deftest rust-string-interpolation-matcher-works () (dolist (test '(("print!\(\"\"\)" 9 11 nil) ("print!\(\"abcd\"\)" 9 15 nil) ("print!\(\"abcd {{}}\"\);" 9 19 nil) ("print!\(\"abcd {{\"\);" 9 18 nil) ("print!\(\"abcd {}\"\);" 9 18 ((14 16))) ("print!\(\"abcd {{{}\"\);" 9 20 ((16 18))) ("print!\(\"abcd {}{{\"\);" 9 20 ((14 16))) ("print!\(\"abcd {} {{\"\);" 9 21 ((14 16))) ("print!\(\"abcd {}}}\"\);" 9 20 ((14 16))) ("print!\(\"abcd {{{}}}\"\);" 9 20 ((16 18))) ("print!\(\"abcd {0}\"\);" 9 18 ((14 17))) ("print!\(\"abcd {0} efgh\"\);" 9 23 ((14 17))) ("print!\(\"{1} abcd {0} efgh\"\);" 9 27 ((9 12) (18 21))) ("print!\(\"{{{1} abcd }} {0}}} {{efgh}}\"\);" 9 33 ((11 14) (23 26))))) (destructuring-bind (text cursor limit matches) test (with-temp-buffer ;; make sure we have a clean slate (save-match-data (set-match-data nil) (insert text) (goto-char cursor) (if (null matches) (should (equal (progn (rust-string-interpolation-matcher limit) (match-data)) nil)) (dolist (pair matches) (rust-string-interpolation-matcher limit) (should (equal (match-beginning 0) (car pair))) (should (equal (match-end 0) (cadr pair)))))))))) (ert-deftest rust-formatting-macro-font-lock () ;; test that the block delimiters aren't highlighted and the comment ;; is ignored (rust-test-font-lock "print!(\"\"); { /* print!(\"\"); */ }" '("print!" rust-builtin-formatting-macro-face "\"\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "print!(\"\"); */" font-lock-comment-face)) ;; with newline directly following delimiter (rust-test-font-lock "print!(\n\"\"\n); { /* print!(\"\"); */ }" '("print!" rust-builtin-formatting-macro-face "\"\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "print!(\"\"); */" font-lock-comment-face)) ;; with empty println!() (rust-test-font-lock "println!(); { /* println!(); */ }" '("println!" rust-builtin-formatting-macro-face "/* " font-lock-comment-delimiter-face "println!(); */" font-lock-comment-face)) ;; other delimiters (rust-test-font-lock "print!{\"\"}; { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; other delimiters (rust-test-font-lock "print![\"\"]; { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; no interpolation (rust-test-font-lock "print!(\"abcd\"); { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"abcd\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; only interpolation (rust-test-font-lock "print!(\"{}\"); { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"" font-lock-string-face "{}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; text + interpolation (rust-test-font-lock "print!(\"abcd {}\", foo); { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; text + interpolation with specification (rust-test-font-lock "print!(\"abcd {0}\", foo); { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; text + interpolation with specification and escape (rust-test-font-lock "print!(\"abcd {0}}}\", foo); { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face "}}\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; multiple pairs (rust-test-font-lock "print!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" '("print!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; println (rust-test-font-lock "println!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" '("println!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; eprint (rust-test-font-lock "eprint!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" '("eprint!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; eprintln (rust-test-font-lock "eprintln!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" '("eprintln!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; format (rust-test-font-lock "format!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" '("format!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; print + raw string (rust-test-font-lock "format!(r\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" '("format!" rust-builtin-formatting-macro-face "r\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; print + raw string with hash (rust-test-font-lock "format!(r#\"abcd {0} efgh {1}\"#, foo, bar); { /* no-op */ }" '("format!" rust-builtin-formatting-macro-face "r#\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"#" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) ;; print + raw string with two hashes (rust-test-font-lock "format!(r##\"abcd {0} efgh {1}\"##, foo, bar); { /* no-op */ }" '("format!" rust-builtin-formatting-macro-face "r##\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face " efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"##" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face))) (ert-deftest rust-write-macro-font-lock () (rust-test-font-lock "write!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }" '("write!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face "}} efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-face)) (rust-test-font-lock "writeln!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }" '("writeln!" rust-builtin-formatting-macro-face "\"abcd " font-lock-string-face "{0}" rust-string-interpolation-face "}} efgh " font-lock-string-face "{1}" rust-string-interpolation-face "\"" font-lock-string-face "/* " font-lock-comment-delimiter-face "no-op */" font-lock-comment-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() { }" '((8 10) ;; Angle brackets (11 12) ;; Parens (14 16) ;; Curly braces ))) (ert-deftest rust-test-paren-matching-generic-fn-with-return-value () (rust-test-matching-parens " fn foo() -> bool { false }" '((8 10) ;; Angle brackets (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 ::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 (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> = Some(Ok(4 >> 1)); let b = a.map(|x| x.map(|y| y << 3)); let trick_question = z<<::method(); // First two ) '(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 are not angle brackets 141 ;; The << before 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 { a:Option<(i32,Option)>, b:Option, c:bool } fn f(x:i32,y:Option) { let z:Option = None; let b:Bla = Bla{ a:None, b:None, c:x (30 49) ;; Outer angle brackets of a:Option<...> (42 47) ;; Inner angle brackets of Option (64 66) ;; Angle brackets of Option (102 106) ;; Angle brackets of y:Option (127 131) ;; Angle brackets of z:Option (154 157) ;; Angle brackets of b:Bla ) '(209 ;; less than operator in c:x 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 { Foo{ bar:Bar{ a:3, b:x ) '(92 ;; less than operator x X>() -> Z { } " '((8 23) ;; The angle brackets of foo (20 22 ;; The angle brackets of X )) '(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; type Bar = (Foo, Bletch); type ThisThing = HereYouGo B>,E>>;" '((17 21) ;; Angle brackets of Blah (32 34) ;; Angle brackets of Bar (50 52) ;; Angle brackets of Bletch (70 78) ;; Angle brackets of ThisThing (91 118) ;; Angle brackets of HereYouGo<...> (95 117) ;; Angle brackets of Y (106 111) ;; Angle brackets of B> (108 110) ;; Angle brackets of C (114 116) ;; Angle brackets of E ))) (ert-deftest rust-test-paren-matching-tuple-like-struct () (rust-test-matching-parens " struct A(Option); struct C(Result);" '((17 19) ;; The angle brackets (10 20) ;; The parens of A(); (31 33) ;; The angle brackets of C (41 47) ;; The angle brackets of Result ) '())) (ert-deftest rust-test-paren-matching-in-enum () (rust-test-matching-parens " enum Boo { TupleLike(Option), StructLike{foo: Result} }" '((10 12) ;; Angle brackets of Boo (36 38) ;; Angle brackets of Option (68 74) ;; Angle brackets of Result ))) (ert-deftest rust-test-paren-matching-assoc-type-bounds () (rust-test-matching-parens "impl >> Thing {}" '((6 29) ;; Outer angle brackets of impl (10 28) ;; Outer angle brackets of B> (24 26) ;; Inner angle brackets of C (36 38) ;; Angle brackets of Thing ) )) (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,B>(a:A,b:B) -> bool where B:Trait3+Trait4 { 2 + a < 3 && 3 + b > 11 } fn bluh() where A:Fn()+MyTrait, MyTrait::AssocType = Option { } fn fluh() where C:Fn(i32) -> (i32, i32) + SomeTrait, C::AssocType = OtherThing { }" '((7 30) ;; Angle brackets of foo<...> (23 27) ;; Angle brackets of Trait2 (63 67) ;; Angle brackets of Trait3 (75 79) ;; Angle brackets of Trait4 (121 123) ;; Angle brackets of bluh (147 151) ;; Angle brackets of MyTrait (161 163) ;; Angle brackets of MyTrait (184 189) ;; Angle brackets of Option (203 205) ;; Angle brackets of (250 254) ;; Angle brackets of SomeTrait (282 287) ;; Angle brackets of Option ) '(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) {}" '((38 46)) )) (ert-deftest rust-test-paren-matching-references-and-logical-and () (rust-test-matching-parens " fn ampersand_check(a: &Option, b:bool) -> &Option { a.map(|x| { b && x < 32 }) }" '((31 35) ;; Option (56 60) ;; Option ) '(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 { let z:If = If(a < 3); }" '((17 21) ;; Box (37 42) ;; If ) '(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] {}" '((21 25)))) (ert-deftest rust-test-paren-matching-angle-bracket-inner-reference () (rust-test-matching-parens "fn x() -> Option<&Node> {}" '((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<::Item>" '((29 51) ;; Outer Option<> (30 44) ;; Inner ) '(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 u128 <= 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) { macro_a!( foo:: ); macro_b![ foo as Option ]; } macro_c!{ struct Boo {} }" '((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 { 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; }" '((31 35) ;; Angle brackets Foo ))) (ert-deftest rust-test-paren-matching-type-ascription () (rust-test-matching-parens " fn rfc803() { let z = a < b:FunnkyThing; let s = Foo { a: b < 3, b: d:CrazyStuff < 3, c: 2 < x:CrazyStuff } }" '((45 49) ;; FunkyThing (111 115) ;; CrazyStuff (149 154) ;; CrazyStuff ) '(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 where T:std::fmt::Debug { Thing(Option) }" '((13 15) ;; MyEnum (59 61) ;; Option ))) (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 where C::X: D, B:FnMut() -> Option+'a { Foo(a < b) } type Foo where T: Copy = Box; " '((10 15) ;; Boo<'a,T> (39 47) ;; Option<&'a str> (72 78) ;; Option<&'a T> (106 110) ;; Result (125 127) ;; D (149 151) ;; Option (184 186) ;; Foo (207 209) ;; Box ) '(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() {}" '((10 11)) '(7 9)))) (ert-deftest redo-syntax-after-change-far-from-point () (let* ((tmp-file-name (make-temp-file "rust-mdoe-test-issue104")) (base-contents (apply 'concat (append '("fn foo() {\n\n}\n") (make-list 500 "// More stuff...\n") '("fn bar() {\n\n}\n"))))) ;; Create the temp file... (with-temp-file tmp-file-name (insert base-contents)) (with-temp-buffer (insert-file-contents tmp-file-name 'VISIT nil nil 'REPLACE) (rust-mode) (goto-char (point-max)) (should (= 0 (rust-paren-level))) (with-temp-file tmp-file-name (insert base-contents) (goto-char 12) ;; On the blank line in the middle of fn foo (insert " let z = 1 < 3;") ) (revert-buffer 'IGNORE-AUTO 'NOCONFIRM 'PRESERVE-MODES) (should (= 0 (rust-paren-level))) ) ) ) (defun test-imenu (code expected-items) (with-temp-buffer (rust-mode) (insert code) (let ((actual-items ;; Replace ("item" . #)) (ert-deftest rust-test-electric-pair-impl-param () (test-electric-pair-insert "impl Foo for Bar" 5 ?< ?>)) (ert-deftest rust-test-electric-pair-impl-for-type-param () (test-electric-pair-insert "impl Foo 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::()), b:x } }" 63 ?< nil)) (ert-deftest rust-test-electric-pair-lt-expression-capitalized-keyword () (test-electric-pair-insert "fn foo() -> Box" 16 ?< ?>)) ) rust-mode-0.4.0/rust-mode.el000066400000000000000000002046301331453514100157000ustar00rootroot00000000000000;;; rust-mode.el --- A major emacs mode for editing Rust source code -*-lexical-binding: t-*- ;; Version: 0.4.0 ;; Author: Mozilla ;; Url: https://github.com/rust-lang/rust-mode ;; Keywords: languages ;; Package-Requires: ((emacs "24.0")) ;; This file is distributed under the terms of both the MIT license and the ;; Apache License (version 2.0). ;;; Commentary: ;; ;;; Code: (eval-when-compile (require 'rx) (require 'compile) (require 'url-vars)) (require 'json) (defvar electric-pair-inhibit-predicate) (defvar electric-indent-chars) (defvar rust-buffer-project) (make-variable-buffer-local 'rust-buffer-project) ;; for GNU Emacs < 24.3 (eval-when-compile (unless (fboundp 'setq-local) (defmacro setq-local (var val) "Set variable VAR to value VAL in current buffer." (list 'set (list 'make-local-variable (list 'quote var)) val)))) (defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") (defconst rust-re-lc-ident "[[:lower:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") (defconst rust-re-uc-ident "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*") (defconst rust-re-vis "pub") (defconst rust-re-unsafe "unsafe") (defconst rust-re-extern "extern") (defconst rust-re-union (rx-to-string `(seq (or space line-start) (group symbol-start "union" symbol-end) (+ space) (regexp ,rust-re-ident)))) ;;; Start of a Rust item (defvar rust-top-item-beg-re (concat "\\s-*\\(?:priv\\|pub\\)?\\s-*" (regexp-opt '("enum" "struct" "union" "type" "mod" "use" "fn" "static" "impl" "extern" "trait")) "\\_>")) (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))) ;; Operators (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?^ ?! ?< ?> ?~ ?@)) (modify-syntax-entry i "." table)) ;; Strings (modify-syntax-entry ?\" "\"" table) (modify-syntax-entry ?\\ "\\" table) ;; Angle brackets. We suppress this with syntactic propertization ;; when needed (modify-syntax-entry ?< "(>" table) (modify-syntax-entry ?> ")<" table) ;; Comments (modify-syntax-entry ?/ ". 124b" table) (modify-syntax-entry ?* ". 23n" table) (modify-syntax-entry ?\n "> b" table) (modify-syntax-entry ?\^m "> b" table) table)) (defgroup rust-mode nil "Support for Rust code." :link '(url-link "https://www.rust-lang.org/") :group 'languages) (defcustom rust-indent-offset 4 "Indent Rust code by this number of spaces." :type 'integer :group 'rust-mode :safe #'integerp) (defcustom rust-indent-method-chain nil "Indent Rust method chains, aligned by the '.' operators" :type 'boolean :group 'rust-mode :safe #'booleanp) (defcustom rust-indent-where-clause nil "Indent the line starting with the where keyword following a function or trait. When nil, where will be aligned with fn or trait." :type 'boolean :group 'rust-mode :safe #'booleanp) (defcustom rust-playpen-url-format "https://play.rust-lang.org/?code=%s" "Format string to use when submitting code to the playpen" :type 'string :group 'rust-mode) (defcustom rust-shortener-url-format "https://is.gd/create.php?format=simple&url=%s" "Format string to use for creating the shortened link of a playpen submission" :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) (defcustom rust-format-on-save nil "Format future rust buffers before saving using rustfmt." :type 'boolean :safe #'booleanp :group 'rust-mode) (defcustom rust-rustfmt-bin "rustfmt" "Path to rustfmt executable." :type 'string :group 'rust-mode) (defcustom rust-cargo-bin "cargo" "Path to cargo executable." :type 'string :group 'rust-mode) (defcustom rust-always-locate-project-on-open nil "Whether to run `cargo locate-project' every time `rust-mode' is activated." :type 'boolean :group 'rust-mode) (defface rust-unsafe-face '((t :inherit font-lock-warning-face)) "Face for the `unsafe' keyword." :group 'rust-mode) (defface rust-question-mark-face '((t :weight bold :inherit font-lock-builtin-face)) "Face for the question mark operator." :group 'rust-mode) (defface rust-builtin-formatting-macro-face '((t :inherit font-lock-builtin-face)) "Face for builtin formatting macros (print! &c.)." :group 'rust-mode) (defface rust-string-interpolation-face '((t :slant italic :inherit font-lock-string-face)) "Face for interpolating braces in builtin formatting macro strings." :group 'rust-mode) (defun rust-paren-level () (nth 0 (syntax-ppss))) (defun rust-in-str () (nth 3 (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 ((continue t)) (while continue (let ((starting (point))) (skip-chars-backward "[:space:]\n") (when (rust-looking-back-str "*/") (backward-char)) (when (rust-in-str-or-cmnt) (rust-rewind-past-str-cmnt)) ;; Rewind until the point no longer moves (setq continue (/= starting (point))))))) (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-looking-at-where () "Return T when looking at the \"where\" keyword." (and (looking-at-p "\\bwhere\\b") (not (rust-in-str-or-cmnt)))) (defun rust-rewind-to-where (&optional limit) "Rewind the point to the closest occurrence of the \"where\" keyword. Return T iff a where-clause was found. Does not rewind past LIMIT when passed, otherwise only stops at the beginning of the buffer." (when (re-search-backward "\\bwhere\\b" limit t) (if (rust-in-str-or-cmnt) (rust-rewind-to-where limit) t))) (defun rust-align-to-expr-after-brace () (save-excursion (forward-char) ;; We don't want to indent out to the open bracket if the ;; open bracket ends the line (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) (when (looking-at "[[:space:]]") (forward-word 1) (backward-word 1)) (current-column)))) (defun rust-rewind-to-beginning-of-current-level-expr () (let ((current-level (rust-paren-level))) (back-to-indentation) (when (looking-at "->") (rust-rewind-irrelevant) (back-to-indentation)) (while (> (rust-paren-level) current-level) (backward-up-list) (back-to-indentation)) ;; When we're in the where clause, skip over it. First find out the start ;; of the function and its paren level. (let ((function-start nil) (function-level nil)) (save-excursion (rust-beginning-of-defun) (back-to-indentation) ;; Avoid using multiple-value-bind (setq function-start (point) function-level (rust-paren-level))) ;; On a where clause (when (or (rust-looking-at-where) ;; or in one of the following lines, e.g. ;; where A: Eq ;; B: Hash <- on this line (and (save-excursion (rust-rewind-to-where function-start)) (= current-level function-level))) (goto-char function-start))))) (defun rust-align-to-method-chain () (save-excursion ;; for method-chain alignment to apply, we must be looking at ;; another method call or field access or something like ;; that. This avoids rather "eager" jumps in situations like: ;; ;; { ;; something.foo() ;; ;; ;; Without this check, we would wind up with the cursor under the ;; `.`. In an older version, I had the inverse of the current ;; check, where we checked for situations that should NOT indent, ;; vs checking for the one situation where we SHOULD. It should be ;; clear that this is more robust, but also I find it mildly less ;; annoying to have to press tab again to align to a method chain ;; than to have an over-eager indent in all other cases which must ;; be undone via tab. (when (looking-at (concat "\s*\." rust-re-ident)) (forward-line -1) (end-of-line) ;; Keep going up (looking for a line that could contain a method chain) ;; while we're in a comment or on a blank line. Stop when the paren ;; level changes. (let ((level (rust-paren-level))) (while (and (or (rust-in-str-or-cmnt) ;; Only whitespace (or nothing) from the beginning to ;; the end of the line. (looking-back "^\s*" (point-at-bol))) (= (rust-paren-level) level)) (forward-line -1) (end-of-line))) (let ;; skip-dot-identifier is used to position the point at the ;; `.` when looking at something like ;; ;; foo.bar ;; ^ ^ ;; | | ;; | position of point ;; returned offset ;; ((skip-dot-identifier (lambda () (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 "[)?]" (1- (point))) (backward-list 1) (funcall skip-dot-identifier)) ;; foo.bar (t (funcall skip-dot-identifier))))))) (defun rust-mode-indent-line () (interactive) (let ((indent (save-excursion (back-to-indentation) ;; Point is now at beginning of current line (let* ((level (rust-paren-level)) (baseline ;; Our "baseline" is one level out from the indentation of the expression ;; containing the innermost enclosing opening bracket. That ;; way if we are within a block that has a different ;; indentation than this mode would give it, we still indent ;; the inside of it correctly relative to the outside. (if (= 0 level) 0 (or (when rust-indent-method-chain (rust-align-to-method-chain)) (save-excursion (rust-rewind-irrelevant) (backward-up-list) (rust-rewind-to-beginning-of-current-level-expr) (+ (current-column) rust-indent-offset)))))) (cond ;; Indent inside a non-raw string only if the the previous line ;; ends with a backslash that is inside the same string ((nth 3 (syntax-ppss)) (let* ((string-begin-pos (nth 8 (syntax-ppss))) (end-of-prev-line-pos (when (> (line-number-at-pos) 1) (save-excursion (forward-line -1) (end-of-line) (point))))) (when (and ;; If the string begins with an "r" it's a raw string and ;; we should not change the indentation (/= ?r (char-after string-begin-pos)) ;; If we're on the first line this will be nil and the ;; rest does not apply end-of-prev-line-pos ;; The end of the previous line needs to be inside the ;; current string... (> end-of-prev-line-pos string-begin-pos) ;; ...and end with a backslash (= ?\\ (char-before end-of-prev-line-pos))) ;; Indent to the same level as the previous line, or the ;; start of the string if the previous line starts the string (if (= (line-number-at-pos end-of-prev-line-pos) (line-number-at-pos string-begin-pos)) ;; The previous line is the start of the string. ;; If the backslash is the only character after the ;; string beginning, indent to the next indent ;; level. Otherwise align with the start of the string. (if (> (- end-of-prev-line-pos string-begin-pos) 2) (save-excursion (goto-char (+ 1 string-begin-pos)) (current-column)) baseline) ;; The previous line is not the start of the string, so ;; match its indentation. (save-excursion (goto-char end-of-prev-line-pos) (back-to-indentation) (current-column)))))) ;; A function return type is indented to the corresponding function arguments ((looking-at "->") (save-excursion (backward-list) (or (rust-align-to-expr-after-brace) (+ baseline rust-indent-offset)))) ;; A closing brace is 1 level unindented ((looking-at "[]})]") (- baseline rust-indent-offset)) ;; Doc comments in /** style with leading * indent to line up the *s ((and (nth 4 (syntax-ppss)) (looking-at "*")) (+ 1 baseline)) ;; When the user chose not to indent the start of the where ;; clause, put it on the baseline. ((and (not rust-indent-where-clause) (rust-looking-at-where)) baseline) ;; If we're in any other token-tree / sexp, then: (t (or ;; If we are inside a pair of braces, with something after the ;; open brace on the same line and ending with a comma, treat ;; it as fields and align them. (when (> level 0) (save-excursion (rust-rewind-irrelevant) (backward-up-list) ;; Point is now at the beginning of the containing set of braces (rust-align-to-expr-after-brace))) ;; When where-clauses are spread over multiple lines, clauses ;; should be aligned on the type parameters. In this case we ;; take care of the second and following clauses (the ones ;; that don't start with "where ") (save-excursion ;; Find the start of the function, we'll use this to limit ;; our search for "where ". (let ((function-start nil) (function-level nil)) (save-excursion ;; If we're already at the start of a function, ;; don't go back any farther. We can easily do ;; this by moving to the end of the line first. (end-of-line) (rust-beginning-of-defun) (back-to-indentation) ;; Avoid using multiple-value-bind (setq function-start (point) function-level (rust-paren-level))) ;; When we're not on a line starting with "where ", but ;; still on a where-clause line, go to "where " (when (and (not (rust-looking-at-where)) ;; We're looking at something like "F: ..." (looking-at (concat rust-re-ident ":")) ;; There is a "where " somewhere after the ;; start of the function. (rust-rewind-to-where function-start) ;; Make sure we're not inside the function ;; already (e.g. initializing a struct) by ;; checking we are the same level. (= function-level level)) ;; skip over "where" (forward-char 5) ;; Unless "where" is at the end of the line (if (eolp) ;; in this case the type parameters bounds are just ;; indented once (+ baseline rust-indent-offset) ;; otherwise, skip over whitespace, (skip-chars-forward "[:space:]") ;; get the column of the type parameter and use that ;; as indentation offset (current-column))))) (progn (back-to-indentation) ;; Point is now at the beginning of the current line (if (or ;; If this line begins with "else" or "{", stay on the ;; baseline as well (we are continuing an expression, ;; but the "else" or "{" should align with the beginning ;; of the expression it's in.) ;; Or, if this line starts a comment, stay on the ;; baseline as well. (looking-at "\\\\|{\\|/[/*]") ;; If this is the start of a top-level item, ;; stay on the baseline. (looking-at rust-top-item-beg-re) (save-excursion (rust-rewind-irrelevant) ;; Point is now at the end of the previous line (or ;; If we are at the start of the buffer, no ;; indentation is needed, so stay at baseline... (= (point) 1) ;; ..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 "[(,:;[{}]\\|[^|]|" (- (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 ;; Otherwise, we are continuing the same expression from the previous line, ;; so add one additional indent level (+ baseline rust-indent-offset)))))))))) (when indent ;; If we're at the beginning of the line (before or at the current ;; indentation), jump with the indentation change. Otherwise, save the ;; excursion so that adding the indentations will leave us at the ;; equivalent position within the line to where we were before. (if (<= (current-column) (current-indentation)) (indent-line-to indent) (save-excursion (indent-line-to indent)))))) ;; Font-locking definitions and helpers (defconst rust-mode-keywords '("as" "box" "break" "const" "continue" "crate" "do" "dyn" "else" "enum" "extern" "false" "fn" "for" "if" "impl" "in" "let" "loop" "match" "mod" "move" "mut" "priv" "pub" "ref" "return" "self" "static" "struct" "super" "true" "trait" "type" "use" "virtual" "where" "while" "yield")) (defconst rust-special-types '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64" "u128" "i128" "f32" "f64" "isize" "usize" "bool" "str" "char")) (defconst rust-re-type-or-constructor (rx symbol-start (group upper (0+ (any word nonascii digit "_"))) symbol-end)) (defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]") (defun rust-re-word (inner) (concat "\\<" inner "\\>")) (defun rust-re-grab (inner) (concat "\\(" inner "\\)")) (defun rust-re-shy (inner) (concat "\\(?:" inner "\\)")) (defun rust-re-item-def (itype) (concat (rust-re-word itype) "[[:space:]]+" (rust-re-grab rust-re-ident))) (defun rust-re-item-def-imenu (itype) (concat "^[[:space:]]*" (rust-re-shy (concat (rust-re-word rust-re-vis) "[[:space:]]+")) "?" (rust-re-shy (concat (rust-re-word "default") "[[:space:]]+")) "?" (rust-re-shy (concat (rust-re-word rust-re-unsafe) "[[:space:]]+")) "?" (rust-re-shy (concat (rust-re-word rust-re-extern) "[[:space:]]+" (rust-re-shy "\"[^\"]+\"[[:space:]]+") "?")) "?" (rust-re-item-def itype))) (defconst rust-re-special-types (regexp-opt rust-special-types 'symbols)) (defun rust-path-font-lock-matcher (re-ident) "Matches names like \"foo::\" or \"Foo::\" (depending on RE-IDENT, which should match the desired identifiers), but does not match type annotations \"foo::<\"." `(lambda (limit) (catch 'rust-path-font-lock-matcher (while t (let* ((symbol-then-colons (rx-to-string '(seq (group (regexp ,re-ident)) "::"))) (match (re-search-forward symbol-then-colons limit t))) (cond ;; If we didn't find a match, there are no more occurrences ;; of foo::, so return. ((null match) (throw 'rust-path-font-lock-matcher nil)) ;; If this isn't a type annotation foo::<, we've found a ;; match, so a return it! ((not (looking-at (rx (0+ space) "<"))) (throw 'rust-path-font-lock-matcher match)))))))) (defun rust-next-string-interpolation (limit) "Search forward from point for next Rust interpolation marker before LIMIT. Set point to the end of the occurrence found, and return match beginning and end." (catch 'match (save-match-data (save-excursion (while (search-forward "{" limit t) (if (eql (char-after (point)) ?{) (forward-char) (let ((start (match-beginning 0))) ;; According to fmt_macros::Parser::next, an opening brace ;; must be followed by an optional argument and/or format ;; specifier, then a closing brace. A single closing brace ;; without a corresponding unescaped opening brace is an ;; error. We don't need to do anything special with ;; arguments, specifiers, or errors, so we only search for ;; the single closing brace. (when (search-forward "}" limit t) (throw 'match (list start (point))))))))))) (defun rust-string-interpolation-matcher (limit) "Match next Rust interpolation marker before LIMIT and set match data if found. Returns nil if not within a Rust string." (when (rust-in-str) (let ((match (rust-next-string-interpolation limit))) (when match (set-match-data match) (goto-char (cadr match)) match)))) (defvar rust-builtin-formatting-macros '("eprint" "eprintln" "format" "print" "println") "List of builtin Rust macros for string formatting used by `rust-mode-font-lock-keywords'. (`write!' is handled separately.)") (defvar rust-formatting-macro-opening-re "[[:space:]\n]*[({[][[:space:]\n]*" "Regular expression to match the opening delimiter of a Rust formatting macro.") (defvar rust-start-of-string-re "\\(?:r#*\\)?\"" "Regular expression to match the start of a Rust raw string.") (defvar rust-mode-font-lock-keywords (append `( ;; Keywords proper (,(regexp-opt rust-mode-keywords 'symbols) . font-lock-keyword-face) ;; Contextual keywords ("\\_<\\(default\\)[[:space:]]+fn\\_>" 1 font-lock-keyword-face) (,rust-re-union 1 font-lock-keyword-face) ;; Special types (,(regexp-opt rust-special-types 'symbols) . font-lock-type-face) ;; The unsafe keyword ("\\_" . 'rust-unsafe-face) ;; Attributes like `#[bar(baz)]` or `#![bar(baz)]` or `#[bar = "baz"]` (,(rust-re-grab (concat "#\\!?\\[" rust-re-ident "[^]]*\\]")) 1 font-lock-preprocessor-face keep) ;; Builtin formatting macros (,(concat (rust-re-grab (concat (regexp-opt rust-builtin-formatting-macros) "!")) (concat rust-formatting-macro-opening-re "\\(?:" rust-start-of-string-re) "\\)?") (1 'rust-builtin-formatting-macro-face) (rust-string-interpolation-matcher (rust-end-of-string) nil (0 'rust-string-interpolation-face t nil))) ;; write! macro (,(concat (rust-re-grab "write\\(ln\\)?!") (concat rust-formatting-macro-opening-re "[[:space:]]*[^\"]+,[[:space:]]*" rust-start-of-string-re)) (1 'rust-builtin-formatting-macro-face) (rust-string-interpolation-matcher (rust-end-of-string) nil (0 'rust-string-interpolation-face t nil))) ;; Syntax extension invocations like `foo!`, highlight including the ! (,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:][]") 1 font-lock-preprocessor-face) ;; Field names like `foo:`, highlight excluding the : (,(concat (rust-re-grab rust-re-ident) ":[^:]") 1 font-lock-variable-name-face) ;; CamelCase Means Type Or Constructor (,rust-re-type-or-constructor 1 font-lock-type-face) ;; Type-inferred binding (,(concat "\\_<\\(?:let\\s-+ref\\|let\\|ref\\)\\s-+\\(?:mut\\s-+\\)?" (rust-re-grab rust-re-ident) "\\_>") 1 font-lock-variable-name-face) ;; Type names like `Foo::`, highlight excluding the :: (,(rust-path-font-lock-matcher rust-re-uc-ident) 1 font-lock-type-face) ;; Module names like `foo::`, highlight excluding the :: (,(rust-path-font-lock-matcher rust-re-lc-ident) 1 font-lock-constant-face) ;; Lifetimes like `'foo` (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face) ;; Question mark operator ("\\?" . 'rust-question-mark-face) ) ;; Ensure we highlight `Foo` in `struct Foo` as a type. (mapcar #'(lambda (x) (list (rust-re-item-def (car x)) 1 (cdr x))) '(("enum" . font-lock-type-face) ("struct" . font-lock-type-face) ("union" . font-lock-type-face) ("type" . font-lock-type-face) ("mod" . font-lock-constant-face) ("use" . font-lock-constant-face) ("fn" . font-lock-function-name-face))))) (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" "union" "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) ;; In a brace context a "]" introduces an expression. ((and (eq token 'open-brace) (rust-looking-back-str "]"))) ;; 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 [{([] ;; 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" "union" "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 { 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 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. (t 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 ",")) (condition-case nil (progn (backward-up-list) (rust-is-in-expression-context 'open-brace)) (scan-error nil))) ;; 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 "::" 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"))) ((rust-looking-back-str "?") (rust-is-in-expression-context 'ambiguous-operator)) ;; 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 rust-re-special-types) (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-ordinary-lt-gt-p () "Test whether the `<' or `>' at point is an ordinary operator of some kind. This returns t if the `<' or `>' is an ordinary operator (like less-than) or part of one (like `->'); and nil if the character should be considered a paired angle bracket." (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 "<")))))))) (defun rust-mode-syntactic-face-function (state) "Syntactic face function to distinguish doc comments from other comments." (if (nth 3 state) 'font-lock-string-face (save-excursion (goto-char (nth 8 state)) (if (looking-at "/\\([*][*!][^*!]\\|/[/!][^/!]\\)") 'font-lock-doc-face 'font-lock-comment-face )))) (eval-and-compile (defconst rust--char-literal-rx (rx (seq (group "'") (or (seq "\\" (or (: "u{" (** 1 6 xdigit) "}") (: "x" (= 2 xdigit)) (any "'nrt0\"\\"))) (not (any "'\\")) ) (group "'"))) "A regular expression matching a character literal.")) (defun rust--syntax-propertize-raw-string (end) "A helper for rust-syntax-propertize. If point is already in a raw string, this will apply the appropriate string syntax to the character up to the end of the raw string, or to `end', whichever comes first." (let ((str-start (nth 8 (syntax-ppss)))) (when str-start (when (save-excursion (goto-char str-start) (looking-at "r\\(#*\\)\\(\"\\)")) ;; In a raw string, so try to find the end. (let ((hashes (match-string 1))) ;; Match \ characters at the end of the string to suppress ;; their normal character-quote syntax. (when (re-search-forward (concat "\\(\\\\*\\)\\(\"" hashes "\\)") end t) (put-text-property (match-beginning 1) (match-end 1) 'syntax-table (string-to-syntax "_")) (put-text-property (1- (match-end 2)) (match-end 2) 'syntax-table (string-to-syntax "|")) (goto-char (match-end 0)))))))) (defun rust-syntax-propertize (start end) "A `syntax-propertize-function' for `rust-mode'." (goto-char start) (rust--syntax-propertize-raw-string end) (funcall (syntax-propertize-rules ;; Character literals. (rust--char-literal-rx (1 "\"") (2 "\"")) ;; Raw strings. ("\\(r\\)#*\"" (1 (prog1 "|" (goto-char (match-end 0)) (rust--syntax-propertize-raw-string end)))) ("[<>]" (0 (ignore (when (save-match-data (save-excursion (goto-char (match-beginning 0)) (rust-ordinary-lt-gt-p))) (put-text-property (match-beginning 0) (match-end 0) 'syntax-table (string-to-syntax ".")) (goto-char (match-end 0))))))) (point) end)) (defun rust-fill-prefix-for-comment-start (line-start) "Determine what to use for `fill-prefix' based on what is at the beginning of a line." (let ((result ;; Replace /* with same number of spaces (replace-regexp-in-string "\\(?:/\\*+?\\)[!*]?" (lambda (s) ;; We want the * to line up with the first * of the ;; comment start (let ((offset (if (eq t (compare-strings "/*" nil nil s (- (length s) 2) (length s))) 1 2))) (concat (make-string (- (length s) offset) ?\x20) "*"))) line-start))) ;; Make sure we've got at least one space at the end (if (not (= (aref result (- (length result) 1)) ?\x20)) (setq result (concat result " "))) result)) (defun rust-in-comment-paragraph (body) ;; We might move the point to fill the next comment, but we don't want it ;; seeming to jump around on the user (save-excursion ;; If we're outside of a comment, with only whitespace and then a comment ;; in front, jump to the comment and prepare to fill it. (when (not (nth 4 (syntax-ppss))) (beginning-of-line) (when (looking-at (concat "[[:space:]\n]*" comment-start-skip)) (goto-char (match-end 0)))) ;; We need this when we're moving the point around and then checking syntax ;; while doing paragraph fills, because the cache it uses isn't always ;; invalidated during this. (syntax-ppss-flush-cache 1) ;; If we're at the beginning of a comment paragraph with nothing but ;; whitespace til the next line, jump to the next line so that we use the ;; existing prefix to figure out what the new prefix should be, rather than ;; inferring it from the comment start. (let ((next-bol (line-beginning-position 2))) (while (save-excursion (end-of-line) (syntax-ppss-flush-cache 1) (and (nth 4 (syntax-ppss)) (save-excursion (beginning-of-line) (looking-at paragraph-start)) (looking-at "[[:space:]]*$") (nth 4 (syntax-ppss next-bol)))) (goto-char next-bol))) (syntax-ppss-flush-cache 1) ;; If we're on the last line of a multiline-style comment that started ;; above, back up one line so we don't mistake the * of the */ that ends ;; the comment for a prefix. (when (save-excursion (and (nth 4 (syntax-ppss (line-beginning-position 1))) (looking-at "[[:space:]]*\\*/"))) (goto-char (line-end-position 0))) (funcall body))) (defun rust-with-comment-fill-prefix (body) (let* ((line-string (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (line-comment-start (when (nth 4 (syntax-ppss)) (cond ;; If we're inside the comment and see a * prefix, use it ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)" line-string) (match-string 1 line-string)) ;; If we're at the start of a comment, figure out what prefix ;; to use for the subsequent lines after it ((string-match (concat "[[:space:]]*" comment-start-skip) line-string) (rust-fill-prefix-for-comment-start (match-string 0 line-string)))))) (fill-prefix (or line-comment-start fill-prefix))) (funcall body))) (defun rust-find-fill-prefix () (rust-in-comment-paragraph (lambda () (rust-with-comment-fill-prefix (lambda () fill-prefix))))) (defun rust-fill-paragraph (&rest args) "Special wrapping for `fill-paragraph' to handle multi-line comments with a * prefix on each line." (rust-in-comment-paragraph (lambda () (rust-with-comment-fill-prefix (lambda () (let ((fill-paragraph-function (if (not (eq fill-paragraph-function 'rust-fill-paragraph)) fill-paragraph-function)) (fill-paragraph-handle-comment t)) (apply 'fill-paragraph args) t)))))) (defun rust-do-auto-fill (&rest args) "Special wrapping for `do-auto-fill' to handle multi-line comments with a * prefix on each line." (rust-with-comment-fill-prefix (lambda () (apply 'do-auto-fill args) t))) (defun rust-fill-forward-paragraph (arg) ;; This is to work around some funny behavior when a paragraph separator is ;; at the very top of the file and there is a fill prefix. (let ((fill-prefix nil)) (forward-paragraph arg))) (defun rust-comment-indent-new-line (&optional arg) (rust-with-comment-fill-prefix (lambda () (comment-indent-new-line arg)))) ;;; Imenu support (defvar rust-imenu-generic-expression (append (mapcar #'(lambda (x) (list (capitalize x) (rust-re-item-def-imenu x) 1)) '("enum" "struct" "union" "type" "mod" "fn" "trait" "impl")) `(("Macro" ,(rust-re-item-def-imenu "macro_rules!") 1))) "Value for `imenu-generic-expression' in Rust mode. Create a hierarchical index of the item definitions in a Rust file. Imenu will show all the enums, structs, etc. in their own subheading. Use idomenu (imenu with `ido-mode') for best mileage.") ;;; Defun Motions (defun rust-beginning-of-defun (&optional arg) "Move backward to the beginning of the current defun. With ARG, move backward multiple defuns. Negative ARG means move forward. This is written mainly to be used as `beginning-of-defun-function' for Rust. Don't move to the beginning of the line. `beginning-of-defun', which calls this, does that afterwards." (interactive "p") (let* ((arg (or arg 1)) (magnitude (abs arg)) (sign (if (< arg 0) -1 1))) ;; If moving forward, don't find the defun we might currently be ;; on. (when (< sign 0) (end-of-line)) (catch 'done (dotimes (_ magnitude) ;; Search until we find a match that is not in a string or comment. (while (if (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)") nil 'move sign) (rust-in-str-or-cmnt) ;; Did not find it. (throw 'done nil))))) t)) (defun rust-end-of-defun () "Move forward to the next end of defun. With argument, do it that many times. Negative argument -N means move back to Nth preceding end of defun. Assume that this is called after beginning-of-defun. So point is at the beginning of the defun body. This is written mainly to be used as `end-of-defun-function' for Rust." (interactive) ;; Find the opening brace (if (re-search-forward "[{]" nil t) (progn (goto-char (match-beginning 0)) ;; Go to the closing brace (condition-case nil (forward-sexp) (scan-error ;; The parentheses are unbalanced; instead of being unable to fontify, just jump to the end of the buffer (goto-char (point-max))))) ;; There is no opening brace, so consider the whole buffer to be one "defun" (goto-char (point-max)))) (defun rust-end-of-string () "Skip to the end of the current string." (save-excursion (skip-syntax-forward "^\"|") (skip-syntax-forward "\"|") (point))) ;; Formatting using rustfmt (defun rust--format-call (buf) "Format BUF using rustfmt." (with-current-buffer (get-buffer-create "*rustfmt*") (erase-buffer) (insert-buffer-substring buf) (let* ((tmpf (make-temp-file "rustfmt")) (ret (call-process-region (point-min) (point-max) rust-rustfmt-bin t `(t ,tmpf) nil))) (unwind-protect (cond ((zerop ret) (if (not (string= (buffer-string) (with-current-buffer buf (buffer-string)))) (copy-to-buffer buf (point-min) (point-max))) (kill-buffer)) ((= ret 3) (if (not (string= (buffer-string) (with-current-buffer buf (buffer-string)))) (copy-to-buffer buf (point-min) (point-max))) (erase-buffer) (insert-file-contents tmpf) (error "Rustfmt could not format some lines, see *rustfmt* buffer for details")) (t (erase-buffer) (insert-file-contents tmpf) (error "Rustfmt failed, see *rustfmt* buffer for details")))) (delete-file tmpf)))) (defconst rust--format-word "\\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|match\\|struct\\|union\\|unsafe\\|while\\)\\b") (defconst rust--format-line "\\([\n]\\)") ;; Counts number of matches of regex beginning up to max-beginning, ;; leaving the point at the beginning of the last match. (defun rust--format-count (regex max-beginning) (let ((count 0) save-point beginning) (while (and (< (point) max-beginning) (re-search-forward regex max-beginning t)) (setq count (1+ count)) (setq beginning (match-beginning 1))) ;; try one more in case max-beginning lies in the middle of a match (setq save-point (point)) (when (re-search-forward regex nil t) (let ((try-beginning (match-beginning 1))) (if (> try-beginning max-beginning) (goto-char save-point) (setq count (1+ count)) (setq beginning try-beginning)))) (when beginning (goto-char beginning)) count)) ;; Gets list describing pos or (point). ;; The list contains: ;; 1. the number of matches of rust--format-word, ;; 2. the number of matches of rust--format-line after that, ;; 3. the number of columns after that. (defun rust--format-get-loc (buffer &optional pos) (with-current-buffer buffer (save-excursion (let ((pos (or pos (point))) words lines columns) (goto-char (point-min)) (setq words (rust--format-count rust--format-word pos)) (setq lines (rust--format-count rust--format-line pos)) (if (> lines 0) (if (= (point) pos) (setq columns -1) (forward-char 1) (goto-char pos) (setq columns (current-column))) (let ((initial-column (current-column))) (goto-char pos) (setq columns (- (current-column) initial-column)))) (list words lines columns))))) ;; Moves the point forward by count matches of regex up to max-pos, ;; and returns new max-pos making sure final position does not include another match. (defun rust--format-forward (regex count max-pos) (when (< (point) max-pos) (let ((beginning (point))) (while (> count 0) (setq count (1- count)) (re-search-forward regex nil t) (setq beginning (match-beginning 1))) (when (re-search-forward regex nil t) (setq max-pos (min max-pos (match-beginning 1)))) (goto-char beginning))) max-pos) ;; Gets the position from a location list obtained using rust--format-get-loc. (defun rust--format-get-pos (buffer loc) (with-current-buffer buffer (save-excursion (goto-char (point-min)) (let ((max-pos (point-max)) (words (pop loc)) (lines (pop loc)) (columns (pop loc))) (setq max-pos (rust--format-forward rust--format-word words max-pos)) (setq max-pos (rust--format-forward rust--format-line lines max-pos)) (when (> lines 0) (forward-char)) (let ((initial-column (current-column)) (save-point (point))) (move-end-of-line nil) (when (> (current-column) (+ initial-column columns)) (goto-char save-point) (forward-char columns))) (min (point) max-pos))))) (defun rust-format-buffer () "Format the current buffer using rustfmt." (interactive) (unless (executable-find rust-rustfmt-bin) (error "Could not locate executable \"%s\"" rust-rustfmt-bin)) (let* ((current (current-buffer)) (base (or (buffer-base-buffer current) current)) buffer-loc window-loc) (dolist (buffer (buffer-list)) (when (or (eq buffer base) (eq (buffer-base-buffer buffer) base)) (push (list buffer (rust--format-get-loc buffer nil)) buffer-loc))) (dolist (window (window-list)) (let ((buffer (window-buffer window))) (when (or (eq buffer base) (eq (buffer-base-buffer buffer) base)) (let ((start (window-start window)) (point (window-point window))) (push (list window (rust--format-get-loc buffer start) (rust--format-get-loc buffer point)) window-loc))))) (unwind-protect (rust--format-call (current-buffer)) (dolist (loc buffer-loc) (let* ((buffer (pop loc)) (pos (rust--format-get-pos buffer (pop loc)))) (with-current-buffer buffer (goto-char pos)))) (dolist (loc window-loc) (let* ((window (pop loc)) (buffer (window-buffer window)) (start (rust--format-get-pos buffer (pop loc))) (pos (rust--format-get-pos buffer (pop loc)))) (unless (eq buffer current) (set-window-start window start)) (set-window-point window pos))))) (message "Formatted buffer with rustfmt.")) (defun rust-enable-format-on-save () "Enable formatting using rustfmt when saving buffer." (interactive) (setq-local rust-format-on-save t)) (defun rust-disable-format-on-save () "Disable formatting using rustfmt when saving buffer." (interactive) (setq-local rust-format-on-save nil)) (defun rust-compile () "Compile using `cargo build`" (interactive) (compile "cargo build")) (defvar rust-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-f") 'rust-format-buffer) map) "Keymap for Rust major mode.") ;;;###autoload (define-derived-mode rust-mode prog-mode "Rust" "Major mode for Rust code. \\{rust-mode-map}" :group 'rust-mode :syntax-table rust-mode-syntax-table ;; Syntax. (setq-local syntax-propertize-function #'rust-syntax-propertize) ;; Indentation (setq-local indent-line-function 'rust-mode-indent-line) ;; Fonts (setq-local font-lock-defaults '(rust-mode-font-lock-keywords nil nil nil nil (font-lock-syntactic-face-function . rust-mode-syntactic-face-function) )) ;; Misc (setq-local comment-start "// ") (setq-local comment-end "") (setq-local indent-tabs-mode nil) (setq-local open-paren-in-column-0-is-defun-start nil) ;; Auto indent on } (setq-local electric-indent-chars (cons ?} (and (boundp 'electric-indent-chars) electric-indent-chars))) ;; Allow paragraph fills for comments (setq-local comment-start-skip "\\(?://[/!]*\\|/\\*[*!]?\\)[[:space:]]*") (setq-local paragraph-start (concat "[[:space:]]*\\(?:" comment-start-skip "\\|\\*/?[[:space:]]*\\|\\)$")) (setq-local paragraph-separate paragraph-start) (setq-local normal-auto-fill-function 'rust-do-auto-fill) (setq-local fill-paragraph-function 'rust-fill-paragraph) (setq-local fill-forward-paragraph-function 'rust-fill-forward-paragraph) (setq-local adaptive-fill-function 'rust-find-fill-prefix) (setq-local adaptive-fill-first-line-regexp "") (setq-local comment-multi-line t) (setq-local comment-line-break-function 'rust-comment-indent-new-line) (setq-local imenu-generic-expression rust-imenu-generic-expression) (setq-local imenu-syntax-alist '((?! . "w"))) ; For macro_rules! (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) (setq-local electric-pair-inhibit-predicate 'rust-electric-pair-inhibit-predicate-wrap) (add-hook 'before-save-hook 'rust--before-save-hook nil t) (setq-local rust-buffer-project nil) (when rust-always-locate-project-on-open (rust-update-buffer-project))) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) (defun rust-mode-reload () (interactive) (unload-feature 'rust-mode) (require 'rust-mode) (rust-mode)) (defun rust--before-save-hook () (when rust-format-on-save (rust-format-buffer))) (defvar rustc-compilation-regexps (let ((file "\\([^\n]+\\)") (start-line "\\([0-9]+\\)") (start-col "\\([0-9]+\\)")) (let ((re (concat "^ *--> " file ":" start-line ":" start-col ; --> 1:2:3 ))) (cons re '(1 2 3)))) "Specifications for matching errors in rustc invocations. See `compilation-error-regexp-alist' for help on their format.") (defvar rustc-colon-compilation-regexps (let ((file "\\([^\n]+\\)") (start-line "\\([0-9]+\\)") (start-col "\\([0-9]+\\)")) (let ((re (concat "^ *::: " file ":" start-line ":" start-col ; ::: foo/bar.rs ))) (cons re '(1 2 3 0)))) ;; 0 for info type "Specifications for matching `:::` hints in rustc invocations. See `compilation-error-regexp-alist' for help on their format.") ;; Match test run failures and panics during compilation as ;; compilation warnings (defvar cargo-compilation-regexps '("^\\s-+thread '[^']+' panicked at \\('[^']+', \\([^:]+\\):\\([0-9]+\\)\\)" 2 3 nil nil 1) "Specifications for matching panics in cargo test invocations. See `compilation-error-regexp-alist' for help on their format.") (defun rustc-scroll-down-after-next-error () "In the new style error messages, the regular expression matches on the file name (which appears after `-->`), but the start of the error appears a few lines earlier. This hook runs after `M-x next-error`; it simply scrolls down a few lines in the compilation window until the top of the error is visible." (save-selected-window (when (eq major-mode 'rust-mode) (select-window (get-buffer-window next-error-last-buffer 'visible)) (when (save-excursion (beginning-of-line) (looking-at " *-->")) (let ((start-of-error (save-excursion (beginning-of-line) (while (not (looking-at "^[a-z]+:\\|^[a-z]+\\[E[0-9]+\\]:")) (forward-line -1)) (point)))) (set-window-start (selected-window) start-of-error)))))) (eval-after-load 'compile '(progn (add-to-list 'compilation-error-regexp-alist-alist (cons 'rustc rustc-compilation-regexps)) (add-to-list 'compilation-error-regexp-alist 'rustc) (add-to-list 'compilation-error-regexp-alist-alist (cons 'rustc-colon rustc-colon-compilation-regexps)) (add-to-list 'compilation-error-regexp-alist 'rustc-colon) (add-to-list 'compilation-error-regexp-alist-alist (cons 'cargo cargo-compilation-regexps)) (add-to-list 'compilation-error-regexp-alist 'cargo) (add-hook 'next-error-hook 'rustc-scroll-down-after-next-error))) ;;; Functions to submit (parts of) buffers to the rust playpen, for ;;; sharing. (defun rust-playpen-region (begin end) "Create a sharable URL for the contents of the current region on the Rust playpen." (interactive "r") (let* ((data (buffer-substring begin end)) (escaped-data (url-hexify-string data)) (escaped-playpen-url (url-hexify-string (format rust-playpen-url-format escaped-data)))) (if (> (length escaped-playpen-url) 5000) (error "encoded playpen data exceeds 5000 character limit (length %s)" (length escaped-playpen-url)) (let ((shortener-url (format rust-shortener-url-format escaped-playpen-url)) (url-request-method "POST")) (url-retrieve shortener-url (lambda (state) ; filter out the headers etc. included at the ; start of the buffer: the relevant text ; (shortened url or error message) is exactly ; the last line. (goto-char (point-max)) (let ((last-line (thing-at-point 'line t)) (err (plist-get state :error))) (kill-buffer) (if err (error "failed to shorten playpen url: %s" last-line) (message "%s" last-line))))))))) (defun rust-playpen-buffer () "Create a sharable URL for the contents of the current buffer on the Rust playpen." (interactive) (rust-playpen-region (point-min) (point-max))) (defun rust-promote-module-into-dir () "Promote the module file visited by the current buffer into its own directory. For example, if the current buffer is visiting the file `foo.rs', then this function creates the directory `foo' and renames the file to `foo/mod.rs'. The current buffer will be updated to visit the new file." (interactive) (let ((filename (buffer-file-name))) (if (not filename) (message "Buffer is not visiting a file.") (if (string-equal (file-name-nondirectory filename) "mod.rs") (message "Won't promote a module file already named mod.rs.") (let* ((basename (file-name-sans-extension (file-name-nondirectory filename))) (mod-dir (file-name-as-directory (concat (file-name-directory filename) basename))) (new-name (concat mod-dir "mod.rs"))) (mkdir mod-dir t) (rename-file filename new-name 1) (set-visited-file-name new-name)))))) (defun rust-run-clippy () "Run `cargo clippy'." (interactive) (when (null rust-buffer-project) (rust-update-buffer-project)) (let* ((args (list rust-cargo-bin "clippy" (concat "--manifest-path=" rust-buffer-project))) ;; set `compile-command' temporarily so `compile' doesn't ;; clobber the existing value (compile-command (mapconcat #'shell-quote-argument args " "))) (compile compile-command))) (defun rust-update-buffer-project () (setq-local rust-buffer-project (rust-buffer-project))) (defun rust-buffer-project () "Get project root if possible." (with-temp-buffer (let ((ret (call-process rust-cargo-bin nil t nil "locate-project"))) (when (/= ret 0) (error "`cargo locate-project' returned %s status: %s" ret (buffer-string))) (goto-char 0) (let ((output (json-read))) (cdr (assoc-string "root" output)))))) (provide 'rust-mode) ;;; rust-mode.el ends here rust-mode-0.4.0/test-project/000077500000000000000000000000001331453514100160555ustar00rootroot00000000000000rust-mode-0.4.0/test-project/Cargo.toml000066400000000000000000000000361331453514100200040ustar00rootroot00000000000000# Dummy file needed for test