summaryrefslogtreecommitdiff
path: root/rust-prog-mode.el
diff options
context:
space:
mode:
authorSibi Prabakaran <sibi@psibi.in>2024-02-25 10:31:22 +0530
committerSibi Prabakaran <sibi@psibi.in>2024-02-25 10:31:22 +0530
commita73d8266a950413fea56f070065aea6a3076cd1e (patch)
tree9699c66fcd28183acbd0093dbe00484e93e09d3b /rust-prog-mode.el
parent25c2535a11fbf34f47078730abfad6f8ae2c9376 (diff)
downloadrust-mode-a73d8266a950413fea56f070065aea6a3076cd1e.tar.gz
Fix rust-mode and tree sitter integration
Diffstat (limited to 'rust-prog-mode.el')
-rw-r--r--rust-prog-mode.el1442
1 files changed, 1441 insertions, 1 deletions
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