aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/emacs/ra-emacs-lsp.el139
1 files changed, 139 insertions, 0 deletions
diff --git a/editors/emacs/ra-emacs-lsp.el b/editors/emacs/ra-emacs-lsp.el
new file mode 100644
index 000000000..b13068ee2
--- /dev/null
+++ b/editors/emacs/ra-emacs-lsp.el
@@ -0,0 +1,139 @@
1;;; ra-emacs-lsp.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 extendSelection (either bind rust-analyzer-extend-selection to a key, or use expand-region)
18
19;; What's missing:
20;; - file system changes in apply-source-change
21;; - semantic highlighting
22;; - onEnter, parentModule, findMatchingBrace
23;; - runnables
24;; - the debugging commands (syntaxTree and analyzerStatus)
25;; - lsp-ui doesn't interpret the markdown we return currently and instead displays it raw (https://github.com/emacs-lsp/lsp-ui/issues/220 )
26;; - more
27
28;; Also, there's a problem with company-lsp's caching being too eager, sometimes
29;; resulting in outdated completions.
30
31(defcustom rust-analyzer-command '("ra_lsp_server")
32 ""
33 :type '(repeat (string)))
34
35(defconst rust-analyzer--notification-handlers
36 '(("rust-analyzer/publishDecorations" . (lambda (_w _p)))))
37
38(defconst rust-analyzer--action-handlers
39 '(("rust-analyzer.applySourceChange" .
40 (lambda (p) (rust-analyzer--apply-source-change-command p)))))
41
42(defun rust-analyzer--uri-filename (text-document)
43 (lsp--uri-to-path (gethash "uri" text-document)))
44
45(defun rust-analyzer--goto-lsp-loc (loc)
46 (-let (((&hash "line" "character") loc))
47 (goto-line (1+ line))
48 (move-to-column character)))
49
50(defun rust-analyzer--apply-text-document-edit (edit)
51 "Like lsp--apply-text-document-edit, but it allows nil version."
52 (let* ((ident (gethash "textDocument" edit))
53 (filename (rust-analyzer--uri-filename ident))
54 (version (gethash "version" ident)))
55 (with-current-buffer (find-file-noselect filename)
56 (when (or (not version) (= version (lsp--cur-file-version)))
57 (lsp--apply-text-edits (gethash "edits" edit))))))
58
59(defun rust-analyzer--apply-source-change (data)
60 ;; TODO fileSystemEdits
61 (--each (-> data (ht-get "workspaceEdit") (ht-get "documentChanges"))
62 (rust-analyzer--apply-text-document-edit it))
63 (-when-let (cursor-position (ht-get data "cursorPosition"))
64 (let ((filename (rust-analyzer--uri-filename (ht-get cursor-position "textDocument")))
65 (position (ht-get cursor-position "position")))
66 (find-file filename)
67 (rust-analyzer--goto-lsp-loc position)
68 )))
69
70(defun rust-analyzer--apply-source-change-command (p)
71 (let ((data (-> p (ht-get "arguments") (car))))
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--join-lines-params ()
84 "Join lines params."
85 (list :textDocument (lsp--text-document-identifier)
86 :range (if (use-region-p)
87 (lsp--region-to-range (region-beginning) (region-end))
88 (lsp--region-to-range (point) (point)))))
89
90(defun rust-analyzer-join-lines ()
91 (interactive)
92 (->
93 (lsp-send-request (lsp-make-request "rust-analyzer/joinLines"
94 (rust-analyzer--join-lines-params)))
95 (rust-analyzer--apply-source-change)))
96
97(with-eval-after-load 'company-lsp
98 ;; company-lsp provides a snippet handler for rust by default that adds () after function calls, which RA does better
99 (setq company-lsp--snippet-functions (assq-delete-all "rust" company-lsp--snippet-functions)))
100
101;; extend selection
102
103(defun rust-analyzer-extend-selection ()
104 (interactive)
105 (-let (((&hash "start" "end") (rust-analyzer--extend-selection)))
106 (rust-analyzer--goto-lsp-loc start)
107 (set-mark (point))
108 (rust-analyzer--goto-lsp-loc end)
109 (exchange-point-and-mark)))
110
111(defun rust-analyzer--extend-selection-params ()
112 "Extend selection params."
113 (list :textDocument (lsp--text-document-identifier)
114 :selections
115 (vector
116 (if (use-region-p)
117 (lsp--region-to-range (region-beginning) (region-end))
118 (lsp--region-to-range (point) (point))))))
119
120(defun rust-analyzer--extend-selection ()
121 (->
122 (lsp-send-request
123 (lsp-make-request
124 "rust-analyzer/extendSelection"
125 (rust-analyzer--extend-selection-params)))
126 (ht-get "selections")
127 (car)))
128
129(defun rust-analyzer--add-er-expansion ()
130 (make-variable-buffer-local 'er/try-expand-list)
131 (setq er/try-expand-list (append
132 er/try-expand-list
133 '(rust-analyzer-extend-selection))))
134
135(with-eval-after-load 'expand-region
136 (add-hook 'rust-mode-hook 'rust-analyzer--add-er-expansion))
137
138(provide 'ra-emacs-lsp)
139;;; ra-emacs-lsp.el ends here