diff options
| -rw-r--r-- | rust-common.el | 18 | ||||
| -rw-r--r-- | rust-mode.el | 1450 | ||||
| -rw-r--r-- | rust-prog-mode.el | 1442 |
3 files changed, 1460 insertions, 1450 deletions
diff --git a/rust-common.el b/rust-common.el new file mode 100644 index 0000000..c216264 --- /dev/null +++ b/rust-common.el @@ -0,0 +1,18 @@ +;;; rust-common.el --- Common code for both modes -*-lexical-binding: t-*- +;;; Commentary: + +;; rust-common code for both prog-mode and tree-sitter one + +;;; Code: +(defcustom rust-before-save-hook 'rust-before-save-method + "Function for formatting before save." + :type 'function + :group 'rust-mode) + +(defcustom rust-after-save-hook 'rust-after-save-method + "Default method to handle rustfmt invocation after save." + :type 'function + :group 'rust-mode) + +(provide 'rust-common) +;;; rust-common.el ends here diff --git a/rust-mode.el b/rust-mode.el index 0c68c43..70c2a2c 100644 --- a/rust-mode.el +++ b/rust-mode.el @@ -14,6 +14,7 @@ ;; This package implements a major-mode for editing Rust source code. ;;; Code: +(require 'rust-common) (eval-when-compile (require 'rx) @@ -29,26 +30,6 @@ This variable might soon be remove again.") (require 'rust-playpen) (require 'rust-rustfmt)) -(defvar electric-pair-inhibit-predicate) -(defvar electric-pair-skip-self) -(defvar electric-indent-chars) - -(defcustom rust-before-save-hook 'rust-before-save-method - "Function for formatting before save." - :type 'function - :group 'rust-mode) - -(defcustom rust-after-save-hook 'rust-after-save-method - "Default method to handle rustfmt invocation after save." - :type 'function - :group 'rust-mode) - -(defvar rust-prettify-symbols-alist - '(("&&" . ?∧) ("||" . ?∨) - ("<=" . ?≤) (">=" . ?≥) ("!=" . ?≠) - ("INFINITY" . ?∞) ("->" . ?→) ("=>" . ?⇒)) - "Alist of symbol prettifications used for `prettify-symbols-alist'.") - ;;; Customization (defgroup rust-mode nil @@ -56,38 +37,6 @@ This variable might soon be remove again.") :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 lines 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-match-angle-brackets t - "Whether to enable angle bracket (`<' and `>') matching where appropriate." - :type 'boolean - :safe #'booleanp - :group 'rust-mode) - -(defcustom rust-indent-return-type-to-arguments t - "Indent a line starting with the `->' (RArrow) following a function, aligning -to the function arguments. When nil, `->' will be indented one level." - :type 'boolean - :group 'rust-mode - :safe #'booleanp) - (defcustom rust-mode-treesitter-derive nil "Whether rust-mode should derive from the new treesitter mode `rust-ts-mode' instead of `prog-mode'. This option requires emacs29+." @@ -106,141 +55,6 @@ instead of `prog-mode'. This option requires emacs29+." (define-obsolete-face-alias 'rust-string-interpolation-face 'rust-string-interpolation "0.6.0") -(defface rust-unsafe - '((t :inherit font-lock-warning-face)) - "Face for the `unsafe' keyword." - :group 'rust-mode) - -(defface rust-question-mark - '((t :weight bold :inherit font-lock-builtin-face)) - "Face for the question mark operator." - :group 'rust-mode) - -(defface rust-ampersand-face - '((t :inherit default)) - "Face for the ampersand reference mark." - :group 'rust-mode) - -(defface rust-builtin-formatting-macro - '((t :inherit font-lock-builtin-face)) - "Face for builtin formatting macros (print! &c.)." - :group 'rust-mode) - -(defface rust-string-interpolation - '((t :slant italic :inherit font-lock-string-face)) - "Face for interpolating braces in builtin formatting macro strings." - :group 'rust-mode) - -;;; Syntax - -(defun rust-re-word (inner) (concat "\\<" inner "\\>")) -(defun rust-re-grab (inner) (concat "\\(" inner "\\)")) -(defun rust-re-shy (inner) (concat "\\(?:" inner "\\)")) - -(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:]]*") -(defvar rust-re-vis - ;; pub | pub ( crate ) | pub ( self ) | pub ( super ) | pub ( in SimplePath ) - (concat - "pub" - (rust-re-shy - (concat - "[[:space:]]*([[:space:]]*" - (rust-re-shy - (concat "crate" "\\|" - "\\(?:s\\(?:elf\\|uper\\)\\)" "\\|" - ;; in SimplePath - (rust-re-shy - (concat - "in[[:space:]]+" - rust-re-ident - (rust-re-shy (concat "::" rust-re-ident)) "*")))) - "[[:space:]]*)")) - "?")) -(defconst rust-re-unsafe "unsafe") -(defconst rust-re-extern "extern") -(defconst rust-re-async-or-const "async\\|const") -(defconst rust-re-generic - (concat "<[[:space:]]*'" rust-re-ident "[[:space:]]*>")) -(defconst rust-re-union - (rx-to-string - `(seq - (or space line-start) - (group symbol-start "union" symbol-end) - (+ space) (regexp ,rust-re-ident)))) - -(defun rust-re-item-def (itype) - (concat (rust-re-word itype) - (rust-re-shy rust-re-generic) "?" - "[[:space:]]+" (rust-re-grab rust-re-ident))) - -;; TODO some of this does only make sense for `fn' (unsafe, extern...) -;; and not other items -(defun rust-re-item-def-imenu (itype) - (concat "^[[:space:]]*" - (rust-re-shy (concat rust-re-vis "[[:space:]]+")) "?" - (rust-re-shy (concat (rust-re-word "default") "[[:space:]]+")) "?" - (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[: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))) - -(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.") - -(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) - "Syntax definitions and helpers.") - -;;; Prettify - -(defun rust--prettify-symbols-compose-p (start end match) - "Return true iff the symbol MATCH should be composed. -See `prettify-symbols-compose-predicate'." - (and (fboundp 'prettify-symbols-default-compose-p) - (prettify-symbols-default-compose-p start end match) - ;; Make sure || is not a closure with 0 arguments and && is not - ;; a double reference. - (pcase match - ("||" (not (save-excursion - (goto-char start) - (looking-back "\\(?:\\<move\\|[[({:=,;]\\) *" - (line-beginning-position))))) - ("&&" (char-equal (char-after end) ?\s)) - (_ t)))) - ;;; Mode (defvar rust-mode-map @@ -264,1268 +78,6 @@ See `prettify-symbols-compose-predicate'." ;;;###autoload (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) -(defvar rust-top-item-beg-re - (concat "\\s-*\\(?:priv\\|pub\\)?\\s-*" - ;; TODO some of this does only make sense for `fn' (unsafe, extern...) - ;; and not other items - (rust-re-shy (concat (rust-re-shy rust-re-vis) "[[:space:]]+")) "?" - (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[:space:]]+")) "?" - (rust-re-shy (concat (rust-re-shy rust-re-unsafe) "[[:space:]]+")) "?" - (regexp-opt - '("enum" "struct" "union" "type" "mod" "fn" "static" "impl" - "extern" "trait" "async")) - "\\_>") - "Start of a Rust item.") - -(defconst rust-re-type-or-constructor - (rx symbol-start - (group upper (0+ (any word nonascii digit "_"))) - symbol-end)) - -(defconst rust-keywords - '("as" "async" "await" - "box" "break" - "const" "continue" "crate" - "do" "dyn" - "else" "enum" "extern" "existential" - "false" "fn" "for" - "if" "impl" "in" - "let" "loop" - "match" "mod" "move" "mut" - "priv" "pub" - "ref" "return" - "self" "static" "struct" "super" - "true" "trait" "type" "try" - "use" - "virtual" - "where" "while" - "yield") - "Font-locking definitions and helpers.") - -(defconst rust-special-types - '("u8" "i8" - "u16" "i16" - "u32" "i32" - "u64" "i64" - "u128" "i128" - - "f32" "f64" - "isize" "usize" - "bool" - "str" "char")) - -(defconst rust-expression-introducers - '("if" "while" "match" "return" "box" "in") - "List of Rust keywords that are always followed by expressions.") - -(defconst rust-number-with-type - (eval-when-compile - (concat - "\\_<\\(?:0[box]?\\|[1-9]\\)[[:digit:]a-fA-F_.]*\\(?:[eE][+-]?[[:digit:]_]\\)?" - (regexp-opt '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64" - "u128" "i128" "usize" "isize" "f32" "f64") - t) - "\\_>")) - "Regular expression matching a number with a type suffix.") - -(defvar rust-builtin-formatting-macros - '("eprint" - "eprintln" - "format" - "print" - "println") - "List of builtin Rust macros for string formatting. -This is used by `rust-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.") - -(defun rust-path-font-lock-matcher (re-ident) - "Match occurrences of RE-IDENT followed by a double-colon. -Examples include to match names like \"foo::\" or \"Foo::\". -Does not match type annotations of the form \"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)))))))) - -(defvar rust-font-lock-keywords - (append - `( - ;; Keywords proper - (,(regexp-opt rust-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 - ("\\_<unsafe\\_>" . 'rust-unsafe) - - ;; 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 (rust-re-word (regexp-opt rust-builtin-formatting-macros)) - "!")) - rust-formatting-macro-opening-re - "\\(?:" rust-start-of-string-re "\\)?") - (1 'rust-builtin-formatting-macro) - (rust-string-interpolation-matcher - (rust-end-of-string) - nil - (0 'rust-string-interpolation t nil))) - - ;; write! macro - (,(concat (rust-re-grab (concat (rust-re-word "write\\(ln\\)?") "!")) - rust-formatting-macro-opening-re - "[[:space:]]*[^\"]+,[[:space:]]*" - rust-start-of-string-re) - (1 'rust-builtin-formatting-macro) - (rust-string-interpolation-matcher - (rust-end-of-string) - nil - (0 'rust-string-interpolation 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) "[[:space:]]*:[^:]") - 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\\|for\\)\\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) - ("\\(&+\\)\\(?:'\\(?:\\<\\|_\\)\\|\\<\\|[[({:*_|]\\)" - 1 'rust-ampersand-face) - ;; Numbers with type suffix - (,rust-number-with-type 1 font-lock-type-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-end-of-string () - "Skip to the end of the current string." - (save-excursion - (skip-syntax-forward "^\"|") - (skip-syntax-forward "\"|") - (point))) - -(defun rust-looking-back-str (str) - "Return non-nil if there's a match on the text before point and 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 (symbols) - "Return non-nil if the point is after a member of SYMBOLS. -SYMBOLS is a list of strings that represent the respective -symbols." - (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) - symbols))))) - -(defun rust-looking-back-ident () - "Non-nil if we are looking backwards at a valid rust identifier. -If we are, regexp match 0 is the identifier." - (let ((outer-point (point))) - (save-excursion - (forward-thing 'symbol -1) - (and (looking-at rust-re-ident) - (eq (match-end 0) outer-point))))) - -(defun rust-looking-back-macro () - "Non-nil if looking back at a potential macro name followed by a \"!\". -If we are, regexp match 0 is the macro name." - (save-excursion - ;; Look past whitespace and line breaks. - ;; > is okay because we only use it for \n and \r, not "*/" - (skip-syntax-backward "->") - (when (eq (char-before) ?!) - (forward-char -1) - (skip-syntax-backward "->") - (when (rust-looking-back-ident) - (let ((ident (match-string 0))) - (not (member ident rust-expression-introducers))))))) - -(defun rust-looking-back-macro-rules () - "Non-nil if looking back at \"macro_rules IDENT !\"." - (save-excursion - (skip-syntax-backward "->") - (let ((outer-point (point))) - (forward-thing 'symbol -2) - (and (looking-at (concat "macro_rules\\s-*!\\s-*" rust-re-ident)) - (eq (match-end 0) outer-point))))) - -;;; Syntax definitions and helpers - -(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 () - "Return non-nil when point is within the scope of a macro. -If we are, return the position of the opening bracket of the macro's arguments." - (let ((ppss (syntax-ppss))) - ;; If we're in a string or comment, we're definitely not on a token a macro - ;; will see. - (when (not (or (nth 3 ppss) (nth 4 ppss))) - ;; Walk outward to enclosing parens, looking for one preceded by "ident !" - ;; or "macro_rules! ident". - (let (result - (enclosing (reverse (nth 9 ppss)))) - (save-excursion - (while enclosing - (goto-char (car enclosing)) - (if (or (rust-looking-back-macro) - (rust-looking-back-macro-rules)) - (setq result (point) enclosing nil) - (setq enclosing (cdr enclosing))))) - result)))) - -(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))) - -(defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]") - -(defconst rust-re-special-types (regexp-opt rust-special-types 'symbols)) - -(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() - ;; <indent> - ;; - ;; 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*" (line-beginning-position))) - (= (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 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 - (and (not (rust--same-line-p (point) (point-min))) - (line-end-position 0)))) - (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 (rust--same-line-p end-of-prev-line-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, if -to-arguments is selected. - ((and rust-indent-return-type-to-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 "\\<else\\>\\|{\\|/[/*]") - - ;; 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)))))) - -(defun rust--same-line-p (pos1 pos2) - "Return non-nil if POS1 and POS2 are on the same line." - (save-excursion (= (progn (goto-char pos1) (line-end-position)) - (progn (goto-char pos2) (line-end-position))))) - -;;; Font-locking definitions and helpers - -(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-let (((rust-in-str)) - (match (rust-next-string-interpolation limit))) - (set-match-data match) - (goto-char (cadr match)) - match)) - -(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 ")"))) - (when-let ((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)))))) - (goto-char dest)))))) - -(defun rust-rewind-to-decl-name () - "Return the point at the beginning of the name in a declaration. -I.e. 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 rust-expression-introducers) 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 <maybe type params> [{([] - ;; where [{([] denotes either a {, ( or [. - ;; This character is bound as postchar. - (cond - ;; If postchar is a paren or square bracket, then if the - ;; brace is a type if the identifier is one - ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) - - ;; If postchar is a curly brace, the brace can only be a type if - ;; ident2 is the name of an enum, struct or trait being declared. - ;; Note that if there is a -> before the ident then the ident would - ;; be a type but the { is not. - ((equal ?{ postchar) - (not (and (rust-rewind-to-decl-name) - (progn - (rust-rewind-irrelevant) - (rust-looking-back-symbols - '("enum" "struct" "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<type params> { Ident = - ;; In this case, this is a c-like enum and despite Ident - ;; representing a type, what comes after the = is an expression - ((and - (> (rust-paren-level) 0) - (save-excursion - (backward-up-list) - (rust-rewind-irrelevant) - (rust-rewind-type-param-list) - (and - (rust-looking-back-ident) - (progn - (rust-rewind-qualified-ident) - (rust-rewind-irrelevant) - (rust-looking-back-str "enum"))))) - t) - - ;; Otherwise the ambiguous operator is a type if the identifier is a type - ((rust-is-in-expression-context 'ident) t))) - - ((equal token 'colon) - (cond - ;; If we see a ident: not inside any braces/parens, we're at top level. - ;; There are no allowed expressions after colons there, just types. - ((<= (rust-paren-level) 0) nil) - - ;; We see ident: inside a list - ((looking-back "[{,]" (1- (point))) - (backward-up-list) - - ;; If a : appears whose surrounding paren/brackets/braces are - ;; anything other than curly braces, it can't be a field - ;; initializer and must be denoting a type. - (when (looking-at "{") - (rust-rewind-irrelevant) - (rust-rewind-type-param-list) - (when (rust-looking-back-ident) - ;; We have a context that looks like this: - ;; ident2 <maybe type params> { [maybe paren-balanced code ending in comma] ident1: - ;; the point is sitting just after ident2, and we trying to - ;; figure out if the colon introduces an expression or a type. - ;; The answer is that ident1 is a field name, and what comes - ;; after the colon is an expression, if ident2 is an - ;; expression. - (rust-rewind-qualified-ident) - (rust-is-in-expression-context 'ident)))) - - ;; Otherwise, if the ident: appeared with anything other than , or { - ;; before it, it can't be part of a struct initializer and therefore - ;; must be denoting a type. - (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 non-nil if the `<' sign just after point is an operator. -Otherwise, if it is an opening angle bracket, then return nil." - (let ((case-fold-search nil)) - (save-excursion - (rust-rewind-irrelevant) - ;; We are now just after the character syntactically before the <. - (cond - - ;; If we are looking back at a < that is not an angle bracket (but not - ;; two of them) then this is the second < in a bit shift operator - ((and (rust-looking-back-str "<") - (not (equal 4 (rust-syntax-class-before-point))) - (not (rust-looking-back-str "<<")))) - - ;; On the other hand, if we are after a closing paren/brace/bracket it - ;; can only be an operator, not an angle bracket. Likewise, if we are - ;; after a string it's an operator. (The string case could actually be - ;; valid in rust for character literals.) - ((member (rust-syntax-class-before-point) '(5 7 15)) t) - - ;; If we are looking back at an operator, we know that we are at - ;; the beginning of an expression, and thus it has to be an angle - ;; bracket (starting a "<Type as Trait>::" construct.) - ((looking-back rust-re-pre-expression-operators (1- (point))) nil) - - ;; If we are looking back at a keyword, it's an angle bracket - ;; unless that keyword is "self", "true" or "false" - ((rust-looking-back-symbols rust-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) - "Prevent \"matching\" with a `>' when CHAR is the less-than operator. -This wraps the default defined by `electric-pair-inhibit-predicate'." - (or - (when (= ?< char) - (save-excursion - (backward-char) - (rust-is-lt-char-operator))) - (funcall (default-value 'electric-pair-inhibit-predicate) char))) - -(defun rust-electric-pair-skip-self (char) - "Skip CHAR instead of inserting a second closing character. -This is added to the default skips defined by `electric-pair-skip-self'." - (= ?> 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) - - ;; This is a cheap check so we do it early. - ;; Don't treat the > in -> or => as an angle bracket - ((and (= (following-char) ?>) (memq (preceding-char) '(?- ?=))) 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) - - ((= (following-char) ?<) - (rust-is-lt-char-operator)) - - ;; Since rust-ordinary-lt-gt-p is called only when either < or > are at the point, - ;; we know that the following char must be > in the clauses below. - - ;; 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) - (/= (following-char) ?<))))) - -(defun rust-mode-syntactic-face-function (state) - "Return face that distinguishes doc and normal comments in given syntax STATE." - (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 (str-start end) - "A helper for rust-syntax-propertize. - -This will apply the appropriate string syntax to the character -from the STR-START up to the end of the raw string, or to END, -whichever comes first." - (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)))))) - -;;; Syntax Propertize - -(defun rust-syntax-propertize (start end) - "A `syntax-propertize-function' to apply properties from START to END." - (goto-char start) - (when-let ((str-start (rust-in-str-or-cmnt))) - (rust--syntax-propertize-raw-string str-start end)) - (funcall - (syntax-propertize-rules - ;; Character literals. - (rust--char-literal-rx (1 "\"") (2 "\"")) - ;; Raw strings. - ("\\(r\\)#*\"" - (0 (ignore - (goto-char (match-end 0)) - (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) - (put-text-property (match-beginning 1) (match-end 1) - 'syntax-table (string-to-syntax "|")) - (rust--syntax-propertize-raw-string (match-beginning 0) 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 the text at LINE-START." - (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'. -This handles 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'. -This handles 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)))) - -;;; 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-mode-reload () - (interactive) - (unload-feature 'rust-mode) - (require 'rust-mode) - (rust-mode)) - (provide 'rust-mode) (require 'rust-utils) diff --git a/rust-prog-mode.el b/rust-prog-mode.el index f443da8..92b475a 100644 --- a/rust-prog-mode.el +++ b/rust-prog-mode.el @@ -4,6 +4,1446 @@ ;; rust-mode code deriving from prog-mode instead of rust-ts-mode ;;; Code: +(require 'rust-common) + +(defvar electric-pair-inhibit-predicate) +(defvar electric-pair-skip-self) +(defvar electric-indent-chars) + +(defvar rust-prettify-symbols-alist + '(("&&" . ?∧) ("||" . ?∨) + ("<=" . ?≤) (">=" . ?≥) ("!=" . ?≠) + ("INFINITY" . ?∞) ("->" . ?→) ("=>" . ?⇒)) + "Alist of symbol prettifications used for `prettify-symbols-alist'.") + +(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 lines 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-match-angle-brackets t + "Whether to enable angle bracket (`<' and `>') matching where appropriate." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-indent-return-type-to-arguments t + "Indent a line starting with the `->' (RArrow) following a function, aligning +to the function arguments. When nil, `->' will be indented one level." + :type 'boolean + :group 'rust-mode + :safe #'booleanp) + +(defface rust-unsafe + '((t :inherit font-lock-warning-face)) + "Face for the `unsafe' keyword." + :group 'rust-mode) + +(defface rust-question-mark + '((t :weight bold :inherit font-lock-builtin-face)) + "Face for the question mark operator." + :group 'rust-mode) + +(defface rust-ampersand-face + '((t :inherit default)) + "Face for the ampersand reference mark." + :group 'rust-mode) + +(defface rust-builtin-formatting-macro + '((t :inherit font-lock-builtin-face)) + "Face for builtin formatting macros (print! &c.)." + :group 'rust-mode) + +(defface rust-string-interpolation + '((t :slant italic :inherit font-lock-string-face)) + "Face for interpolating braces in builtin formatting macro strings." + :group 'rust-mode) + +;;; Syntax + +(defun rust-re-word (inner) (concat "\\<" inner "\\>")) +(defun rust-re-grab (inner) (concat "\\(" inner "\\)")) +(defun rust-re-shy (inner) (concat "\\(?:" inner "\\)")) + +(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:]]*") +(defvar rust-re-vis + ;; pub | pub ( crate ) | pub ( self ) | pub ( super ) | pub ( in SimplePath ) + (concat + "pub" + (rust-re-shy + (concat + "[[:space:]]*([[:space:]]*" + (rust-re-shy + (concat "crate" "\\|" + "\\(?:s\\(?:elf\\|uper\\)\\)" "\\|" + ;; in SimplePath + (rust-re-shy + (concat + "in[[:space:]]+" + rust-re-ident + (rust-re-shy (concat "::" rust-re-ident)) "*")))) + "[[:space:]]*)")) + "?")) +(defconst rust-re-unsafe "unsafe") +(defconst rust-re-extern "extern") +(defconst rust-re-async-or-const "async\\|const") +(defconst rust-re-generic + (concat "<[[:space:]]*'" rust-re-ident "[[:space:]]*>")) +(defconst rust-re-union + (rx-to-string + `(seq + (or space line-start) + (group symbol-start "union" symbol-end) + (+ space) (regexp ,rust-re-ident)))) + +(defun rust-re-item-def (itype) + (concat (rust-re-word itype) + (rust-re-shy rust-re-generic) "?" + "[[:space:]]+" (rust-re-grab rust-re-ident))) + +;; TODO some of this does only make sense for `fn' (unsafe, extern...) +;; and not other items +(defun rust-re-item-def-imenu (itype) + (concat "^[[:space:]]*" + (rust-re-shy (concat rust-re-vis "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-word "default") "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[: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))) + +(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.") + +;;; Prettify + +(defun rust--prettify-symbols-compose-p (start end match) + "Return true iff the symbol MATCH should be composed. +See `prettify-symbols-compose-predicate'." + (and (fboundp 'prettify-symbols-default-compose-p) + (prettify-symbols-default-compose-p start end match) + ;; Make sure || is not a closure with 0 arguments and && is not + ;; a double reference. + (pcase match + ("||" (not (save-excursion + (goto-char start) + (looking-back "\\(?:\\<move\\|[[({:=,;]\\) *" + (line-beginning-position))))) + ("&&" (char-equal (char-after end) ?\s)) + (_ t)))) + +(defvar rust-top-item-beg-re + (concat "\\s-*\\(?:priv\\|pub\\)?\\s-*" + ;; TODO some of this does only make sense for `fn' (unsafe, extern...) + ;; and not other items + (rust-re-shy (concat (rust-re-shy rust-re-vis) "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-shy rust-re-unsafe) "[[:space:]]+")) "?" + (regexp-opt + '("enum" "struct" "union" "type" "mod" "fn" "static" "impl" + "extern" "trait" "async")) + "\\_>") + "Start of a Rust item.") + +(defconst rust-re-type-or-constructor + (rx symbol-start + (group upper (0+ (any word nonascii digit "_"))) + symbol-end)) + +(defconst rust-keywords + '("as" "async" "await" + "box" "break" + "const" "continue" "crate" + "do" "dyn" + "else" "enum" "extern" "existential" + "false" "fn" "for" + "if" "impl" "in" + "let" "loop" + "match" "mod" "move" "mut" + "priv" "pub" + "ref" "return" + "self" "static" "struct" "super" + "true" "trait" "type" "try" + "use" + "virtual" + "where" "while" + "yield") + "Font-locking definitions and helpers.") + +(defconst rust-special-types + '("u8" "i8" + "u16" "i16" + "u32" "i32" + "u64" "i64" + "u128" "i128" + + "f32" "f64" + "isize" "usize" + "bool" + "str" "char")) + +(defconst rust-expression-introducers + '("if" "while" "match" "return" "box" "in") + "List of Rust keywords that are always followed by expressions.") + +(defconst rust-number-with-type + (eval-when-compile + (concat + "\\_<\\(?:0[box]?\\|[1-9]\\)[[:digit:]a-fA-F_.]*\\(?:[eE][+-]?[[:digit:]_]\\)?" + (regexp-opt '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64" + "u128" "i128" "usize" "isize" "f32" "f64") + t) + "\\_>")) + "Regular expression matching a number with a type suffix.") + +(defvar rust-builtin-formatting-macros + '("eprint" + "eprintln" + "format" + "print" + "println") + "List of builtin Rust macros for string formatting. +This is used by `rust-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.") + +(defun rust-path-font-lock-matcher (re-ident) + "Match occurrences of RE-IDENT followed by a double-colon. +Examples include to match names like \"foo::\" or \"Foo::\". +Does not match type annotations of the form \"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)))))))) + +(defvar rust-font-lock-keywords + (append + `( + ;; Keywords proper + (,(regexp-opt rust-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 + ("\\_<unsafe\\_>" . 'rust-unsafe) + + ;; 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 (rust-re-word (regexp-opt rust-builtin-formatting-macros)) + "!")) + rust-formatting-macro-opening-re + "\\(?:" rust-start-of-string-re "\\)?") + (1 'rust-builtin-formatting-macro) + (rust-string-interpolation-matcher + (rust-end-of-string) + nil + (0 'rust-string-interpolation t nil))) + + ;; write! macro + (,(concat (rust-re-grab (concat (rust-re-word "write\\(ln\\)?") "!")) + rust-formatting-macro-opening-re + "[[:space:]]*[^\"]+,[[:space:]]*" + rust-start-of-string-re) + (1 'rust-builtin-formatting-macro) + (rust-string-interpolation-matcher + (rust-end-of-string) + nil + (0 'rust-string-interpolation 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) "[[:space:]]*:[^:]") + 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\\|for\\)\\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) + ("\\(&+\\)\\(?:'\\(?:\\<\\|_\\)\\|\\<\\|[[({:*_|]\\)" + 1 'rust-ampersand-face) + ;; Numbers with type suffix + (,rust-number-with-type 1 font-lock-type-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-end-of-string () + "Skip to the end of the current string." + (save-excursion + (skip-syntax-forward "^\"|") + (skip-syntax-forward "\"|") + (point))) + +(defun rust-looking-back-str (str) + "Return non-nil if there's a match on the text before point and 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 (symbols) + "Return non-nil if the point is after a member of SYMBOLS. +SYMBOLS is a list of strings that represent the respective +symbols." + (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) + symbols))))) + +(defun rust-looking-back-ident () + "Non-nil if we are looking backwards at a valid rust identifier. +If we are, regexp match 0 is the identifier." + (let ((outer-point (point))) + (save-excursion + (forward-thing 'symbol -1) + (and (looking-at rust-re-ident) + (eq (match-end 0) outer-point))))) + +(defun rust-looking-back-macro () + "Non-nil if looking back at a potential macro name followed by a \"!\". +If we are, regexp match 0 is the macro name." + (save-excursion + ;; Look past whitespace and line breaks. + ;; > is okay because we only use it for \n and \r, not "*/" + (skip-syntax-backward "->") + (when (eq (char-before) ?!) + (forward-char -1) + (skip-syntax-backward "->") + (when (rust-looking-back-ident) + (let ((ident (match-string 0))) + (not (member ident rust-expression-introducers))))))) + +(defun rust-looking-back-macro-rules () + "Non-nil if looking back at \"macro_rules IDENT !\"." + (save-excursion + (skip-syntax-backward "->") + (let ((outer-point (point))) + (forward-thing 'symbol -2) + (and (looking-at (concat "macro_rules\\s-*!\\s-*" rust-re-ident)) + (eq (match-end 0) outer-point))))) + +;;; Syntax definitions and helpers + +(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 () + "Return non-nil when point is within the scope of a macro. +If we are, return the position of the opening bracket of the macro's arguments." + (let ((ppss (syntax-ppss))) + ;; If we're in a string or comment, we're definitely not on a token a macro + ;; will see. + (when (not (or (nth 3 ppss) (nth 4 ppss))) + ;; Walk outward to enclosing parens, looking for one preceded by "ident !" + ;; or "macro_rules! ident". + (let (result + (enclosing (reverse (nth 9 ppss)))) + (save-excursion + (while enclosing + (goto-char (car enclosing)) + (if (or (rust-looking-back-macro) + (rust-looking-back-macro-rules)) + (setq result (point) enclosing nil) + (setq enclosing (cdr enclosing))))) + result)))) + +(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))) + +(defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]") + +(defconst rust-re-special-types (regexp-opt rust-special-types 'symbols)) + +(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() + ;; <indent> + ;; + ;; 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*" (line-beginning-position))) + (= (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 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 + (and (not (rust--same-line-p (point) (point-min))) + (line-end-position 0)))) + (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 (rust--same-line-p end-of-prev-line-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, if -to-arguments is selected. + ((and rust-indent-return-type-to-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 "\\<else\\>\\|{\\|/[/*]") + + ;; 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)))))) + +(defun rust--same-line-p (pos1 pos2) + "Return non-nil if POS1 and POS2 are on the same line." + (save-excursion (= (progn (goto-char pos1) (line-end-position)) + (progn (goto-char pos2) (line-end-position))))) + +;;; Font-locking definitions and helpers + +(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-let (((rust-in-str)) + (match (rust-next-string-interpolation limit))) + (set-match-data match) + (goto-char (cadr match)) + match)) + +(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 ")"))) + (when-let ((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)))))) + (goto-char dest)))))) + +(defun rust-rewind-to-decl-name () + "Return the point at the beginning of the name in a declaration. +I.e. 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 rust-expression-introducers) 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 <maybe type params> [{([] + ;; where [{([] denotes either a {, ( or [. + ;; This character is bound as postchar. + (cond + ;; If postchar is a paren or square bracket, then if the + ;; brace is a type if the identifier is one + ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) + + ;; If postchar is a curly brace, the brace can only be a type if + ;; ident2 is the name of an enum, struct or trait being declared. + ;; Note that if there is a -> before the ident then the ident would + ;; be a type but the { is not. + ((equal ?{ postchar) + (not (and (rust-rewind-to-decl-name) + (progn + (rust-rewind-irrelevant) + (rust-looking-back-symbols + '("enum" "struct" "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<type params> { Ident = + ;; In this case, this is a c-like enum and despite Ident + ;; representing a type, what comes after the = is an expression + ((and + (> (rust-paren-level) 0) + (save-excursion + (backward-up-list) + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (and + (rust-looking-back-ident) + (progn + (rust-rewind-qualified-ident) + (rust-rewind-irrelevant) + (rust-looking-back-str "enum"))))) + t) + + ;; Otherwise the ambiguous operator is a type if the identifier is a type + ((rust-is-in-expression-context 'ident) t))) + + ((equal token 'colon) + (cond + ;; If we see a ident: not inside any braces/parens, we're at top level. + ;; There are no allowed expressions after colons there, just types. + ((<= (rust-paren-level) 0) nil) + + ;; We see ident: inside a list + ((looking-back "[{,]" (1- (point))) + (backward-up-list) + + ;; If a : appears whose surrounding paren/brackets/braces are + ;; anything other than curly braces, it can't be a field + ;; initializer and must be denoting a type. + (when (looking-at "{") + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (when (rust-looking-back-ident) + ;; We have a context that looks like this: + ;; ident2 <maybe type params> { [maybe paren-balanced code ending in comma] ident1: + ;; the point is sitting just after ident2, and we trying to + ;; figure out if the colon introduces an expression or a type. + ;; The answer is that ident1 is a field name, and what comes + ;; after the colon is an expression, if ident2 is an + ;; expression. + (rust-rewind-qualified-ident) + (rust-is-in-expression-context 'ident)))) + + ;; Otherwise, if the ident: appeared with anything other than , or { + ;; before it, it can't be part of a struct initializer and therefore + ;; must be denoting a type. + (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 non-nil if the `<' sign just after point is an operator. +Otherwise, if it is an opening angle bracket, then return nil." + (let ((case-fold-search nil)) + (save-excursion + (rust-rewind-irrelevant) + ;; We are now just after the character syntactically before the <. + (cond + + ;; If we are looking back at a < that is not an angle bracket (but not + ;; two of them) then this is the second < in a bit shift operator + ((and (rust-looking-back-str "<") + (not (equal 4 (rust-syntax-class-before-point))) + (not (rust-looking-back-str "<<")))) + + ;; On the other hand, if we are after a closing paren/brace/bracket it + ;; can only be an operator, not an angle bracket. Likewise, if we are + ;; after a string it's an operator. (The string case could actually be + ;; valid in rust for character literals.) + ((member (rust-syntax-class-before-point) '(5 7 15)) t) + + ;; If we are looking back at an operator, we know that we are at + ;; the beginning of an expression, and thus it has to be an angle + ;; bracket (starting a "<Type as Trait>::" construct.) + ((looking-back rust-re-pre-expression-operators (1- (point))) nil) + + ;; If we are looking back at a keyword, it's an angle bracket + ;; unless that keyword is "self", "true" or "false" + ((rust-looking-back-symbols rust-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) + "Prevent \"matching\" with a `>' when CHAR is the less-than operator. +This wraps the default defined by `electric-pair-inhibit-predicate'." + (or + (when (= ?< char) + (save-excursion + (backward-char) + (rust-is-lt-char-operator))) + (funcall (default-value 'electric-pair-inhibit-predicate) char))) + +(defun rust-electric-pair-skip-self (char) + "Skip CHAR instead of inserting a second closing character. +This is added to the default skips defined by `electric-pair-skip-self'." + (= ?> 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) + + ;; This is a cheap check so we do it early. + ;; Don't treat the > in -> or => as an angle bracket + ((and (= (following-char) ?>) (memq (preceding-char) '(?- ?=))) 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) + + ((= (following-char) ?<) + (rust-is-lt-char-operator)) + + ;; Since rust-ordinary-lt-gt-p is called only when either < or > are at the point, + ;; we know that the following char must be > in the clauses below. + + ;; 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) + (/= (following-char) ?<))))) + +(defun rust-mode-syntactic-face-function (state) + "Return face that distinguishes doc and normal comments in given syntax STATE." + (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-fill-prefix-for-comment-start (line-start) + "Determine what to use for `fill-prefix' based on the text at LINE-START." + (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'. +This handles 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'. +This handles 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)))) + +;;; 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-mode-reload () + (interactive) + (unload-feature 'rust-mode) + (require 'rust-mode) + (rust-mode)) + +(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) + "Syntax definitions and helpers.") + +(defun rust--syntax-propertize-raw-string (str-start end) + "A helper for rust-syntax-propertize. + +This will apply the appropriate string syntax to the character +from the STR-START up to the end of the raw string, or to END, +whichever comes first." + (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)))))) + +;;; Syntax Propertize + +(defun rust-syntax-propertize (start end) + "A `syntax-propertize-function' to apply properties from START to END." + (goto-char start) + (when-let ((str-start (rust-in-str-or-cmnt))) + (rust--syntax-propertize-raw-string str-start end)) + (funcall + (syntax-propertize-rules + ;; Character literals. + (rust--char-literal-rx (1 "\"") (2 "\"")) + ;; Raw strings. + ("\\(r\\)#*\"" + (0 (ignore + (goto-char (match-end 0)) + (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) + (put-text-property (match-beginning 1) (match-end 1) + 'syntax-table (string-to-syntax "|")) + (rust--syntax-propertize-raw-string (match-beginning 0) 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)) ;;;###autoload (define-derived-mode rust-mode prog-mode "Rust" @@ -67,4 +1507,4 @@ (add-hook 'after-save-hook rust-after-save-hook nil t)) (provide 'rust-prog-mode) -;;; rust-prog-mode.el ends here
\ No newline at end of file +;;; rust-prog-mode.el ends here |
