summaryrefslogtreecommitdiff
path: root/rust-cargo.el
blob: 613b8610785b4f20af540b02da63766e2a301ddf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
;;; rust-cargo.el --- Support for cargo                -*-lexical-binding: t-*-
;;; Commentary:

;; This library implements support for running `cargo'.

;;; Code:

(require 'json)

;;; Options

(defcustom rust-cargo-bin "cargo"
  "Path to cargo executable."
  :type 'string
  :group 'rust-mode)

(defcustom rust-always-locate-project-on-open nil
  "Whether to run `cargo locate-project' every time `rust-mode' is activated."
  :type 'boolean
  :group 'rust-mode)

(defcustom rust-locate-project-in-workspace t
  "Whether to use `--workspace` with `cargo locate-project`. If t,
 rust-mode will run commands for the entire workspace. If nil,
 rust will search for the Cargo.toml in the local crated"
  :type 'boolean
  :group 'rust-mode)

(defcustom rust-cargo-default-arguments ""
  "Default arguments when running common cargo commands."
  :type 'string
  :group 'rust-mode)

;;; Buffer Project

(defvar-local rust-buffer-project nil)

(defun rust-buffer-project ()
  "Get project root if possible."
  ;; Copy environment variables into the new buffer, since
  ;; with-temp-buffer will re-use the variables' defaults, even if
  ;; they have been changed in this variable using e.g. envrc-mode.
  ;; See https://github.com/purcell/envrc/issues/12.
  (let ((env process-environment)
        (path exec-path))
    (with-temp-buffer
      ;; Copy the entire environment just in case there's something we
      ;; don't know we need.
      (setq-local process-environment env)
      ;; Set PATH so we can find cargo.
      (setq-local exec-path path)
      (let ((ret
             (let ((args (list rust-cargo-bin nil (list (current-buffer) nil) nil "locate-project")))
               (when rust-locate-project-in-workspace
                 (setq args (append args (list "--workspace"))))
                 (apply #'process-file args))))
        (when (/= ret 0)
          (error "`cargo locate-project' returned %s status: %s" ret (buffer-string)))
        (goto-char 0)
        (let ((output (let ((json-object-type 'alist))
                        (json-read))))
          (cdr (assoc-string "root" output)))))))

(defun rust-buffer-crate ()
  "Try to locate Cargo.toml using `locate-dominating-file'."
  (let ((dir (locate-dominating-file default-directory "Cargo.toml")))
    (if dir dir default-directory)))

(defun rust-update-buffer-project ()
  (setq-local rust-buffer-project (rust-buffer-project)))

(defun rust-maybe-initialize-buffer-project ()
  (setq-local rust-buffer-project nil)
  (when rust-always-locate-project-on-open
    (rust-update-buffer-project)))

(add-hook 'rust-mode-hook #'rust-maybe-initialize-buffer-project)

;;; Internal

(defun rust--compile (format-string &rest args)
  (when (null rust-buffer-project)
    (rust-update-buffer-project))
  (let ((default-directory
          (or (and rust-buffer-project
                   (file-name-directory rust-buffer-project))
              default-directory)))
    (compile (apply #'format format-string args))))

;;; Commands

(defun rust-check ()
  "Compile using `cargo check`"
  (interactive)
  (rust--compile "%s check %s" rust-cargo-bin rust-cargo-default-arguments))

(defun rust-compile ()
  "Compile using `cargo build`"
  (interactive)
  (rust--compile "%s build %s" rust-cargo-bin rust-cargo-default-arguments))

(defun rust-compile-release ()
  "Compile using `cargo build --release`"
  (interactive)
  (rust--compile "%s build --release" rust-cargo-bin))

(defun rust-run ()
  "Run using `cargo run`"
  (interactive)
  (rust--compile "%s run %s" rust-cargo-bin rust-cargo-default-arguments))

(defun rust-run-release ()
  "Run using `cargo run --release`"
  (interactive)
  (rust--compile "%s run --release" rust-cargo-bin))

(defun rust-test ()
  "Test using `cargo test`"
  (interactive)
  (rust--compile "%s test %s" rust-cargo-bin rust-cargo-default-arguments))

(defun rust-run-clippy ()
  "Run `cargo clippy'."
  (interactive)
  (when (null rust-buffer-project)
    (rust-update-buffer-project))
  (let* ((args (list rust-cargo-bin "clippy"
                     (concat "--manifest-path=" rust-buffer-project)))
         ;; set `compile-command' temporarily so `compile' doesn't
         ;; clobber the existing value
         (compile-command (mapconcat #'shell-quote-argument args " ")))
    (rust--compile compile-command)))

;;; _
(provide 'rust-cargo)
;;; rust-cargo.el ends here