diff -Nru elpa-rust-mode-0.3.0/debian/changelog elpa-rust-mode-0.4.0/debian/changelog --- elpa-rust-mode-0.3.0/debian/changelog 2019-08-31 12:52:17.000000000 +0000 +++ elpa-rust-mode-0.4.0/debian/changelog 2021-01-27 12:00:03.000000000 +0000 @@ -1,3 +1,11 @@ +elpa-rust-mode (0.4.0-2) unstable; urgency=medium + + * Team upload + * Rebuild with dh-elpa 2.x + * Upload to unstable + + -- David Bremner Wed, 27 Jan 2021 08:00:03 -0400 + elpa-rust-mode (0.3.0-3) unstable; urgency=medium * Team upload. @@ -5,6 +13,14 @@ -- David Bremner Sat, 31 Aug 2019 09:52:17 -0300 +elpa-rust-mode (0.4.0-1) experimental; urgency=medium + + * New upstream release. + * Use full name. + * Change priority to optional. + + -- Matthew Kraai Mon, 04 Mar 2019 14:43:53 -0800 + elpa-rust-mode (0.3.0-2) unstable; urgency=medium * Team upload. diff -Nru elpa-rust-mode-0.3.0/debian/control elpa-rust-mode-0.4.0/debian/control --- elpa-rust-mode-0.3.0/debian/control 2019-08-31 12:52:17.000000000 +0000 +++ elpa-rust-mode-0.4.0/debian/control 2021-01-27 12:00:03.000000000 +0000 @@ -1,8 +1,8 @@ Source: elpa-rust-mode Maintainer: Debian Emacsen team -Uploaders: Matt Kraai +Uploaders: Matthew Kraai Section: editors -Priority: extra +Priority: optional Build-Depends: debhelper (>= 9), dh-elpa (>= 0.0.17) Standards-Version: 3.9.8 Homepage: https://github.com/rust-lang/rust-mode diff -Nru elpa-rust-mode-0.3.0/debian/patches/compilation-regexps-Match-line-before-too.patch elpa-rust-mode-0.4.0/debian/patches/compilation-regexps-Match-line-before-too.patch --- elpa-rust-mode-0.3.0/debian/patches/compilation-regexps-Match-line-before-too.patch 2019-08-31 12:52:17.000000000 +0000 +++ elpa-rust-mode-0.4.0/debian/patches/compilation-regexps-Match-line-before-too.patch 2021-01-27 12:00:03.000000000 +0000 @@ -24,11 +24,11 @@ rust-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -diff --git a/rust-mode.el b/rust-mode.el -index 60a81f1..8abea3b 100644 ---- a/rust-mode.el -+++ b/rust-mode.el -@@ -1417,7 +1417,7 @@ See `compilation-error-regexp-alist' for help on their format.") +Index: rust-mode/rust-mode.el +=================================================================== +--- rust-mode.orig/rust-mode.el ++++ rust-mode/rust-mode.el +@@ -1578,7 +1578,7 @@ This is written mainly to be used as `en (let ((file "\\([^\n]+\\)") (start-line "\\([0-9]+\\)") (start-col "\\([0-9]+\\)")) @@ -36,4 +36,4 @@ + (let ((re (concat "^.*\n *--> " file ":" start-line ":" start-col ; --> 1:2:3 ))) (cons re '(1 2 3)))) - "Specifications for matching errors in rustc invocations (new style). + "Specifications for matching errors in rustc invocations. diff -Nru elpa-rust-mode-0.3.0/README.md elpa-rust-mode-0.4.0/README.md --- elpa-rust-mode-0.3.0/README.md 2016-11-01 04:09:05.000000000 +0000 +++ elpa-rust-mode-0.4.0/README.md 2018-06-26 22:12:49.000000000 +0000 @@ -4,6 +4,8 @@ `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 @@ -37,6 +39,16 @@ * 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 @@ -64,6 +76,18 @@ 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 diff -Nru elpa-rust-mode-0.3.0/rust-mode.el elpa-rust-mode-0.4.0/rust-mode.el --- elpa-rust-mode-0.3.0/rust-mode.el 2016-11-01 04:09:05.000000000 +0000 +++ elpa-rust-mode-0.4.0/rust-mode.el 2018-06-26 22:12:49.000000000 +0000 @@ -1,6 +1,6 @@ ;;; rust-mode.el --- A major emacs mode for editing Rust source code -*-lexical-binding: t-*- -;; Version: 0.3.0 +;; Version: 0.4.0 ;; Author: Mozilla ;; Url: https://github.com/rust-lang/rust-mode ;; Keywords: languages @@ -18,9 +18,14 @@ (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) @@ -32,67 +37,22 @@ (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)))) -(defconst rust-re-non-standard-string - (rx - (or - ;; Raw string: if it matches, it ends up with the starting character - ;; of the string as group 1, any ending backslashes as group 4, and - ;; the ending character as either group 5 or group 6. - (seq - ;; The "r" starts the raw string. Capture it as group 1 to mark it as such syntactically: - (group "r") - - ;; Then either: - (or - ;; a sequence at least one "#" (followed by quote). Capture all - ;; but the last "#" as group 2 for this case. - (seq (group (* "#")) "#\"") - - ;; ...or a quote without any "#". Capture it as group 3. This is - ;; used later to match the opposite quote only if this capture - ;; occurred - (group "\"")) - - ;; The contents of the string: - (*? anything) - - ;; If there are any backslashes at the end of the string, capture - ;; them as group 4 so we can suppress the normal escape syntax - ;; parsing: - (group (* "\\")) - - ;; Then the end of the string--the backreferences ensure that we - ;; only match the kind of ending that corresponds to the beginning - ;; we had: - (or - ;; There were "#"s - capture the last one as group 5 to mark it as - ;; the end of the string: - (seq "\"" (backref 2) (group "#")) - - ;; No "#"s - capture the ending quote (using a backref to group 3, - ;; so that we can't match a quote if we had "#"s) as group 6 - (group (backref 3)) - - ;; If the raw string wasn't actually closed, go all the way to the end - string-end)) - - ;; Character literal: match the beginning ' of a character literal - ;; as group 7, and the ending one as group 8 - (seq - (group "'") - (or - (seq - "\\" - (or - (: "u{" (** 1 6 xdigit) "}") - (: "x" (= 2 xdigit)) - (any "'nrt0\"\\"))) - (not (any "'\\")) - ) - (group "'")) - ) - )) +;;; 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)" @@ -124,15 +84,15 @@ (let ((table (make-syntax-table))) ;; Operators - (dolist (i '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@)) + (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?^ ?! ?< ?> ?~ ?@)) (modify-syntax-entry i "." table)) ;; Strings (modify-syntax-entry ?\" "\"" table) (modify-syntax-entry ?\\ "\\" table) - ;; Angle brackets. We suppress this with syntactic fontification when - ;; needed + ;; Angle brackets. We suppress this with syntactic propertization + ;; when needed (modify-syntax-entry ?< "(>" table) (modify-syntax-entry ?> ")<" table) @@ -146,7 +106,7 @@ (defgroup rust-mode nil "Support for Rust code." - :link '(url-link "http://www.rust-lang.org/") + :link '(url-link "https://www.rust-lang.org/") :group 'languages) (defcustom rust-indent-offset 4 @@ -161,7 +121,7 @@ :group 'rust-mode :safe #'booleanp) -(defcustom rust-indent-where-clause t +(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 @@ -172,7 +132,7 @@ "Format string to use when submitting code to the playpen" :type 'string :group 'rust-mode) -(defcustom rust-shortener-url-format "http://is.gd/create.php?format=simple&url=%s" +(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) @@ -187,18 +147,47 @@ (defcustom rust-format-on-save nil "Format future rust buffers before saving using rustfmt." :type 'boolean - :safe #'booleanp) + :safe #'booleanp + :group 'rust-mode) (defcustom rust-rustfmt-bin "rustfmt" "Path to rustfmt executable." - :type 'string) + :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)))) @@ -331,7 +320,7 @@ (- (current-column) rust-indent-offset))))) (cond ;; foo.bar(...) - ((rust-looking-back-str ")") + ((looking-back "[)?]" (1- (point))) (backward-list 1) (funcall skip-dot-identifier)) @@ -451,6 +440,10 @@ ;; 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 @@ -494,6 +487,10 @@ ;; 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 @@ -504,7 +501,7 @@ ;; ..or if the previous line ends with any of these: ;; { ? : ( , ; [ } ;; then we are at the beginning of an expression, so stay on the baseline... - (looking-back "[(,:;?[{}]\\|[^|]|" (- (point) 2)) + (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 @@ -528,7 +525,7 @@ '("as" "box" "break" "const" "continue" "crate" - "do" + "do" "dyn" "else" "enum" "extern" "false" "fn" "for" "if" "impl" "in" @@ -540,16 +537,18 @@ "true" "trait" "type" "use" "virtual" - "where" "while")) + "where" "while" + "yield")) (defconst rust-special-types '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64" + "u128" "i128" "f32" "f64" - "float" "int" "uint" "isize" "usize" + "isize" "usize" "bool" "str" "char")) @@ -567,6 +566,10 @@ (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)) @@ -589,12 +592,64 @@ ((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) @@ -605,6 +660,22 @@ (,(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) @@ -612,6 +683,12 @@ ;; 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) @@ -621,109 +698,22 @@ ;; Lifetimes like `'foo` (,(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) + ;; Question mark operator + ("\\?" . 'rust-question-mark-face) ) - ;; Item definitions + ;; 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))))) -(defvar font-lock-beg) -(defvar font-lock-end) - -(defun rust-font-lock-extend-region () - "Extend the region given by `font-lock-beg' and `font-lock-end' - to include the beginning of a string or comment if it includes - part of it. Adjusts to include the r[#] of a raw string as - well." - - (save-excursion - (let ((orig-beg font-lock-beg) - (orig-end font-lock-end)) - - (let* - ;; It's safe to call `syntax-ppss' here on positions that are - ;; already syntactically fontified - ((beg-ppss (syntax-ppss font-lock-beg)) - (beg-in-cmnt (and beg-ppss (nth 4 beg-ppss) (nth 8 beg-ppss))) - (beg-in-str (and beg-ppss (nth 3 beg-ppss) (nth 8 beg-ppss)))) - - (when (and beg-in-str (>= font-lock-beg beg-in-str)) - (setq font-lock-beg (nth 8 beg-ppss)) - (while (equal ?# (char-before font-lock-beg)) - (setq font-lock-beg (1- font-lock-beg))) - (when (equal ?r (char-before font-lock-beg)) - (setq font-lock-beg (1- font-lock-beg)))) - - (when (and beg-in-cmnt (> font-lock-beg beg-in-cmnt)) - (setq font-lock-beg beg-in-cmnt))) - - ;; We need to make sure that if the region ends inside a raw string, we - ;; extend it out past the end of it. But we can't use `syntax-ppss' to - ;; detect that, becaue that depends on font-lock already being done, and we - ;; are trying to figure out how much to font-lock before that. So we use - ;; the regexp directly. - (save-match-data - (goto-char font-lock-beg) - (while (and (< (point) font-lock-end) - (re-search-forward rust-re-non-standard-string (buffer-end 1) t) - (<= (match-beginning 0) font-lock-end)) - (setq font-lock-end (max font-lock-end (match-end 0))) - (goto-char (1+ (match-beginning 0))))) - - (or (/= font-lock-beg orig-beg) - (/= font-lock-end orig-end)) - ))) - -(defun rust-conditional-re-search-forward (regexp bound condition) - ;; Search forward for regexp (with bound). If found, call condition and return the found - ;; match only if it returns true. - (let* (found - found-ret-list - (ret-list (save-excursion - (while (and (not found) (re-search-forward regexp bound t)) - (setq - found-ret-list (list (point) (match-data)) - found (save-match-data (save-excursion (ignore-errors (funcall condition))))) - ;; If the condition filters out a match, need to search - ;; again just after its beginning. This will allow - ;; cases such as: - ;; "bar" r"foo" - ;; where the filtered out search (r" r") should not - ;; prevent finding another one that begins in the middle - ;; of it (r"foo") - (when (not found) - (goto-char (1+ (match-beginning 0)))) - ) - (when found found-ret-list)))) - (when ret-list - (goto-char (nth 0 ret-list)) - (set-match-data (nth 1 ret-list)) - (nth 0 ret-list)))) - -(defun rust-look-for-non-standard-string (bound) - ;; Find a raw string or character literal, but only if it's not in the middle - ;; of another string or a comment. - - (rust-conditional-re-search-forward - rust-re-non-standard-string - bound - (lambda () - (let ((pstate (syntax-ppss (match-beginning 0)))) - (not - (or - (nth 4 pstate) ;; Skip if in a comment - (and (nth 3 pstate) (wholenump (nth 8 pstate)) (< (nth 8 pstate) (match-beginning 0))) ;; Skip if in a string that isn't starting here - )))))) - (defun rust-syntax-class-before-point () (when (> (point) 1) (syntax-class (syntax-after (1- (point)))))) @@ -765,13 +755,13 @@ can have a where clause, rewind back to just before the name of the subject of that where clause and return the new point. Otherwise return nil" - + (let* ((ident-pos (point)) (newpos (save-excursion (rust-rewind-irrelevant) (rust-rewind-type-param-list) (cond - ((rust-looking-back-symbols '("fn" "trait" "enum" "struct" "impl" "type")) ident-pos) + ((rust-looking-back-symbols '("fn" "trait" "enum" "struct" "union" "impl" "type")) ident-pos) ((equal 5 (rust-syntax-class-before-point)) (backward-sexp) @@ -808,7 +798,7 @@ ;; 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 @@ -823,7 +813,10 @@ ;; 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))) @@ -837,7 +830,7 @@ (backward-sexp) (rust-rewind-irrelevant) (looking-back "[{;]" (1- (point)))) - + ((rust-looking-back-ident) (rust-rewind-qualified-ident) (rust-rewind-irrelevant) @@ -858,9 +851,9 @@ (not (and (rust-rewind-to-decl-name) (progn (rust-rewind-irrelevant) - (rust-looking-back-symbols '("enum" "struct" "trait" "type")))))) + (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 @@ -891,7 +884,7 @@ (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))) @@ -940,7 +933,7 @@ ;; 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)) @@ -953,8 +946,11 @@ ((or (equal 4 (rust-syntax-class-before-point)) (rust-looking-back-str ",")) - (backward-up-list) - (rust-is-in-expression-context 'open-brace)) + (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) @@ -976,7 +972,7 @@ (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) @@ -988,7 +984,7 @@ ((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 @@ -1005,6 +1001,9 @@ ((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) @@ -1013,7 +1012,7 @@ ;; 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 @@ -1030,56 +1029,42 @@ (rust-is-lt-char-operator))) (funcall (default-value 'electric-pair-inhibit-predicate) char))) -(defun rust-look-for-non-angle-bracket-lt-gt (bound) - "Find an angle bracket (\"<\" or \">\") that should be part of - a matched pair Relies on the fact that when it finds a < or >, - we have already decided which previous ones are angle brackets - and which ones are not. So this only really works as a - font-lock-syntactic-keywords matcher--it won't work at - arbitrary positions without the earlier parts of the buffer - having already been covered." +(defun rust-ordinary-lt-gt-p () + "Test whether the `<' or `>' at point is an ordinary operator of some kind. - (rust-conditional-re-search-forward - "[<>]" bound - (lambda () - (goto-char (match-beginning 0)) - (cond - ;; If matching is turned off suppress all of them - ((not rust-match-angle-brackets) t) - - ;; We don't take < or > in strings or comments to be angle brackets - ((rust-in-str-or-cmnt) t) - - ;; Inside a macro we don't really know the syntax. Any < or > may be an - ;; angle bracket or it may not. But we know that the other braces have - ;; to balance regardless of the < and >, so if we don't treat any < or > - ;; as angle brackets it won't mess up any paren balancing. - ((rust-in-macro) t) - - ((looking-at "<") - (rust-is-lt-char-operator)) - - ((looking-at ">") - (cond - ;; Don't treat the > in -> or => as an angle bracket - ((member (char-before (point)) '(?- ?=)) t) - - ;; If we are at top level and not in any list, it can't be a closing - ;; angle bracket - ((>= 0 (rust-paren-level)) t) - - ;; Otherwise, treat the > as a closing angle bracket if it would - ;; match an opening one - ((save-excursion - (backward-up-list) - (not (looking-at "<")))))))))) +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) -(defvar rust-mode-font-lock-syntactic-keywords - (append - ;; Handle raw strings and character literals: - `((rust-look-for-non-standard-string (1 "|" nil t) (4 "_" nil t) (5 "|" nil t) (6 "|" nil t) (7 "\"" nil t) (8 "\"" nil t))) - ;; Find where < and > characters represent operators rather than angle brackets: - '((rust-look-for-non-angle-bracket-lt-gt (0 "." 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." @@ -1091,6 +1076,68 @@ '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 @@ -1213,7 +1260,7 @@ (defvar rust-imenu-generic-expression (append (mapcar #'(lambda (x) (list (capitalize x) (rust-re-item-def-imenu x) 1)) - '("enum" "struct" "type" "mod" "fn" "trait" "impl")) + '("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. @@ -1224,13 +1271,6 @@ ;;; Defun Motions -;;; Start of a Rust item -(defvar rust-top-item-beg-re - (concat "^\\s-*\\(?:priv\\|pub\\)?\\s-*" - (regexp-opt - '("enum" "struct" "type" "mod" "use" "fn" "static" "impl" - "extern" "trait")))) - (defun rust-beginning-of-defun (&optional arg) "Move backward to the beginning of the current defun. @@ -1241,8 +1281,22 @@ Don't move to the beginning of the line. `beginning-of-defun', which calls this, does that afterwards." (interactive "p") - (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)\\_>") - nil 'move (or arg 1))) + (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. @@ -1268,18 +1322,123 @@ ;; 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) - (if (zerop (call-process-region (point-min) (point-max) rust-rustfmt-bin t t nil)) - (progn - (if (not (string= (buffer-string) (with-current-buffer buf (buffer-string)))) - (copy-to-buffer buf (point-min) (point-max))) - (kill-buffer)) - (error "Rustfmt failed, see *rustfmt* buffer for details")))) + (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." @@ -1287,22 +1446,41 @@ (unless (executable-find rust-rustfmt-bin) (error "Could not locate executable \"%s\"" rust-rustfmt-bin)) - (let ((cur-line (line-number-at-pos)) - (cur-column (current-column)) - (cur-win-start (window-start))) - (rust--format-call (current-buffer)) - ;; Move to the same line and column as before. This is best - ;; effort: if rustfmt inserted lines before point, we end up in - ;; the wrong place. See issue #162. - (goto-char (point-min)) - (forward-line (1- cur-line)) - (forward-char cur-column) - (set-window-start (selected-window) cur-win-start)) - - ;; Issue #127: Running this on a buffer acts like a revert, and could cause - ;; the fontification to get out of sync. Call the same hook to ensure it is - ;; restored. - (rust--after-revert-hook) + (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.")) @@ -1316,6 +1494,11 @@ (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) @@ -1330,14 +1513,15 @@ :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 - (add-to-list 'font-lock-extend-region-functions 'rust-font-lock-extend-region) (setq-local font-lock-defaults '(rust-mode-font-lock-keywords nil nil nil nil - (font-lock-syntactic-keywords . rust-mode-font-lock-syntactic-keywords) (font-lock-syntactic-face-function . rust-mode-syntactic-face-function) )) @@ -1345,6 +1529,7 @@ (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 @@ -1369,8 +1554,13 @@ (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 'after-revert-hook 'rust--after-revert-hook nil t) - (add-hook 'before-save-hook 'rust--before-save-hook nil t)) + + (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)) @@ -1381,46 +1571,27 @@ (require 'rust-mode) (rust-mode)) -;; Issue #104: When reverting the buffer, make sure all fontification is redone -;; so that we don't end up missing a non-angle-bracket '<' or '>' character. -(defun rust--after-revert-hook () - ;; In Emacs 25 and later, the preferred method to force fontification is - ;; to use `font-lock-ensure', which doesn't exist in Emacs 24 and earlier. - ;; If it's not available, fall back to calling `font-lock-fontify-region' - ;; on the whole buffer. - (save-excursion - (if (fboundp 'font-lock-ensure) - (font-lock-ensure) - (font-lock-fontify-region (point-min) (point-max))))) - (defun rust--before-save-hook () (when rust-format-on-save (rust-format-buffer))) -;; Issue #6887: Rather than inheriting the 'gnu compilation error -;; regexp (which is broken on a few edge cases), add our own 'rust -;; compilation error regexp and use it instead. (defvar rustc-compilation-regexps (let ((file "\\([^\n]+\\)") (start-line "\\([0-9]+\\)") - (start-col "\\([0-9]+\\)") - (end-line "\\([0-9]+\\)") - (end-col "\\([0-9]+\\)") - (msg-type "\\(?:[Ee]rror\\|\\([Ww]arning\\)\\|\\([Nn]ote\\|[Hh]elp\\)\\)")) - (let ((re (concat "^" file ":" start-line ":" start-col - ": " end-line ":" end-col - " " msg-type ":"))) - (cons re '(1 (2 . 4) (3 . 5) (6 . 7))))) + (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-new-compilation-regexps +(defvar rustc-colon-compilation-regexps (let ((file "\\([^\n]+\\)") (start-line "\\([0-9]+\\)") (start-col "\\([0-9]+\\)")) - (let ((re (concat "^ *--> " file ":" start-line ":" start-col ; --> 1:2:3 + (let ((re (concat "^ *::: " file ":" start-line ":" start-col ; ::: foo/bar.rs ))) - (cons re '(1 2 3)))) - "Specifications for matching errors in rustc invocations (new style). + (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 @@ -1438,7 +1609,7 @@ 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)) + (select-window (get-buffer-window next-error-last-buffer 'visible)) (when (save-excursion (beginning-of-line) (looking-at " *-->")) @@ -1453,15 +1624,15 @@ (eval-after-load 'compile '(progn (add-to-list 'compilation-error-regexp-alist-alist - (cons 'rustc-new rustc-new-compilation-regexps)) - (add-to-list 'compilation-error-regexp-alist 'rustc-new) - (add-hook 'next-error-hook 'rustc-scroll-down-after-next-error) - (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-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. @@ -1519,6 +1690,30 @@ (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 diff -Nru elpa-rust-mode-0.3.0/rust-mode-tests.el elpa-rust-mode-0.4.0/rust-mode-tests.el --- elpa-rust-mode-0.3.0/rust-mode-tests.el 2016-11-01 04:09:05.000000000 +0000 +++ elpa-rust-mode-0.4.0/rust-mode-tests.el 2018-06-26 22:12:49.000000000 +0000 @@ -3,6 +3,7 @@ (require 'rust-mode) (require 'ert) (require 'cl) +(require 'imenu) (setq rust-test-fill-column 32) @@ -308,9 +309,6 @@ deindented 1 (lambda () - ;; The indentation will fail in some cases if the syntax properties are - ;; not set. This only happens when font-lock fontifies the buffer. - (font-lock-fontify-buffer) (indent-region 1 (+ 1 (buffer-size)))) indented))) @@ -318,11 +316,11 @@ (ert-deftest indent-struct-fields-aligned () (test-indent " -struct Foo { bar: int, - baz: int } +struct Foo { bar: i32, + baz: i32 } -struct Blah {x:int, - y:int, +struct Blah {x:i32, + y:i32, z:String")) (ert-deftest indent-doc-comments () @@ -347,12 +345,12 @@ " // struct fields out one level: struct foo { - a:int, + a:i32, // comments too b:char } -fn bar(x:Box) { // comment here should not affect the next indent +fn bar(x:Box) { // comment here should not affect the next indent bla(); bla(); }")) @@ -399,11 +397,11 @@ " // Indent out one level because no params appear on the first line fn xyzzy( - a:int, + a:i32, b:char) { } fn abcdef( - a:int, + a:i32, b:char) -> char { }")) @@ -412,17 +410,17 @@ (test-indent " // Align the second line of params to the first -fn foo(a:int, +fn foo(a:i32, b:char) { } -fn bar( a:int, +fn bar( a:i32, b:char) - -> int + -> i32 { } -fn baz( a:int, // should work with a comment here +fn baz( a:i32, // should work with a comment here b:char) - -> int + -> i32 { } ")) @@ -430,30 +428,31 @@ (test-indent " // Indent function body only one level after `-> {` -fn foo1(a:int, b:char) -> int { +fn foo1(a:i32, b:char) -> i32 { let body; } -fn foo2(a:int, - b:char) -> int { +fn foo2(a:i32, + b:char) -> i32 { let body; } -fn foo3(a:int, +fn foo3(a:i32, b:char) - -> int { + -> i32 { let body; } -fn foo4(a:int, +fn foo4(a:i32, b:char) - -> int where int:A { + -> i32 where i32:A { let body; } ")) (ert-deftest indent-body-after-where () - (test-indent + (let ((rust-indent-where-clause t)) + (test-indent " fn foo1(a: A, b: B) -> A where A: Clone + Default, B: Eq { @@ -471,10 +470,11 @@ bar: 3 } } -")) +"))) (ert-deftest indent-align-where-clauses-style1a () - (test-indent + (let ((rust-indent-where-clause t)) + (test-indent " fn foo1a(a: A, b: B, c: C) -> D where A: Clone + Default, @@ -486,10 +486,11 @@ bar: 3 } } -")) +"))) (ert-deftest indent-align-where-clauses-style1b () - (test-indent + (let ((rust-indent-where-clause t)) + (test-indent " fn foo1b(a: A, b: B, c: C) -> D where A: Clone + Default, @@ -502,7 +503,7 @@ bar: 3 } } -")) +"))) (ert-deftest indent-align-where-clauses-style2a () (test-indent @@ -598,7 +599,8 @@ "))) (ert-deftest indent-align-where-clauses-impl-example () - (test-indent + (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, @@ -610,10 +612,11 @@ bar: 3 } } -")) +"))) (ert-deftest indent-align-where-clauses-first-line () - (test-indent + (let ((rust-indent-where-clause t)) + (test-indent "fn foo1(a: A, b: B) -> A where A: Clone + Default, B: Eq { let body; @@ -621,7 +624,7 @@ bar: 3 } } -")) +"))) (ert-deftest indent-align-where-in-comment1 () (test-indent @@ -634,7 +637,8 @@ ")) (ert-deftest indent-align-where-in-comment2 () - (test-indent + (let ((rust-indent-where-clause t)) + (test-indent "fn foo(f:F, g:G) where F:Send, // where @@ -642,9 +646,10 @@ { 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, @@ -653,13 +658,13 @@ { let body; } -")) +"))) (ert-deftest indent-square-bracket-alignment () (test-indent " fn args_on_the_next_line( // with a comment - a:int, + a:i32, b:String) { let aaaaaa = [ 1, @@ -694,11 +699,11 @@ (ert-deftest indent-nested-fns () (test-indent " -fn nexted_fns(a: fn(b:int, +fn nexted_fns(a: fn(b:i32, c:char) - -> int, - d: int) - -> uint + -> i32, + d: i32) + -> u128 { 0 } @@ -851,32 +856,32 @@ (setq rust-test-motion-string " -fn fn1(arg: int) -> bool { +fn fn1(arg: i32) -> bool { let x = 5; let y = b(); true } -fn fn2(arg: int) -> bool { +fn fn2(arg: i32) -> bool { let x = 5; let y = b(); true } -pub fn fn3(arg: int) -> bool { +pub fn fn3(arg: i32) -> bool { let x = 5; let y = b(); true } struct Foo { - x: int + x: i32 } " rust-test-region-string rust-test-motion-string rust-test-indent-motion-string " -fn blank_line(arg:int) -> bool { +fn blank_line(arg:i32) -> bool { } @@ -950,7 +955,7 @@ ;;; 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 &optional &rest args) +(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. @@ -959,12 +964,11 @@ (with-temp-buffer (rust-mode) (insert source-code) - (font-lock-fontify-buffer) (goto-char (rust-get-buffer-pos init-pos)) (apply manip-func args) (should (equal (point) (rust-get-buffer-pos final-pos))))) -(defun rust-test-region (source-code init-pos reg-beg reg-end manip-func &optional &rest args) +(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. @@ -973,7 +977,6 @@ (with-temp-buffer (rust-mode) (insert source-code) - (font-lock-fontify-buffer) (goto-char (rust-get-buffer-pos init-pos)) (apply manip-func args) (should (equal (list (region-beginning) (region-end)) @@ -1029,6 +1032,43 @@ '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 @@ -1251,12 +1291,60 @@ '("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 () @@ -1265,6 +1353,7 @@ '("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 () @@ -1273,18 +1362,21 @@ '("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 () @@ -1376,24 +1468,6 @@ "r\" this is a comment\n" font-lock-comment-face "\"this is a string\"" font-lock-string-face))) -(ert-deftest font-lock-raw-string-constant () - ;; There was an issue in which a multi-line raw string would be fontified - ;; correctly if inserted, but then incorrectly if one of the lines was then - ;; edited. This test replicates how font-lock responds when text in the - ;; buffer is modified in order to reproduce it. - (with-temp-buffer - (rust-mode) - (font-lock-fontify-buffer) - (insert "const BOO:&str = r#\"\nBOO\"#;") - (beginning-of-buffer) - (insert " ") - (font-lock-after-change-function 1 2 0) - - (should (equal 'font-lock-string-face (get-text-property 19 'face))) ;; Opening "r" of raw string - (should (equal 'font-lock-string-face (get-text-property 27 'face))) ;; Closing "#" of raw string - (should (equal nil (get-text-property 28 'face))) ;; Semicolon--should not be part of the string - )) - (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" @@ -1532,6 +1606,62 @@ ;; 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 " @@ -1543,6 +1673,18 @@ " ))) +(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 " @@ -1554,6 +1696,18 @@ " ))) +(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 " @@ -1650,6 +1804,34 @@ " ))) +(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 @@ -1674,7 +1856,7 @@ ;; would make the failure go away. (with-temp-buffer (rust-mode) - (insert "fn blank_line(arg:int) -> bool { + (insert "fn blank_line(arg:i32) -> bool { } @@ -1875,6 +2057,39 @@ 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 @@ -1955,6 +2170,239 @@ '("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 " @@ -2375,7 +2823,7 @@ (ert-deftest rust-test-paren-matching-lt-operator-after-special-type () (rust-test-matching-parens - "fn foo() { low as uint <= c }" + "fn foo() { low as u128 <= c }" '((10 29)) '(24))) @@ -2488,16 +2936,16 @@ (rust-test-matching-parens " fn rfc803() { - let z = a < b:FunnkyThing; + let z = a < b:FunnkyThing; let s = Foo { a: b < 3, - b: d:CrazyStuff < 3, - c: 2 < x:CrazyStuff + b: d:CrazyStuff < 3, + c: 2 < x:CrazyStuff } }" - '((45 49) ;; FunkyThing - (111 115) ;; CrazyStuff - (149 154) ;; CrazyStuff + '((45 49) ;; FunkyThing + (111 115) ;; CrazyStuff + (149 154) ;; CrazyStuff ) '(30 ;; a < b 83 ;; b < 3 @@ -2551,34 +2999,6 @@ '(7 9)))) -(ert-deftest font-lock-extend-region-in-string () - - (with-temp-buffer - (rust-mode) - (insert " -fn foo() { - let x = r\" -Fontification needs to include this whole string or none of it. - \" -}") - (font-lock-fontify-buffer) - (let ((font-lock-beg 13) - (font-lock-end 42)) - (rust-font-lock-extend-region) - (should (<= font-lock-beg 13)) - (should (>= font-lock-end 106)) - ) - (let ((font-lock-beg 42) - (font-lock-end 108)) - (rust-font-lock-extend-region) - (should (<= font-lock-beg 25)) - (should (>= font-lock-end 108))) - (let ((font-lock-beg 1) - (font-lock-end 12)) - (rust-font-lock-extend-region) - (should (<= font-lock-beg 1)) - (should (>= font-lock-end 12))))) - (ert-deftest redo-syntax-after-change-far-from-point () (let* ((tmp-file-name (make-temp-file "rust-mdoe-test-issue104")) @@ -2602,17 +3022,51 @@ ) ) -(ert-deftest rust-test-revert-hook-preserves-point () +(defun test-imenu (code expected-items) (with-temp-buffer - ;; Insert some code, and put point in the middle. - (insert "fn foo() {}\n") - (insert "fn bar() {}\n") - (insert "fn baz() {}\n") - (goto-char (point-min)) - (forward-line 1) - (let ((initial-point (point))) - (rust--after-revert-hook) - (should (equal initial-point (point)))))) + (rust-mode) + (insert code) + (let ((actual-items + ;; Replace ("item" . #