-
Notifications
You must be signed in to change notification settings - Fork 24
/
lsp-dart-flutter-daemon.el
241 lines (217 loc) · 11.3 KB
/
lsp-dart-flutter-daemon.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
;;; lsp-dart-flutter-daemon.el --- Helper for the Flutter daemon -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2022 Eric Dallo
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;
;;; Commentary:
;;
;; Helper for the Flutter daemon
;;
;;; Code:
(require 'jsonrpc)
(require 'ht)
(require 'dash)
(require 'lsp-mode)
(require 'lsp-dart-protocol)
(require 'lsp-dart-utils)
(defconst lsp-dart-flutter-daemon-buffer-name "*LSP Dart - Flutter daemon*")
(defconst lsp-dart-flutter-daemon-name "LSP Dart - Flutter daemon")
(defvar lsp-dart-flutter-daemon-devices '())
(defvar lsp-dart-flutter-daemon-commands '())
(defvar lsp-dart-flutter-daemon-device-added-listeners '())
(defvar lsp-dart-flutter-daemon--conn nil
"JSONRPC Connection object to the Flutter daemon process.")
(defclass lsp-dart-flutter-daemon-connection (jsonrpc-process-connection) ()
"A connection based on stdio to connect to a Flutter daemon.")
(cl-defmethod jsonrpc-connection-send ((conn lsp-dart-flutter-daemon-connection)
&rest args
&key method params &allow-other-keys)
"Implement send method to format JSON properly.
CONN ARGS METHOD PARAMS"
(with-temp-buffer
(when method
(plist-put args :method
(cond ((keywordp method) (substring (symbol-name method) 1))
((and method (symbolp method)) (symbol-name method))
((stringp method) method))))
(unless params
(cl-remf args :params))
(let ((json (concat "[" (jsonrpc--json-encode args) "]\r\n")))
(jsonrpc--log-event conn args 'client)
(process-send-string
(jsonrpc--process conn)
json))))
(cl-defmethod initialize-instance :after ((conn lsp-dart-flutter-daemon-connection) _slots)
"CONN."
(let ((proc (jsonrpc--process conn)))
(when proc
(set-process-filter proc #'lsp-dart-flutter-daemon--process-filter))))
(defun lsp-dart-flutter-daemon--plist->hash-table (plist &optional test)
"Create a hash table initialized from PLIST.
Optionally use TEST to compare the hash keys."
(let ((ht (ht-create test)))
(dolist (pair (nreverse (-partition 2 plist)) ht)
(let ((key (lsp-keyword->string (car pair)))
(value (cadr pair)))
(ht-set! ht key value)))))
(defun lsp-dart-flutter-daemon--hash-table->plist (h)
"Convert hash-table H to plist recurevely."
(if (hash-table-p h)
(apply 'append
(ht-map (lambda (key value)
(list (intern (concat ":" key))
(if (or (vectorp value)
(listp value))
(-map #'lsp-dart-flutter-daemon--hash-table->plist value)
(lsp-dart-flutter-daemon--hash-table->plist value))))
h))
h))
(defun lsp-dart-flutter-daemon--process-filter (proc response)
"Invoked when a new RESPONSE has arrived from PROC."
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
(goto-char (process-mark proc))
(let* ((response (string-trim response))
(should-trim (and (string-prefix-p (regexp-quote "][") response))))
(when (string-prefix-p "[" response)
(-map (lambda (msg)
(let ((json-message (condition-case-unless-debug oops
(lsp--read-json (if should-trim
(substring msg 1 -1)
msg))
(error
(jsonrpc--warn "Invalid JSON: %S %s %s"
oops (buffer-string) msg)
nil)))
(conn (process-get proc 'jsonrpc-connection)))
(when-let ((json (if (vectorp json-message)
(aref json-message 0)
json-message)))
(if (lsp-get json :error)
(lsp-dart-flutter-daemon--log "ERROR" (lsp-get json :error) (lsp-get json :trace)))
(if (lsp-get json :event)
(pcase (lsp-get json :event)
("device.removed" (lsp-dart-flutter-daemon--device-removed (lsp-get json :params)))
("device.added" (lsp-dart-flutter-daemon--device-added (lsp-get json :params)))
("daemon.connected" (lsp-dart-flutter-daemon--send "device.enable"))
("daemon.logMessage" (lsp-dart-flutter-daemon--log (lsp-get (lsp-get json :params) :level)
(lsp-get (lsp-get json :params) :message))))
(with-temp-buffer
(jsonrpc-connection-receive conn (if lsp-use-plists
json
(lsp-dart-flutter-daemon--hash-table->plist json))))))))
(split-string response "\n")))))))
(defun lsp-dart-flutter-daemon--log (level msg &rest args)
"Log for LEVEL, MSG with ARGS adding lsp-dart-flutter-daemon prefix."
(unless (string= "STATUS" (upcase level))
(apply #'lsp-dart-custom-log (concat "[FLUTTER " (upcase level) "] ")
msg
args)))
(defun lsp-dart-flutter-daemon--generate-command-id ()
"Generate a random command id."
(random 100000))
(defun lsp-dart-flutter-daemon--running-p ()
"Return non-nil if the Flutter daemon is already running."
(and (not (null lsp-dart-flutter-daemon--conn))
(process-live-p (get-process lsp-dart-flutter-daemon-name))))
(defun lsp-dart-flutter-daemon-start ()
"Start the Flutter daemon."
(unless (lsp-dart-flutter-daemon--running-p)
(let ((conn (lsp-dart-flutter-daemon-connection
:name lsp-dart-flutter-daemon-name
:process (lambda ()
(make-process
:name lsp-dart-flutter-daemon-name
:command `(,@(lsp-dart-flutter-command) "daemon")
:coding 'utf-8-emacs-unix
:connection-type 'pipe
:buffer lsp-dart-flutter-daemon-buffer-name
:stderr (get-buffer-create
(format "*%s stderr*" lsp-dart-flutter-daemon-name)))))))
(setq lsp-dart-flutter-daemon--conn conn))))
(defun lsp-dart-flutter-daemon-->safe-object-type (obj)
"Convert OBJ to the object type following lsp-use-plists variable.
OBJ should be a hash-table or plist."
(if lsp-use-plists
(if (or (vectorp obj)
(listp obj))
(-map #'lsp-dart-flutter-daemon--hash-table->plist obj)
(lsp-dart-flutter-daemon--hash-table->plist obj))
(if (or (vectorp obj)
(listp obj))
(-map #'lsp-dart-flutter-daemon--plist->hash-table obj)
(lsp-dart-flutter-daemon--plist->hash-table obj))))
(defun lsp-dart-flutter-daemon--send (method &optional params callback)
"Build and send command with METHOD with optional PARAMS.
Call CALLBACK if provided when the receive a response with the built id
of this command."
(unless (lsp-dart-flutter-daemon--running-p)
(lsp-dart-flutter-daemon-start))
(jsonrpc-async-request lsp-dart-flutter-daemon--conn
method (when params
(if lsp-use-plists
params
(lsp-dart-flutter-daemon--hash-table->plist params)))
:success-fn (lambda (result)
(when result
(funcall callback
(lsp-dart-flutter-daemon-->safe-object-type result))))))
(defun lsp-dart-flutter-daemon--device-removed (device)
"Remove DEVICE from the devices list."
(--> (lsp-get device :id)
(lsp-dart-remove-from-alist it lsp-dart-flutter-daemon-devices)
(setq lsp-dart-flutter-daemon-devices it)))
(defun lsp-dart-flutter-daemon--device-added (device)
"Add DEVICE to the devices list."
(-let* ((device-id (lsp:flutter-daemon-device-id device))
(device-to-add (cons device-id device)))
(lsp:set-flutter-daemon-device-is-device? device t)
(setq lsp-dart-flutter-daemon-devices
(lsp-dart-remove-from-alist device-id lsp-dart-flutter-daemon-devices))
(add-to-list 'lsp-dart-flutter-daemon-devices device-to-add)
(-when-let (listener (alist-get device-id lsp-dart-flutter-daemon-device-added-listeners))
(setq lsp-dart-flutter-daemon-device-added-listeners
(lsp-dart-remove-from-alist device-id lsp-dart-flutter-daemon-device-added-listeners))
(funcall (plist-get listener :callback) device))))
(defun lsp-dart-flutter-daemon-get-devices (callback)
"Call CALLBACK with the available emulators and devices from Flutter daemon."
(lsp-dart-flutter-daemon--send
"emulator.getEmulators"
nil
(-lambda (emulators)
(let ((devices-excluding-emulators (-remove (-lambda ((&FlutterDaemonDevice :emulator-id?))
(and emulator-id?
(-first (lambda (emulator) (string= emulator-id? (lsp:flutter-daemon-device-id emulator)))
(append emulators nil))))
(-map #'cdr lsp-dart-flutter-daemon-devices))))
(->> devices-excluding-emulators
(append emulators)
(funcall callback))))))
(defun lsp-dart-flutter-daemon-launch (device callback)
"Launch DEVICE and wait for connected state and call CALLBACK."
(let* ((device-id (lsp:flutter-daemon-device-id device))
(is-device? (lsp:flutter-daemon-device-is-device? device)))
(if is-device?
(funcall callback device)
(lsp-dart-flutter-daemon--send
"device.getDevices"
nil
(-lambda (devices)
(if-let (emulator-running (-first (-lambda ((&FlutterDaemonDevice :emulator-id?)) (string= emulator-id? device-id)) (append devices nil)))
(funcall callback emulator-running)
(-let* ((params (lsp-make-flutter-daemon-emulator-launch :emulator-id device-id)))
(add-to-list 'lsp-dart-flutter-daemon-device-added-listeners
(cons device-id (list :callback callback)))
(lsp-dart-flutter-daemon--send "emulator.launch" params callback))))))))
(provide 'lsp-dart-flutter-daemon)
;;; lsp-dart-flutter-daemon.el ends here