aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/dev/README.md12
-rw-r--r--docs/user/README.md18
-rw-r--r--editors/emacs/rust-analyzer.el295
3 files changed, 12 insertions, 313 deletions
diff --git a/docs/dev/README.md b/docs/dev/README.md
index d30727786..732e4bdd3 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -43,7 +43,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0
43 43
44We use GitHub Actions for CI. Most of the things, including formatting, are checked by 44We use GitHub Actions for CI. Most of the things, including formatting, are checked by
45`cargo test` so, if `cargo test` passes locally, that's a good sign that CI will 45`cargo test` so, if `cargo test` passes locally, that's a good sign that CI will
46be green as well. The only exception is that long-running by default a skipped locally. 46be green as well. The only exception is that some long-running tests are skipped locally by default.
47Use `env RUN_SLOW_TESTS=1 cargo test` to run the full suite. 47Use `env RUN_SLOW_TESTS=1 cargo test` to run the full suite.
48 48
49We use bors-ng to enforce the [not rocket science](https://graydon2.dreamwidth.org/1597.html) rule. 49We use bors-ng to enforce the [not rocket science](https://graydon2.dreamwidth.org/1597.html) rule.
@@ -54,9 +54,9 @@ You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rus
54 54
55All Rust code lives in the `crates` top-level directory, and is organized as a 55All Rust code lives in the `crates` top-level directory, and is organized as a
56single Cargo workspace. The `editors` top-level directory contains code for 56single Cargo workspace. The `editors` top-level directory contains code for
57integrating with editors. Currently, it contains plugins for VS Code (in 57integrating with editors. Currently, it contains the plugin for VS Code (in
58typescript) and Emacs (in elisp). The `docs` top-level directory contains both 58typescript). The `docs` top-level directory contains both developer and user
59developer and user documentation. 59documentation.
60 60
61We have some automation infra in Rust in the `xtask` package. It contains 61We have some automation infra in Rust in the `xtask` package. It contains
62stuff like formatting checking, code generation and powers `cargo xtask install`. 62stuff like formatting checking, code generation and powers `cargo xtask install`.
@@ -107,8 +107,8 @@ If I need to fix something simultaneously in the server and in the client, I
107feel even more sad. I don't have a specific workflow for this case. 107feel even more sad. I don't have a specific workflow for this case.
108 108
109Additionally, I use `cargo run --release -p ra_cli -- analysis-stats 109Additionally, I use `cargo run --release -p ra_cli -- analysis-stats
110path/to/some/rust/crate` to run a batch analysis. This is primaraly useful for 110path/to/some/rust/crate` to run a batch analysis. This is primarily useful for
111performance optimiations, or for bug minimization. 111performance optimizations, or for bug minimization.
112 112
113# Logging 113# Logging
114 114
diff --git a/docs/user/README.md b/docs/user/README.md
index 18867cd11..da99a063c 100644
--- a/docs/user/README.md
+++ b/docs/user/README.md
@@ -5,8 +5,7 @@ install lsp server, clone the repository and then run `cargo xtask install
5./crates/ra_lsp_server`). This will produce a binary named `ra_lsp_server` which 5./crates/ra_lsp_server`). This will produce a binary named `ra_lsp_server` which
6you should be able to use it with any LSP-compatible editor. We use custom 6you should be able to use it with any LSP-compatible editor. We use custom
7extensions to LSP, so special client-side support is required to take full 7extensions to LSP, so special client-side support is required to take full
8advantage of rust-analyzer. This repository contains support code for VS Code 8advantage of rust-analyzer. This repository contains support code for VS Code.
9and Emacs.
10 9
11``` 10```
12$ git clone [email protected]:rust-analyzer/rust-analyzer && cd rust-analyzer 11$ git clone [email protected]:rust-analyzer/rust-analyzer && cd rust-analyzer
@@ -130,17 +129,12 @@ host.
130 129
131## Emacs 130## Emacs
132 131
133Prerequisites: 132* install recent version of `emacs-lsp` package by following the instructions [here][emacs-lsp]
134 133* set `lsp-rust-server` to `'rust-analyzer`
135`emacs-lsp`, `dash` and `ht` packages. 134* run `lsp` in a Rust buffer
136 135* (Optionally) bind commands like `lsp-rust-analyzer-join-lines`, `lsp-extend-selection` and `lsp-rust-analyzer-expand-macro` to keys
137Installation:
138 136
139* add 137[emacs-lsp]: https://github.com/emacs-lsp/lsp-mode
140[rust-analyzer.el](../../editors/emacs/rust-analyzer.el)
141to load path and require it in `init.el`
142* run `lsp` in a rust buffer
143* (Optionally) bind commands like `rust-analyzer-join-lines`, `rust-analyzer-extend-selection` and `rust-analyzer-expand-macro` to keys, and enable `rust-analyzer-inlay-hints-mode` to get inline type hints
144 138
145 139
146## Vim and NeoVim (coc-rust-analyzer) 140## Vim and NeoVim (coc-rust-analyzer)
diff --git a/editors/emacs/rust-analyzer.el b/editors/emacs/rust-analyzer.el
deleted file mode 100644
index 9b426fcae..000000000
--- a/editors/emacs/rust-analyzer.el
+++ /dev/null
@@ -1,295 +0,0 @@
1;;; rust-analyzer.el --- Rust analyzer emacs bindings for emacs-lsp -*- lexical-binding: t; -*-
2;;; Code:
3
4(require 'lsp)
5(require 'dash)
6(require 'ht)
7
8;; This currently
9;; - sets up rust-analyzer with emacs-lsp, giving
10;; - code actions
11;; - completion (use company-lsp for proper snippet support)
12;; - imenu support
13;; - on-type formatting
14;; - 'hover' type information & documentation (with lsp-ui)
15;; - implements source changes (for code actions etc.), except for file system changes
16;; - implements joinLines (you need to bind rust-analyzer-join-lines to a key)
17;; - implements selectionRanges (either bind lsp-extend-selection to a key, or use expand-region)
18;; - provides rust-analyzer-inlay-hints-mode for inline type hints
19;; - provides rust-analyzer-expand-macro to expand macros
20
21;; What's missing:
22;; - file system changes in apply-source-change
23;; - semantic highlighting
24;; - onEnter, parentModule, findMatchingBrace
25;; - runnables
26;; - the debugging commands (syntaxTree and analyzerStatus)
27;; - more
28
29;; Also, there's a problem with company-lsp's caching being too eager, sometimes
30;; resulting in outdated completions.
31
32(defcustom rust-analyzer-command '("ra_lsp_server")
33 ""
34 :type '(repeat (string)))
35
36(defconst rust-analyzer--notification-handlers
37 '(("rust-analyzer/publishDecorations" . (lambda (_w _p)))))
38
39(defconst rust-analyzer--action-handlers
40 '(("rust-analyzer.applySourceChange" .
41 (lambda (p) (rust-analyzer--apply-source-change-command p)))
42 ("rust-analyzer.selectAndApplySourceChange" .
43 (lambda (p) (rust-analyzer--select-and-apply-source-change-command p)))))
44
45(defun rust-analyzer--uri-filename (text-document)
46 (lsp--uri-to-path (gethash "uri" text-document)))
47
48(defun rust-analyzer--goto-lsp-loc (loc)
49 (-let (((&hash "line" "character") loc))
50 (goto-line (1+ line))
51 (move-to-column character)))
52
53(defun rust-analyzer--apply-text-document-edit (edit)
54 "Like lsp--apply-text-document-edit, but it allows nil version."
55 (let* ((ident (gethash "textDocument" edit))
56 (filename (rust-analyzer--uri-filename ident))
57 (version (gethash "version" ident)))
58 (with-current-buffer (find-file-noselect filename)
59 (when (or (not version) (= version (lsp--cur-file-version)))
60 (lsp--apply-text-edits (gethash "edits" edit))))))
61
62(defun rust-analyzer--apply-source-change (data)
63 ;; TODO fileSystemEdits
64 (seq-doseq (it (-> data (ht-get "workspaceEdit") (ht-get "documentChanges")))
65 (rust-analyzer--apply-text-document-edit it))
66 (-when-let (cursor-position (ht-get data "cursorPosition"))
67 (let ((filename (rust-analyzer--uri-filename (ht-get cursor-position "textDocument")))
68 (position (ht-get cursor-position "position")))
69 (find-file filename)
70 (rust-analyzer--goto-lsp-loc position))))
71
72(defun rust-analyzer--apply-source-change-command (p)
73 (let ((data (-> p (ht-get "arguments") (lsp-seq-first))))
74 (rust-analyzer--apply-source-change data)))
75
76(defun rust-analyzer--select-and-apply-source-change-command (p)
77 (let* ((options (-> p (ht-get "arguments") (lsp-seq-first)))
78 (chosen-option (lsp--completing-read "Select option:" options
79 (-lambda ((&hash "label")) label))))
80 (rust-analyzer--apply-source-change chosen-option)))
81
82(lsp-register-client
83 (make-lsp-client
84 :new-connection (lsp-stdio-connection (lambda () rust-analyzer-command))
85 :notification-handlers (ht<-alist rust-analyzer--notification-handlers)
86 :action-handlers (ht<-alist rust-analyzer--action-handlers)
87 :major-modes '(rust-mode)
88 :ignore-messages nil
89 :server-id 'rust-analyzer))
90
91(defun rust-analyzer--initialized? ()
92 (when-let ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name))))
93 (eq 'initialized (lsp--workspace-status workspace))))
94
95(with-eval-after-load 'company-lsp
96 ;; company-lsp provides a snippet handler for rust by default that adds () after function calls, which RA does better
97 (setq company-lsp--snippet-functions (cl-delete "rust" company-lsp--snippet-functions :key #'car :test #'equal)))
98
99;; join lines
100
101(defun rust-analyzer--join-lines-params ()
102 "Join lines params."
103 (list :textDocument (lsp--text-document-identifier)
104 :range (if (use-region-p)
105 (lsp--region-to-range (region-beginning) (region-end))
106 (lsp--region-to-range (point) (point)))))
107
108(defun rust-analyzer-join-lines ()
109 (interactive)
110 (->
111 (lsp-send-request (lsp-make-request "rust-analyzer/joinLines"
112 (rust-analyzer--join-lines-params)))
113 (rust-analyzer--apply-source-change)))
114
115;; selection ranges
116
117(defun rust-analyzer--add-er-expansion ()
118 (make-variable-buffer-local 'er/try-expand-list)
119 (setq er/try-expand-list (append
120 er/try-expand-list
121 '(lsp-extend-selection))))
122
123(with-eval-after-load 'expand-region
124 ;; add the expansion for all existing rust-mode buffers. If expand-region is
125 ;; loaded lazily, it might be loaded when the first rust buffer is opened, and
126 ;; then it's too late for the hook for that buffer
127 (dolist (buf (buffer-list))
128 (with-current-buffer buf
129 (when (eq 'rust-mode major-mode)
130 (rust-analyzer--add-er-expansion))))
131 (add-hook 'rust-mode-hook 'rust-analyzer--add-er-expansion))
132
133;; runnables
134(defvar rust-analyzer--last-runnable nil)
135
136(defun rust-analyzer--runnables-params ()
137 (list :textDocument (lsp--text-document-identifier)
138 :position (lsp--cur-position)))
139
140(defun rust-analyzer--runnables ()
141 (lsp-send-request (lsp-make-request "rust-analyzer/runnables"
142 (rust-analyzer--runnables-params))))
143
144(defun rust-analyzer--select-runnable ()
145 (lsp--completing-read
146 "Select runnable:"
147 (if rust-analyzer--last-runnable
148 (cons rust-analyzer--last-runnable (rust-analyzer--runnables))
149 (rust-analyzer--runnables))
150 (-lambda ((&hash "label")) label)))
151
152(defun rust-analyzer-run (runnable)
153 (interactive (list (rust-analyzer--select-runnable)))
154 (-let* (((&hash "env" "bin" "args" "label") runnable)
155 (compilation-environment (-map (-lambda ((k v)) (concat k "=" v)) (ht-items env))))
156 (compilation-start
157 (string-join (append (list bin) args '()) " ")
158 ;; cargo-process-mode is nice, but try to work without it...
159 (if (functionp 'cargo-process-mode) 'cargo-process-mode nil)
160 (lambda (_) (concat "*" label "*")))
161 (setq rust-analyzer--last-runnable runnable)))
162
163(defun rust-analyzer-rerun (&optional runnable)
164 (interactive (list (or rust-analyzer--last-runnable
165 (rust-analyzer--select-runnable))))
166 (rust-analyzer-run (or runnable rust-analyzer--last-runnable)))
167
168;; analyzer status buffer
169(define-derived-mode rust-analyzer-status-mode special-mode "Rust-Analyzer-Status"
170 "Mode for the rust-analyzer status buffer.")
171
172(defvar-local rust-analyzer--status-buffer-workspace nil)
173
174(defun rust-analyzer-status ()
175 "Displays status information for rust-analyzer."
176 (interactive)
177 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
178 (buf (get-buffer-create (concat "*rust-analyzer status " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
179 (with-current-buffer buf
180 (rust-analyzer-status-mode)
181 (setq rust-analyzer--status-buffer-workspace workspace)
182 (rust-analyzer-status-buffer-refresh))
183 (pop-to-buffer buf)))
184
185(defun rust-analyzer-status-buffer-refresh ()
186 (interactive)
187 (when rust-analyzer--status-buffer-workspace
188 (let ((inhibit-read-only t))
189 (erase-buffer)
190 (insert (with-lsp-workspace rust-analyzer--status-buffer-workspace
191 (lsp-send-request (lsp-make-request
192 "rust-analyzer/analyzerStatus")))))))
193
194
195(defun rust-analyzer--syntax-tree-params ()
196 "Syntax tree params."
197 (list :textDocument (lsp--text-document-identifier)
198 :range (if (use-region-p)
199 (lsp--region-to-range (region-beginning) (region-end))
200 (lsp--region-to-range (point-min) (point-max)))))
201
202(defun rust-analyzer-syntax-tree ()
203 "Displays syntax tree for current buffer."
204 (interactive)
205 (when (eq 'rust-mode major-mode)
206 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
207 (buf (get-buffer-create (concat "*rust-analyzer syntax tree " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
208 (when workspace
209 (let ((parse-result (with-lsp-workspace workspace
210 (lsp-send-request (lsp-make-request
211 "rust-analyzer/syntaxTree"
212 (rust-analyzer--syntax-tree-params))))))
213 (with-current-buffer buf
214 (let ((inhibit-read-only t))
215 (erase-buffer)
216 (insert parse-result)))
217 (pop-to-buffer buf))))))
218
219;; inlay hints
220(defun rust-analyzer--update-inlay-hints (buffer)
221 (if (and (rust-analyzer--initialized?) (eq buffer (current-buffer)))
222 (lsp-request-async
223 "rust-analyzer/inlayHints"
224 (list :textDocument (lsp--text-document-identifier))
225 (lambda (res)
226 (remove-overlays (point-min) (point-max) 'rust-analyzer--inlay-hint t)
227 (dolist (hint res)
228 (-let* (((&hash "range" "label" "kind") hint)
229 ((beg . end) (lsp--range-to-region range))
230 (overlay (make-overlay beg end)))
231 (overlay-put overlay 'rust-analyzer--inlay-hint t)
232 (overlay-put overlay 'evaporate t)
233 (cond
234 ((string= kind "TypeHint")
235 (overlay-put overlay 'after-string (propertize (concat ": " label)
236 'font-lock-face 'font-lock-comment-face)))
237 ((string= kind "ParameterHint")
238 (overlay-put overlay 'before-string (propertize (concat label ": ")
239 'font-lock-face 'font-lock-comment-face)))
240 )
241 )))
242 :mode 'tick))
243 nil)
244
245(defvar-local rust-analyzer--inlay-hints-timer nil)
246
247(defun rust-analyzer--inlay-hints-change-handler (&rest rest)
248 (when rust-analyzer--inlay-hints-timer
249 (cancel-timer rust-analyzer--inlay-hints-timer))
250 (setq rust-analyzer--inlay-hints-timer
251 (run-with-idle-timer 0.1 nil #'rust-analyzer--update-inlay-hints (current-buffer))))
252
253(define-minor-mode rust-analyzer-inlay-hints-mode
254 "Mode for showing inlay hints."
255 nil nil nil
256 (cond
257 (rust-analyzer-inlay-hints-mode
258 (rust-analyzer--update-inlay-hints (current-buffer))
259 (add-hook 'lsp-after-initialize-hook #'rust-analyzer--inlay-hints-change-handler nil t)
260 (add-hook 'after-change-functions #'rust-analyzer--inlay-hints-change-handler nil t))
261 (t
262 (remove-overlays (point-min) (point-max) 'rust-analyzer--inlay-hint t)
263 (remove-hook 'lsp-after-initialize-hook #'rust-analyzer--inlay-hints-change-handler t)
264 (remove-hook 'after-change-functions #'rust-analyzer--inlay-hints-change-handler t))))
265
266
267
268;; expand macros
269(defun rust-analyzer-expand-macro ()
270 "Expands the macro call at point recursively."
271 (interactive)
272 (when (eq 'rust-mode major-mode)
273 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
274 (params (list :textDocument (lsp--text-document-identifier)
275 :position (lsp--cur-position))))
276 (when workspace
277 (let* ((response (with-lsp-workspace workspace
278 (lsp-send-request (lsp-make-request
279 "rust-analyzer/expandMacro"
280 params))))
281 (result (when response (ht-get response "expansion"))))
282 (if result
283 (let ((buf (get-buffer-create (concat "*rust-analyzer macro expansion " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
284 (with-current-buffer buf
285 (let ((inhibit-read-only t))
286 (erase-buffer)
287 (insert result)
288 (setq buffer-read-only t)
289 (special-mode)))
290 (pop-to-buffer buf))
291 (message "No macro found at point, or it could not be expanded")))))))
292
293
294(provide 'rust-analyzer)
295;;; rust-analyzer.el ends here