From 236114bdeab5edab09969e5ad398c32c2738dead Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Thu, 14 Mar 2024 22:27:34 +0100 Subject: Upgrade markdown-mode, rust and yasnippet --- .emacs.d/lisp/markdown-mode.el | 6 +- .emacs.d/lisp/rust-cargo.el | 3 +- .emacs.d/lisp/rust-common.el | 20 + .emacs.d/lisp/rust-compile.el | 9 + .emacs.d/lisp/rust-mode-tests.el | 202 +++-- .emacs.d/lisp/rust-mode-treesitter.el | 25 + .emacs.d/lisp/rust-mode.el | 1575 +-------------------------------- .emacs.d/lisp/rust-playpen.el | 42 +- .emacs.d/lisp/rust-prog-mode.el | 1509 +++++++++++++++++++++++++++++++ .emacs.d/lisp/rust-rustfmt.el | 94 +- .emacs.d/lisp/rust-utils.el | 38 +- .emacs.d/lisp/yasnippet.el | 107 ++- 12 files changed, 1883 insertions(+), 1747 deletions(-) create mode 100644 .emacs.d/lisp/rust-common.el create mode 100644 .emacs.d/lisp/rust-mode-treesitter.el create mode 100644 .emacs.d/lisp/rust-prog-mode.el diff --git a/.emacs.d/lisp/markdown-mode.el b/.emacs.d/lisp/markdown-mode.el index 8094e02..1514edb 100644 --- a/.emacs.d/lisp/markdown-mode.el +++ b/.emacs.d/lisp/markdown-mode.el @@ -1855,6 +1855,10 @@ START and END delimit region to propertize." '(face markdown-markup-face invisible markdown-markup) "List of properties and values to apply to markup.") +(defconst markdown-line-break-properties + '(face markdown-line-break-face invisible markdown-markup) + "List of properties and values to apply to line break markup.") + (defconst markdown-language-keyword-properties '(face markdown-language-keyword-face invisible markdown-markup) "List of properties and values to apply to code block language names.") @@ -2298,7 +2302,7 @@ Depending on your font, some reasonable choices are: (markdown--match-highlighting . ((3 markdown-markup-properties) (4 'markdown-highlighting-face) (5 markdown-markup-properties))) - (,markdown-regex-line-break . (1 'markdown-line-break-face prepend)) + (,markdown-regex-line-break . (1 markdown-line-break-properties prepend)) (markdown-match-escape . ((1 markdown-markup-properties prepend))) (markdown-fontify-sub-superscripts) (markdown-match-inline-attributes . ((0 markdown-markup-properties prepend))) diff --git a/.emacs.d/lisp/rust-cargo.el b/.emacs.d/lisp/rust-cargo.el index cfbcd81..bda23d8 100644 --- a/.emacs.d/lisp/rust-cargo.el +++ b/.emacs.d/lisp/rust-cargo.el @@ -46,7 +46,8 @@ (when (/= ret 0) (error "`cargo locate-project' returned %s status: %s" ret (buffer-string))) (goto-char 0) - (let ((output (json-read))) + (let ((output (let ((json-object-type 'alist)) + (json-read)))) (cdr (assoc-string "root" output))))))) (defun rust-buffer-crate () diff --git a/.emacs.d/lisp/rust-common.el b/.emacs.d/lisp/rust-common.el new file mode 100644 index 0000000..b7dd695 --- /dev/null +++ b/.emacs.d/lisp/rust-common.el @@ -0,0 +1,20 @@ +;;; rust-common.el --- Common code for both modes -*-lexical-binding: t-*- +;;; Commentary: + +;; rust-common code for both prog-mode and tree-sitter one + +;;; Code: +(require 'rust-rustfmt) + +(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/.emacs.d/lisp/rust-compile.el b/.emacs.d/lisp/rust-compile.el index 1bb3103..04ac6b6 100644 --- a/.emacs.d/lisp/rust-compile.el +++ b/.emacs.d/lisp/rust-compile.el @@ -34,6 +34,12 @@ See `compilation-error-regexp-alist' for help on their format.") "Specifications for matching code references in rustc invocations. See `compilation-error-regexp-alist' for help on their format.") +(defvar rustc-panics-compilation-regexps + (let ((re (concat "thread '[^']+' panicked at " rustc-compilation-location))) + (cons re '(2 3 4 nil 1))) + "Specifications for matching panics in rustc invocations. +See `compilation-error-regexp-alist' for help on their format.") + ;; Match test run failures and panics during compilation as ;; compilation warnings (defvar cargo-compilation-regexps @@ -75,6 +81,9 @@ the compilation window until the top of the error is visible." (add-to-list 'compilation-error-regexp-alist 'rustc-colon) (add-to-list 'compilation-error-regexp-alist-alist (cons 'cargo cargo-compilation-regexps)) + (add-to-list 'compilation-error-regexp-alist-alist + (cons 'rustc-panics rustc-panics-compilation-regexps)) + (add-to-list 'compilation-error-regexp-alist 'rustc-panics) (add-to-list 'compilation-error-regexp-alist 'cargo) (add-hook 'next-error-hook #'rustc-scroll-down-after-next-error))) diff --git a/.emacs.d/lisp/rust-mode-tests.el b/.emacs.d/lisp/rust-mode-tests.el index e4949b2..62cd261 100644 --- a/.emacs.d/lisp/rust-mode-tests.el +++ b/.emacs.d/lisp/rust-mode-tests.el @@ -45,14 +45,23 @@ (put 'rust-compare-code-after-manip 'ert-explainer 'rust-test-explain-bad-manip) -(defun rust-test-manip-code (original point-pos manip-func expected) +(defun rust-test-manip-code (original manip-pos manip-func expected &optional final-pos) (with-temp-buffer (rust-mode) (insert original) - (goto-char point-pos) + (goto-char manip-pos) (funcall manip-func) (should (rust-compare-code-after-manip - original point-pos manip-func expected (buffer-string))))) + original manip-pos manip-func expected (buffer-string))) + (if final-pos + (should (equal (point) final-pos))))) + +(defmacro rust-test-with-standard-fill-settings (&rest body) + (declare (indent defun)) + `(let ((fill-column rust-test-fill-column) + (sentence-end-double-space t) + (colon-double-space nil)) + ,@body)) (defun rust-test-fill-paragraph (unfilled expected &optional start-pos end-pos) "We're going to run through many scenarios here--the point should be able to be anywhere from the start-pos (defaults to 1) through end-pos (defaults to the length of what was passed in) and (fill-paragraph) should return the same result. It should also work with fill-region from start-pos to end-pos. @@ -78,7 +87,7 @@ Also, the result should be the same regardless of whether the code is at the beg (concat padding-beginning unfilled padding-end) pos (lambda () - (let ((fill-column rust-test-fill-column)) + (rust-test-with-standard-fill-settings (fill-paragraph))) (concat padding-beginning expected padding-end))))) ;; In addition to all the fill-paragraph tests, check that it works using fill-region @@ -86,7 +95,7 @@ Also, the result should be the same regardless of whether the code is at the beg unfilled start-pos (lambda () - (let ((fill-column rust-test-fill-column)) + (rust-test-with-standard-fill-settings (fill-region start-pos end-pos))) expected) )) @@ -1094,6 +1103,25 @@ fn test4();") (beginning-of-defun 2) (should (eq (point) fn-1))))) +(ert-deftest rust-beginning-of-defun-pub-scoped () + (let (fn-1-start fn-1-end fn-2-start fn-2-end) + (with-temp-buffer + (rust-mode) + (setq fn-1-start (point)) + (insert "pub(crate::mod1) fn test2() {}\n") + (setq fn-1-end (point)) + (setq fn-2-start (point)) + (insert "pub(self) fn test1() {}\n") + (setq fn-3-end (point)) + + (goto-char (point-max)) + + (beginning-of-defun) + (should (eq (point) fn-2-start)) + + (beginning-of-defun) + (should (eq (point) fn-1-start))))) + (ert-deftest rust-end-of-defun-from-middle-of-fn () (rust-test-motion rust-test-motion-string @@ -1136,6 +1164,25 @@ fn test4();") 'between-fn1-fn2 #'end-of-defun -2)) +(ert-deftest rust-end-of-defun-pub-scoped () + (let (fn-1-start fn-1-end fn-2-start fn-2-end) + (with-temp-buffer + (rust-mode) + (setq fn-1-start (point)) + (insert "pub(crate::mod1) fn test2() {}\n") + (setq fn-1-end (point)) + (setq fn-2-start (point)) + (insert "pub(self) fn test1() {}\n") + (setq fn-2-end (point)) + + (goto-char (point-min)) + + (end-of-defun) + (should (eq (point) fn-1-end)) + + (end-of-defun) + (should (eq (point) fn-2-end))))) + (ert-deftest rust-mark-defun-from-middle-of-fn () (rust-test-region rust-test-region-string @@ -2752,7 +2799,7 @@ fn foo X>() -> Z { ) )) -(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params-1 () (rust-test-matching-parens " fn foo(x:i32) { @@ -2764,7 +2811,7 @@ fn foo(x:i32) { ) )) -(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params-2 () (rust-test-matching-parens " fn foo(x:i32) -> bool { @@ -3116,7 +3163,7 @@ macro_c!{ (syntax-ppss)))) -(ert-deftest rust-test-in-macro-no-caching () +(ert-deftest rust-test-in-macro-around-opening () (should-not (with-temp-buffer (insert @@ -3125,66 +3172,38 @@ macro_c!{ struct Boo {} ") (rust-mode) - (search-backward "macro") - ;; do not use the cache - (let ((rust-macro-scopes nil)) - (rust-in-macro))))) - -(ert-deftest rust-test-in-macro-fake-cache () - (should - (with-temp-buffer - (insert - "fn foo(a:A) { - macro_c!{ - struct Boo {} -") - (rust-mode) - (search-backward "macro") - ;; make the cache lie to make the whole buffer in scope - ;; we need to be at paren level 1 for this to work - (let ((rust-macro-scopes `((,(point-min) ,(point-max))))) - (rust-in-macro))))) - -(ert-deftest rust-test-in-macro-broken-cache () - (should-error - (with-temp-buffer - (insert - "fn foo(a:A) { - macro_c!{ - struct Boo {} -") - (rust-mode) - (search-backward "Boo") - ;; do we use the cache at all - (let ((rust-macro-scopes '(I should break))) - (rust-in-macro))))) + (search-backward "macro_c") + (and + (not (rust-in-macro)) + (progn (forward-thing 'symbol 1) (not (rust-in-macro))) + (progn (forward-char 1) (rust-in-macro)) + (progn (goto-char (point-max)) (rust-in-macro)))))) (ert-deftest rust-test-in-macro-nested () - (should - (equal - (with-temp-buffer - (insert - "macro_rules! outer { + (with-temp-buffer + (insert + "macro_rules! outer { () => { vec![] }; }") - (rust-mode) - (rust-macro-scope (point-min) (point-max))) - '((38 40) (20 45))))) + (rust-mode) + (should (progn (goto-char 20) (not (rust-in-macro)))) + (should (progn (goto-char 21) (eq (rust-in-macro) 20))) + (should (progn (goto-char 38) (eq (rust-in-macro) 20))) + (should (progn (goto-char 39) (eq (rust-in-macro) 38))) + (should (progn (goto-char 40) (eq (rust-in-macro) 20))) + (should (progn (goto-char 44) (eq (rust-in-macro) 20))) + (should (progn (goto-char 45) (not (rust-in-macro)))))) (ert-deftest rust-test-in-macro-not-with-space () - (should - (equal - (with-temp-buffer - (insert + (with-temp-buffer + (insert "fn foo() { if !(mem::size_of::() > 8) { bar() } }") - (rust-mode) - (rust-macro-scope (point-min) (point-max))) - 'empty))) - + (rust-mode) + (should (progn (goto-char 24) (not (rust-in-macro)))))) (ert-deftest rust-test-paren-matching-type-with-module-name () (rust-test-matching-parens @@ -3430,19 +3449,72 @@ impl Two<'a> { "Foo" font-lock-type-face "in" font-lock-keyword-face))) -(ert-deftest rust-test-dbg-wrap-symbol () +(ert-deftest rust-test-dbg-wrap-sexp () + "a valid sexp ahead of current pos" (rust-test-manip-code "let x = add(first, second);" 15 #'rust-dbg-wrap-or-unwrap - "let x = add(dbg!(first), second);")) + "let x = add(dbg!(first), second);" + 24)) + +(ert-deftest rust-test-dbg-wrap-sexp-fallback () + "a invalid sexp ahead of current pos" + ;; inside + (rust-test-manip-code + "if let Ok(val) = may_val {}" + 27 + #'rust-dbg-wrap-or-unwrap + "if let Ok(val) = may_val {dbg!()}" + 32) + ;; before + (rust-test-manip-code + "let a = {}" + 9 + #'rust-dbg-wrap-or-unwrap + "let a = dbg!({})" + 17)) + +(ert-deftest rust-test-dbg-wrap-empty-line () + (rust-test-manip-code + "let a = 1; + +let b = 1;" + 12 + #'rust-dbg-wrap-or-unwrap + "let a = 1; +dbg!() +let b = 1;" + 17)) + +(ert-deftest rust-test-dbg-wrap-empty-before-comment () + (rust-test-manip-code + "let a = 1; +// comment +let b = 1;" + 12 + #'rust-dbg-wrap-or-unwrap + "let a = 1; +dbg!()// comment +let b = 1;" + 17) + ;; between statements and comments + (rust-test-manip-code + "let a = 1;// comment +let b = 1;" + 11 + #'rust-dbg-wrap-or-unwrap + "let a = 1;dbg!()// comment +let b = 1;" + 16)) (ert-deftest rust-test-dbg-wrap-symbol-unbalanced () (rust-test-manip-code "let x = add((first, second);" 14 #'rust-dbg-wrap-or-unwrap - "let x = add((dbg!(first), second);")) + "let x = add((dbg!(first), second);" + 25)) (ert-deftest rust-test-dbg-wrap-region () (rust-test-manip-code @@ -3453,7 +3525,8 @@ impl Two<'a> { (push-mark nil t t) (goto-char 26) (rust-dbg-wrap-or-unwrap)) - "let x = dbg!(add(first, second));")) + "let x = dbg!(add(first, second));" + 33)) (defun rust-test-dbg-unwrap (position) (rust-test-manip-code @@ -3527,6 +3600,7 @@ impl Two<'a> { (insert "warning found a -> b\n --> file3.rs:12:34\n\n") (insert "note: `ZZZ` could also refer to the constant imported here -> b\n --> file4.rs:12:34\n\n") (insert " ::: file5.rs:12:34\n\n") + (insert "thread 'main' panicked at src/file7.rs:12:34:\n\n") ;; should not match (insert "werror found a -> b\n --> no_match.rs:12:34\n\n") (insert "error[E0061]: this function takes 1 parameter but 2 parameters were supplied\n --> file6.rs:132:34 @@ -3545,11 +3619,13 @@ impl Two<'a> { ("file6.rs" "132" "34" compilation-error "file6.rs:132:34")) (("file5.rs" "12" "34" compilation-info "file5.rs:12:34")) ((like-previous-one "82" back-to-indentation compilation-info "82") - (like-previous-one "132" back-to-indentation compilation-info "132"))) + (like-previous-one "132" back-to-indentation compilation-info "132")) + (("src/file7.rs" "12" "34" nil "src/file7.rs:12:34"))) (mapcar #'rust-collect-matches (list rustc-compilation-regexps rustc-colon-compilation-regexps - rustc-refs-compilation-regexps)))))) + rustc-refs-compilation-regexps + rustc-panics-compilation-regexps)))))) ;; If electric-pair-mode is available, load it and run the tests that use it. If not, ;; no error--the tests will be skipped. diff --git a/.emacs.d/lisp/rust-mode-treesitter.el b/.emacs.d/lisp/rust-mode-treesitter.el new file mode 100644 index 0000000..89c7cd4 --- /dev/null +++ b/.emacs.d/lisp/rust-mode-treesitter.el @@ -0,0 +1,25 @@ +;;; rust-mode-treesitter.el --- use native rust-ts-mode -*-lexical-binding: t-*- +;;; Commentary: + +;; Derive from rust-ts-mode instead of prog-mode + +;;; Code: + +(when (version<= "29.1" emacs-version) + ;; We have the when macro because of + ;; https://github.com/rust-lang/rust-mode/issues/520 + (require 'treesit) + (require 'rust-ts-mode) + (require 'rust-common) + + (define-derived-mode rust-mode rust-ts-mode "Rust" + "Major mode for Rust code. + +\\{rust-mode-map}" + :group 'rust-mode + + (add-hook 'before-save-hook rust-before-save-hook nil t) + (add-hook 'after-save-hook rust-after-save-hook nil t))) + +(provide 'rust-mode-treesitter) +;;; rust-mode-treesitter.el ends here diff --git a/.emacs.d/lisp/rust-mode.el b/.emacs.d/lisp/rust-mode.el index 80106b8..a0f6542 100644 --- a/.emacs.d/lisp/rust-mode.el +++ b/.emacs.d/lisp/rust-mode.el @@ -1,7 +1,7 @@ ;;; rust-mode.el --- A major-mode for editing Rust source code -*-lexical-binding: t-*- ;; Version: 1.0.5 -;; Author: Mozilla +;; Author: Mozilla ;; Url: https://github.com/rust-lang/rust-mode ;; Keywords: languages ;; Package-Requires: ((emacs "25.1")) @@ -14,8 +14,11 @@ ;; This package implements a major-mode for editing Rust source code. ;;; Code: +(require 'rust-common) -(eval-when-compile (require 'rx)) +(eval-when-compile + (require 'rx) + (require 'subr-x)) (defvar rust-load-optional-libraries t "Whether loading `rust-mode' also loads optional libraries. @@ -27,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 @@ -54,37 +37,12 @@ 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'." +(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+." + :version "29.1" :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) + :group 'rustic) ;;; Faces @@ -97,141 +55,6 @@ to the function arguments. When nil, `->' will be indented one level." (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 "\\(?:\\") - "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-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 - ("\\_" . '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." - (let ((beg-of-symbol (save-excursion (forward-thing 'symbol -1) (point)))) - (looking-back rust-re-ident beg-of-symbol))) - -(defun rust-looking-back-macro () - "Non-nil if looking back at an ident followed by a ! - -This is stricter than rust syntax which allows a space between -the ident and the ! symbol. If this space is allowed, then we -would also need a keyword check to avoid `if !(condition)` being -seen as a macro." - (if (> (- (point) (point-min)) 1) - (save-excursion - (backward-char) - (and (= ?! (char-after)) - (rust-looking-back-ident))))) - -;;; 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))))))) - -(defvar-local rust-macro-scopes nil - "Cache for the scopes calculated by `rust-macro-scope'. - -This variable can be `let' bound directly or indirectly around -`rust-macro-scope' as an optimization but should not be otherwise -set.") - -(defun rust-macro-scope (start end) - "Return the scope of macros in the buffer. - -The return value is a list of (START END) positions in the -buffer. - -If set START and END are optimizations which limit the return -value to scopes which are approximately with this range." - (save-excursion - ;; need to special case macro_rules which has unique syntax - (let ((scope nil) - (start (or start (point-min))) - (end (or end (point-max)))) - (goto-char start) - ;; if there is a start move back to the previous top level, - ;; as any macros before that must have closed by this time. - (let ((top (syntax-ppss-toplevel-pos (syntax-ppss)))) - (when top - (goto-char top))) - (while - (and - ;; The movement below may have moved us passed end, in - ;; which case search-forward will error - (< (point) end) - (search-forward "!" end t)) - (let ((pt (point))) - (cond - ;; in a string or comment is boring, move straight on - ((rust-in-str-or-cmnt)) - ;; in a normal macro, - ((and (skip-chars-forward " \t\n\r") - (memq (char-after) - '(?\[ ?\( ?\{)) - ;; Check that we have a macro declaration after. - (rust-looking-back-macro)) - (let ((start (point))) - (ignore-errors (forward-list)) - (setq scope (cons (list start (point)) scope)))) - ;; macro_rules, why, why, why did you not use macro syntax?? - ((save-excursion - ;; yuck -- last test moves point, even if it fails - (goto-char (- pt 1)) - (skip-chars-backward " \t\n\r") - (rust-looking-back-str "macro_rules")) - (save-excursion - (when (re-search-forward "[[({]" nil t) - (backward-char) - (let ((start (point))) - (ignore-errors (forward-list)) - (setq scope (cons (list start (point)) scope))))))))) - ;; Return 'empty rather than nil, to indicate a buffer with no - ;; macros at all. - (or scope 'empty)))) - -(defun rust-in-macro (&optional start end) - "Return non-nil when point is within the scope of a macro. - -If START and END are set, minimize the buffer analysis to -approximately this location as an optimization. - -Alternatively, if `rust-macro-scopes' is a list use the scope -information in this variable. This last is an optimization and -the caller is responsible for ensuring that the data in -`rust-macro-scopes' is up to date." - (when (> (rust-paren-level) 0) - (let ((scopes - (or - rust-macro-scopes - (rust-macro-scope start end)))) - ;; `rust-macro-scope' can return the symbol `empty' if the - ;; buffer has no macros at all. - (when (listp scopes) - (seq-some - (lambda (sc) - (and (>= (point) (car sc)) - (< (point) (cadr sc)))) - scopes))))) - -(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() - ;; - ;; - ;; 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 "\\\\|{\\|/[/*]") - - ;; 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 (rust-in-str) - (let ((match (rust-next-string-interpolation limit))) - (when match - (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 ")")) - (dest (save-excursion - (backward-sexp) - (rust-rewind-irrelevant) - (or - (when (rust-looking-back-str "->") - (backward-char 2) - (rust-rewind-irrelevant) - (when (rust-looking-back-str ")") - (backward-sexp) - (point))) - (and is-paren (point)))))) - (when dest - (goto-char dest)))))) - -(defun rust-rewind-to-decl-name () - "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 '("if" "while" "match" "return" "box" "in")) t) - - ;; "as" introduces a type - ((rust-looking-back-symbols '("as")) nil) - - ;; An open angle bracket never introduces expression context WITHIN the angle brackets - ((and (equal token 'open-brace) (equal postchar ?<)) nil) - - ;; An ident! followed by an open brace is a macro invocation. Consider - ;; it to be an expression. - ((and (equal token 'open-brace) (rust-looking-back-macro)) t) - - ;; In a brace context a "]" introduces an expression. - ((and (eq token 'open-brace) (rust-looking-back-str "]"))) - - ;; An identifier is right after an ending paren, bracket, angle bracket - ;; or curly brace. It's a type if the last sexp was a type. - ((and (equal token 'ident) (equal 5 (rust-syntax-class-before-point))) - (backward-sexp) - (rust-is-in-expression-context 'open-brace)) - - ;; If a "for" appears without a ; or { before it, it's part of an - ;; "impl X for y", so the y is a type. Otherwise it's - ;; introducing a loop, so the y is an expression - ((and (equal token 'ident) (rust-looking-back-symbols '("for"))) - (backward-sexp) - (rust-rewind-irrelevant) - (looking-back "[{;]" (1- (point)))) - - ((rust-looking-back-ident) - (rust-rewind-qualified-ident) - (rust-rewind-irrelevant) - (cond - ((equal token 'open-brace) - ;; We now know we have: - ;; ident [{([] - ;; where [{([] denotes either a {, ( or [. - ;; This character is bound as postchar. - (cond - ;; If postchar is a paren or square bracket, then if the - ;; brace is a type if the identifier is one - ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) - - ;; If postchar is a curly brace, the brace can only be a type if - ;; ident2 is the name of an enum, struct or trait being declared. - ;; Note that if there is a -> before the ident then the ident would - ;; be a type but the { is not. - ((equal ?{ postchar) - (not (and (rust-rewind-to-decl-name) - (progn - (rust-rewind-irrelevant) - (rust-looking-back-symbols - '("enum" "struct" "union" "trait" "type")))))))) - - ((equal token 'ambiguous-operator) - (cond - ;; An ampersand after an ident has to be an operator rather - ;; than a & at the beginning of a ref type - ((equal postchar ?&) t) - - ;; A : followed by a type then an = introduces an - ;; expression (unless it is part of a where clause of a - ;; "type" declaration) - ((and (equal postchar ?=) - (looking-back "[^:]:" (- (point) 2)) - (not (save-excursion - (and (rust-rewind-to-decl-name) - (progn (rust-rewind-irrelevant) - (rust-looking-back-symbols '("type")))))))) - - ;; "let ident =" introduces an expression--and so does "const" and "mut" - ((and (equal postchar ?=) (rust-looking-back-symbols '("let" "const" "mut"))) t) - - ;; As a specific special case, see if this is the = in this situation: - ;; enum EnumName { Ident = - ;; In this case, this is a c-like enum and despite Ident - ;; representing a type, what comes after the = is an expression - ((and - (> (rust-paren-level) 0) - (save-excursion - (backward-up-list) - (rust-rewind-irrelevant) - (rust-rewind-type-param-list) - (and - (rust-looking-back-ident) - (progn - (rust-rewind-qualified-ident) - (rust-rewind-irrelevant) - (rust-looking-back-str "enum"))))) - t) - - ;; Otherwise the ambiguous operator is a type if the identifier is a type - ((rust-is-in-expression-context 'ident) t))) - - ((equal token 'colon) - (cond - ;; If we see a ident: not inside any braces/parens, we're at top level. - ;; There are no allowed expressions after colons there, just types. - ((<= (rust-paren-level) 0) nil) - - ;; We see ident: inside a list - ((looking-back "[{,]" (1- (point))) - (backward-up-list) - - ;; If a : appears whose surrounding paren/brackets/braces are - ;; anything other than curly braces, it can't be a field - ;; initializer and must be denoting a type. - (when (looking-at "{") - (rust-rewind-irrelevant) - (rust-rewind-type-param-list) - (when (rust-looking-back-ident) - ;; We have a context that looks like this: - ;; ident2 { [maybe paren-balanced code ending in comma] ident1: - ;; the point is sitting just after ident2, and we trying to - ;; figure out if the colon introduces an expression or a type. - ;; The answer is that ident1 is a field name, and what comes - ;; after the colon is an expression, if ident2 is an - ;; expression. - (rust-rewind-qualified-ident) - (rust-is-in-expression-context 'ident)))) - - ;; Otherwise, if the ident: appeared with anything other than , or { - ;; before it, it can't be part of a struct initializer and therefore - ;; must be denoting a type. - (t nil))))) - - ;; An operator-like character after a string is indeed an operator - ((and (equal token 'ambiguous-operator) - (member (rust-syntax-class-before-point) '(5 7 15))) t) - - ;; A colon that has something other than an identifier before it is a - ;; type ascription - ((equal token 'colon) nil) - - ;; A :: introduces a type (or module, but not an expression in any case) - ((rust-looking-back-str "::") nil) - - ((rust-looking-back-str ":") - (backward-char) - (rust-is-in-expression-context 'colon)) - - ;; A -> introduces a type - ((rust-looking-back-str "->") nil) - - ;; If we are up against the beginning of a list, or after a comma inside - ;; of one, back up out of it and check what the list itself is - ((or - (equal 4 (rust-syntax-class-before-point)) - (rust-looking-back-str ",")) - (condition-case nil - (progn - (backward-up-list) - (rust-is-in-expression-context 'open-brace)) - (scan-error nil))) - - ;; A => introduces an expression - ((rust-looking-back-str "=>") t) - - ;; A == introduces an expression - ((rust-looking-back-str "==") t) - - ;; These operators can introduce expressions or types - ((looking-back "[-+=!?&*]" (1- (point))) - (backward-char) - (rust-is-in-expression-context 'ambiguous-operator)) - - ;; These operators always introduce expressions. (Note that if this - ;; regexp finds a < it must not be an angle bracket, or it'd - ;; have been caught in the syntax-class check above instead of this.) - ((looking-back rust-re-pre-expression-operators (1- (point))) t))))) - -(defun rust-is-lt-char-operator () - "Return 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 "::" 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." - ;; Cache all macro scopes as an optimization. See issue #208 - (let ((rust-macro-scopes (rust-macro-scope start end))) - (goto-char start) - (let ((str-start (rust-in-str-or-cmnt))) - (when str-start - (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/.emacs.d/lisp/rust-playpen.el b/.emacs.d/lisp/rust-playpen.el index 1a9e583..a80a184 100644 --- a/.emacs.d/lisp/rust-playpen.el +++ b/.emacs.d/lisp/rust-playpen.el @@ -16,6 +16,12 @@ :type 'string :group 'rust-mode) +(defcustom rust-playpen-enable-shortener t + "Enable shortended URL for playpen links." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + (defcustom rust-shortener-url-format "https://is.gd/create.php?format=simple&url=%s" "Format string to use for creating the shortened link of a playpen submission." :type 'string @@ -28,26 +34,28 @@ (interactive "r") (let* ((data (buffer-substring begin end)) (escaped-data (url-hexify-string data)) - (escaped-playpen-url (url-hexify-string - (format rust-playpen-url-format escaped-data)))) + (playpen-url (format rust-playpen-url-format escaped-data)) + (escaped-playpen-url (url-hexify-string playpen-url))) (if (> (length escaped-playpen-url) 5000) (error "encoded playpen data exceeds 5000 character limit (length %s)" (length escaped-playpen-url)) - (let ((shortener-url (format rust-shortener-url-format escaped-playpen-url)) - (url-request-method "POST")) - (url-retrieve shortener-url - (lambda (state) - ;; filter out the headers etc. included at the - ;; start of the buffer: the relevant text - ;; (shortened url or error message) is exactly - ;; the last line. - (goto-char (point-max)) - (let ((last-line (thing-at-point 'line t)) - (err (plist-get state :error))) - (kill-buffer) - (if err - (error "failed to shorten playpen url: %s" last-line) - (message "%s" last-line))))))))) + (if (not rust-playpen-enable-shortener) + (message "%s" playpen-url) + (let ((shortener-url (format rust-shortener-url-format escaped-playpen-url)) + (url-request-method "POST")) + (url-retrieve shortener-url + (lambda (state) + ;; filter out the headers etc. included at the + ;; start of the buffer: the relevant text + ;; (shortened url or error message) is exactly + ;; the last line. + (goto-char (point-max)) + (let ((last-line (thing-at-point 'line t)) + (err (plist-get state :error))) + (kill-buffer) + (if err + (error "failed to shorten playpen url: %s" last-line) + (message "%s" last-line)))))))))) (defun rust-playpen-buffer () "Create a shareable URL for the contents of the buffer on the Rust playpen." diff --git a/.emacs.d/lisp/rust-prog-mode.el b/.emacs.d/lisp/rust-prog-mode.el new file mode 100644 index 0000000..51e802d --- /dev/null +++ b/.emacs.d/lisp/rust-prog-mode.el @@ -0,0 +1,1509 @@ +;;; rust-prog-mode.el --- old rust-mode without treesitter -*-lexical-binding: t-*- +;;; Commentary: + +;; 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 "\\(?:\\") + "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 + ("\\_" . '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() + ;; + ;; + ;; 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 "\\\\|{\\|/[/*]") + + ;; 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 [{([] + ;; where [{([] denotes either a {, ( or [. + ;; This character is bound as postchar. + (cond + ;; If postchar is a paren or square bracket, then if the + ;; brace is a type if the identifier is one + ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) + + ;; If postchar is a curly brace, the brace can only be a type if + ;; ident2 is the name of an enum, struct or trait being declared. + ;; Note that if there is a -> before the ident then the ident would + ;; be a type but the { is not. + ((equal ?{ postchar) + (not (and (rust-rewind-to-decl-name) + (progn + (rust-rewind-irrelevant) + (rust-looking-back-symbols + '("enum" "struct" "union" "trait" "type")))))))) + + ((equal token 'ambiguous-operator) + (cond + ;; An ampersand after an ident has to be an operator rather + ;; than a & at the beginning of a ref type + ((equal postchar ?&) t) + + ;; A : followed by a type then an = introduces an + ;; expression (unless it is part of a where clause of a + ;; "type" declaration) + ((and (equal postchar ?=) + (looking-back "[^:]:" (- (point) 2)) + (not (save-excursion + (and (rust-rewind-to-decl-name) + (progn (rust-rewind-irrelevant) + (rust-looking-back-symbols '("type")))))))) + + ;; "let ident =" introduces an expression--and so does "const" and "mut" + ((and (equal postchar ?=) (rust-looking-back-symbols '("let" "const" "mut"))) t) + + ;; As a specific special case, see if this is the = in this situation: + ;; enum EnumName { Ident = + ;; In this case, this is a c-like enum and despite Ident + ;; representing a type, what comes after the = is an expression + ((and + (> (rust-paren-level) 0) + (save-excursion + (backward-up-list) + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (and + (rust-looking-back-ident) + (progn + (rust-rewind-qualified-ident) + (rust-rewind-irrelevant) + (rust-looking-back-str "enum"))))) + t) + + ;; Otherwise the ambiguous operator is a type if the identifier is a type + ((rust-is-in-expression-context 'ident) t))) + + ((equal token 'colon) + (cond + ;; If we see a ident: not inside any braces/parens, we're at top level. + ;; There are no allowed expressions after colons there, just types. + ((<= (rust-paren-level) 0) nil) + + ;; We see ident: inside a list + ((looking-back "[{,]" (1- (point))) + (backward-up-list) + + ;; If a : appears whose surrounding paren/brackets/braces are + ;; anything other than curly braces, it can't be a field + ;; initializer and must be denoting a type. + (when (looking-at "{") + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (when (rust-looking-back-ident) + ;; We have a context that looks like this: + ;; ident2 { [maybe paren-balanced code ending in comma] ident1: + ;; the point is sitting just after ident2, and we trying to + ;; figure out if the colon introduces an expression or a type. + ;; The answer is that ident1 is a field name, and what comes + ;; after the colon is an expression, if ident2 is an + ;; expression. + (rust-rewind-qualified-ident) + (rust-is-in-expression-context 'ident)))) + + ;; Otherwise, if the ident: appeared with anything other than , or { + ;; before it, it can't be part of a struct initializer and therefore + ;; must be denoting a type. + (t nil))))) + + ;; An operator-like character after a string is indeed an operator + ((and (equal token 'ambiguous-operator) + (member (rust-syntax-class-before-point) '(5 7 15))) t) + + ;; A colon that has something other than an identifier before it is a + ;; type ascription + ((equal token 'colon) nil) + + ;; A :: introduces a type (or module, but not an expression in any case) + ((rust-looking-back-str "::") nil) + + ((rust-looking-back-str ":") + (backward-char) + (rust-is-in-expression-context 'colon)) + + ;; A -> introduces a type + ((rust-looking-back-str "->") nil) + + ;; If we are up against the beginning of a list, or after a comma inside + ;; of one, back up out of it and check what the list itself is + ((or + (equal 4 (rust-syntax-class-before-point)) + (rust-looking-back-str ",")) + (condition-case nil + (progn + (backward-up-list) + (rust-is-in-expression-context 'open-brace)) + (scan-error nil))) + + ;; A => introduces an expression + ((rust-looking-back-str "=>") t) + + ;; A == introduces an expression + ((rust-looking-back-str "==") t) + + ;; These operators can introduce expressions or types + ((looking-back "[-+=!?&*]" (1- (point))) + (backward-char) + (rust-is-in-expression-context 'ambiguous-operator)) + + ;; These operators always introduce expressions. (Note that if this + ;; regexp finds a < it must not be an angle bracket, or it'd + ;; have been caught in the syntax-class check above instead of this.) + ((looking-back rust-re-pre-expression-operators (1- (point))) t))))) + +(defun rust-is-lt-char-operator () + "Return 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 "::" 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)) + +(define-derived-mode rust-mode prog-mode "Rust" + "Major mode for Rust code. + +\\{rust-mode-map}" + :group 'rust-mode + :syntax-table rust-mode-syntax-table + + ;; Syntax + (setq-local syntax-propertize-function #'rust-syntax-propertize) + + ;; Indentation + (setq-local indent-line-function 'rust-mode-indent-line) + + ;; Fonts + (setq-local font-lock-defaults + '(rust-font-lock-keywords + nil nil nil nil + (font-lock-syntactic-face-function + . rust-mode-syntactic-face-function))) + + ;; Misc + (setq-local comment-start "// ") + (setq-local comment-end "") + (setq-local open-paren-in-column-0-is-defun-start nil) + + ;; Auto indent on } + (setq-local electric-indent-chars + (cons ?} (and (boundp 'electric-indent-chars) + electric-indent-chars))) + + ;; Allow paragraph fills for comments + (setq-local comment-start-skip "\\(?://[/!]*\\|/\\*[*!]?\\)[[:space:]]*") + (setq-local paragraph-start + (concat "[[:space:]]*\\(?:" + comment-start-skip + "\\|\\*/?[[:space:]]*\\|\\)$")) + (setq-local paragraph-separate paragraph-start) + (setq-local normal-auto-fill-function #'rust-do-auto-fill) + (setq-local fill-paragraph-function #'rust-fill-paragraph) + (setq-local fill-forward-paragraph-function #'rust-fill-forward-paragraph) + (setq-local adaptive-fill-function #'rust-find-fill-prefix) + (setq-local adaptive-fill-first-line-regexp "") + (setq-local comment-multi-line t) + (setq-local comment-line-break-function #'rust-comment-indent-new-line) + (setq-local imenu-generic-expression rust-imenu-generic-expression) + (setq-local imenu-syntax-alist '((?! . "w"))) ; For macro_rules! + (setq-local beginning-of-defun-function #'rust-beginning-of-defun) + (setq-local end-of-defun-function #'rust-end-of-defun) + (setq-local parse-sexp-lookup-properties t) + (setq-local electric-pair-inhibit-predicate + #'rust-electric-pair-inhibit-predicate-wrap) + (add-function :before-until (local 'electric-pair-skip-self) + #'rust-electric-pair-skip-self) + ;; Configure prettify + (setq prettify-symbols-alist rust-prettify-symbols-alist) + (setq prettify-symbols-compose-predicate #'rust--prettify-symbols-compose-p) + + (add-hook 'before-save-hook rust-before-save-hook nil t) + (add-hook 'after-save-hook rust-after-save-hook nil t)) + +(provide 'rust-prog-mode) +;;; rust-prog-mode.el ends here diff --git a/.emacs.d/lisp/rust-rustfmt.el b/.emacs.d/lisp/rust-rustfmt.el index bb88647..f609980 100644 --- a/.emacs.d/lisp/rust-rustfmt.el +++ b/.emacs.d/lisp/rust-rustfmt.el @@ -41,49 +41,54 @@ (defun rust--format-call (buf) "Format BUF using rustfmt." - (with-current-buffer (get-buffer-create rust-rustfmt-buffername) - (view-mode +1) - (let ((inhibit-read-only t)) - (erase-buffer) - (insert-buffer-substring buf) - (let* ((tmpf (make-temp-file "rustfmt")) - (ret (apply #'call-process-region - (point-min) - (point-max) - rust-rustfmt-bin - t - `(t ,tmpf) - nil - rust-rustfmt-switches))) - (unwind-protect - (cond - ((zerop ret) - (if (not (string= (buffer-string) - (with-current-buffer buf (buffer-string)))) - ;; replace-buffer-contents was in emacs 26.1, but it - ;; was broken for non-ASCII strings, so we need 26.2. - (if (and (fboundp 'replace-buffer-contents) - (version<= "26.2" emacs-version)) - (with-current-buffer buf - (replace-buffer-contents rust-rustfmt-buffername)) - (copy-to-buffer buf (point-min) (point-max)))) - (kill-buffer)) - ((= ret 3) - (if (not (string= (buffer-string) - (with-current-buffer buf (buffer-string)))) - (copy-to-buffer buf (point-min) (point-max))) - (erase-buffer) - (insert-file-contents tmpf) - (rust--format-fix-rustfmt-buffer (buffer-name buf)) - (error "Rustfmt could not format some lines, see %s buffer for details" - rust-rustfmt-buffername)) - (t - (erase-buffer) - (insert-file-contents tmpf) - (rust--format-fix-rustfmt-buffer (buffer-name buf)) - (error "Rustfmt failed, see %s buffer for details" - rust-rustfmt-buffername)))) - (delete-file tmpf))))) + (let ((path exec-path)) + (with-current-buffer (get-buffer-create rust-rustfmt-buffername) + (setq-local exec-path path) + (view-mode +1) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert-buffer-substring buf) + (let* ((tmpf (make-temp-file "rustfmt")) + (ret (apply #'call-process-region + (point-min) + (point-max) + rust-rustfmt-bin + t + `(t ,tmpf) + nil + rust-rustfmt-switches))) + (unwind-protect + (cond + ((zerop ret) + (if (not (string= (buffer-string) + (with-current-buffer buf (buffer-string)))) + ;; replace-buffer-contents was in emacs 26.1, but it + ;; was broken for non-ASCII strings, so we need 26.2. + (if (and (fboundp 'replace-buffer-contents) + (version<= "26.2" emacs-version)) + (with-current-buffer buf + (replace-buffer-contents rust-rustfmt-buffername)) + (copy-to-buffer buf (point-min) (point-max)))) + (let ((win (get-buffer-window rust-rustfmt-buffername))) + (if win + (quit-window t win) + (kill-buffer rust-rustfmt-buffername)))) + ((= ret 3) + (if (not (string= (buffer-string) + (with-current-buffer buf (buffer-string)))) + (copy-to-buffer buf (point-min) (point-max))) + (erase-buffer) + (insert-file-contents tmpf) + (rust--format-fix-rustfmt-buffer (buffer-name buf)) + (error "Rustfmt could not format some lines, see %s buffer for details" + rust-rustfmt-buffername)) + (t + (erase-buffer) + (insert-file-contents tmpf) + (rust--format-fix-rustfmt-buffer (buffer-name buf)) + (error "Rustfmt failed, see %s buffer for details" + rust-rustfmt-buffername)))) + (delete-file tmpf)))))) ;; Since we run rustfmt through stdin we get markers in the ;; output. This replaces them with the buffer name instead. @@ -147,7 +152,8 @@ rustfmt complain in the echo area." (goto-char (point-min)) (forward-line (1- (car target-point))) (forward-char (1- (cdr target-point)))) - (message target-problem))))) + (unless rust-format-show-buffer + (message target-problem)))))) (defconst rust--format-word "\ \\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|\ diff --git a/.emacs.d/lisp/rust-utils.el b/.emacs.d/lisp/rust-utils.el index cb55172..d93bd0a 100644 --- a/.emacs.d/lisp/rust-utils.el +++ b/.emacs.d/lisp/rust-utils.el @@ -41,22 +41,22 @@ visit the new file." if not. Move cursor to the end of macro." (when (rust-in-str) (up-list -1 t t)) - (setq safe-to-forward t) - (save-excursion - (condition-case nil - (forward-sexp) - (error (setq safe-to-forward nil) - nil))) - (cond - ((not safe-to-forward) - (rust-insert-dbg-alone)) - (t - (insert "(") - (forward-sexp) - (insert ")") - (backward-sexp) - (insert "dbg!") - (forward-sexp)))) + (let ((safe-to-forward t)) + (save-excursion + (condition-case nil + (forward-sexp) + (error (setq safe-to-forward nil) + nil))) + (cond + ((not safe-to-forward) + (rust-insert-dbg-alone)) + (t + (insert "(") + (forward-sexp) + (insert ")") + (backward-sexp) + (insert "dbg!") + (forward-sexp))))) (defun rust-insert-dbg-region () "Insert the dbg! macro around a region. Move cursor to the end of macro." @@ -77,9 +77,9 @@ if not. Move cursor to the end of macro." (defun rust-dbg-wrap-or-unwrap () "Either remove or add the dbg! macro." (interactive) - + (cond - + ;; region ((region-active-p) (rust-insert-dbg-region)) @@ -106,7 +106,7 @@ if not. Move cursor to the end of macro." (delete-pair)) (t (rust-insert-dbg-sexp))))) ) -) + ) (defun rust-toggle-mutability () "Toggles the mutability of the variable defined on the current line" diff --git a/.emacs.d/lisp/yasnippet.el b/.emacs.d/lisp/yasnippet.el index d130cb9..fa36be7 100644 --- a/.emacs.d/lisp/yasnippet.el +++ b/.emacs.d/lisp/yasnippet.el @@ -824,6 +824,10 @@ which decides on the snippet to expand.") ;; Prevent sharing the tail. (append lists '(()) ))))))) +(defun yas--flush-all-parents (mode) + (if (get mode 'yas--all-parents) + (put mode 'yas--all-parents nil))) + (defun yas--all-parents (mode) "Like `derived-mode-all-parents' but obeying `yas--parents'." (or (get mode 'yas--all-parents) ;; FIXME: Use `with-memoization'? @@ -854,6 +858,7 @@ which decides on the snippet to expand.") 'fundamental-mode) ,(let ((alias (symbol-function mode))) (when (symbolp alias) alias)) + ,@(get mode 'derived-mode-extra-parents) ,@(gethash mode yas--parents))))))))) (dolist (parent all-parents) (cl-pushnew mode (get parent 'yas--cached-children))) @@ -1833,10 +1838,17 @@ the current buffers contents." (insert "\n\n")) ;; Normal case. (let ((snippet-table (yas--table-get-create mode)) + (uuids nil) (template nil)) (dolist (snippet snippets) (setq template (yas--define-snippets-1 snippet - snippet-table))) + snippet-table)) + (let ((uuid (yas--template-uuid template))) + (if (member uuid uuids) + ;; It's normal for a snippet to override another one + ;; in `snippet-table`, but not one in `snippets`. + (message "Multiple snippets with same identity: %S" uuid) + (push uuid uuids)))) template))) @@ -2002,6 +2014,9 @@ prefix argument." (with-current-buffer buffer yas--editing-template)) (buffer-list)))) + + (mapatoms #'yas--flush-all-parents) + ;; Warn if there are buffers visiting snippets, since reloading will break ;; any on-line editing of those buffers. ;; @@ -3994,7 +4009,7 @@ SNIPPET may be a snippet structure (e.g., as returned by `yas-lookup-snippet'), or just a snippet body (which is a string for normal snippets, and a list for command snippets)." (cl-assert (and yas-minor-mode - (memq 'yas--post-command-handler post-command-hook)) + (memq #'yas--post-command-handler post-command-hook)) nil "[yas] `yas-expand-snippet' needs properly setup `yas-minor-mode'") (run-hooks 'yas-before-expand-snippet-hook) @@ -4715,20 +4730,22 @@ When multiple expressions are found, only the last one counts." (point))) (number (and (match-string-no-properties 1) (string-to-number (match-string-no-properties 1)))) - (brand-new-field (and real-match-end-0 - ;; break if on "$(" immediately - ;; after the ":", this will be - ;; caught as a mirror with - ;; transform later. - (not (string-match-p "\\`\\$[ \t\n]*(" - (match-string-no-properties 2))) - ;; allow ${0: some exit text} - ;; (not (and number (zerop number))) - (yas--make-field number - (yas--make-marker (match-beginning 2)) - (yas--make-marker (1- real-match-end-0)) - parent-field)))) - (when brand-new-field + (field2 (match-string-no-properties 2)) + (simple-fom (string-match-p "\\`[0-9]+\\'" field2)) + (brand-new-field + (and ;; break if on "$(" immediately after the ":", this + ;; will be caught as a mirror with transform later. + (not (string-match-p "\\`\\$[ \t\n]*(" field2)) + ;; allow ${0: some exit text} + ;; (not (and number (zerop number))) + (yas--make-field number + (yas--make-marker (match-beginning 2)) + (yas--make-marker (1- real-match-end-0)) + parent-field)))) + (cond + ((and (not number) simple-fom) + (yas--one-simple-fom snippet field2)) + (brand-new-field (goto-char real-match-end-0) (push (cons (1- real-match-end-0) real-match-end-0) yas--dollar-regions) @@ -4737,9 +4754,11 @@ When multiple expressions are found, only the last one counts." (push brand-new-field (yas--snippet-fields snippet)) (save-excursion (save-restriction - (narrow-to-region (yas--field-start brand-new-field) (yas--field-end brand-new-field)) + (narrow-to-region (yas--field-start brand-new-field) + (yas--field-end brand-new-field)) (goto-char (point-min)) - (yas--field-parse-create snippet brand-new-field))))))) + (yas--field-parse-create snippet brand-new-field)))))))) + ;; if we entered from a parent field, now search for the ;; `yas--multi-dollar-lisp-expression-regexp'. This is used for ;; primary field transformations @@ -4796,31 +4815,35 @@ When multiple expressions are found, only the last one counts." (defun yas--simple-fom-create (snippet) "Parse the simple \"$n\" fields/mirrors/exitmarkers in SNIPPET." (while (re-search-forward yas--simple-mirror-regexp nil t) - (let ((number (string-to-number (match-string-no-properties 1)))) - (cond ((zerop number) - (setf (yas--snippet-exit snippet) - (yas--make-exit (yas--make-marker (match-end 0)))) - (push (cons (match-beginning 0) (yas--exit-marker (yas--snippet-exit snippet))) - yas--dollar-regions)) - (t - (let ((field (yas--snippet-find-field snippet number)) - (fom)) - (if field - (push - (setq fom (yas--make-mirror - (yas--make-marker (match-beginning 0)) - (yas--make-marker (match-beginning 0)) - nil)) - (yas--field-mirrors field)) + (yas--one-simple-fom snippet (match-string-no-properties 1)))) + +(defun yas--one-simple-fom (snippet numstring) + (let ((number (string-to-number numstring))) + (cond ((zerop number) + (setf (yas--snippet-exit snippet) + (yas--make-exit (yas--make-marker (match-end 0)))) + (push (cons (match-beginning 0) + (yas--exit-marker (yas--snippet-exit snippet))) + yas--dollar-regions)) + (t + (let ((field (yas--snippet-find-field snippet number)) + (fom)) + (if field (push - (setq fom (yas--make-field number - (yas--make-marker (match-beginning 0)) - (yas--make-marker (match-beginning 0)) - nil)) - (yas--snippet-fields snippet))) - (yas--calculate-simple-fom-parentage snippet fom)) - (push (cons (match-beginning 0) (match-end 0)) - yas--dollar-regions)))))) + (setq fom (yas--make-mirror + (yas--make-marker (match-beginning 0)) + (yas--make-marker (match-beginning 0)) + nil)) + (yas--field-mirrors field)) + (push + (setq fom (yas--make-field number + (yas--make-marker (match-beginning 0)) + (yas--make-marker (match-beginning 0)) + nil)) + (yas--snippet-fields snippet))) + (yas--calculate-simple-fom-parentage snippet fom)) + (push (cons (match-beginning 0) (match-end 0)) + yas--dollar-regions))))) (defun yas--delete-regions (regions) "Sort disjuct REGIONS by start point, then delete from the back." -- cgit v1.3