diff options
-rw-r--r-- | editors/emacs/ra-emacs-lsp.el | 139 |
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 | ||