diff options
-rw-r--r-- | docs/dev/README.md | 12 | ||||
-rw-r--r-- | docs/user/README.md | 18 | ||||
-rw-r--r-- | editors/emacs/rust-analyzer.el | 295 |
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 | ||
44 | We use GitHub Actions for CI. Most of the things, including formatting, are checked by | 44 | We 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 |
46 | be green as well. The only exception is that long-running by default a skipped locally. | 46 | be green as well. The only exception is that some long-running tests are skipped locally by default. |
47 | Use `env RUN_SLOW_TESTS=1 cargo test` to run the full suite. | 47 | Use `env RUN_SLOW_TESTS=1 cargo test` to run the full suite. |
48 | 48 | ||
49 | We use bors-ng to enforce the [not rocket science](https://graydon2.dreamwidth.org/1597.html) rule. | 49 | We 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 | ||
55 | All Rust code lives in the `crates` top-level directory, and is organized as a | 55 | All Rust code lives in the `crates` top-level directory, and is organized as a |
56 | single Cargo workspace. The `editors` top-level directory contains code for | 56 | single Cargo workspace. The `editors` top-level directory contains code for |
57 | integrating with editors. Currently, it contains plugins for VS Code (in | 57 | integrating with editors. Currently, it contains the plugin for VS Code (in |
58 | typescript) and Emacs (in elisp). The `docs` top-level directory contains both | 58 | typescript). The `docs` top-level directory contains both developer and user |
59 | developer and user documentation. | 59 | documentation. |
60 | 60 | ||
61 | We have some automation infra in Rust in the `xtask` package. It contains | 61 | We have some automation infra in Rust in the `xtask` package. It contains |
62 | stuff like formatting checking, code generation and powers `cargo xtask install`. | 62 | stuff 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 | |||
107 | feel even more sad. I don't have a specific workflow for this case. | 107 | feel even more sad. I don't have a specific workflow for this case. |
108 | 108 | ||
109 | Additionally, I use `cargo run --release -p ra_cli -- analysis-stats | 109 | Additionally, I use `cargo run --release -p ra_cli -- analysis-stats |
110 | path/to/some/rust/crate` to run a batch analysis. This is primaraly useful for | 110 | path/to/some/rust/crate` to run a batch analysis. This is primarily useful for |
111 | performance optimiations, or for bug minimization. | 111 | performance 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 |
6 | you should be able to use it with any LSP-compatible editor. We use custom | 6 | you should be able to use it with any LSP-compatible editor. We use custom |
7 | extensions to LSP, so special client-side support is required to take full | 7 | extensions to LSP, so special client-side support is required to take full |
8 | advantage of rust-analyzer. This repository contains support code for VS Code | 8 | advantage of rust-analyzer. This repository contains support code for VS Code. |
9 | and 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 | ||
133 | Prerequisites: | 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 | |
137 | Installation: | ||
138 | 136 | ||
139 | * add | 137 | [emacs-lsp]: https://github.com/emacs-lsp/lsp-mode |
140 | [rust-analyzer.el](../../editors/emacs/rust-analyzer.el) | ||
141 | to 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 | ||