aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/emacs/rust-analyzer.el295
1 files changed, 0 insertions, 295 deletions
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