forked from cxxxr/cl-lsp
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy patheval.lisp
207 lines (184 loc) · 7.34 KB
/
eval.lisp
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
(defpackage :cl-lsp/eval
(:use :cl
:cl-lsp/logger
:cl-lsp/server
:cl-lsp/protocol
:cl-lsp/protocol-util
:cl-lsp/slime
:cl-lsp/swank
:cl-lsp/gray-streams)
(:import-from :cl-lsp.lem-base
:with-point
:points-to-string))
(in-package :cl-lsp/eval)
(defvar *eval-thread* nil)
(let ((wait (bt:make-condition-variable))
(lock (bt:make-lock))
(queue (list)))
(defun receive ()
(bt:with-lock-held (lock)
(bt:condition-wait wait lock)
(pop queue)))
(defun send (x)
(bt:with-lock-held (lock)
(setf queue (nconc queue (list x)))
(bt:condition-notify wait))))
(defun ensure-package (package)
(or (find-package package)
(find-package "CL-USER")))
(defun start-eval-thread ()
(unless *eval-thread*
(setf *eval-thread*
(bt:make-thread
(lambda ()
(with-error-handle
(loop :for event := (receive) :do
(funcall event))))))))
(pushnew 'start-eval-thread *initialized-hooks*)
(defun send-eval (function)
(jsonrpc:notify-async *server* "lisp/evalBegin" nil)
(send (lambda ()
(funcall function)
(bt:with-lock-held (*method-lock*)
(jsonrpc:notify-async *server* "lisp/evalEnd" nil)))))
(defun lsp-output-fn (string)
(bt:with-lock-held (*method-lock*)
(notify-log-message |MessageType.Log| string)))
(defun call-with-eval-stream (function)
(let ((out (make-instance 'lsp-output-stream :output-fn #'lsp-output-fn)))
(with-input-from-string (in "")
(with-open-stream (eval-stream (make-two-way-stream in out))
(let ((*standard-output* eval-stream)
(*error-output* eval-stream)
(*standard-input* eval-stream)
(*terminal-io* eval-stream)
(*query-io* eval-stream)
(*debug-io* eval-stream)
(*trace-output* eval-stream))
(funcall function eval-stream))))))
(defmacro with-eval-stream ((stream-var) &body body)
`(call-with-eval-stream (lambda (,stream-var) ,@body)))
(defun call-with-muffle-streams (function)
(let ((stream (make-broadcast-stream)))
(let ((*standard-output* stream)
(*error-output* stream)
(*standard-input* stream)
(*terminal-io* stream)
(*query-io* stream)
(*debug-io* stream)
(*trace-output* stream))
(funcall function))))
(defmacro with-muffle-streams (() &body body)
`(call-with-muffle-streams (lambda () ,@body)))
(defun compilation-notes-to-diagnostics (notes)
(let ((diagnostics '()))
(compilation-notes
notes
(lambda (start end severity message)
(push (make-instance '|Diagnostic|
:|range| (make-lsp-range start end)
:|severity| (case severity
((:error :read-error)
|DiagnosticSeverity.Error|)
((:warning :style-warning)
|DiagnosticSeverity.Warning|)
((:note :redefinition)
|DiagnosticSeverity.Information|))
;; :|code|
;; :|source|
:|message| message)
diagnostics)))
(list-to-object[] diagnostics)))
(defun compilation-message (notes secs successp)
(with-output-to-string (out)
(if successp
(princ "Compilation finished" out)
(princ "Compilation failed" out))
(princ (if (null notes)
". (No warnings)"
". ")
out)
(when secs
(format nil "[~,2f secs]" secs))))
(defun compile-and-load-file (uri)
(let ((filename (uri-to-filename uri))
result)
(handler-case (with-muffle-streams ()
(setf result (swank-compile-file filename t)))
(error (c)
(bt:with-lock-held (*method-lock*)
(notify-show-message |MessageType.Error|
(princ-to-string c)))
(setf result nil)))
(when result
(destructuring-bind (notes successp duration loadp fastfile)
(rest result)
(bt:with-lock-held (*method-lock*)
(notify-show-message |MessageType.Info|
(compilation-message
notes duration successp))
(jsonrpc:notify-async
*server*
"textDocument/publishDiagnostics"
(convert-to-hash-table
(make-instance '|PublishDiagnosticsParams|
:|uri| uri
:|diagnostics| (compilation-notes-to-diagnostics notes)))))
(when (and loadp fastfile successp)
(handler-case
(with-eval-stream (eval-stream)
(load fastfile)
(finish-output eval-stream))
(error (condition)
(bt:with-lock-held (*method-lock*)
(notify-show-message |MessageType.Error|
(princ-to-string condition))))))))))
(define-method "lisp/compileAndLoadFile" (params |TextDocumentIdentifier|)
(let* ((uri (slot-value params '|uri|)))
(send-eval (lambda () (compile-and-load-file uri))))
nil)
(defun eval-string (string package)
(let ((*package* (ensure-package package))
results)
(with-eval-stream (eval-stream)
(handler-bind
((error (lambda (err)
(finish-output eval-stream)
(bt:with-lock-held (*method-lock*)
(notify-log-message |MessageType.Error|
(with-output-to-string (out)
(format out "~%~A~%~%" err)
(uiop:print-backtrace :stream out)))
(notify-show-message |MessageType.Error|
(princ-to-string err)))
(return-from eval-string))))
(setf results
(multiple-value-list
(eval (read-from-string string)))))
(finish-output eval-stream)
(bt:with-lock-held (*method-lock*)
(notify-show-message |MessageType.Info| (format nil "~{~A~^, ~}" results))))))
(defun send-eval-string (string package)
(send-eval (lambda () (eval-string string package))))
(define-method "lisp/eval" (params |TextDocumentPositionParams|)
(with-text-document-position (point) params
(let ((string (form-string point)))
(when string
(let ((package (search-buffer-package point)))
(send-eval-string string package)))
nil)))
(define-method "lisp/rangeEval" (params)
(let* ((uri (gethash "uri" (gethash "textDocument" params)))
(range (convert-from-hash-table '|Range| (gethash "range" params))))
(with-slots (|start| |end|) range
(with-document-position (start uri |start|)
(with-point ((end start))
(move-to-lsp-position end |end|)
(send-eval-string (points-to-string start end)
(search-buffer-package start)))))))
(define-method "lisp/interrupt" (params nil t)
(when *eval-thread*
(bt:interrupt-thread *eval-thread*
(lambda ()
(error "interrupt"))))
nil)