aboutsummaryrefslogtreecommitdiff
path: root/editors/emacs/rust-analyzer.el
diff options
context:
space:
mode:
Diffstat (limited to 'editors/emacs/rust-analyzer.el')
-rw-r--r--editors/emacs/rust-analyzer.el286
1 files changed, 0 insertions, 286 deletions
diff --git a/editors/emacs/rust-analyzer.el b/editors/emacs/rust-analyzer.el
deleted file mode 100644
index 06db4f15f..000000000
--- a/editors/emacs/rust-analyzer.el
+++ /dev/null
@@ -1,286 +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
43(defun rust-analyzer--uri-filename (text-document)
44 (lsp--uri-to-path (gethash "uri" text-document)))
45
46(defun rust-analyzer--goto-lsp-loc (loc)
47 (-let (((&hash "line" "character") loc))
48 (goto-line (1+ line))
49 (move-to-column character)))
50
51(defun rust-analyzer--apply-text-document-edit (edit)
52 "Like lsp--apply-text-document-edit, but it allows nil version."
53 (let* ((ident (gethash "textDocument" edit))
54 (filename (rust-analyzer--uri-filename ident))
55 (version (gethash "version" ident)))
56 (with-current-buffer (find-file-noselect filename)
57 (when (or (not version) (= version (lsp--cur-file-version)))
58 (lsp--apply-text-edits (gethash "edits" edit))))))
59
60(defun rust-analyzer--apply-source-change (data)
61 ;; TODO fileSystemEdits
62 (seq-doseq (it (-> data (ht-get "workspaceEdit") (ht-get "documentChanges")))
63 (rust-analyzer--apply-text-document-edit it))
64 (-when-let (cursor-position (ht-get data "cursorPosition"))
65 (let ((filename (rust-analyzer--uri-filename (ht-get cursor-position "textDocument")))
66 (position (ht-get cursor-position "position")))
67 (find-file filename)
68 (rust-analyzer--goto-lsp-loc position))))
69
70(defun rust-analyzer--apply-source-change-command (p)
71 (let ((data (-> p (ht-get "arguments") (lsp-seq-first))))
72 (rust-analyzer--apply-source-change data)))
73
74(lsp-register-client
75 (make-lsp-client
76 :new-connection (lsp-stdio-connection (lambda () rust-analyzer-command))
77 :notification-handlers (ht<-alist rust-analyzer--notification-handlers)
78 :action-handlers (ht<-alist rust-analyzer--action-handlers)
79 :major-modes '(rust-mode)
80 :ignore-messages nil
81 :server-id 'rust-analyzer))
82
83(defun rust-analyzer--initialized? ()
84 (when-let ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name))))
85 (eq 'initialized (lsp--workspace-status workspace))))
86
87(with-eval-after-load 'company-lsp
88 ;; company-lsp provides a snippet handler for rust by default that adds () after function calls, which RA does better
89 (setq company-lsp--snippet-functions (cl-delete "rust" company-lsp--snippet-functions :key #'car :test #'equal)))
90
91;; join lines
92
93(defun rust-analyzer--join-lines-params ()
94 "Join lines params."
95 (list :textDocument (lsp--text-document-identifier)
96 :range (if (use-region-p)
97 (lsp--region-to-range (region-beginning) (region-end))
98 (lsp--region-to-range (point) (point)))))
99
100(defun rust-analyzer-join-lines ()
101 (interactive)
102 (->
103 (lsp-send-request (lsp-make-request "rust-analyzer/joinLines"
104 (rust-analyzer--join-lines-params)))
105 (rust-analyzer--apply-source-change)))
106
107;; selection ranges
108
109(defun rust-analyzer--add-er-expansion ()
110 (make-variable-buffer-local 'er/try-expand-list)
111 (setq er/try-expand-list (append
112 er/try-expand-list
113 '(lsp-extend-selection))))
114
115(with-eval-after-load 'expand-region
116 ;; add the expansion for all existing rust-mode buffers. If expand-region is
117 ;; loaded lazily, it might be loaded when the first rust buffer is opened, and
118 ;; then it's too late for the hook for that buffer
119 (dolist (buf (buffer-list))
120 (with-current-buffer buf
121 (when (eq 'rust-mode major-mode)
122 (rust-analyzer--add-er-expansion))))
123 (add-hook 'rust-mode-hook 'rust-analyzer--add-er-expansion))
124
125;; runnables
126(defvar rust-analyzer--last-runnable nil)
127
128(defun rust-analyzer--runnables-params ()
129 (list :textDocument (lsp--text-document-identifier)
130 :position (lsp--cur-position)))
131
132(defun rust-analyzer--runnables ()
133 (lsp-send-request (lsp-make-request "rust-analyzer/runnables"
134 (rust-analyzer--runnables-params))))
135
136(defun rust-analyzer--select-runnable ()
137 (lsp--completing-read
138 "Select runnable:"
139 (if rust-analyzer--last-runnable
140 (cons rust-analyzer--last-runnable (rust-analyzer--runnables))
141 (rust-analyzer--runnables))
142 (-lambda ((&hash "label")) label)))
143
144(defun rust-analyzer-run (runnable)
145 (interactive (list (rust-analyzer--select-runnable)))
146 (-let (((&hash "env" "bin" "args" "label") runnable))
147 (compilation-start
148 (string-join (append (list bin) args '()) " ")
149 ;; cargo-process-mode is nice, but try to work without it...
150 (if (functionp 'cargo-process-mode) 'cargo-process-mode nil)
151 (lambda (_) (concat "*" label "*")))
152 (setq rust-analyzer--last-runnable runnable)))
153
154(defun rust-analyzer-rerun (&optional runnable)
155 (interactive (list (or rust-analyzer--last-runnable
156 (rust-analyzer--select-runnable))))
157 (rust-analyzer-run (or runnable rust-analyzer--last-runnable)))
158
159;; analyzer status buffer
160(define-derived-mode rust-analyzer-status-mode special-mode "Rust-Analyzer-Status"
161 "Mode for the rust-analyzer status buffer.")
162
163(defvar-local rust-analyzer--status-buffer-workspace nil)
164
165(defun rust-analyzer-status ()
166 "Displays status information for rust-analyzer."
167 (interactive)
168 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
169 (buf (get-buffer-create (concat "*rust-analyzer status " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
170 (with-current-buffer buf
171 (rust-analyzer-status-mode)
172 (setq rust-analyzer--status-buffer-workspace workspace)
173 (rust-analyzer-status-buffer-refresh))
174 (pop-to-buffer buf)))
175
176(defun rust-analyzer-status-buffer-refresh ()
177 (interactive)
178 (when rust-analyzer--status-buffer-workspace
179 (let ((inhibit-read-only t))
180 (erase-buffer)
181 (insert (with-lsp-workspace rust-analyzer--status-buffer-workspace
182 (lsp-send-request (lsp-make-request
183 "rust-analyzer/analyzerStatus")))))))
184
185
186(defun rust-analyzer--syntax-tree-params ()
187 "Syntax tree params."
188 (list :textDocument (lsp--text-document-identifier)
189 :range (if (use-region-p)
190 (lsp--region-to-range (region-beginning) (region-end))
191 (lsp--region-to-range (point-min) (point-max)))))
192
193(defun rust-analyzer-syntax-tree ()
194 "Displays syntax tree for current buffer."
195 (interactive)
196 (when (eq 'rust-mode major-mode)
197 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
198 (buf (get-buffer-create (concat "*rust-analyzer syntax tree " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
199 (when workspace
200 (let ((parse-result (with-lsp-workspace workspace
201 (lsp-send-request (lsp-make-request
202 "rust-analyzer/syntaxTree"
203 (rust-analyzer--syntax-tree-params))))))
204 (with-current-buffer buf
205 (let ((inhibit-read-only t))
206 (erase-buffer)
207 (insert parse-result)))
208 (pop-to-buffer buf))))))
209
210;; inlay hints
211(defun rust-analyzer--update-inlay-hints (buffer)
212 (if (and (rust-analyzer--initialized?) (eq buffer (current-buffer)))
213 (lsp-request-async
214 "rust-analyzer/inlayHints"
215 (list :textDocument (lsp--text-document-identifier))
216 (lambda (res)
217 (remove-overlays (point-min) (point-max) 'rust-analyzer--inlay-hint t)
218 (dolist (hint res)
219 (-let* (((&hash "range" "label" "kind") hint)
220 ((beg . end) (lsp--range-to-region range))
221 (overlay (make-overlay beg end)))
222 (overlay-put overlay 'rust-analyzer--inlay-hint t)
223 (overlay-put overlay 'evaporate t)
224 (cond
225 ((string= kind "TypeHint")
226 (overlay-put overlay 'after-string (propertize (concat ": " label)
227 'font-lock-face 'font-lock-comment-face)))
228 ((string= kind "ParameterHint")
229 (overlay-put overlay 'before-string (propertize (concat label ": ")
230 'font-lock-face 'font-lock-comment-face)))
231 )
232 )))
233 :mode 'tick))
234 nil)
235
236(defvar-local rust-analyzer--inlay-hints-timer nil)
237
238(defun rust-analyzer--inlay-hints-change-handler (&rest rest)
239 (when rust-analyzer--inlay-hints-timer
240 (cancel-timer rust-analyzer--inlay-hints-timer))
241 (setq rust-analyzer--inlay-hints-timer
242 (run-with-idle-timer 0.1 nil #'rust-analyzer--update-inlay-hints (current-buffer))))
243
244(define-minor-mode rust-analyzer-inlay-hints-mode
245 "Mode for showing inlay hints."
246 nil nil nil
247 (cond
248 (rust-analyzer-inlay-hints-mode
249 (rust-analyzer--update-inlay-hints (current-buffer))
250 (add-hook 'lsp-after-initialize-hook #'rust-analyzer--inlay-hints-change-handler nil t)
251 (add-hook 'after-change-functions #'rust-analyzer--inlay-hints-change-handler nil t))
252 (t
253 (remove-overlays (point-min) (point-max) 'rust-analyzer--inlay-hint t)
254 (remove-hook 'lsp-after-initialize-hook #'rust-analyzer--inlay-hints-change-handler t)
255 (remove-hook 'after-change-functions #'rust-analyzer--inlay-hints-change-handler t))))
256
257
258
259;; expand macros
260(defun rust-analyzer-expand-macro ()
261 "Expands the macro call at point recursively."
262 (interactive)
263 (when (eq 'rust-mode major-mode)
264 (let* ((workspace (lsp-find-workspace 'rust-analyzer (buffer-file-name)))
265 (params (list :textDocument (lsp--text-document-identifier)
266 :position (lsp--cur-position))))
267 (when workspace
268 (let* ((response (with-lsp-workspace workspace
269 (lsp-send-request (lsp-make-request
270 "rust-analyzer/expandMacro"
271 params))))
272 (result (when response (ht-get response "expansion"))))
273 (if result
274 (let ((buf (get-buffer-create (concat "*rust-analyzer macro expansion " (with-lsp-workspace workspace (lsp-workspace-root)) "*"))))
275 (with-current-buffer buf
276 (let ((inhibit-read-only t))
277 (erase-buffer)
278 (insert result)
279 (setq buffer-read-only t)
280 (special-mode)))
281 (pop-to-buffer buf))
282 (message "No macro found at point, or it could not be expanded")))))))
283
284
285(provide 'rust-analyzer)
286;;; rust-analyzer.el ends here