├── triagebot.toml ├── .dir-locals.el ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── test-project ├── src │ ├── lib.rs │ ├── rustfmt-default.rs │ └── main.rs └── Cargo.toml ├── rust-common.el ├── Changelog.md ├── Eask ├── run_rust_emacs_tests_docker.sh ├── Makefile ├── LICENSE-MIT ├── rust-mode-treesitter.el ├── rust-playpen.el ├── rust-mode.el ├── rust-cargo-tests.el ├── rust-utils.el ├── rust-compile.el ├── rust-cargo.el ├── README.md ├── LICENSE-APACHE ├── rust-rustfmt.el └── rust-prog-mode.el /triagebot.toml: -------------------------------------------------------------------------------- 1 | [assign] 2 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((emacs-lisp-mode 2 | (indent-tabs-mode . nil))) 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | *-autoloads.el 3 | /rust-mode-pkg.el 4 | .eask 5 | /dist 6 | test-project/Cargo.lock 7 | test-project/target/ 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: 'github-actions' 5 | directory: '/' 6 | schedule: 7 | interval: 'daily' 8 | -------------------------------------------------------------------------------- /test-project/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn test_project() { 2 | // This is only present, because cargo locate-project --workspace actually 3 | // validates the rust project and fails if there is no target. 4 | } 5 | -------------------------------------------------------------------------------- /test-project/src/rustfmt-default.rs: -------------------------------------------------------------------------------- 1 | // this file ensures that by default rustfmt is invoked without parameters 2 | // and formats with its default (--edition=2015 as of today) 3 | 4 | // With --edition=2024 this import will be reordered 5 | use std::{i16, i8}; 6 | -------------------------------------------------------------------------------- /test-project/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Dummy file needed for test. 2 | # 3 | # Needs to have at least a few fields set up, because 4 | # 5 | # cargo locate-project --workspace 6 | # 7 | # will attempt to parse it and fail, if the file is empty. 8 | [package] 9 | name = "test-project" 10 | version = "0.1.0" 11 | -------------------------------------------------------------------------------- /rust-common.el: -------------------------------------------------------------------------------- 1 | ;;; rust-common.el --- Common code for both modes -*-lexical-binding: t-*- 2 | ;;; Commentary: 3 | 4 | ;; rust-common code for both prog-mode and tree-sitter one 5 | 6 | ;;; Code: 7 | (require 'rust-rustfmt) 8 | 9 | (defcustom rust-before-save-hook 'rust-before-save-method 10 | "Function for formatting before save." 11 | :type 'function 12 | :group 'rust-mode) 13 | 14 | (defcustom rust-after-save-hook 'rust-after-save-method 15 | "Default method to handle rustfmt invocation after save." 16 | :type 'function 17 | :group 'rust-mode) 18 | 19 | (provide 'rust-common) 20 | ;;; rust-common.el ends here 21 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | - Ensure rustfmt is invoked without overriding parameters [#571](https://github.com/rust-lang/rust-mode/pull/571) 4 | - Fix native compilation warnings ([#509](https://github.com/rust-lang/rust-mode/issues/509)). 5 | - Introduce `rust-format-mode` for `rust-format-buffer` ([#556](https://github.com/rust-lang/rust-mode/pull/556)). 6 | 7 | # v1.0.6 8 | 9 | - Add support for treesitter mode. 10 | - Fix warnings resulting from rust-utils.el. [Fixes #509](https://github.com/rust-lang/rust-mode/issues/509). 11 | - Fix tree sitter mode with hooks integraiton. [Fixes #516](https://github.com/rust-lang/rust-mode/issues/516). 12 | -------------------------------------------------------------------------------- /Eask: -------------------------------------------------------------------------------- 1 | ;; -*- mode: eask; lexical-binding: t -*- 2 | 3 | (package "rust-mode" 4 | "1.0.6" 5 | "A major-mode for editing Rust source code") 6 | 7 | (website-url "https://github.com/rust-lang/rust-mode") 8 | (keywords "languages") 9 | 10 | (package-file "rust-mode.el") 11 | 12 | (files 13 | "rust-cargo.el" 14 | "rust-compile.el" 15 | "rust-playpen.el" 16 | "rust-rustfmt.el" 17 | "rust-utils.el") 18 | 19 | (script "test" "echo \"Error: no test specified\" && exit 1") 20 | 21 | (depends-on "emacs" "25.1") 22 | 23 | (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 24 | -------------------------------------------------------------------------------- /test-project/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, io}; 2 | 3 | fn main() { 4 | let mut args = env::args(); 5 | 6 | if args.len() == 1 { 7 | println!("***run not interactive"); 8 | } else { 9 | match args.nth(1).unwrap().as_str() { 10 | "interactive" => { 11 | let mut line = String::new(); 12 | 13 | io::stdin() 14 | .read_line(&mut line) 15 | .expect("Failed to read line"); 16 | 17 | println!("***run interactive: {line}"); 18 | } 19 | 20 | _ => { 21 | panic!("unexpected argument"); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /run_rust_emacs_tests_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Testing Local files with Emacs 26 (latest)" 4 | docker run -it --rm --name docker-cp -v `pwd`:/usr/src/app -w /usr/src/app --entrypoint=/bin/bash silex/emacs:26.2-dev ./test-by-cp 5 | 6 | echo Testing Local files with Emacs 25 7 | docker run -it --rm --name docker-cp -v `pwd`:/usr/src/app -w /usr/src/app --entrypoint=/bin/bash silex/emacs:25.3-dev ./test-by-cp 8 | 9 | echo "Testing commits with Emacs 26 (latest)" 10 | docker run -it --rm --name docker-cp -v `pwd`:/usr/src/app -w /usr/src/app --entrypoint=/bin/bash silex/emacs:26.2-dev ./test-from-git 11 | 12 | echo Testing commits with Emacs 25 13 | docker run -it --rm --name docker-cp -v `pwd`:/usr/src/app -w /usr/src/app --entrypoint=/bin/bash silex/emacs:25.3-dev ./test-from-git 14 | 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include config.mk 2 | 3 | .PHONY: test 4 | 5 | all: lisp 6 | 7 | PKG = rust-mode 8 | 9 | EMACS ?= emacs 10 | EMACS_ARGS ?= 11 | EASK ?= eask 12 | 13 | DEPS = 14 | 15 | LOAD_PATH ?= $(addprefix -L ../,$(DEPS)) 16 | LOAD_PATH += -L . 17 | 18 | # TODO: add checkdoc and lint 19 | ci: build compile test 20 | 21 | build: 22 | $(EASK) package 23 | $(EASK) install 24 | 25 | compile: 26 | @printf "Compiling $<\n" 27 | $(EASK) compile 28 | 29 | test: 30 | $(EASK) test ert rust-mode-tests.el 31 | $(EASK) test ert rust-cargo-tests.el 32 | 33 | checkdoc: 34 | $(EASK) lint checkdoc 35 | 36 | lint: 37 | $(EASK) lint package 38 | 39 | CLEAN = $(PKG)-autoloads.el 40 | 41 | clean: 42 | @printf "Cleaning...\n" 43 | @rm -rf $(CLEAN) 44 | $(EASK) clean all 45 | 46 | $(PKG)-autoloads.el: 47 | @printf "Generating $@\n" 48 | $(EASK) autoloads 49 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /rust-mode-treesitter.el: -------------------------------------------------------------------------------- 1 | ;;; rust-mode-treesitter.el --- use native rust-ts-mode -*-lexical-binding: t-*- 2 | ;;; Commentary: 3 | 4 | ;; Derive from rust-ts-mode instead of prog-mode 5 | 6 | ;;; Code: 7 | 8 | (require 'rust-mode) 9 | 10 | ;; Do not compile or load on Emacs releases that don't support 11 | ;; this. See https://github.com/rust-lang/rust-mode/issues/520. 12 | (when (version<= "29.1" emacs-version) 13 | (require 'treesit) 14 | (require 'rust-ts-mode) 15 | 16 | ;; HACK: `rust-ts-mode' adds itself to the `auto-mode-alist' 17 | ;; after us, so we need to readd `rust-mode' to the front of 18 | ;; the list after loading `rust-ts-mode'. 19 | (setq auto-mode-alist (delete '("\\.rs\\'" . rust-mode) auto-mode-alist)) 20 | (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) 21 | 22 | (define-derived-mode rust-mode rust-ts-mode "Rust" 23 | "Major mode for Rust code. 24 | 25 | \\{rust-mode-map}" 26 | :group 'rust-mode 27 | 28 | (add-hook 'before-save-hook rust-before-save-hook nil t) 29 | (add-hook 'after-save-hook rust-after-save-hook nil t))) 30 | 31 | (provide 'rust-mode-treesitter) 32 | ;;; rust-mode-treesitter.el ends here 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ubuntu-latest, macos-latest, windows-latest] 21 | emacs-version: 22 | - 26.3 23 | - 27.2 24 | - 28.2 25 | - 29.4 26 | - 30.2 27 | include: 28 | - os: ubuntu-latest 29 | emacs-version: snapshot 30 | experimental: true 31 | - os: macos-latest 32 | emacs-version: snapshot 33 | experimental: true 34 | - os: windows-latest 35 | emacs-version: snapshot 36 | experimental: true 37 | exclude: 38 | - os: macos-latest 39 | emacs-version: 26.3 40 | - os: macos-latest 41 | emacs-version: 27.2 42 | 43 | steps: 44 | - uses: actions/checkout@v6 45 | - uses: jcs090218/setup-emacs@master 46 | with: 47 | version: ${{ matrix.emacs-version }} 48 | - uses: dtolnay/rust-toolchain@stable 49 | with: 50 | components: rustfmt 51 | - uses: emacs-eask/setup-eask@master 52 | with: 53 | version: "snapshot" 54 | - name: Run tests 55 | run: make ci 56 | -------------------------------------------------------------------------------- /rust-playpen.el: -------------------------------------------------------------------------------- 1 | ;;; rust-playpen.el --- Support for the Rust Playground -*- lexical-binding:t -*- 2 | ;;; Commentary: 3 | 4 | ;; This library implements support for the Rust Playground, 5 | ;; which is hosted at https://play.rust-lang.org and developed 6 | ;; at https://github.com/integer32llc/rust-playground. 7 | 8 | ;;; Code: 9 | 10 | (eval-when-compile (require 'url)) 11 | 12 | ;;; Options 13 | 14 | (defcustom rust-playpen-url-format "https://play.rust-lang.org/?code=%s" 15 | "Format string to use when submitting code to the playpen." 16 | :type 'string 17 | :group 'rust-mode) 18 | 19 | (defcustom rust-playpen-enable-shortener t 20 | "Enable shortended URL for playpen links." 21 | :type 'boolean 22 | :safe #'booleanp 23 | :group 'rust-mode) 24 | 25 | (defcustom rust-shortener-url-format "https://is.gd/create.php?format=simple&url=%s" 26 | "Format string to use for creating the shortened link of a playpen submission." 27 | :type 'string 28 | :group 'rust-mode) 29 | 30 | ;;; Commands 31 | 32 | (defun rust-playpen-region (begin end) 33 | "Create a shareable URL for the region from BEGIN to END on the Rust playpen." 34 | (interactive "r") 35 | (let* ((data (buffer-substring begin end)) 36 | (escaped-data (url-hexify-string data)) 37 | (playpen-url (format rust-playpen-url-format escaped-data)) 38 | (escaped-playpen-url (url-hexify-string playpen-url))) 39 | (if (> (length escaped-playpen-url) 5000) 40 | (error "encoded playpen data exceeds 5000 character limit (length %s)" 41 | (length escaped-playpen-url)) 42 | (if (not rust-playpen-enable-shortener) 43 | (message "%s" playpen-url) 44 | (let ((shortener-url (format rust-shortener-url-format escaped-playpen-url)) 45 | (url-request-method "POST")) 46 | (url-retrieve shortener-url 47 | (lambda (state) 48 | ;; filter out the headers etc. included at the 49 | ;; start of the buffer: the relevant text 50 | ;; (shortened url or error message) is exactly 51 | ;; the last line. 52 | (goto-char (point-max)) 53 | (let ((last-line (thing-at-point 'line t)) 54 | (err (plist-get state :error))) 55 | (kill-buffer) 56 | (if err 57 | (error "failed to shorten playpen url: %s" last-line) 58 | (message "%s" last-line)))))))))) 59 | 60 | (defun rust-playpen-buffer () 61 | "Create a shareable URL for the contents of the buffer on the Rust playpen." 62 | (interactive) 63 | (rust-playpen-region (point-min) (point-max))) 64 | 65 | ;;; _ 66 | (provide 'rust-playpen) 67 | ;;; rust-playpen.el ends here 68 | -------------------------------------------------------------------------------- /rust-mode.el: -------------------------------------------------------------------------------- 1 | ;;; rust-mode.el --- A major-mode for editing Rust source code -*-lexical-binding: t-*- 2 | 3 | ;; Version: 1.0.6 4 | ;; Author: Mozilla 5 | ;; Url: https://github.com/rust-lang/rust-mode 6 | ;; Keywords: languages 7 | ;; Package-Requires: ((emacs "25.1")) 8 | 9 | ;; This file is distributed under the terms of both the MIT license and the 10 | ;; Apache License (version 2.0). 11 | 12 | ;;; Commentary: 13 | 14 | ;; This package implements a major-mode for editing Rust source code. 15 | 16 | ;;; Code: 17 | (require 'rust-common) 18 | 19 | (eval-when-compile 20 | (require 'rx) 21 | (require 'subr-x)) 22 | 23 | (defvar rust-load-optional-libraries t 24 | "Whether loading `rust-mode' also loads optional libraries. 25 | This variable might soon be remove again.") 26 | 27 | (when rust-load-optional-libraries 28 | (require 'rust-cargo) 29 | (require 'rust-compile) 30 | (require 'rust-playpen) 31 | (require 'rust-rustfmt)) 32 | 33 | ;;; Customization 34 | 35 | (defgroup rust-mode nil 36 | "Support for Rust code." 37 | :link '(url-link "https://www.rust-lang.org/") 38 | :group 'languages) 39 | 40 | (defcustom rust-mode-treesitter-derive nil 41 | "Whether rust-mode should derive from the new treesitter mode `rust-ts-mode' 42 | instead of `prog-mode'. This option requires emacs29+." 43 | :version "29.1" 44 | :type 'boolean 45 | :group 'rustic) 46 | 47 | ;;; Faces 48 | 49 | (define-obsolete-face-alias 'rust-unsafe-face 50 | 'rust-unsafe "0.6.0") 51 | (define-obsolete-face-alias 'rust-question-mark-face 52 | 'rust-question-mark "0.6.0") 53 | (define-obsolete-face-alias 'rust-builtin-formatting-macro-face 54 | 'rust-builtin-formatting-macro "0.6.0") 55 | (define-obsolete-face-alias 'rust-string-interpolation-face 56 | 'rust-string-interpolation "0.6.0") 57 | 58 | ;;; Mode 59 | 60 | (defvar rust-mode-map 61 | (let ((map (make-sparse-keymap))) 62 | (define-key map (kbd "C-c C-d") #'rust-dbg-wrap-or-unwrap) 63 | (when rust-load-optional-libraries 64 | (define-key map (kbd "C-c C-c C-u") 'rust-compile) 65 | (define-key map (kbd "C-c C-c C-k") 'rust-check) 66 | (define-key map (kbd "C-c C-c C-t") 'rust-test) 67 | (define-key map (kbd "C-c C-c C-r") 'rust-run) 68 | (define-key map (kbd "C-c C-c C-l") 'rust-run-clippy) 69 | (define-key map (kbd "C-c C-f") 'rust-format-buffer) 70 | (define-key map (kbd "C-c C-n") 'rust-goto-format-problem)) 71 | map) 72 | "Keymap for Rust major mode.") 73 | 74 | ;;;###autoload 75 | (autoload 'rust-mode "rust-mode" "Major mode for Rust code." t) 76 | 77 | ;;;###autoload 78 | (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) 79 | 80 | (provide 'rust-mode) 81 | 82 | (if (and rust-mode-treesitter-derive 83 | (version<= "29.1" emacs-version)) 84 | (require 'rust-mode-treesitter) 85 | (require 'rust-prog-mode)) 86 | 87 | (require 'rust-utils) 88 | 89 | ;;; rust-mode.el ends here 90 | -------------------------------------------------------------------------------- /rust-cargo-tests.el: -------------------------------------------------------------------------------- 1 | ;;; rust-cargo-tests.el --- ERT tests for rust-cargo.el -*- lexical-binding: t; -*- 2 | (require 'rust-cargo) 3 | (require 'rust-rustfmt) 4 | (require 'ert) 5 | 6 | (defun rust-test--wait-process-exit () 7 | "Wait for comint process for current buffer to exit." 8 | (let ((proc (get-buffer-process (current-buffer)))) 9 | (while (not (eq (process-status proc) 'exit)) 10 | (sit-for 0.2)))) 11 | 12 | (defun rust-test--send-process-string (string) 13 | "Send STRING to comint process for current buffer." 14 | (let ((proc (get-buffer-process (current-buffer)))) 15 | (comint-send-string proc string))) 16 | 17 | (defmacro rust-test--with-main-file-buffer (expr) 18 | `(let* ((test-dir (expand-file-name "test-project/" default-directory)) 19 | (main-file (expand-file-name "src/main.rs" test-dir))) 20 | (save-current-buffer 21 | (find-file main-file) 22 | ,expr))) 23 | 24 | (defmacro rust-test--with-snippet-buffer (expr) 25 | `(let* ((test-dir (expand-file-name "test-project/" default-directory)) 26 | (snippet-file (expand-file-name "src/rustfmt-default.rs" test-dir))) 27 | (save-current-buffer 28 | (find-file snippet-file) 29 | ,expr))) 30 | 31 | (defun rust-test--find-string (string) 32 | "Find STRING in current buffer." 33 | (goto-char (point-min)) 34 | (not (null (search-forward string nil t)))) 35 | 36 | 37 | (ert-deftest rust-test-compile () 38 | (skip-unless (executable-find rust-cargo-bin)) 39 | (setq rust-cargo-default-arguments "") 40 | (rust-test--with-main-file-buffer 41 | (with-current-buffer (rust-compile) 42 | (rust-test--wait-process-exit) 43 | (should (rust-test--find-string "Compilation finished"))))) 44 | 45 | (ert-deftest rust-test-run () 46 | (skip-unless (executable-find rust-cargo-bin)) 47 | (setq rust-cargo-default-arguments "") 48 | (rust-test--with-main-file-buffer 49 | (with-current-buffer (rust-run) 50 | (rust-test--wait-process-exit) 51 | (should (rust-test--find-string "***run not interactive"))))) 52 | 53 | (ert-deftest rust-test-run-interactive () 54 | (skip-unless (executable-find rust-cargo-bin)) 55 | (setq rust-cargo-default-arguments "interactive") 56 | (rust-test--with-main-file-buffer 57 | ;; '(4) is default value when called interactively with universal argument 58 | (with-current-buffer (rust-run '(4)) 59 | (rust-test--send-process-string "1234\n") 60 | (rust-test--wait-process-exit) 61 | (should (rust-test--find-string "***run interactive: 1234"))))) 62 | 63 | (ert-deftest rust-test-rustfmt () 64 | (skip-unless (executable-find "rustfmt")) 65 | (rust-test--with-main-file-buffer 66 | (let ((old-content (buffer-string)) 67 | (ret (rust-format-buffer))) 68 | (should (string= ret "Formatted buffer with rustfmt.")) 69 | (should (string= old-content (buffer-string)))))) 70 | 71 | (ert-deftest rust-test-rustfmt-parsing-errors () 72 | (skip-unless (executable-find "rustfmt")) 73 | (with-temp-buffer 74 | (insert "fn main() {") 75 | (rust-format-buffer) 76 | (with-current-buffer "*rustfmt*" 77 | (should (eq major-mode 'rust-format-mode)) 78 | (should (rust-test--find-string "error:"))) 79 | (kill-buffer "*rustfmt*"))) 80 | 81 | (ert-deftest rust-test-respect-rustfmt-defaults () 82 | (skip-unless (executable-find "rustfmt")) 83 | (rust-test--with-snippet-buffer 84 | (let ((old-content (buffer-string)) 85 | (ret (rust-format-buffer))) 86 | (should (string= old-content (buffer-string)))))) 87 | 88 | (ert-deftest rust-test-ensure-rustfmt-switches-nil () 89 | (should (eq rust-rustfmt-switches nil))) 90 | -------------------------------------------------------------------------------- /rust-utils.el: -------------------------------------------------------------------------------- 1 | ;;; rust-utils.el --- Various Rust utilities -*- lexical-binding:t -*- 2 | ;;; Commentary: 3 | 4 | ;; This library implements various utilities for dealing with Rust 5 | ;; code. 6 | 7 | ;;; Code: 8 | 9 | (require 'thingatpt) 10 | 11 | (require 'rust-mode) ; for `rust-in-str' and `rust-looking-back-str' 12 | 13 | ;;; Promote module 14 | 15 | (defun rust-promote-module-into-dir () 16 | "Promote the module file visited by the current buffer into its own directory. 17 | 18 | For example, if the current buffer is visiting the file `foo.rs', 19 | then this function creates the directory `foo' and renames the 20 | file to `foo/mod.rs'. The current buffer will be updated to 21 | visit the new file." 22 | (interactive) 23 | (let ((filename (buffer-file-name))) 24 | (if (not filename) 25 | (message "Buffer is not visiting a file.") 26 | (if (string-equal (file-name-nondirectory filename) "mod.rs") 27 | (message "Won't promote a module file already named mod.rs.") 28 | (let* ((basename (file-name-sans-extension 29 | (file-name-nondirectory filename))) 30 | (mod-dir (file-name-as-directory 31 | (concat (file-name-directory filename) basename))) 32 | (new-name (concat mod-dir "mod.rs"))) 33 | (mkdir mod-dir t) 34 | (rename-file filename new-name 1) 35 | (set-visited-file-name new-name)))))) 36 | 37 | ;;; dbg! macro 38 | 39 | (defun rust-insert-dbg-sexp () 40 | "Insert the dbg! macro around a sexp if possible, insert at current position 41 | if not. Move cursor to the end of macro." 42 | (when (rust-in-str) 43 | (up-list -1 t t)) 44 | (let ((safe-to-forward t)) 45 | (save-excursion 46 | (condition-case nil 47 | (forward-sexp) 48 | (error (setq safe-to-forward nil) 49 | nil))) 50 | (cond 51 | ((not safe-to-forward) 52 | (rust-insert-dbg-alone)) 53 | (t 54 | (insert "(") 55 | (forward-sexp) 56 | (insert ")") 57 | (backward-sexp) 58 | (insert "dbg!") 59 | (forward-sexp))))) 60 | 61 | (defun rust-insert-dbg-region () 62 | "Insert the dbg! macro around a region. Move cursor to the end of macro." 63 | (when (< (mark) (point)) 64 | (exchange-point-and-mark)) 65 | (let ((old-point (point))) 66 | (insert-parentheses) 67 | (goto-char old-point)) 68 | (insert "dbg!") 69 | (forward-sexp)) 70 | 71 | (defun rust-insert-dbg-alone () 72 | "Insert the dbg! macro alone. Move cursor in between the brackets." 73 | (insert "dbg!()") 74 | (backward-char)) 75 | 76 | ;;;###autoload 77 | (defun rust-dbg-wrap-or-unwrap () 78 | "Either remove or add the dbg! macro." 79 | (interactive) 80 | 81 | (cond 82 | 83 | ;; region 84 | ((region-active-p) 85 | (rust-insert-dbg-region)) 86 | 87 | ;; alone 88 | ((or (looking-at-p " *$") (looking-at-p " *//.*")) 89 | (rust-insert-dbg-alone)) 90 | 91 | ;; symbol 92 | (t 93 | (let ((beginning-of-symbol (ignore-errors (beginning-of-thing 'symbol)))) 94 | (when beginning-of-symbol 95 | (goto-char beginning-of-symbol))) 96 | 97 | (let ((dbg-point (save-excursion 98 | (or (and (looking-at-p "dbg!") (+ 4 (point))) 99 | (ignore-errors 100 | (while (not (rust-looking-back-str "dbg!")) 101 | (backward-up-list)) 102 | (point)))))) 103 | (cond (dbg-point 104 | (goto-char dbg-point) 105 | (delete-char -4) 106 | (delete-pair)) 107 | (t (rust-insert-dbg-sexp))))) 108 | ) 109 | ) 110 | 111 | (defun rust-toggle-mutability () 112 | "Toggles the mutability of the variable defined on the current line" 113 | (interactive) 114 | (save-excursion 115 | (back-to-indentation) 116 | (forward-word) 117 | (if (string= " mut" (buffer-substring (point) (+ (point) 4))) 118 | (delete-region (point) (+ (point) 4)) 119 | (insert " mut")))) 120 | ;;; _ 121 | (provide 'rust-utils) 122 | ;;; rust-utils.el ends here 123 | -------------------------------------------------------------------------------- /rust-compile.el: -------------------------------------------------------------------------------- 1 | ;;; rust-compile.el --- Compile facilities -*-lexical-binding: t-*- 2 | ;;; Commentary: 3 | 4 | ;; This library teaches `compilation-mode' about "rustc" output. 5 | 6 | ;;; Code: 7 | 8 | (require 'compile) 9 | 10 | ;;; _ 11 | 12 | (defvar rustc-compilation-location 13 | (let ((file "\\([^\n]+\\)") 14 | (start-line "\\([0-9]+\\)") 15 | (start-col "\\([0-9]+\\)")) 16 | (concat "\\(" file ":" start-line ":" start-col "\\)"))) 17 | 18 | (defvar rustc-compilation-regexps 19 | (let ((re (concat "^\\(?:error\\|\\(warning\\)\\|\\(note\\)\\)[^\0]+?--> " 20 | rustc-compilation-location))) 21 | (cons re '(4 5 6 (1 . 2) 3))) 22 | "Specifications for matching errors in rustc invocations. 23 | See `compilation-error-regexp-alist' for help on their format.") 24 | 25 | (defvar rustc-colon-compilation-regexps 26 | (let ((re (concat "^ *::: " rustc-compilation-location))) 27 | (cons re '(2 3 4 0 1))) 28 | "Specifications for matching `:::` hints in rustc invocations. 29 | See `compilation-error-regexp-alist' for help on their format.") 30 | 31 | (defvar rustc-backtrace-compilation-regexps 32 | (let ((re (concat "^ +at " rustc-compilation-location))) 33 | (cons re '(2 3 4 0 1))) 34 | "Specifications for matching stack backtraces in rustc invocations. 35 | See `compilation-error-regexp-alist' for help on their format.") 36 | 37 | (defvar rustc-refs-compilation-regexps 38 | (let ((re "^\\([0-9]+\\)[[:space:]]*|")) 39 | (cons re '(nil 1 nil 0 1))) 40 | "Specifications for matching code references in rustc invocations. 41 | See `compilation-error-regexp-alist' for help on their format.") 42 | 43 | (defvar rustc-panics-compilation-regexps 44 | (let ((re (concat "thread '[^']+' panicked at " rustc-compilation-location))) 45 | (cons re '(2 3 4 nil 1))) 46 | "Specifications for matching panics in rustc invocations. 47 | See `compilation-error-regexp-alist' for help on their format.") 48 | 49 | ;; Match test run failures and panics during compilation as 50 | ;; compilation warnings 51 | (defvar cargo-compilation-regexps 52 | '("', \\(\\([^:]+\\):\\([0-9]+\\)\\)" 53 | 2 3 nil nil 1) 54 | "Specifications for matching panics in cargo test invocations. 55 | See `compilation-error-regexp-alist' for help on their format.") 56 | 57 | (defvar rustc-dbg!-compilation-regexps 58 | (let ((re (concat "\\[" rustc-compilation-location "\\]"))) 59 | (cons re '(2 3 4 0 1))) 60 | "Specifications for matching dbg! output. 61 | See `compilation-error-regexp-alist' for help on their format.") 62 | 63 | (defun rustc-scroll-down-after-next-error () 64 | "In the new style error messages, the regular expression 65 | matches on the file name (which appears after `-->`), but the 66 | start of the error appears a few lines earlier. This hook runs 67 | after `next-error' (\\[next-error]); it simply scrolls down a few lines in 68 | the compilation window until the top of the error is visible." 69 | (save-selected-window 70 | (when (eq major-mode 'rust-mode) 71 | (select-window (get-buffer-window next-error-last-buffer 'visible)) 72 | (when (save-excursion 73 | (beginning-of-line) 74 | (looking-at " *-->")) 75 | (let ((start-of-error 76 | (save-excursion 77 | (beginning-of-line) 78 | (while (not (looking-at "^[a-z]+:\\|^[a-z]+\\[E[0-9]+\\]:")) 79 | (forward-line -1)) 80 | (point)))) 81 | (set-window-start (selected-window) start-of-error)))))) 82 | 83 | (eval-after-load 'compile 84 | '(progn 85 | (add-to-list 'compilation-error-regexp-alist-alist 86 | (cons 'rustc-refs rustc-refs-compilation-regexps)) 87 | (add-to-list 'compilation-error-regexp-alist 'rustc-refs) 88 | (add-to-list 'compilation-error-regexp-alist-alist 89 | (cons 'rustc rustc-compilation-regexps)) 90 | (add-to-list 'compilation-error-regexp-alist 'rustc) 91 | (add-to-list 'compilation-error-regexp-alist-alist 92 | (cons 'rustc-colon rustc-colon-compilation-regexps)) 93 | (add-to-list 'compilation-error-regexp-alist 'rustc-colon) 94 | (add-to-list 'compilation-error-regexp-alist-alist 95 | (cons 'rustc-backtrace rustc-backtrace-compilation-regexps)) 96 | (add-to-list 'compilation-error-regexp-alist 'rustc-backtrace) 97 | (add-to-list 'compilation-error-regexp-alist-alist 98 | (cons 'cargo cargo-compilation-regexps)) 99 | (add-to-list 'compilation-error-regexp-alist-alist 100 | (cons 'rustc-panics rustc-panics-compilation-regexps)) 101 | (add-to-list 'compilation-error-regexp-alist 'rustc-panics) 102 | (add-to-list 'compilation-error-regexp-alist 'cargo) 103 | (add-to-list 'compilation-error-regexp-alist-alist 104 | (cons 'rust-dbg! rustc-dbg!-compilation-regexps)) 105 | (add-to-list 'compilation-error-regexp-alist 'rust-dbg!) 106 | (add-hook 'next-error-hook #'rustc-scroll-down-after-next-error))) 107 | 108 | ;;; _ 109 | (provide 'rust-compile) 110 | ;;; rust-compile.el ends here 111 | -------------------------------------------------------------------------------- /rust-cargo.el: -------------------------------------------------------------------------------- 1 | ;;; rust-cargo.el --- Support for cargo -*-lexical-binding: t-*- 2 | ;;; Commentary: 3 | 4 | ;; This library implements support for running `cargo'. 5 | 6 | ;;; Code: 7 | 8 | (require 'json) 9 | 10 | ;;; Options 11 | 12 | (defcustom rust-cargo-bin "cargo" 13 | "Path to cargo executable." 14 | :type 'string 15 | :group 'rust-mode) 16 | 17 | (defcustom rust-always-locate-project-on-open nil 18 | "Whether to run `cargo locate-project' every time `rust-mode' is activated." 19 | :type 'boolean 20 | :group 'rust-mode) 21 | 22 | (defcustom rust-cargo-locate-default-arguments '("--workspace") 23 | "Arguments for `cargo locate-project`. Remove `--workspace` if you 24 | would prefer to use the local crate Cargo.toml instead of the 25 | worksapce for commands like `cargo check`." 26 | :type '(repeat string) 27 | :group 'rust-mode) 28 | 29 | (defcustom rust-cargo-default-arguments "" 30 | "Default arguments when running common cargo commands." 31 | :type 'string 32 | :group 'rust-mode) 33 | 34 | (defcustom rust-cargo-clippy-default-arguments '() 35 | "Default arguments for running `cargo clippy`." 36 | :type '(repeat string) 37 | :group 'rust-mode) 38 | 39 | ;;; Buffer Project 40 | 41 | (defvar-local rust-buffer-project nil) 42 | 43 | (defun rust-buffer-project () 44 | "Get project root if possible." 45 | ;; Copy environment variables into the new buffer, since 46 | ;; with-temp-buffer will re-use the variables' defaults, even if 47 | ;; they have been changed in this variable using e.g. envrc-mode. 48 | ;; See https://github.com/purcell/envrc/issues/12. 49 | (let ((env process-environment) 50 | (path exec-path)) 51 | (with-temp-buffer 52 | ;; Copy the entire environment just in case there's something we 53 | ;; don't know we need. 54 | (setq-local process-environment env) 55 | ;; Set PATH so we can find cargo. 56 | (setq-local exec-path path) 57 | (let ((ret 58 | (let ((args 59 | (append 60 | (list rust-cargo-bin nil (list (current-buffer) nil) nil 61 | "locate-project") 62 | rust-cargo-locate-default-arguments))) 63 | (apply #'process-file args)))) 64 | (when (/= ret 0) 65 | (error "`cargo locate-project' returned %s status: %s" ret (buffer-string))) 66 | (goto-char 0) 67 | (let ((output (let ((json-object-type 'alist)) 68 | (json-read)))) 69 | (cdr (assoc-string "root" output))))))) 70 | 71 | (defun rust-buffer-crate () 72 | "Try to locate Cargo.toml using `locate-dominating-file'." 73 | (let ((dir (locate-dominating-file default-directory "Cargo.toml"))) 74 | (if dir dir default-directory))) 75 | 76 | (defun rust-update-buffer-project () 77 | (setq-local rust-buffer-project (rust-buffer-project))) 78 | 79 | (defun rust-maybe-initialize-buffer-project () 80 | (setq-local rust-buffer-project nil) 81 | (when rust-always-locate-project-on-open 82 | (rust-update-buffer-project))) 83 | 84 | (add-hook 'rust-mode-hook #'rust-maybe-initialize-buffer-project) 85 | 86 | ;;; Internal 87 | 88 | (defun rust--compile (comint format-string &rest args) 89 | (when (null rust-buffer-project) 90 | (rust-update-buffer-project)) 91 | (let ((default-directory 92 | (or (and rust-buffer-project 93 | (file-name-directory rust-buffer-project)) 94 | default-directory)) 95 | ;; make sure comint is a boolean value 96 | (comint (not (not comint)))) 97 | (compile (apply #'format format-string args) comint))) 98 | 99 | ;;; Commands 100 | 101 | (defun rust-check () 102 | "Compile using `cargo check`" 103 | (interactive) 104 | (rust--compile nil "%s check %s" rust-cargo-bin rust-cargo-default-arguments)) 105 | 106 | (defun rust-compile () 107 | "Compile using `cargo build`" 108 | (interactive) 109 | (rust--compile nil "%s build %s" rust-cargo-bin rust-cargo-default-arguments)) 110 | 111 | (defun rust-compile-release () 112 | "Compile using `cargo build --release`" 113 | (interactive) 114 | (rust--compile nil "%s build --release" rust-cargo-bin)) 115 | 116 | (defun rust-run (&optional comint) 117 | "Run using `cargo run` 118 | 119 | If optional arg COMINT is t or invoked with universal prefix arg, 120 | output buffer will be in comint mode, i.e. interactive." 121 | (interactive "P") 122 | (rust--compile comint "%s run %s" rust-cargo-bin rust-cargo-default-arguments)) 123 | 124 | (defun rust-run-release (&optional comint) 125 | "Run using `cargo run --release` 126 | 127 | If optional arg COMINT is t or invoked with universal prefix arg, 128 | output buffer will be in comint mode, i.e. interactive." 129 | (interactive "P") 130 | (rust--compile comint "%s run --release" rust-cargo-bin)) 131 | 132 | (defun rust-test () 133 | "Test using `cargo test`" 134 | (interactive) 135 | (rust--compile nil "%s test %s" rust-cargo-bin rust-cargo-default-arguments)) 136 | 137 | (defun rust-run-clippy () 138 | "Run `cargo clippy'." 139 | (interactive) 140 | (when (null rust-buffer-project) 141 | (rust-update-buffer-project)) 142 | (let* ((args (append (list rust-cargo-bin "clippy" 143 | (concat "--manifest-path=" rust-buffer-project)) 144 | rust-cargo-clippy-default-arguments)) 145 | ;; set `compile-command' temporarily so `compile' doesn't 146 | ;; clobber the existing value 147 | (compile-command (concat (mapconcat #'shell-quote-argument args " ") 148 | " " rust-cargo-default-arguments))) 149 | (rust--compile nil compile-command))) 150 | 151 | ;;; _ 152 | (provide 'rust-cargo) 153 | ;;; rust-cargo.el ends here 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-mode 2 | 3 | [![NonGNU ELPA](https://elpa.nongnu.org/nongnu/rust-mode.svg)](https://elpa.nongnu.org/nongnu/rust-mode.html) 4 | [![MELPA](https://melpa.org/packages/rust-mode-badge.svg)](https://melpa.org/#/rust-mode) 5 | [![](https://github.com/rust-lang/rust-mode/workflows/CI/badge.svg)](https://github.com/rust-lang/rust-mode/actions?query=workflow%3ACI) 6 | 7 | 8 | **Table of Contents** 9 | 10 | - [rust-mode](#rust-mode) 11 | - [Introduction](#introduction) 12 | - [Known issues](#known-issues) 13 | - [Installation](#installation) 14 | - [Melpa](#melpa) 15 | - [Manual installation](#manual-installation) 16 | - [Feature guide](#feature-guide) 17 | - [Indentation](#indentation) 18 | - [Code formatting](#code-formatting) 19 | - [Prettifying](#prettifying) 20 | - [Running / testing / compiling code](#running--testing--compiling-code) 21 | - [Clippy](#clippy) 22 | - [Easy insertion of dbg!](#easy-insertion-of-dbg) 23 | - [More commands](#more-commands) 24 | - [tree-sitter](#tree-sitter) 25 | - [LSP](#lsp) 26 | - [eglot](#eglot) 27 | - [lsp-mode](#lsp-mode) 28 | - [Auto-completion](#auto-completion) 29 | - [Other recommended packages](#other-recommended-packages) 30 | - [flycheck](#flycheck) 31 | - [cargo.el](#cargoel) 32 | - [cargo-mode](#cargo-mode) 33 | - [rustic](#rustic) 34 | - [Optional features](#optional-features) 35 | - [For package maintainers](#for-package-maintainers) 36 | - [Tests](#tests) 37 | - [Contributing](#contributing) 38 | 39 | 40 | 41 | ## Introduction 42 | 43 | `rust-mode` makes editing [Rust](http://rust-lang.org) code with Emacs 44 | enjoyable. It requires Emacs 25 or later, and is included in both 45 | [Emacs Prelude](https://github.com/bbatsov/prelude) and 46 | [Spacemacs](https://github.com/syl20bnr/spacemacs) by default. 47 | 48 | This mode provides: 49 | - Syntax highlighting (for Font Lock Mode) 50 | - Indentation 51 | - Integration with Cargo, clippy and rustfmt 52 | 53 | This mode does _not_ provide auto completion, or jumping to function / 54 | trait definitions. See [Auto-completion](#auto-completion) below for tips on how 55 | to enable this. 56 | 57 | If you are missing features in rust-mode, please check out 58 | [rustic](https://github.com/emacs-rustic/rustic) before you open a feature 59 | request. It depends on rust-mode and provides additional features. This 60 | allows us to keep rust-mode light-weight for users that are happy with 61 | basic functionality. 62 | 63 | ## Known issues 64 | 65 | - `rust-syntax-propertize` and `adaptive-wrap-prefix-mode` can lead to 66 | severe lag when editing larger files 67 | (https://github.com/brotzeit/rustic/issues/107) 68 | 69 | ## Installation 70 | 71 | ### Melpa 72 | 73 | The package is available on MELPA. Add this to your init.el. 74 | 75 | ``` elisp 76 | (require 'package) 77 | (add-to-list 'package-archives 78 | '("melpa" . "https://melpa.org/packages/") t) 79 | (package-initialize) 80 | (package-refresh-contents) 81 | ``` 82 | 83 | Now you can install `rust-mode` with: 84 | 85 | `M-x package-install rust-mode` 86 | 87 | And put this in your config to load rust-mode automatically: 88 | 89 | `(require 'rust-mode)` 90 | 91 | ### NonGNU ELPA 92 | 93 | [NonGNU ELPA](https://elpa.nongnu.org/) can be used out of the box in 94 | emacs28. 95 | 96 | For older versions you need to add something like the following to 97 | your init file: 98 | 99 | ``` elisp 100 | (with-eval-after-load 'package (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/"))) 101 | ``` 102 | 103 | ### Manual installation 104 | 105 | Clone this repository locally, and add this to your init.el: 106 | 107 | ``` elisp 108 | (add-to-list 'load-path "/path/to/rust-mode/") 109 | (autoload 'rust-mode "rust-mode" nil t) 110 | (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) 111 | ``` 112 | 113 | ## Feature guide 114 | 115 | ### Indentation 116 | 117 | Commands like TAB should indent correctly. 118 | 119 | The Rust style guide recommends spaces rather than tabs for 120 | indentation; to follow the recommendation add this to your init.el, 121 | which forces indentation to always use spaces. 122 | 123 | ```elisp 124 | (add-hook 'rust-mode-hook 125 | (lambda () (setq indent-tabs-mode nil))) 126 | ``` 127 | 128 | Since Emacs ≥ 24.4, [`electric-indent-mode`][] is turned on by 129 | default. If you do not like it, call `(electric-indent-mode 0)` in 130 | `rust-mode-hook`. 131 | 132 | [`electric-indent-mode`]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Indent-Convenience.html 133 | 134 | ### Code formatting 135 | 136 | The `rust-format-buffer` function will format your code with 137 | [rustfmt](https://github.com/rust-lang/rustfmt) if installed. By 138 | default, this is bound to C-c C-f. 139 | 140 | The variable `rust-format-on-save` enables automatic formatting on 141 | save. For example, add the following in your init.el to enable format 142 | on save: 143 | 144 | ``` elisp 145 | (setq rust-format-on-save t) 146 | ``` 147 | 148 | ### Prettifying 149 | 150 | You can toggle prettification of your code by running `M-x 151 | prettify-symbols-mode`. If you'd like to automatically enable this 152 | for all rust files, add the following to your init.el. 153 | 154 | ```elisp 155 | (add-hook 'rust-mode-hook 156 | (lambda () (prettify-symbols-mode))) 157 | ``` 158 | 159 | You can add your own prettifications to `rust-prettify-symbols-alist`. 160 | For example, to display `x.add(y)` as `x∔(y)`, simply add to your init 161 | file `(push '(".add" . ?∔) rust-prettify-symbols-alist)`. 162 | 163 | ### Running / testing / compiling code 164 | 165 | The `rust-run`, `rust-test`, `rust-compile` and `rust-check` functions 166 | shell out to Cargo to run, test, build and check your code. Under the 167 | hood, these use the standard Emacs `compile` function. 168 | 169 | By default these are bound to: 170 | 171 | - C-c C-c C-u `rust-compile` 172 | - C-c C-c C-k `rust-check` 173 | - C-c C-c C-t `rust-test` 174 | - C-c C-c C-r `rust-run` 175 | 176 | To run programs requiring user input use universal argument when invoking 177 | `rust-run` (C-u C-c C-c C-r). 178 | 179 | ### Clippy 180 | 181 | `rust-run-clippy` runs 182 | [Clippy](https://github.com/rust-lang/rust-clippy), a linter. By 183 | default, this is bound to C-c C-c C-l. 184 | 185 | ### Easy insertion of dbg! 186 | 187 | `rust-dbg-wrap-or-unwrap` either wraps or unwraps the current region 188 | in `dbg!`. This can be useful for easily adding debug lines to your 189 | program. 190 | 191 | This is bound to C-c C-d by default. 192 | 193 | ### More commands 194 | 195 | - `rust-toggle-mutability` toggle mut for var defined at current line 196 | 197 | ## tree-sitter 198 | 199 | You can try the new native treesitter mode `rust-ts-mode` with: 200 | 201 | ```elisp 202 | (use-package rust-mode 203 | :init 204 | (setq rust-mode-treesitter-derive t)) 205 | ``` 206 | 207 | In case you want to use treesitter but can't use Emacs 29.1, you can 208 | take a look at 209 | [tree-sitter](https://github.com/emacs-tree-sitter/elisp-tree-sitter). When 210 | the dependencies are installed you can activate the feature with: 211 | 212 | ```elisp 213 | (use-package tree-sitter 214 | :config 215 | (require 'tree-sitter-langs) 216 | (global-tree-sitter-mode) 217 | (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)) 218 | ``` 219 | 220 | ## LSP 221 | 222 | ### eglot 223 | 224 | A lightweight lsp client. 225 | 226 | ```elisp 227 | (add-hook 'rust-mode-hook 'eglot-ensure) 228 | ``` 229 | 230 | ### lsp-mode 231 | 232 | Provides more features and you can enhance the functionality 233 | by using additional packages. You can find more information in the 234 | [lsp-mode wiki](https://emacs-lsp.github.io/lsp-mode/page/installation/#vanilla-emacs). 235 | 236 | ```elisp 237 | (add-hook 'rust-mode-hook #'lsp) 238 | ``` 239 | 240 | ## Auto-completion 241 | 242 | You can either use a lsp client or [racer](https://github.com/racer-rust/racer) 243 | with [emacs-racer](https://github.com/racer-rust/emacs-racer#installation). 244 | 245 | Note that racer and rls are considered deprecated. You should use rust-analyzer 246 | instead. 247 | 248 | ## Other recommended packages 249 | 250 | ### flycheck 251 | 252 | [flycheck](https://github.com/flycheck/flycheck) allows highlighting 253 | compile errors and Clippy lints inline. 254 | 255 | ### cargo.el 256 | 257 | [cargo.el](https://github.com/kwrooijen/cargo.el) provides a minor 258 | mode for integration with Cargo, Rust's package manager. 259 | 260 | ### cargo-mode 261 | 262 | [cargo-mode](https://github.com/ayrat555/cargo-mode) is an Emacs minor 263 | mode which allows to dynamically select a Cargo command. The reasons 264 | behind this package can be found in [the 265 | post](https://www.badykov.com/emacs/2021/05/29/emacs-cargo-mode/). 266 | 267 | ### rustic 268 | 269 | [rustic](https://github.com/emacs-rustic/rustic) is based on rust-mode, 270 | extending it with other features such as integration with LSP and with 271 | flycheck. 272 | 273 | ## Optional features 274 | 275 | The features of the following files can be disabled with `rust-load-optional-libraries`. 276 | 277 | - rust-cargo.el 278 | - rust-compile.el 279 | - rust-playpen.el 280 | - rust-rustfmt.el 281 | 282 | They are disabled by default when you use rustic as it has its own implementations 283 | for those features. 284 | 285 | ## Customization 286 | 287 | `rust-cargo-default-arguments` set additional cargo args used for check,compile,run,test 288 | 289 | ## For package maintainers 290 | 291 | ### Tests 292 | 293 | Run elisp tests: 294 | 295 | ``` bash 296 | make test 297 | ``` 298 | 299 | ## Contributing 300 | 301 | Contributions are very welcome. We are also looking for additional maintainers. 302 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /rust-rustfmt.el: -------------------------------------------------------------------------------- 1 | ;;; rust-rustfmt.el --- Support for rustfmt -*- lexical-binding:t -*- 2 | ;;; Commentary: 3 | 4 | ;; This library implements support for "rustfmt", a tool for 5 | ;; formatting Rust code according to style guidelines. 6 | 7 | ;;; Code: 8 | ;;; Options 9 | 10 | (require 'rust-compile) 11 | (require 'compile) 12 | 13 | (defcustom rust-format-on-save nil 14 | "Format future rust buffers before saving using rustfmt." 15 | :type 'boolean 16 | :safe #'booleanp 17 | :group 'rust-mode) 18 | 19 | (defcustom rust-format-show-buffer t 20 | "Show *rustfmt* buffer if formatting detected problems." 21 | :type 'boolean 22 | :safe #'booleanp 23 | :group 'rust-mode) 24 | 25 | (defcustom rust-format-goto-problem t 26 | "Jump to location of first detected problem when formatting buffer." 27 | :type 'boolean 28 | :safe #'booleanp 29 | :group 'rust-mode) 30 | 31 | (defcustom rust-rustfmt-bin "rustfmt" 32 | "Path to rustfmt executable." 33 | :type 'string 34 | :group 'rust-mode) 35 | 36 | (defcustom rust-rustfmt-switches nil 37 | "Arguments to pass when invoking the `rustfmt' executable. This variable 38 | will override any user configuration (e.g. rustfmt.toml). Recommendation 39 | is to not modify this and rely on declarative configuration instead." 40 | :type '(repeat string) 41 | :group 'rust-mode) 42 | 43 | ;;; _ 44 | 45 | (defconst rust-rustfmt-buffername "*rustfmt*") 46 | 47 | (define-compilation-mode rust-format-mode "rust-format" 48 | "Major mode for Rust compilation output." 49 | 50 | (setq-local compilation-error-regexp-alist-alist nil) 51 | (add-to-list 'compilation-error-regexp-alist-alist 52 | (cons 'rustc-refs rustc-refs-compilation-regexps)) 53 | (add-to-list 'compilation-error-regexp-alist-alist 54 | (cons 'rustc rustc-compilation-regexps)) 55 | (add-to-list 'compilation-error-regexp-alist-alist 56 | (cons 'rustc-colon rustc-colon-compilation-regexps)) 57 | (add-to-list 'compilation-error-regexp-alist-alist 58 | (cons 'cargo cargo-compilation-regexps)) 59 | (add-to-list 'compilation-error-regexp-alist-alist 60 | (cons 'rustc-panics rustc-panics-compilation-regexps)) 61 | 62 | (setq-local compilation-error-regexp-alist nil) 63 | (add-to-list 'compilation-error-regexp-alist 'rustc-refs) 64 | (add-to-list 'compilation-error-regexp-alist 'rustc) 65 | (add-to-list 'compilation-error-regexp-alist 'rustc-colon) 66 | (add-to-list 'compilation-error-regexp-alist 'cargo) 67 | (add-to-list 'compilation-error-regexp-alist 'rustc-panics) 68 | 69 | (add-hook 'next-error-hook #'rustc-scroll-down-after-next-error) 70 | 71 | (if (or compilation-auto-jump-to-first-error 72 | (eq compilation-scroll-output 'first-error)) 73 | (set (make-local-variable 'compilation-auto-jump-to-next) t))) 74 | 75 | (defun rust--format-call (buf) 76 | "Format BUF using rustfmt." 77 | (let ((path exec-path)) 78 | (with-current-buffer (get-buffer-create rust-rustfmt-buffername) 79 | (setq-local exec-path path) 80 | (view-mode +1) 81 | (let ((inhibit-read-only t)) 82 | (erase-buffer) 83 | (insert-buffer-substring buf) 84 | (let* ((tmpf (make-temp-file "rustfmt")) 85 | (ret (apply #'call-process-region 86 | (point-min) 87 | (point-max) 88 | rust-rustfmt-bin 89 | t 90 | `(t ,tmpf) 91 | nil 92 | rust-rustfmt-switches))) 93 | (unwind-protect 94 | (cond 95 | ((zerop ret) 96 | (if (not (string= (buffer-string) 97 | (with-current-buffer buf (buffer-string)))) 98 | ;; replace-buffer-contents was in emacs 26.1, but it 99 | ;; was broken for non-ASCII strings, so we need 26.2. 100 | (if (and (fboundp 'replace-buffer-contents) 101 | (version<= "26.2" emacs-version)) 102 | (with-current-buffer buf 103 | (replace-buffer-contents rust-rustfmt-buffername)) 104 | (copy-to-buffer buf (point-min) (point-max)))) 105 | (let ((win (get-buffer-window rust-rustfmt-buffername))) 106 | (if win 107 | (quit-window t win) 108 | (kill-buffer rust-rustfmt-buffername)))) 109 | ((= ret 1) 110 | (erase-buffer) 111 | (insert-file-contents tmpf) 112 | 113 | (rust-format-mode) ;; render compilation errors in compilation-mode 114 | (setq-local compile-command (format "%s %s" rust-rustfmt-bin (buffer-file-name buf))) 115 | 116 | (rust--format-fix-rustfmt-buffer (buffer-name buf)) 117 | (error "Rustfmt failed because of parsing errors, see %s buffer for details" 118 | rust-rustfmt-buffername)) 119 | ((= ret 3) 120 | (if (not (string= (buffer-string) 121 | (with-current-buffer buf (buffer-string)))) 122 | (copy-to-buffer buf (point-min) (point-max))) 123 | (erase-buffer) 124 | (insert-file-contents tmpf) 125 | (rust--format-fix-rustfmt-buffer (buffer-name buf)) 126 | (error "Rustfmt could not format some lines, see %s buffer for details" 127 | rust-rustfmt-buffername)) 128 | (t 129 | (erase-buffer) 130 | (insert-file-contents tmpf) 131 | (rust--format-fix-rustfmt-buffer (buffer-name buf)) 132 | (error "Rustfmt failed, see %s buffer for details" 133 | rust-rustfmt-buffername))) 134 | (delete-file tmpf))))))) 135 | 136 | ;; Since we run rustfmt through stdin we get markers in the 137 | ;; output. This replaces them with the buffer name instead. 138 | (defun rust--format-fix-rustfmt-buffer (buffer-name) 139 | (save-match-data 140 | (with-current-buffer (get-buffer rust-rustfmt-buffername) 141 | (let ((inhibit-read-only t) 142 | (fixed (format "--> %s:" buffer-name))) 143 | (goto-char (point-min)) 144 | (while (re-search-forward "--> \\(?:\\|stdin\\):" nil t) 145 | (replace-match fixed)))))) 146 | 147 | ;; If rust-mode has been configured to navigate to source of the error 148 | ;; or display it, do so -- and return true. Otherwise return nil to 149 | ;; indicate nothing was done. 150 | (defun rust--format-error-handler () 151 | (let ((ok nil)) 152 | (when rust-format-show-buffer 153 | (display-buffer (get-buffer rust-rustfmt-buffername)) 154 | (setq ok t)) 155 | (when rust-format-goto-problem 156 | (rust-goto-format-problem) 157 | (setq ok t)) 158 | ok)) 159 | 160 | (defun rust-goto-format-problem () 161 | "Jumps to problem reported by rustfmt, if any. 162 | 163 | In case of multiple problems cycles through them. Displays the 164 | rustfmt complain in the echo area." 165 | (interactive) 166 | ;; This uses position in *rustfmt* buffer to know which is the next 167 | ;; error to jump to, and source: line in the buffer to figure which 168 | ;; buffer it is from. 169 | (let ((rustfmt (get-buffer rust-rustfmt-buffername))) 170 | (if (not rustfmt) 171 | (message "No %s, no problems." rust-rustfmt-buffername) 172 | (let ((target-buffer (with-current-buffer rustfmt 173 | (save-excursion 174 | (goto-char (point-min)) 175 | (when (re-search-forward "--> \\([^:]+\\):" nil t) 176 | (match-string 1))))) 177 | (target-point (with-current-buffer rustfmt 178 | ;; No save-excursion, this is how we cycle through! 179 | (let ((regex "--> [^:]+:\\([0-9]+\\):\\([0-9]+\\)")) 180 | (when (or (re-search-forward regex nil t) 181 | (progn (goto-char (point-min)) 182 | (re-search-forward regex nil t))) 183 | (cons (string-to-number (match-string 1)) 184 | (string-to-number (match-string 2))))))) 185 | (target-problem (with-current-buffer rustfmt 186 | (save-excursion 187 | (when (re-search-backward "^error:.+\n" nil t) 188 | (forward-char (length "error: ")) 189 | (let ((p0 (point))) 190 | (if (re-search-forward "\nerror:.+\n" nil t) 191 | (buffer-substring p0 (point)) 192 | (buffer-substring p0 (point-max))))))))) 193 | (when (and target-buffer (get-buffer target-buffer) target-point) 194 | (switch-to-buffer target-buffer) 195 | (goto-char (point-min)) 196 | (forward-line (1- (car target-point))) 197 | (forward-char (1- (cdr target-point)))) 198 | (unless rust-format-show-buffer 199 | (message target-problem)))))) 200 | 201 | (defconst rust--format-word "\ 202 | \\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|\ 203 | match\\|struct\\|union\\|unsafe\\|while\\)\\b") 204 | (defconst rust--format-line "\\(\n\\)") 205 | 206 | ;; Counts number of matches of regex beginning up to max-beginning, 207 | ;; leaving the point at the beginning of the last match. 208 | (defun rust--format-count (regex max-beginning) 209 | (let ((count 0) 210 | save-point 211 | beginning) 212 | (while (and (< (point) max-beginning) 213 | (re-search-forward regex max-beginning t)) 214 | (setq count (1+ count)) 215 | (setq beginning (match-beginning 1))) 216 | ;; try one more in case max-beginning lies in the middle of a match 217 | (setq save-point (point)) 218 | (when (re-search-forward regex nil t) 219 | (let ((try-beginning (match-beginning 1))) 220 | (if (> try-beginning max-beginning) 221 | (goto-char save-point) 222 | (setq count (1+ count)) 223 | (setq beginning try-beginning)))) 224 | (when beginning (goto-char beginning)) 225 | count)) 226 | 227 | ;; Gets list describing pos or (point). 228 | ;; The list contains: 229 | ;; 1. the number of matches of rust--format-word, 230 | ;; 2. the number of matches of rust--format-line after that, 231 | ;; 3. the number of columns after that. 232 | (defun rust--format-get-loc (buffer &optional pos) 233 | (with-current-buffer buffer 234 | (save-excursion 235 | (let ((pos (or pos (point))) 236 | words lines columns) 237 | (goto-char (point-min)) 238 | (setq words (rust--format-count rust--format-word pos)) 239 | (setq lines (rust--format-count rust--format-line pos)) 240 | (if (> lines 0) 241 | (if (= (point) pos) 242 | (setq columns -1) 243 | (forward-char 1) 244 | (goto-char pos) 245 | (setq columns (current-column))) 246 | (let ((initial-column (current-column))) 247 | (goto-char pos) 248 | (setq columns (- (current-column) initial-column)))) 249 | (list words lines columns))))) 250 | 251 | ;; Moves the point forward by count matches of regex up to max-pos, 252 | ;; and returns new max-pos making sure final position does not include another match. 253 | (defun rust--format-forward (regex count max-pos) 254 | (when (< (point) max-pos) 255 | (let ((beginning (point))) 256 | (while (> count 0) 257 | (setq count (1- count)) 258 | (re-search-forward regex nil t) 259 | (setq beginning (match-beginning 1))) 260 | (when (re-search-forward regex nil t) 261 | (setq max-pos (min max-pos (match-beginning 1)))) 262 | (goto-char beginning))) 263 | max-pos) 264 | 265 | ;; Gets the position from a location list obtained using rust--format-get-loc. 266 | (defun rust--format-get-pos (buffer loc) 267 | (with-current-buffer buffer 268 | (save-excursion 269 | (goto-char (point-min)) 270 | (let ((max-pos (point-max)) 271 | (words (pop loc)) 272 | (lines (pop loc)) 273 | (columns (pop loc))) 274 | (setq max-pos (rust--format-forward rust--format-word words max-pos)) 275 | (setq max-pos (rust--format-forward rust--format-line lines max-pos)) 276 | (when (> lines 0) (forward-char)) 277 | (let ((initial-column (current-column)) 278 | (save-point (point))) 279 | (move-end-of-line nil) 280 | (when (> (current-column) (+ initial-column columns)) 281 | (goto-char save-point) 282 | (forward-char columns))) 283 | (min (point) max-pos))))) 284 | 285 | (defun rust-format-diff-buffer () 286 | "Show diff to current buffer from rustfmt. 287 | 288 | Return the created process." 289 | (interactive) 290 | (unless (executable-find rust-rustfmt-bin) 291 | (error "Could not locate executable %S" rust-rustfmt-bin)) 292 | (let* ((buffer 293 | (with-current-buffer 294 | (get-buffer-create "*rustfmt-diff*") 295 | (let ((inhibit-read-only t)) 296 | (erase-buffer)) 297 | (current-buffer))) 298 | (proc 299 | (apply #'start-process 300 | "rustfmt-diff" 301 | buffer 302 | rust-rustfmt-bin 303 | "--check" 304 | (cons (buffer-file-name) 305 | rust-rustfmt-switches)))) 306 | (set-process-sentinel proc #'rust-format-diff-buffer-sentinel) 307 | proc)) 308 | 309 | (defun rust-format-diff-buffer-sentinel (process _e) 310 | (when (eq 'exit (process-status process)) 311 | (if (> (process-exit-status process) 0) 312 | (with-current-buffer "*rustfmt-diff*" 313 | (let ((inhibit-read-only t)) 314 | (diff-mode)) 315 | (pop-to-buffer (current-buffer))) 316 | (message "rustfmt check passed.")))) 317 | 318 | (defun rust--format-buffer-using-replace-buffer-contents () 319 | (condition-case err 320 | (progn 321 | (rust--format-call (current-buffer)) 322 | (message "Formatted buffer with rustfmt.")) 323 | (error 324 | (or (rust--format-error-handler) 325 | (signal (car err) (cdr err)))))) 326 | 327 | (defun rust--format-buffer-saving-position-manually () 328 | (let* ((current (current-buffer)) 329 | (base (or (buffer-base-buffer current) current)) 330 | buffer-loc 331 | window-loc) 332 | (dolist (buffer (buffer-list)) 333 | (when (or (eq buffer base) 334 | (eq (buffer-base-buffer buffer) base)) 335 | (push (list buffer 336 | (rust--format-get-loc buffer nil)) 337 | buffer-loc))) 338 | (dolist (frame (frame-list)) 339 | (dolist (window (window-list frame)) 340 | (let ((buffer (window-buffer window))) 341 | (when (or (eq buffer base) 342 | (eq (buffer-base-buffer buffer) base)) 343 | (let ((start (window-start window)) 344 | (point (window-point window))) 345 | (push (list window 346 | (rust--format-get-loc buffer start) 347 | (rust--format-get-loc buffer point)) 348 | window-loc)))))) 349 | (condition-case err 350 | (unwind-protect 351 | ;; save and restore window start position 352 | ;; after reformatting 353 | ;; to avoid the disturbing scrolling 354 | (let ((w-start (window-start))) 355 | (rust--format-call (current-buffer)) 356 | (set-window-start (selected-window) w-start) 357 | (message "Formatted buffer with rustfmt.")) 358 | (dolist (loc buffer-loc) 359 | (let* ((buffer (pop loc)) 360 | (pos (rust--format-get-pos buffer (pop loc)))) 361 | (with-current-buffer buffer 362 | (goto-char pos)))) 363 | (dolist (loc window-loc) 364 | (let* ((window (pop loc)) 365 | (buffer (window-buffer window)) 366 | (start (rust--format-get-pos buffer (pop loc))) 367 | (pos (rust--format-get-pos buffer (pop loc)))) 368 | (unless (eq buffer current) 369 | (set-window-start window start)) 370 | (set-window-point window pos)))) 371 | (error 372 | (or (rust--format-error-handler) 373 | (signal (car err) (cdr err))))))) 374 | 375 | (defun rust-format-buffer () 376 | "Format the current buffer using rustfmt." 377 | (interactive) 378 | (unless (executable-find rust-rustfmt-bin) 379 | (error "Could not locate executable \"%s\"" rust-rustfmt-bin)) 380 | ;; If emacs version >= 26.2, we can use replace-buffer-contents to 381 | ;; preserve location and markers in buffer, otherwise we can try to 382 | ;; save locations as best we can, though we still lose markers. 383 | (save-excursion 384 | (if (version<= "26.2" emacs-version) 385 | (rust--format-buffer-using-replace-buffer-contents) 386 | (rust--format-buffer-saving-position-manually)))) 387 | 388 | (defun rust-enable-format-on-save () 389 | "Enable formatting using rustfmt when saving buffer." 390 | (interactive) 391 | (setq-local rust-format-on-save t)) 392 | 393 | (defun rust-disable-format-on-save () 394 | "Disable formatting using rustfmt when saving buffer." 395 | (interactive) 396 | (setq-local rust-format-on-save nil)) 397 | 398 | ;;; Hooks 399 | 400 | (defun rust-before-save-method () 401 | (when rust-format-on-save 402 | (condition-case e 403 | (rust-format-buffer) 404 | (message (format "rust-before-save-hook: %S %S" 405 | (car e) 406 | (cdr e)))))) 407 | 408 | (defun rust-after-save-method () 409 | (when rust-format-on-save 410 | (if (not (executable-find rust-rustfmt-bin)) 411 | (error "Could not locate executable \"%s\"" rust-rustfmt-bin) 412 | (when (get-buffer rust-rustfmt-buffername) 413 | ;; KLDUGE: re-run the error handlers -- otherwise message area 414 | ;; would show "Wrote ..." instead of the error description. 415 | (or (rust--format-error-handler) 416 | (message "rustfmt detected problems, see %s for more." 417 | rust-rustfmt-buffername)))))) 418 | 419 | ;;; _ 420 | (provide 'rust-rustfmt) 421 | ;;; rust-rustfmt.el ends here 422 | -------------------------------------------------------------------------------- /rust-prog-mode.el: -------------------------------------------------------------------------------- 1 | ;;; rust-prog-mode.el --- old rust-mode without treesitter -*-lexical-binding: t-*- 2 | ;;; Commentary: 3 | 4 | ;; rust-mode code deriving from prog-mode instead of rust-ts-mode 5 | 6 | ;;; Code: 7 | 8 | (require 'rust-mode) 9 | 10 | (defvar electric-pair-inhibit-predicate) 11 | (defvar electric-pair-skip-self) 12 | (defvar electric-indent-chars) 13 | 14 | (defvar rust-prettify-symbols-alist 15 | '(("&&" . ?∧) ("||" . ?∨) 16 | ("<=" . ?≤) (">=" . ?≥) ("!=" . ?≠) 17 | ("INFINITY" . ?∞) ("->" . ?→) ("=>" . ?⇒)) 18 | "Alist of symbol prettifications used for `prettify-symbols-alist'.") 19 | 20 | (defcustom rust-indent-offset 4 21 | "Indent Rust code by this number of spaces." 22 | :type 'integer 23 | :group 'rust-mode 24 | :safe #'integerp) 25 | 26 | (defcustom rust-indent-method-chain nil 27 | "Indent Rust method chains, aligned by the `.' operators." 28 | :type 'boolean 29 | :group 'rust-mode 30 | :safe #'booleanp) 31 | 32 | (defcustom rust-indent-where-clause nil 33 | "Indent lines starting with the `where' keyword following a function or trait. 34 | When nil, `where' will be aligned with `fn' or `trait'." 35 | :type 'boolean 36 | :group 'rust-mode 37 | :safe #'booleanp) 38 | 39 | (defcustom rust-match-angle-brackets t 40 | "Whether to enable angle bracket (`<' and `>') matching where appropriate." 41 | :type 'boolean 42 | :safe #'booleanp 43 | :group 'rust-mode) 44 | 45 | (defcustom rust-indent-return-type-to-arguments t 46 | "Indent a line starting with the `->' (RArrow) following a function, aligning 47 | to the function arguments. When nil, `->' will be indented one level." 48 | :type 'boolean 49 | :group 'rust-mode 50 | :safe #'booleanp) 51 | 52 | (defface rust-unsafe 53 | '((t :inherit font-lock-warning-face)) 54 | "Face for the `unsafe' keyword." 55 | :group 'rust-mode) 56 | 57 | (defface rust-question-mark 58 | '((t :weight bold :inherit font-lock-builtin-face)) 59 | "Face for the question mark operator." 60 | :group 'rust-mode) 61 | 62 | (defface rust-ampersand-face 63 | '((t :inherit default)) 64 | "Face for the ampersand reference mark." 65 | :group 'rust-mode) 66 | 67 | (defface rust-builtin-formatting-macro 68 | '((t :inherit font-lock-builtin-face)) 69 | "Face for builtin formatting macros (print! &c.)." 70 | :group 'rust-mode) 71 | 72 | (defface rust-string-interpolation 73 | '((t :slant italic :inherit font-lock-string-face)) 74 | "Face for interpolating braces in builtin formatting macro strings." 75 | :group 'rust-mode) 76 | 77 | ;;; Syntax 78 | 79 | (defun rust-re-word (inner) (concat "\\<" inner "\\>")) 80 | (defun rust-re-grab (inner) (concat "\\(" inner "\\)")) 81 | (defun rust-re-shy (inner) (concat "\\(?:" inner "\\)")) 82 | 83 | (defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") 84 | (defconst rust-re-lc-ident "[[:lower:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") 85 | (defconst rust-re-uc-ident "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*") 86 | (defvar rust-re-vis 87 | ;; pub | pub ( crate ) | pub ( self ) | pub ( super ) | pub ( in SimplePath ) 88 | (concat 89 | "pub" 90 | (rust-re-shy 91 | (concat 92 | "[[:space:]]*([[:space:]]*" 93 | (rust-re-shy 94 | (concat "crate" "\\|" 95 | "\\(?:s\\(?:elf\\|uper\\)\\)" "\\|" 96 | ;; in SimplePath 97 | (rust-re-shy 98 | (concat 99 | "in[[:space:]]+" 100 | rust-re-ident 101 | (rust-re-shy (concat "::" rust-re-ident)) "*")))) 102 | "[[:space:]]*)")) 103 | "?")) 104 | (defconst rust-re-unsafe "unsafe") 105 | (defconst rust-re-extern "extern") 106 | (defconst rust-re-async-or-const "async\\|const") 107 | (defconst rust-re-generic 108 | (concat "<[[:space:]]*'" rust-re-ident "[[:space:]]*>")) 109 | (defconst rust-re-union 110 | (rx-to-string 111 | `(seq 112 | (or space line-start) 113 | (group symbol-start "union" symbol-end) 114 | (+ space) (regexp ,rust-re-ident)))) 115 | 116 | (defun rust-re-item-def (itype) 117 | (concat (rust-re-word itype) 118 | (rust-re-shy rust-re-generic) "?" 119 | "[[:space:]]+" (rust-re-grab rust-re-ident))) 120 | 121 | ;; TODO some of this does only make sense for `fn' (unsafe, extern...) 122 | ;; and not other items 123 | (defun rust-re-item-def-imenu (itype) 124 | (concat "^[[:space:]]*" 125 | (rust-re-shy (concat rust-re-vis "[[:space:]]+")) "?" 126 | (rust-re-shy (concat (rust-re-word "default") "[[:space:]]+")) "?" 127 | (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[:space:]]+")) "?" 128 | (rust-re-shy (concat (rust-re-word rust-re-unsafe) "[[:space:]]+")) "?" 129 | (rust-re-shy (concat (rust-re-word rust-re-extern) "[[:space:]]+" 130 | (rust-re-shy "\"[^\"]+\"[[:space:]]+") "?")) "?" 131 | (rust-re-item-def itype))) 132 | 133 | (defvar rust-imenu-generic-expression 134 | (append (mapcar #'(lambda (x) 135 | (list (capitalize x) (rust-re-item-def-imenu x) 1)) 136 | '("enum" "struct" "union" "type" "mod" "fn" "trait" "impl")) 137 | `(("Macro" ,(rust-re-item-def-imenu "macro_rules!") 1))) 138 | "Value for `imenu-generic-expression' in Rust mode. 139 | 140 | Create a hierarchical index of the item definitions in a Rust file. 141 | 142 | Imenu will show all the enums, structs, etc. in their own subheading. 143 | Use idomenu (imenu with `ido-mode') for best mileage.") 144 | 145 | ;;; Prettify 146 | 147 | (defun rust--prettify-symbols-compose-p (start end match) 148 | "Return true iff the symbol MATCH should be composed. 149 | See `prettify-symbols-compose-predicate'." 150 | (and (fboundp 'prettify-symbols-default-compose-p) 151 | (prettify-symbols-default-compose-p start end match) 152 | ;; Make sure || is not a closure with 0 arguments and && is not 153 | ;; a double reference. 154 | (pcase match 155 | ("||" (not (save-excursion 156 | (goto-char start) 157 | (looking-back "\\(?:\\") 173 | "Start of a Rust item.") 174 | 175 | (defconst rust-re-type-or-constructor 176 | (rx symbol-start 177 | (group upper (0+ (any word nonascii digit "_"))) 178 | symbol-end)) 179 | 180 | (defconst rust-keywords 181 | '("as" "async" "await" 182 | "box" "break" 183 | "const" "continue" "crate" 184 | "do" "dyn" 185 | "else" "enum" "extern" "existential" 186 | "false" "fn" "for" 187 | "if" "impl" "in" 188 | "let" "loop" 189 | "match" "mod" "move" "mut" 190 | "priv" "pub" 191 | "ref" "return" 192 | "self" "static" "struct" "super" 193 | "true" "trait" "type" "try" 194 | "use" 195 | "virtual" 196 | "where" "while" 197 | "yield") 198 | "Font-locking definitions and helpers.") 199 | 200 | (defconst rust-special-types 201 | '("u8" "i8" 202 | "u16" "i16" 203 | "u32" "i32" 204 | "u64" "i64" 205 | "u128" "i128" 206 | 207 | "f32" "f64" 208 | "isize" "usize" 209 | "bool" 210 | "str" "char")) 211 | 212 | (defconst rust-expression-introducers 213 | '("if" "while" "match" "return" "box" "in") 214 | "List of Rust keywords that are always followed by expressions.") 215 | 216 | (defconst rust-number-with-type 217 | (eval-when-compile 218 | (concat 219 | "\\_<\\(?:0[box]?\\|[1-9]\\)[[:digit:]a-fA-F_.]*\\(?:[eE][+-]?[[:digit:]_]\\)?" 220 | (regexp-opt '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64" 221 | "u128" "i128" "usize" "isize" "f32" "f64") 222 | t) 223 | "\\_>")) 224 | "Regular expression matching a number with a type suffix.") 225 | 226 | (defvar rust-builtin-formatting-macros 227 | '("eprint" 228 | "eprintln" 229 | "format" 230 | "print" 231 | "println") 232 | "List of builtin Rust macros for string formatting. 233 | This is used by `rust-font-lock-keywords'. 234 | \(`write!' is handled separately).") 235 | 236 | (defvar rust-formatting-macro-opening-re 237 | "[[:space:]\n]*[({[][[:space:]\n]*" 238 | "Regular expression to match the opening delimiter of a Rust formatting macro.") 239 | 240 | (defvar rust-start-of-string-re 241 | "\\(?:r#*\\)?\"" 242 | "Regular expression to match the start of a Rust raw string.") 243 | 244 | (defun rust-path-font-lock-matcher (re-ident) 245 | "Match occurrences of RE-IDENT followed by a double-colon. 246 | Examples include to match names like \"foo::\" or \"Foo::\". 247 | Does not match type annotations of the form \"foo::<\"." 248 | `(lambda (limit) 249 | (catch 'rust-path-font-lock-matcher 250 | (while t 251 | (let* ((symbol-then-colons (rx-to-string '(seq (group (regexp ,re-ident)) "::"))) 252 | (match (re-search-forward symbol-then-colons limit t))) 253 | (cond 254 | ;; If we didn't find a match, there are no more occurrences 255 | ;; of foo::, so return. 256 | ((null match) (throw 'rust-path-font-lock-matcher nil)) 257 | ;; If this isn't a type annotation foo::<, we've found a 258 | ;; match, so a return it! 259 | ((not (looking-at (rx (0+ space) "<"))) 260 | (throw 'rust-path-font-lock-matcher match)))))))) 261 | 262 | (defvar rust-font-lock-keywords 263 | (append 264 | `( 265 | ;; Keywords proper 266 | (,(regexp-opt rust-keywords 'symbols) . font-lock-keyword-face) 267 | 268 | ;; Contextual keywords 269 | ("\\_<\\(default\\)[[:space:]]+fn\\_>" 1 font-lock-keyword-face) 270 | (,rust-re-union 1 font-lock-keyword-face) 271 | 272 | ;; Special types 273 | (,(regexp-opt rust-special-types 'symbols) . font-lock-type-face) 274 | 275 | ;; The unsafe keyword 276 | ("\\_" . 'rust-unsafe) 277 | 278 | ;; Attributes like `#[bar(baz)]` or `#![bar(baz)]` or `#[bar = "baz"]` 279 | (,(rust-re-grab (concat "#\\!?\\[" rust-re-ident "[^]]*\\]")) 280 | 1 font-lock-preprocessor-face keep) 281 | 282 | ;; Builtin formatting macros 283 | (,(concat (rust-re-grab 284 | (concat (rust-re-word (regexp-opt rust-builtin-formatting-macros)) 285 | "!")) 286 | rust-formatting-macro-opening-re 287 | "\\(?:" rust-start-of-string-re "\\)?") 288 | (1 'rust-builtin-formatting-macro) 289 | (rust-string-interpolation-matcher 290 | (rust-end-of-string) 291 | nil 292 | (0 'rust-string-interpolation t nil))) 293 | 294 | ;; write! macro 295 | (,(concat (rust-re-grab (concat (rust-re-word "write\\(ln\\)?") "!")) 296 | rust-formatting-macro-opening-re 297 | "[[:space:]]*[^\"]+,[[:space:]]*" 298 | rust-start-of-string-re) 299 | (1 'rust-builtin-formatting-macro) 300 | (rust-string-interpolation-matcher 301 | (rust-end-of-string) 302 | nil 303 | (0 'rust-string-interpolation t nil))) 304 | 305 | ;; Syntax extension invocations like `foo!`, highlight including the ! 306 | (,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:][]") 307 | 1 font-lock-preprocessor-face) 308 | 309 | ;; Field names like `foo:`, highlight excluding the : 310 | (,(concat (rust-re-grab rust-re-ident) "[[:space:]]*:[^:]") 311 | 1 font-lock-variable-name-face) 312 | 313 | ;; CamelCase Means Type Or Constructor 314 | (,rust-re-type-or-constructor 1 font-lock-type-face) 315 | 316 | ;; Type-inferred binding 317 | (,(concat "\\_<\\(?:let\\s-+ref\\|let\\|ref\\|for\\)\\s-+\\(?:mut\\s-+\\)?" 318 | (rust-re-grab rust-re-ident) 319 | "\\_>") 320 | 1 font-lock-variable-name-face) 321 | 322 | ;; Type names like `Foo::`, highlight excluding the :: 323 | (,(rust-path-font-lock-matcher rust-re-uc-ident) 1 font-lock-type-face) 324 | 325 | ;; Module names like `foo::`, highlight excluding the :: 326 | (,(rust-path-font-lock-matcher rust-re-lc-ident) 1 font-lock-constant-face) 327 | 328 | ;; Lifetimes like `'foo` 329 | (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face) 330 | 331 | ;; Question mark operator 332 | ("\\?" . 'rust-question-mark) 333 | ("\\(&+\\)\\(?:'\\(?:\\<\\|_\\)\\|\\<\\|[[({:*_|]\\)" 334 | 1 'rust-ampersand-face) 335 | ;; Numbers with type suffix 336 | (,rust-number-with-type 1 font-lock-type-face) 337 | ) 338 | 339 | ;; Ensure we highlight `Foo` in `struct Foo` as a type. 340 | (mapcar #'(lambda (x) 341 | (list (rust-re-item-def (car x)) 342 | 1 (cdr x))) 343 | '(("enum" . font-lock-type-face) 344 | ("struct" . font-lock-type-face) 345 | ("union" . font-lock-type-face) 346 | ("type" . font-lock-type-face) 347 | ("mod" . font-lock-constant-face) 348 | ("use" . font-lock-constant-face) 349 | ("fn" . font-lock-function-name-face))))) 350 | 351 | (defun rust-end-of-string () 352 | "Skip to the end of the current string." 353 | (save-excursion 354 | (skip-syntax-forward "^\"|") 355 | (skip-syntax-forward "\"|") 356 | (point))) 357 | 358 | (defun rust-looking-back-str (str) 359 | "Return non-nil if there's a match on the text before point and STR. 360 | Like `looking-back' but for fixed strings rather than regexps (so 361 | that it's not so slow)." 362 | (let ((len (length str))) 363 | (and (> (point) len) 364 | (equal str (buffer-substring-no-properties (- (point) len) (point)))))) 365 | 366 | (defun rust-looking-back-symbols (symbols) 367 | "Return non-nil if the point is after a member of SYMBOLS. 368 | SYMBOLS is a list of strings that represent the respective 369 | symbols." 370 | (save-excursion 371 | (let* ((pt-orig (point)) 372 | (beg-of-symbol (progn (forward-thing 'symbol -1) (point))) 373 | (end-of-symbol (progn (forward-thing 'symbol 1) (point)))) 374 | (and 375 | (= end-of-symbol pt-orig) 376 | (member (buffer-substring-no-properties beg-of-symbol pt-orig) 377 | symbols))))) 378 | 379 | (defun rust-looking-back-ident () 380 | "Non-nil if we are looking backwards at a valid rust identifier. 381 | If we are, regexp match 0 is the identifier." 382 | (let ((outer-point (point))) 383 | (save-excursion 384 | (forward-thing 'symbol -1) 385 | (and (looking-at rust-re-ident) 386 | (eq (match-end 0) outer-point))))) 387 | 388 | (defun rust-looking-back-macro () 389 | "Non-nil if looking back at a potential macro name followed by a \"!\". 390 | If we are, regexp match 0 is the macro name." 391 | (save-excursion 392 | ;; Look past whitespace and line breaks. 393 | ;; > is okay because we only use it for \n and \r, not "*/" 394 | (skip-syntax-backward "->") 395 | (when (eq (char-before) ?!) 396 | (forward-char -1) 397 | (skip-syntax-backward "->") 398 | (when (rust-looking-back-ident) 399 | (let ((ident (match-string 0))) 400 | (not (member ident rust-expression-introducers))))))) 401 | 402 | (defun rust-looking-back-macro-rules () 403 | "Non-nil if looking back at \"macro_rules IDENT !\"." 404 | (save-excursion 405 | (skip-syntax-backward "->") 406 | (let ((outer-point (point))) 407 | (forward-thing 'symbol -2) 408 | (and (looking-at (concat "macro_rules\\s-*!\\s-*" rust-re-ident)) 409 | (eq (match-end 0) outer-point))))) 410 | 411 | ;;; Syntax definitions and helpers 412 | 413 | (defun rust-paren-level () (nth 0 (syntax-ppss))) 414 | (defun rust-in-str () (nth 3 (syntax-ppss))) 415 | (defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss))) 416 | (defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss)))) 417 | 418 | (defun rust-rewind-irrelevant () 419 | (let ((continue t)) 420 | (while continue 421 | (let ((starting (point))) 422 | (skip-chars-backward "[:space:]\n") 423 | (when (rust-looking-back-str "*/") 424 | (backward-char)) 425 | (when (rust-in-str-or-cmnt) 426 | (rust-rewind-past-str-cmnt)) 427 | ;; Rewind until the point no longer moves 428 | (setq continue (/= starting (point))))))) 429 | 430 | (defun rust-in-macro () 431 | "Return non-nil when point is within the scope of a macro. 432 | If we are, return the position of the opening bracket of the macro's arguments." 433 | (let ((ppss (syntax-ppss))) 434 | ;; If we're in a string or comment, we're definitely not on a token a macro 435 | ;; will see. 436 | (when (not (or (nth 3 ppss) (nth 4 ppss))) 437 | ;; Walk outward to enclosing parens, looking for one preceded by "ident !" 438 | ;; or "macro_rules! ident". 439 | (let (result 440 | (enclosing (reverse (nth 9 ppss)))) 441 | (save-excursion 442 | (while enclosing 443 | (goto-char (car enclosing)) 444 | (if (or (rust-looking-back-macro) 445 | (rust-looking-back-macro-rules)) 446 | (setq result (point) enclosing nil) 447 | (setq enclosing (cdr enclosing))))) 448 | result)))) 449 | 450 | (defun rust-looking-at-where () 451 | "Return T when looking at the \"where\" keyword." 452 | (and (looking-at-p "\\bwhere\\b") 453 | (not (rust-in-str-or-cmnt)))) 454 | 455 | (defun rust-rewind-to-where (&optional limit) 456 | "Rewind the point to the closest occurrence of the \"where\" keyword. 457 | Return T iff a where-clause was found. Does not rewind past 458 | LIMIT when passed, otherwise only stops at the beginning of the 459 | buffer." 460 | (when (re-search-backward "\\bwhere\\b" limit t) 461 | (if (rust-in-str-or-cmnt) 462 | (rust-rewind-to-where limit) 463 | t))) 464 | 465 | (defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]") 466 | 467 | (defconst rust-re-special-types (regexp-opt rust-special-types 'symbols)) 468 | 469 | (defun rust-align-to-expr-after-brace () 470 | (save-excursion 471 | (forward-char) 472 | ;; We don't want to indent out to the open bracket if the 473 | ;; open bracket ends the line 474 | (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) 475 | (when (looking-at "[[:space:]]") 476 | (forward-word 1) 477 | (backward-word 1)) 478 | (current-column)))) 479 | 480 | (defun rust-rewind-to-beginning-of-current-level-expr () 481 | (let ((current-level (rust-paren-level))) 482 | (back-to-indentation) 483 | (when (looking-at "->") 484 | (rust-rewind-irrelevant) 485 | (back-to-indentation)) 486 | (while (> (rust-paren-level) current-level) 487 | (backward-up-list) 488 | (back-to-indentation)) 489 | ;; When we're in the where clause, skip over it. First find out the start 490 | ;; of the function and its paren level. 491 | (let ((function-start nil) (function-level nil)) 492 | (save-excursion 493 | (rust-beginning-of-defun) 494 | (back-to-indentation) 495 | ;; Avoid using multiple-value-bind 496 | (setq function-start (point) 497 | function-level (rust-paren-level))) 498 | ;; On a where clause 499 | (when (or (rust-looking-at-where) 500 | ;; or in one of the following lines, e.g. 501 | ;; where A: Eq 502 | ;; B: Hash <- on this line 503 | (and (save-excursion 504 | (rust-rewind-to-where function-start)) 505 | (= current-level function-level))) 506 | (goto-char function-start))))) 507 | 508 | (defun rust-align-to-method-chain () 509 | (save-excursion 510 | ;; for method-chain alignment to apply, we must be looking at 511 | ;; another method call or field access or something like 512 | ;; that. This avoids rather "eager" jumps in situations like: 513 | ;; 514 | ;; { 515 | ;; something.foo() 516 | ;; 517 | ;; 518 | ;; Without this check, we would wind up with the cursor under the 519 | ;; `.`. In an older version, I had the inverse of the current 520 | ;; check, where we checked for situations that should NOT indent, 521 | ;; vs checking for the one situation where we SHOULD. It should be 522 | ;; clear that this is more robust, but also I find it mildly less 523 | ;; annoying to have to press tab again to align to a method chain 524 | ;; than to have an over-eager indent in all other cases which must 525 | ;; be undone via tab. 526 | 527 | (when (looking-at (concat "\s*\." rust-re-ident)) 528 | (forward-line -1) 529 | (end-of-line) 530 | ;; Keep going up (looking for a line that could contain a method chain) 531 | ;; while we're in a comment or on a blank line. Stop when the paren 532 | ;; level changes. 533 | (let ((level (rust-paren-level))) 534 | (while (and (or (rust-in-str-or-cmnt) 535 | ;; Only whitespace (or nothing) from the beginning to 536 | ;; the end of the line. 537 | (looking-back "^\s*" (line-beginning-position))) 538 | (= (rust-paren-level) level)) 539 | (forward-line -1) 540 | (end-of-line))) 541 | 542 | (let 543 | ;; skip-dot-identifier is used to position the point at the 544 | ;; `.` when looking at something like 545 | ;; 546 | ;; foo.bar 547 | ;; ^ ^ 548 | ;; | | 549 | ;; | position of point 550 | ;; returned offset 551 | ;; 552 | ((skip-dot-identifier 553 | (lambda () 554 | (when (and (rust-looking-back-ident) 555 | (save-excursion 556 | (forward-thing 'symbol -1) 557 | (= ?. (char-before)))) 558 | (forward-thing 'symbol -1) 559 | (backward-char) 560 | (- (current-column) rust-indent-offset))))) 561 | (cond 562 | ;; foo.bar(...) 563 | ((looking-back "[)?]" (1- (point))) 564 | (backward-list 1) 565 | (funcall skip-dot-identifier)) 566 | 567 | ;; foo.bar 568 | (t (funcall skip-dot-identifier))))))) 569 | 570 | (defun rust-mode--indent-line () 571 | (interactive) 572 | (let ((indent 573 | (save-excursion 574 | (back-to-indentation) 575 | ;; Point is now at beginning of current line 576 | (let* ((level (rust-paren-level)) 577 | (baseline 578 | ;; Our "baseline" is one level out from the 579 | ;; indentation of the expression containing the 580 | ;; innermost enclosing opening bracket. That way 581 | ;; if we are within a block that has a different 582 | ;; indentation than this mode would give it, we 583 | ;; still indent the inside of it correctly relative 584 | ;; to the outside. 585 | (if (= 0 level) 586 | 0 587 | (or 588 | (when rust-indent-method-chain 589 | (rust-align-to-method-chain)) 590 | (save-excursion 591 | (rust-rewind-irrelevant) 592 | (backward-up-list) 593 | (rust-rewind-to-beginning-of-current-level-expr) 594 | (+ (current-column) rust-indent-offset)))))) 595 | (cond 596 | ;; Indent inside a non-raw string only if the previous line 597 | ;; ends with a backslash that is inside the same string 598 | ((nth 3 (syntax-ppss)) 599 | (let* 600 | ((string-begin-pos (nth 8 (syntax-ppss))) 601 | (end-of-prev-line-pos 602 | (and (not (rust--same-line-p (point) (point-min))) 603 | (line-end-position 0)))) 604 | (when 605 | (and 606 | ;; If the string begins with an "r" it's a raw string and 607 | ;; we should not change the indentation 608 | (/= ?r (char-after string-begin-pos)) 609 | 610 | ;; If we're on the first line this will be nil and the 611 | ;; rest does not apply 612 | end-of-prev-line-pos 613 | 614 | ;; The end of the previous line needs to be inside the 615 | ;; current string... 616 | (> end-of-prev-line-pos string-begin-pos) 617 | 618 | ;; ...and end with a backslash 619 | (= ?\\ (char-before end-of-prev-line-pos))) 620 | 621 | ;; Indent to the same level as the previous line, or the 622 | ;; start of the string if the previous line starts the string 623 | (if (rust--same-line-p end-of-prev-line-pos string-begin-pos) 624 | ;; The previous line is the start of the string. 625 | ;; If the backslash is the only character after the 626 | ;; string beginning, indent to the next indent 627 | ;; level. Otherwise align with the start of the string. 628 | (if (> (- end-of-prev-line-pos string-begin-pos) 2) 629 | (save-excursion 630 | (goto-char (+ 1 string-begin-pos)) 631 | (current-column)) 632 | baseline) 633 | 634 | ;; The previous line is not the start of the string, so 635 | ;; match its indentation. 636 | (save-excursion 637 | (goto-char end-of-prev-line-pos) 638 | (back-to-indentation) 639 | (current-column)))))) 640 | 641 | ;; A function return type is indented to the corresponding 642 | ;; function arguments, if -to-arguments is selected. 643 | ((and rust-indent-return-type-to-arguments 644 | (looking-at "->")) 645 | (save-excursion 646 | (backward-list) 647 | (or (rust-align-to-expr-after-brace) 648 | (+ baseline rust-indent-offset)))) 649 | 650 | ;; A closing brace is 1 level unindented 651 | ((looking-at "[]})]") (- baseline rust-indent-offset)) 652 | 653 | ;; Doc comments in /** style with leading * indent to line up the *s 654 | ((and (nth 4 (syntax-ppss)) (looking-at "*")) 655 | (+ 1 baseline)) 656 | 657 | ;; When the user chose not to indent the start of the where 658 | ;; clause, put it on the baseline. 659 | ((and (not rust-indent-where-clause) 660 | (rust-looking-at-where)) 661 | baseline) 662 | 663 | ;; If we're in any other token-tree / sexp, then: 664 | (t 665 | (or 666 | ;; If we are inside a pair of braces, with something after the 667 | ;; open brace on the same line and ending with a comma, treat 668 | ;; it as fields and align them. 669 | (when (> level 0) 670 | (save-excursion 671 | (rust-rewind-irrelevant) 672 | (backward-up-list) 673 | ;; Point is now at the beginning of the containing set of braces 674 | (rust-align-to-expr-after-brace))) 675 | 676 | ;; When where-clauses are spread over multiple lines, clauses 677 | ;; should be aligned on the type parameters. In this case we 678 | ;; take care of the second and following clauses (the ones 679 | ;; that don't start with "where ") 680 | (save-excursion 681 | ;; Find the start of the function, we'll use this to limit 682 | ;; our search for "where ". 683 | (let ((function-start nil) (function-level nil)) 684 | (save-excursion 685 | ;; If we're already at the start of a function, 686 | ;; don't go back any farther. We can easily do 687 | ;; this by moving to the end of the line first. 688 | (end-of-line) 689 | (rust-beginning-of-defun) 690 | (back-to-indentation) 691 | ;; Avoid using multiple-value-bind 692 | (setq function-start (point) 693 | function-level (rust-paren-level))) 694 | ;; When we're not on a line starting with "where ", but 695 | ;; still on a where-clause line, go to "where " 696 | (when (and 697 | (not (rust-looking-at-where)) 698 | ;; We're looking at something like "F: ..." 699 | (looking-at (concat rust-re-ident ":")) 700 | ;; There is a "where " somewhere after the 701 | ;; start of the function. 702 | (rust-rewind-to-where function-start) 703 | ;; Make sure we're not inside the function 704 | ;; already (e.g. initializing a struct) by 705 | ;; checking we are the same level. 706 | (= function-level level)) 707 | ;; skip over "where" 708 | (forward-char 5) 709 | ;; Unless "where" is at the end of the line 710 | (if (eolp) 711 | ;; in this case the type parameters bounds are just 712 | ;; indented once 713 | (+ baseline rust-indent-offset) 714 | ;; otherwise, skip over whitespace, 715 | (skip-chars-forward "[:space:]") 716 | ;; get the column of the type parameter and use that 717 | ;; as indentation offset 718 | (current-column))))) 719 | 720 | (progn 721 | (back-to-indentation) 722 | ;; Point is now at the beginning of the current line 723 | (if (or 724 | ;; If this line begins with "else" or "{", stay on the 725 | ;; baseline as well (we are continuing an expression, 726 | ;; but the "else" or "{" should align with the beginning 727 | ;; of the expression it's in.) 728 | ;; Or, if this line starts a comment, stay on the 729 | ;; baseline as well. 730 | (looking-at "\\\\|{\\|/[/*]") 731 | 732 | ;; If this is the start of a top-level item, 733 | ;; stay on the baseline. 734 | (looking-at rust-top-item-beg-re) 735 | 736 | (save-excursion 737 | (rust-rewind-irrelevant) 738 | ;; Point is now at the end of the previous line 739 | (or 740 | ;; If we are at the start of the buffer, no 741 | ;; indentation is needed, so stay at baseline... 742 | (= (point) 1) 743 | ;; ..or if the previous line ends with any of these: 744 | ;; { ? : ( , ; [ } 745 | ;; then we are at the beginning of an 746 | ;; expression, so stay on the baseline... 747 | (looking-back "[(,:;[{}]\\|[^|]|" (- (point) 2)) 748 | ;; or if the previous line is the end of an 749 | ;; attribute, stay at the baseline... 750 | (progn (rust-rewind-to-beginning-of-current-level-expr) 751 | (looking-at "#"))))) 752 | baseline 753 | 754 | ;; Otherwise, we are continuing the same expression from 755 | ;; the previous line, so add one additional indent level 756 | (+ baseline rust-indent-offset)))))))))) 757 | 758 | (when indent 759 | ;; If we're at the beginning of the line (before or at the current 760 | ;; indentation), jump with the indentation change. Otherwise, save the 761 | ;; excursion so that adding the indentations will leave us at the 762 | ;; equivalent position within the line to where we were before. 763 | (if (<= (current-column) (current-indentation)) 764 | (indent-line-to indent) 765 | (save-excursion (indent-line-to indent)))))) 766 | 767 | (defun rust--same-line-p (pos1 pos2) 768 | "Return non-nil if POS1 and POS2 are on the same line." 769 | (save-excursion (= (progn (goto-char pos1) (line-end-position)) 770 | (progn (goto-char pos2) (line-end-position))))) 771 | 772 | (defun rust-mode-indent-line () 773 | "Indent the current line, and indent code examples in comments. 774 | 775 | Indent the current line as `rust-mode-indent-line' does. If 776 | point is inside a block comment containing a Markdown code 777 | example (delimited by triple backquotes), then also indent the 778 | current line within the code example." 779 | (interactive) 780 | 781 | ;; First, reindent the current line. 782 | (rust-mode--indent-line) 783 | 784 | ;; If point is inside a comment: 785 | (let ((ppss (syntax-ppss))) 786 | (when (nth 4 ppss) 787 | (rust-with-comment-fill-prefix 788 | (lambda () 789 | (let* ((orig-buf (current-buffer)) 790 | (orig-point (point)) 791 | (orig-eol (line-end-position)) 792 | (orig-bol (line-beginning-position)) 793 | (orig-mode major-mode) 794 | (com-start (nth 8 ppss)) 795 | (com-prefix (replace-regexp-in-string "\\s-*\\'" "" 796 | (or fill-prefix ""))) 797 | (com-re (regexp-quote com-prefix)) 798 | (cb-re (concat "^" com-re "\\(\\s-*\\)```")) 799 | cb-start cb-pad cb-hidden-marker indented rel-point) 800 | 801 | ;; If point is within the prefix (not inside the comment 802 | ;; body), don't do anything fancy. 803 | (when (>= orig-point (+ orig-bol (length com-prefix))) 804 | (save-excursion 805 | ;; If we're in a // comment block, use the fill prefix to 806 | ;; find the start of the block. If we're in a /* 807 | ;; comment, the start is determined by ppss. 808 | (when (string-match "^\\s-*//" com-prefix) 809 | (setq com-start orig-bol) 810 | (while (and (= (forward-line -1) 0) 811 | (looking-at com-re)) 812 | (setq com-start (point)))) 813 | 814 | ;; Search for ``` lines within the comment block, and 815 | ;; identify the start of the current code block if any. 816 | (goto-char com-start) 817 | (while (re-search-forward cb-re orig-bol t) 818 | (setq cb-start (unless cb-start (line-end-position)) 819 | cb-pad (match-string 1)))) 820 | 821 | (when cb-start 822 | ;; We're inside a code block. Copy preceding contents to 823 | ;; a temporary buffer. 824 | (with-temp-buffer 825 | (insert-buffer-substring orig-buf cb-start orig-eol) 826 | (forward-char (- orig-point orig-eol)) 827 | (save-excursion 828 | ;; For each line in the temporary buffer, remove 829 | ;; the comment prefix, left padding if present, and 830 | ;; hidden-line marker if present. For example, if 831 | ;; the code block begins with: 832 | ;; 833 | ;; ^ /// ```$ 834 | ;; 835 | ;; then trim lines as follows: 836 | ;; 837 | ;; ^ ///$ becomes ^$ 838 | ;; ^ /// let x = 2;$ becomes ^ let x = 2;$ 839 | ;; ^ /// # fn main() {$ becomes ^fn main() {$ 840 | ;; 841 | ;; If the line we're indenting isn't a hidden line, 842 | ;; then remove hidden lines completely - non-hidden 843 | ;; lines are indented as if the hidden lines don't 844 | ;; exist. 845 | (let ((trim-re (concat com-re "\\(?:" cb-pad "\\)?" 846 | "\\(\\s-*# \\)?"))) 847 | (beginning-of-line) 848 | (if (and (looking-at trim-re) (match-beginning 1)) 849 | (setq cb-hidden-marker "# ") 850 | (setq cb-hidden-marker "" 851 | trim-re (concat com-re "\\(?:" cb-pad "\\)?" 852 | "\\(\\s-*# .*\\)?"))) 853 | 854 | (goto-char (point-min)) 855 | (while (not (eobp)) 856 | (when (looking-at trim-re) 857 | (delete-region (point) (match-end 0))) 858 | (forward-line 1)))) 859 | 860 | ;; Reindent the line. Copy local settings from the 861 | ;; parent buffer, but disable indent-tabs-mode unless 862 | ;; it is enabled in the parent buffer and the code 863 | ;; block is already tab-aligned. 864 | (funcall orig-mode) 865 | (mapc (lambda (v) 866 | (when (custom-variable-p (or (car-safe v) v)) 867 | (if (symbolp v) 868 | (makunbound (make-local-variable v)) 869 | (set (make-local-variable (car v)) (cdr v))))) 870 | (buffer-local-variables orig-buf)) 871 | (or (string-suffix-p "\t" cb-pad) 872 | (= 0 (length com-prefix) (length cb-pad)) 873 | (setq-local indent-tabs-mode nil)) 874 | (rust-mode--indent-line) 875 | 876 | ;; Extract the indented line and copy back into the 877 | ;; original buffer. 878 | (setq rel-point (- (point) (point-max))) 879 | (beginning-of-line) 880 | (setq indented 881 | (concat com-prefix cb-pad cb-hidden-marker 882 | (buffer-substring (point) (point-max))))) 883 | (goto-char orig-eol) 884 | (unless (equal indented (buffer-substring orig-bol orig-eol)) 885 | (delete-region orig-bol orig-eol) 886 | (insert indented)) 887 | (forward-char rel-point))))))))) 888 | 889 | ;;; Font-locking definitions and helpers 890 | 891 | (defun rust-next-string-interpolation (limit) 892 | "Search forward from point for next Rust interpolation marker before LIMIT. 893 | Set point to the end of the occurrence found, and return match beginning 894 | and end." 895 | (catch 'match 896 | (save-match-data 897 | (save-excursion 898 | (while (search-forward "{" limit t) 899 | (if (eql (char-after (point)) ?{) 900 | (forward-char) 901 | (let ((start (match-beginning 0))) 902 | ;; According to fmt_macros::Parser::next, an opening brace 903 | ;; must be followed by an optional argument and/or format 904 | ;; specifier, then a closing brace. A single closing brace 905 | ;; without a corresponding unescaped opening brace is an 906 | ;; error. We don't need to do anything special with 907 | ;; arguments, specifiers, or errors, so we only search for 908 | ;; the single closing brace. 909 | (when (search-forward "}" limit t) 910 | (throw 'match (list start (point))))))))))) 911 | 912 | (defun rust-string-interpolation-matcher (limit) 913 | "Match next Rust interpolation marker before LIMIT and set match data if found. 914 | Returns nil if not within a Rust string." 915 | (when-let (((rust-in-str)) 916 | (match (rust-next-string-interpolation limit))) 917 | (set-match-data match) 918 | (goto-char (cadr match)) 919 | match)) 920 | 921 | (defun rust-syntax-class-before-point () 922 | (when (> (point) 1) 923 | (syntax-class (syntax-after (1- (point)))))) 924 | 925 | (defun rust-rewind-qualified-ident () 926 | (while (rust-looking-back-ident) 927 | (backward-sexp) 928 | (when (save-excursion (rust-rewind-irrelevant) (rust-looking-back-str "::")) 929 | (rust-rewind-irrelevant) 930 | (backward-char 2) 931 | (rust-rewind-irrelevant)))) 932 | 933 | (defun rust-rewind-type-param-list () 934 | (cond 935 | ((and (rust-looking-back-str ">") (equal 5 (rust-syntax-class-before-point))) 936 | (backward-sexp) 937 | (rust-rewind-irrelevant)) 938 | 939 | ;; We need to be able to back up past the Fn(args) -> RT form as well. If 940 | ;; we're looking back at this, we want to end up just after "Fn". 941 | ((member (char-before) '(?\] ?\) )) 942 | (let ((is-paren (rust-looking-back-str ")"))) 943 | (when-let ((dest (save-excursion 944 | (backward-sexp) 945 | (rust-rewind-irrelevant) 946 | (or 947 | (when (rust-looking-back-str "->") 948 | (backward-char 2) 949 | (rust-rewind-irrelevant) 950 | (when (rust-looking-back-str ")") 951 | (backward-sexp) 952 | (point))) 953 | (and is-paren (point)))))) 954 | (goto-char dest)))))) 955 | 956 | (defun rust-rewind-to-decl-name () 957 | "Return the point at the beginning of the name in a declaration. 958 | I.e. if we are before an ident that is part of a declaration that 959 | can have a where clause, rewind back to just before the name of 960 | the subject of that where clause and return the new point. 961 | Otherwise return nil." 962 | (let* ((ident-pos (point)) 963 | (newpos (save-excursion 964 | (rust-rewind-irrelevant) 965 | (rust-rewind-type-param-list) 966 | (cond 967 | ((rust-looking-back-symbols 968 | '("fn" "trait" "enum" "struct" "union" "impl" "type")) 969 | ident-pos) 970 | 971 | ((equal 5 (rust-syntax-class-before-point)) 972 | (backward-sexp) 973 | (rust-rewind-to-decl-name)) 974 | 975 | ((looking-back "[:,'+=]" (1- (point))) 976 | (backward-char) 977 | (rust-rewind-to-decl-name)) 978 | 979 | ((rust-looking-back-str "->") 980 | (backward-char 2) 981 | (rust-rewind-to-decl-name)) 982 | 983 | ((rust-looking-back-ident) 984 | (rust-rewind-qualified-ident) 985 | (rust-rewind-to-decl-name)))))) 986 | (when newpos (goto-char newpos)) 987 | newpos)) 988 | 989 | (defun rust-is-in-expression-context (token) 990 | "Return t if what comes right after the point is part of an 991 | expression (as opposed to starting a type) by looking at what 992 | comes before. Takes a symbol that roughly indicates what is 993 | after the point. 994 | 995 | This function is used as part of `rust-is-lt-char-operator' as 996 | part of angle bracket matching, and is not intended to be used 997 | outside of this context." 998 | (save-excursion 999 | (let ((postchar (char-after))) 1000 | (rust-rewind-irrelevant) 1001 | ;; A type alias or ascription could have a type param list. Skip backwards past it. 1002 | (when (member token '(ambiguous-operator open-brace)) 1003 | (rust-rewind-type-param-list)) 1004 | (cond 1005 | 1006 | ;; Certain keywords always introduce expressions 1007 | ((rust-looking-back-symbols rust-expression-introducers) t) 1008 | 1009 | ;; "as" introduces a type 1010 | ((rust-looking-back-symbols '("as")) nil) 1011 | 1012 | ;; An open angle bracket never introduces expression context WITHIN the angle brackets 1013 | ((and (equal token 'open-brace) (equal postchar ?<)) nil) 1014 | 1015 | ;; An ident! followed by an open brace is a macro invocation. Consider 1016 | ;; it to be an expression. 1017 | ((and (equal token 'open-brace) (rust-looking-back-macro)) t) 1018 | 1019 | ;; In a brace context a "]" introduces an expression. 1020 | ((and (eq token 'open-brace) (rust-looking-back-str "]"))) 1021 | 1022 | ;; An identifier is right after an ending paren, bracket, angle bracket 1023 | ;; or curly brace. It's a type if the last sexp was a type. 1024 | ((and (equal token 'ident) (equal 5 (rust-syntax-class-before-point))) 1025 | (backward-sexp) 1026 | (rust-is-in-expression-context 'open-brace)) 1027 | 1028 | ;; If a "for" appears without a ; or { before it, it's part of an 1029 | ;; "impl X for y", so the y is a type. Otherwise it's 1030 | ;; introducing a loop, so the y is an expression 1031 | ((and (equal token 'ident) (rust-looking-back-symbols '("for"))) 1032 | (backward-sexp) 1033 | (rust-rewind-irrelevant) 1034 | (looking-back "[{;]" (1- (point)))) 1035 | 1036 | ((rust-looking-back-ident) 1037 | (rust-rewind-qualified-ident) 1038 | (rust-rewind-irrelevant) 1039 | (cond 1040 | ((equal token 'open-brace) 1041 | ;; We now know we have: 1042 | ;; ident [{([] 1043 | ;; where [{([] denotes either a {, ( or [. 1044 | ;; This character is bound as postchar. 1045 | (cond 1046 | ;; If postchar is a paren or square bracket, then if the 1047 | ;; brace is a type if the identifier is one 1048 | ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) 1049 | 1050 | ;; If postchar is a curly brace, the brace can only be a type if 1051 | ;; ident2 is the name of an enum, struct or trait being declared. 1052 | ;; Note that if there is a -> before the ident then the ident would 1053 | ;; be a type but the { is not. 1054 | ((equal ?{ postchar) 1055 | (not (and (rust-rewind-to-decl-name) 1056 | (progn 1057 | (rust-rewind-irrelevant) 1058 | (rust-looking-back-symbols 1059 | '("enum" "struct" "union" "trait" "type")))))))) 1060 | 1061 | ((equal token 'ambiguous-operator) 1062 | (cond 1063 | ;; An ampersand after an ident has to be an operator rather 1064 | ;; than a & at the beginning of a ref type 1065 | ((equal postchar ?&) t) 1066 | 1067 | ;; A : followed by a type then an = introduces an 1068 | ;; expression (unless it is part of a where clause of a 1069 | ;; "type" declaration) 1070 | ((and (equal postchar ?=) 1071 | (looking-back "[^:]:" (- (point) 2)) 1072 | (not (save-excursion 1073 | (and (rust-rewind-to-decl-name) 1074 | (progn (rust-rewind-irrelevant) 1075 | (rust-looking-back-symbols '("type")))))))) 1076 | 1077 | ;; "let ident =" introduces an expression--and so does "const" and "mut" 1078 | ((and (equal postchar ?=) (rust-looking-back-symbols '("let" "const" "mut"))) t) 1079 | 1080 | ;; As a specific special case, see if this is the = in this situation: 1081 | ;; enum EnumName { Ident = 1082 | ;; In this case, this is a c-like enum and despite Ident 1083 | ;; representing a type, what comes after the = is an expression 1084 | ((and 1085 | (> (rust-paren-level) 0) 1086 | (save-excursion 1087 | (backward-up-list) 1088 | (rust-rewind-irrelevant) 1089 | (rust-rewind-type-param-list) 1090 | (and 1091 | (rust-looking-back-ident) 1092 | (progn 1093 | (rust-rewind-qualified-ident) 1094 | (rust-rewind-irrelevant) 1095 | (rust-looking-back-str "enum"))))) 1096 | t) 1097 | 1098 | ;; Otherwise the ambiguous operator is a type if the identifier is a type 1099 | ((rust-is-in-expression-context 'ident) t))) 1100 | 1101 | ((equal token 'colon) 1102 | (cond 1103 | ;; If we see a ident: not inside any braces/parens, we're at top level. 1104 | ;; There are no allowed expressions after colons there, just types. 1105 | ((<= (rust-paren-level) 0) nil) 1106 | 1107 | ;; We see ident: inside a list 1108 | ((looking-back "[{,]" (1- (point))) 1109 | (backward-up-list) 1110 | 1111 | ;; If a : appears whose surrounding paren/brackets/braces are 1112 | ;; anything other than curly braces, it can't be a field 1113 | ;; initializer and must be denoting a type. 1114 | (when (looking-at "{") 1115 | (rust-rewind-irrelevant) 1116 | (rust-rewind-type-param-list) 1117 | (when (rust-looking-back-ident) 1118 | ;; We have a context that looks like this: 1119 | ;; ident2 { [maybe paren-balanced code ending in comma] ident1: 1120 | ;; the point is sitting just after ident2, and we trying to 1121 | ;; figure out if the colon introduces an expression or a type. 1122 | ;; The answer is that ident1 is a field name, and what comes 1123 | ;; after the colon is an expression, if ident2 is an 1124 | ;; expression. 1125 | (rust-rewind-qualified-ident) 1126 | (rust-is-in-expression-context 'ident)))) 1127 | 1128 | ;; Otherwise, if the ident: appeared with anything other than , or { 1129 | ;; before it, it can't be part of a struct initializer and therefore 1130 | ;; must be denoting a type. 1131 | (t nil))))) 1132 | 1133 | ;; An operator-like character after a string is indeed an operator 1134 | ((and (equal token 'ambiguous-operator) 1135 | (member (rust-syntax-class-before-point) '(5 7 15))) t) 1136 | 1137 | ;; A colon that has something other than an identifier before it is a 1138 | ;; type ascription 1139 | ((equal token 'colon) nil) 1140 | 1141 | ;; A :: introduces a type (or module, but not an expression in any case) 1142 | ((rust-looking-back-str "::") nil) 1143 | 1144 | ((rust-looking-back-str ":") 1145 | (backward-char) 1146 | (rust-is-in-expression-context 'colon)) 1147 | 1148 | ;; A -> introduces a type 1149 | ((rust-looking-back-str "->") nil) 1150 | 1151 | ;; If we are up against the beginning of a list, or after a comma inside 1152 | ;; of one, back up out of it and check what the list itself is 1153 | ((or 1154 | (equal 4 (rust-syntax-class-before-point)) 1155 | (rust-looking-back-str ",")) 1156 | (condition-case nil 1157 | (progn 1158 | (backward-up-list) 1159 | (rust-is-in-expression-context 'open-brace)) 1160 | (scan-error nil))) 1161 | 1162 | ;; A => introduces an expression 1163 | ((rust-looking-back-str "=>") t) 1164 | 1165 | ;; A == introduces an expression 1166 | ((rust-looking-back-str "==") t) 1167 | 1168 | ;; These operators can introduce expressions or types 1169 | ((looking-back "[-+=!?&*]" (1- (point))) 1170 | (backward-char) 1171 | (rust-is-in-expression-context 'ambiguous-operator)) 1172 | 1173 | ;; These operators always introduce expressions. (Note that if this 1174 | ;; regexp finds a < it must not be an angle bracket, or it'd 1175 | ;; have been caught in the syntax-class check above instead of this.) 1176 | ((looking-back rust-re-pre-expression-operators (1- (point))) t))))) 1177 | 1178 | (defun rust-is-lt-char-operator () 1179 | "Return non-nil if the `<' sign just after point is an operator. 1180 | Otherwise, if it is an opening angle bracket, then return nil." 1181 | (let ((case-fold-search nil)) 1182 | (save-excursion 1183 | (rust-rewind-irrelevant) 1184 | ;; We are now just after the character syntactically before the <. 1185 | (cond 1186 | 1187 | ;; If we are looking back at a < that is not an angle bracket (but not 1188 | ;; two of them) then this is the second < in a bit shift operator 1189 | ((and (rust-looking-back-str "<") 1190 | (not (equal 4 (rust-syntax-class-before-point))) 1191 | (not (rust-looking-back-str "<<")))) 1192 | 1193 | ;; On the other hand, if we are after a closing paren/brace/bracket it 1194 | ;; can only be an operator, not an angle bracket. Likewise, if we are 1195 | ;; after a string it's an operator. (The string case could actually be 1196 | ;; valid in rust for character literals.) 1197 | ((member (rust-syntax-class-before-point) '(5 7 15)) t) 1198 | 1199 | ;; If we are looking back at an operator, we know that we are at 1200 | ;; the beginning of an expression, and thus it has to be an angle 1201 | ;; bracket (starting a "::" construct.) 1202 | ((looking-back rust-re-pre-expression-operators (1- (point))) nil) 1203 | 1204 | ;; If we are looking back at a keyword, it's an angle bracket 1205 | ;; unless that keyword is "self", "true" or "false" 1206 | ((rust-looking-back-symbols rust-keywords) 1207 | (rust-looking-back-symbols '("self" "true" "false"))) 1208 | 1209 | ((rust-looking-back-str "?") 1210 | (rust-is-in-expression-context 'ambiguous-operator)) 1211 | 1212 | ;; If we're looking back at an identifier, this depends on whether 1213 | ;; the identifier is part of an expression or a type 1214 | ((rust-looking-back-ident) 1215 | (backward-sexp) 1216 | (or 1217 | ;; The special types can't take type param lists, so a < after one is 1218 | ;; always an operator 1219 | (looking-at rust-re-special-types) 1220 | 1221 | (rust-is-in-expression-context 'ident))) 1222 | 1223 | ;; Otherwise, assume it's an angle bracket 1224 | )))) 1225 | 1226 | (defun rust-electric-pair-inhibit-predicate-wrap (char) 1227 | "Prevent \"matching\" with a `>' when CHAR is the less-than operator. 1228 | This wraps the default defined by `electric-pair-inhibit-predicate'." 1229 | (or 1230 | (when (= ?< char) 1231 | (save-excursion 1232 | (backward-char) 1233 | (rust-is-lt-char-operator))) 1234 | (funcall (default-value 'electric-pair-inhibit-predicate) char))) 1235 | 1236 | (defun rust-electric-pair-skip-self (char) 1237 | "Skip CHAR instead of inserting a second closing character. 1238 | This is added to the default skips defined by `electric-pair-skip-self'." 1239 | (= ?> char)) 1240 | 1241 | (defun rust-ordinary-lt-gt-p () 1242 | "Test whether the `<' or `>' at point is an ordinary operator of some kind. 1243 | 1244 | This returns t if the `<' or `>' is an ordinary operator (like 1245 | less-than) or part of one (like `->'); and nil if the character 1246 | should be considered a paired angle bracket." 1247 | (cond 1248 | ;; If matching is turned off suppress all of them 1249 | ((not rust-match-angle-brackets) t) 1250 | 1251 | ;; This is a cheap check so we do it early. 1252 | ;; Don't treat the > in -> or => as an angle bracket 1253 | ((and (= (following-char) ?>) (memq (preceding-char) '(?- ?=))) t) 1254 | 1255 | ;; We don't take < or > in strings or comments to be angle brackets 1256 | ((rust-in-str-or-cmnt) t) 1257 | 1258 | ;; Inside a macro we don't really know the syntax. Any < or > may be an 1259 | ;; angle bracket or it may not. But we know that the other braces have 1260 | ;; to balance regardless of the < and >, so if we don't treat any < or > 1261 | ;; as angle brackets it won't mess up any paren balancing. 1262 | ((rust-in-macro) t) 1263 | 1264 | ((= (following-char) ?<) 1265 | (rust-is-lt-char-operator)) 1266 | 1267 | ;; Since rust-ordinary-lt-gt-p is called only when either < or > are at the point, 1268 | ;; we know that the following char must be > in the clauses below. 1269 | 1270 | ;; If we are at top level and not in any list, it can't be a closing 1271 | ;; angle bracket 1272 | ((>= 0 (rust-paren-level)) t) 1273 | 1274 | ;; Otherwise, treat the > as a closing angle bracket if it would 1275 | ;; match an opening one 1276 | ((save-excursion 1277 | (backward-up-list) 1278 | (/= (following-char) ?<))))) 1279 | 1280 | (defun rust-mode-syntactic-face-function (state) 1281 | "Return face that distinguishes doc and normal comments in given syntax STATE." 1282 | (if (nth 3 state) 1283 | 'font-lock-string-face 1284 | (save-excursion 1285 | (goto-char (nth 8 state)) 1286 | (if (looking-at "/\\([*][*!][^*!]\\|/[/!][^/!]\\)") 1287 | 'font-lock-doc-face 1288 | 'font-lock-comment-face)))) 1289 | 1290 | (eval-and-compile 1291 | (defconst rust--char-literal-rx 1292 | (rx (seq 1293 | (group "'") 1294 | (or 1295 | (seq 1296 | "\\" 1297 | (or 1298 | (: "u{" (** 1 6 xdigit) "}") 1299 | (: "x" (= 2 xdigit)) 1300 | (any "'nrt0\"\\"))) 1301 | (not (any "'\\"))) 1302 | (group "'"))) 1303 | "A regular expression matching a character literal.")) 1304 | 1305 | (defun rust-fill-prefix-for-comment-start (line-start) 1306 | "Determine what to use for `fill-prefix' based on the text at LINE-START." 1307 | (let ((result 1308 | ;; Replace /* with same number of spaces 1309 | (replace-regexp-in-string 1310 | "\\(?:/\\*+?\\)[!*]?" 1311 | (lambda (s) 1312 | ;; We want the * to line up with the first * of the 1313 | ;; comment start 1314 | (let ((offset (if (eq t 1315 | (compare-strings "/*" nil nil 1316 | s 1317 | (- (length s) 2) 1318 | (length s))) 1319 | 1 2))) 1320 | (concat (make-string (- (length s) offset) 1321 | ?\x20) "*"))) 1322 | line-start))) 1323 | ;; Make sure we've got at least one space at the end 1324 | (if (not (= (aref result (- (length result) 1)) ?\x20)) 1325 | (setq result (concat result " "))) 1326 | result)) 1327 | 1328 | (defun rust-in-comment-paragraph (body) 1329 | ;; We might move the point to fill the next comment, but we don't want it 1330 | ;; seeming to jump around on the user 1331 | (save-excursion 1332 | ;; If we're outside of a comment, with only whitespace and then a comment 1333 | ;; in front, jump to the comment and prepare to fill it. 1334 | (when (not (nth 4 (syntax-ppss))) 1335 | (beginning-of-line) 1336 | (when (looking-at (concat "[[:space:]\n]*" comment-start-skip)) 1337 | (goto-char (match-end 0)))) 1338 | 1339 | ;; We need this when we're moving the point around and then checking syntax 1340 | ;; while doing paragraph fills, because the cache it uses isn't always 1341 | ;; invalidated during this. 1342 | (syntax-ppss-flush-cache 1) 1343 | ;; If we're at the beginning of a comment paragraph with nothing but 1344 | ;; whitespace til the next line, jump to the next line so that we use the 1345 | ;; existing prefix to figure out what the new prefix should be, rather than 1346 | ;; inferring it from the comment start. 1347 | (let ((next-bol (line-beginning-position 2))) 1348 | (while (save-excursion 1349 | (end-of-line) 1350 | (syntax-ppss-flush-cache 1) 1351 | (and (nth 4 (syntax-ppss)) 1352 | (save-excursion 1353 | (beginning-of-line) 1354 | (looking-at paragraph-start)) 1355 | (looking-at "[[:space:]]*$") 1356 | (nth 4 (syntax-ppss next-bol)))) 1357 | (goto-char next-bol))) 1358 | 1359 | (syntax-ppss-flush-cache 1) 1360 | ;; If we're on the last line of a multiline-style comment that started 1361 | ;; above, back up one line so we don't mistake the * of the */ that ends 1362 | ;; the comment for a prefix. 1363 | (when (save-excursion 1364 | (and (nth 4 (syntax-ppss (line-beginning-position 1))) 1365 | (looking-at "[[:space:]]*\\*/"))) 1366 | (goto-char (line-end-position 0))) 1367 | (funcall body))) 1368 | 1369 | (defun rust-with-comment-fill-prefix (body) 1370 | (let* 1371 | ((line-string (buffer-substring-no-properties 1372 | (line-beginning-position) (line-end-position))) 1373 | (line-comment-start 1374 | (when (nth 4 (syntax-ppss)) 1375 | (cond 1376 | ;; If we're inside the comment and see a * prefix, use it 1377 | ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)" 1378 | line-string) 1379 | (match-string 1 line-string)) 1380 | ;; If we're at the start of a comment, figure out what prefix 1381 | ;; to use for the subsequent lines after it 1382 | ((string-match (concat "[[:space:]]*" comment-start-skip) line-string) 1383 | (rust-fill-prefix-for-comment-start 1384 | (match-string 0 line-string)))))) 1385 | (fill-prefix 1386 | (or line-comment-start 1387 | fill-prefix))) 1388 | (funcall body))) 1389 | 1390 | (defun rust-find-fill-prefix () 1391 | (rust-in-comment-paragraph 1392 | (lambda () 1393 | (rust-with-comment-fill-prefix 1394 | (lambda () 1395 | fill-prefix))))) 1396 | 1397 | (defun rust-fill-paragraph (&rest args) 1398 | "Special wrapping for `fill-paragraph'. 1399 | This handles multi-line comments with a * prefix on each line." 1400 | (rust-in-comment-paragraph 1401 | (lambda () 1402 | (rust-with-comment-fill-prefix 1403 | (lambda () 1404 | (let 1405 | ((fill-paragraph-function 1406 | (if (not (eq fill-paragraph-function #'rust-fill-paragraph)) 1407 | fill-paragraph-function)) 1408 | (fill-paragraph-handle-comment t)) 1409 | (apply #'fill-paragraph args) 1410 | t)))))) 1411 | 1412 | (defun rust-do-auto-fill (&rest args) 1413 | "Special wrapping for `do-auto-fill'. 1414 | This handles multi-line comments with a * prefix on each line." 1415 | (rust-with-comment-fill-prefix 1416 | (lambda () 1417 | (apply #'do-auto-fill args) 1418 | t))) 1419 | 1420 | (defun rust-fill-forward-paragraph (arg) 1421 | ;; This is to work around some funny behavior when a paragraph separator is 1422 | ;; at the very top of the file and there is a fill prefix. 1423 | (let ((fill-prefix nil)) (forward-paragraph arg))) 1424 | 1425 | (defun rust-comment-indent-new-line (&optional arg) 1426 | (rust-with-comment-fill-prefix 1427 | (lambda () (comment-indent-new-line arg)))) 1428 | 1429 | ;;; Defun Motions 1430 | 1431 | (defun rust-beginning-of-defun (&optional arg) 1432 | "Move backward to the beginning of the current defun. 1433 | 1434 | With ARG, move backward multiple defuns. Negative ARG means 1435 | move forward. 1436 | 1437 | This is written mainly to be used as `beginning-of-defun-function' for Rust. 1438 | Don't move to the beginning of the line. `beginning-of-defun', 1439 | which calls this, does that afterwards." 1440 | (interactive "p") 1441 | (let* ((arg (or arg 1)) 1442 | (magnitude (abs arg)) 1443 | (sign (if (< arg 0) -1 1))) 1444 | ;; If moving forward, don't find the defun we might currently be 1445 | ;; on. 1446 | (when (< sign 0) 1447 | (end-of-line)) 1448 | (catch 'done 1449 | (dotimes (_ magnitude) 1450 | ;; Search until we find a match that is not in a string or comment. 1451 | (while (if (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)") 1452 | nil 'move sign) 1453 | (rust-in-str-or-cmnt) 1454 | ;; Did not find it. 1455 | (throw 'done nil))))) 1456 | t)) 1457 | 1458 | (defun rust-end-of-defun () 1459 | "Move forward to the next end of defun. 1460 | 1461 | With argument, do it that many times. 1462 | Negative argument -N means move back to Nth preceding end of defun. 1463 | 1464 | Assume that this is called after `beginning-of-defun'. So point is 1465 | at the beginning of the defun body. 1466 | 1467 | This is written mainly to be used as `end-of-defun-function' for Rust." 1468 | (interactive) 1469 | ;; Find the opening brace 1470 | (if (re-search-forward "[{]" nil t) 1471 | (progn 1472 | (goto-char (match-beginning 0)) 1473 | ;; Go to the closing brace 1474 | (condition-case nil 1475 | (forward-sexp) 1476 | (scan-error 1477 | ;; The parentheses are unbalanced; instead of being unable 1478 | ;; to fontify, just jump to the end of the buffer 1479 | (goto-char (point-max))))) 1480 | ;; There is no opening brace, so consider the whole buffer to be one "defun" 1481 | (goto-char (point-max)))) 1482 | 1483 | ;;; _ 1484 | 1485 | (defun rust-mode-reload () 1486 | (interactive) 1487 | (unload-feature 'rust-mode) 1488 | (require 'rust-mode) 1489 | (rust-mode)) 1490 | 1491 | (defvar rust-mode-syntax-table 1492 | (let ((table (make-syntax-table))) 1493 | 1494 | ;; Operators 1495 | (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?^ ?! ?< ?> ?~ ?@)) 1496 | (modify-syntax-entry i "." table)) 1497 | 1498 | ;; Strings 1499 | (modify-syntax-entry ?\" "\"" table) 1500 | (modify-syntax-entry ?\\ "\\" table) 1501 | 1502 | ;; Angle brackets. We suppress this with syntactic propertization 1503 | ;; when needed 1504 | (modify-syntax-entry ?< "(>" table) 1505 | (modify-syntax-entry ?> ")<" table) 1506 | 1507 | ;; Comments 1508 | (modify-syntax-entry ?/ ". 124b" table) 1509 | (modify-syntax-entry ?* ". 23n" table) 1510 | (modify-syntax-entry ?\n "> b" table) 1511 | (modify-syntax-entry ?\^m "> b" table) 1512 | 1513 | table) 1514 | "Syntax definitions and helpers.") 1515 | 1516 | (defun rust--syntax-propertize-raw-string (str-start end) 1517 | "A helper for rust-syntax-propertize. 1518 | 1519 | This will apply the appropriate string syntax to the character 1520 | from the STR-START up to the end of the raw string, or to END, 1521 | whichever comes first." 1522 | (when (save-excursion 1523 | (goto-char str-start) 1524 | (looking-at "r\\(#*\\)\\(\"\\)")) 1525 | ;; In a raw string, so try to find the end. 1526 | (let ((hashes (match-string 1))) 1527 | ;; Match \ characters at the end of the string to suppress 1528 | ;; their normal character-quote syntax. 1529 | (when (re-search-forward (concat "\\(\\\\*\\)\\(\"" hashes "\\)") end t) 1530 | (put-text-property (match-beginning 1) (match-end 1) 1531 | 'syntax-table (string-to-syntax "_")) 1532 | (put-text-property (1- (match-end 2)) (match-end 2) 1533 | 'syntax-table (string-to-syntax "|")) 1534 | (goto-char (match-end 0)))))) 1535 | 1536 | ;;; Syntax Propertize 1537 | 1538 | (defun rust-syntax-propertize (start end) 1539 | "A `syntax-propertize-function' to apply properties from START to END." 1540 | (goto-char start) 1541 | (when-let ((str-start (rust-in-str-or-cmnt))) 1542 | (rust--syntax-propertize-raw-string str-start end)) 1543 | (funcall 1544 | (syntax-propertize-rules 1545 | ;; Character literals. 1546 | (rust--char-literal-rx (1 "\"") (2 "\"")) 1547 | ;; Raw strings. 1548 | ("\\(r\\)#*\"" 1549 | (0 (ignore 1550 | (goto-char (match-end 0)) 1551 | (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) 1552 | (put-text-property (match-beginning 1) (match-end 1) 1553 | 'syntax-table (string-to-syntax "|")) 1554 | (rust--syntax-propertize-raw-string (match-beginning 0) end))))) 1555 | ("[<>]" 1556 | (0 (ignore 1557 | (when (save-match-data 1558 | (save-excursion 1559 | (goto-char (match-beginning 0)) 1560 | (rust-ordinary-lt-gt-p))) 1561 | (put-text-property (match-beginning 0) (match-end 0) 1562 | 'syntax-table (string-to-syntax ".")) 1563 | (goto-char (match-end 0))))))) 1564 | (point) end)) 1565 | 1566 | (define-derived-mode rust-mode prog-mode "Rust" 1567 | "Major mode for Rust code. 1568 | 1569 | \\{rust-mode-map}" 1570 | :group 'rust-mode 1571 | :syntax-table rust-mode-syntax-table 1572 | 1573 | ;; Syntax 1574 | (setq-local syntax-propertize-function #'rust-syntax-propertize) 1575 | 1576 | ;; Indentation 1577 | (setq-local indent-line-function 'rust-mode-indent-line) 1578 | 1579 | ;; Fonts 1580 | (setq-local font-lock-defaults 1581 | '(rust-font-lock-keywords 1582 | nil nil nil nil 1583 | (font-lock-syntactic-face-function 1584 | . rust-mode-syntactic-face-function))) 1585 | 1586 | ;; Misc 1587 | (setq-local comment-start "// ") 1588 | (setq-local comment-end "") 1589 | (setq-local open-paren-in-column-0-is-defun-start nil) 1590 | 1591 | ;; Auto indent on } 1592 | (setq-local electric-indent-chars 1593 | (cons ?} (and (boundp 'electric-indent-chars) 1594 | electric-indent-chars))) 1595 | 1596 | ;; Allow paragraph fills for comments 1597 | (setq-local comment-start-skip "\\(?://[/!]*\\|/\\*[*!]?\\)[[:space:]]*") 1598 | (setq-local paragraph-start 1599 | (concat "[[:space:]]*\\(?:" 1600 | comment-start-skip 1601 | "\\|\\*/?[[:space:]]*\\|\\)$")) 1602 | (setq-local paragraph-separate paragraph-start) 1603 | (setq-local normal-auto-fill-function #'rust-do-auto-fill) 1604 | (setq-local fill-paragraph-function #'rust-fill-paragraph) 1605 | (setq-local fill-forward-paragraph-function #'rust-fill-forward-paragraph) 1606 | (setq-local adaptive-fill-function #'rust-find-fill-prefix) 1607 | (setq-local adaptive-fill-first-line-regexp "") 1608 | (setq-local comment-multi-line t) 1609 | (setq-local comment-line-break-function #'rust-comment-indent-new-line) 1610 | (setq-local imenu-generic-expression rust-imenu-generic-expression) 1611 | (setq-local imenu-syntax-alist '((?! . "w"))) ; For macro_rules! 1612 | (setq-local beginning-of-defun-function #'rust-beginning-of-defun) 1613 | (setq-local end-of-defun-function #'rust-end-of-defun) 1614 | (setq-local parse-sexp-lookup-properties t) 1615 | (setq-local electric-pair-inhibit-predicate 1616 | #'rust-electric-pair-inhibit-predicate-wrap) 1617 | (add-function :before-until (local 'electric-pair-skip-self) 1618 | #'rust-electric-pair-skip-self) 1619 | ;; Configure prettify 1620 | (setq prettify-symbols-alist rust-prettify-symbols-alist) 1621 | (setq prettify-symbols-compose-predicate #'rust--prettify-symbols-compose-p) 1622 | 1623 | (add-hook 'before-save-hook rust-before-save-hook nil t) 1624 | (add-hook 'after-save-hook rust-after-save-hook nil t)) 1625 | 1626 | (provide 'rust-prog-mode) 1627 | ;;; rust-prog-mode.el ends here 1628 | --------------------------------------------------------------------------------