Skip to content

Commit 670ecf3

Browse files
committed
refactor(lsp): move lsp related code from elsa-analyser to elsa-lsp-core
1 parent 2feaf6b commit 670ecf3

File tree

3 files changed

+175
-113
lines changed

3 files changed

+175
-113
lines changed

elsa-analyser.el

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,85 +1170,9 @@ FORM is a result of `elsa-read-form'."
11701170
(when (elsa-check-should-run it form scope state)
11711171
(elsa-check-check it form scope state)))
11721172
(when-let ((method (oref state lsp-method))
1173-
(params (oref state lsp-params)))
1174-
(cond
1175-
((equal method "textDocument/hover")
1176-
(-let* (((&HoverParams :position (&Position :line :character))
1177-
params))
1178-
(when (and (= (oref form line) (1+ line))
1179-
(<= (oref form column) character)
1180-
(or (< (1+ line) (oref form end-line))
1181-
(<= character (oref form end-column))))
1182-
(throw 'lsp-response
1183-
(lsp-make-hover
1184-
:contents (lsp-make-markup-content
1185-
:kind "plaintext"
1186-
:value (format
1187-
"%s: %s"
1188-
(elsa-form-print form)
1189-
(elsa-type-describe (elsa-get-type form))))
1190-
:range (lsp-make-range
1191-
:start (lsp-make-position
1192-
:line (1- (oref form line))
1193-
:character (oref form column))
1194-
:end (lsp-make-position
1195-
:line (1- (oref form end-line))
1196-
:character (oref form end-column))))))))
1197-
((equal method "textDocument/completion")
1198-
(-let* (((&CompletionParams :position (&Position :line :character))
1199-
params))
1200-
(when (and (= (oref form line) (1+ line))
1201-
(<= (oref form column) character)
1202-
(or (< (1+ line) (oref form end-line))
1203-
(<= character (oref form end-column))))
1204-
(when form (message "form %s" (elsa-tostring form)))
1205-
(when-let ((call-form (if (elsa-form-function-call-p form)
1206-
form
1207-
(and (slot-boundp form 'parent)
1208-
(oref form parent)
1209-
(elsa-form-function-call-p (oref form parent))
1210-
(oref form parent)))))
1211-
;; special completion inside a function call form
1212-
(message "call-form %s" (elsa-tostring call-form))
1213-
(cond
1214-
((eq (elsa-get-name call-form) 'oref)
1215-
(let* ((inst-form (elsa-cadr call-form))
1216-
(inst-type (elsa-get-type inst-form)))
1217-
(when (elsa-struct-type-p inst-type)
1218-
(when-let* ((class (get (oref inst-type name) 'elsa-defclass)))
1219-
(let ((slots (--map
1220-
(oref it name)
1221-
(elsa-get-slots class))))
1222-
(throw 'lsp-response
1223-
(lsp-make-completion-list
1224-
:is-incomplete json-false
1225-
:items (elsa-lsp--list-completion-items
1226-
slots 'symbol-name))))))))))
1227-
1228-
;; could still be a generic function call with point at the
1229-
;; first arg
1230-
(when (or (elsa-form-function-call-p form)
1231-
(and (elsa-form-symbol-p form)
1232-
(eq (elsa-get-name form) 'nil)))
1233-
(message "generic call form %s" (elsa-tostring form))
1234-
(save-excursion
1235-
(goto-char (1- (oref form end)))
1236-
(when-let ((candidates (elsa-lsp--completions)))
1237-
(throw 'lsp-response
1238-
(lsp-make-completion-list
1239-
:is-incomplete json-false
1240-
:items (elsa-lsp--list-completion-items candidates))))))
1241-
1242-
;; regular symbol, we should use defvars and scope
1243-
;; variables
1244-
(message "variable form %s" (elsa-tostring form))
1245-
(throw 'lsp-response
1246-
(lsp-make-completion-list
1247-
:is-incomplete json-false
1248-
:items (elsa-lsp--list-completion-items
1249-
(append (hash-table-keys (oref scope vars))
1250-
(elsa-state-get-var-symbols state))
1251-
'symbol-name)))))))))
1173+
(params (oref state lsp-params))
1174+
(lsp-analyzer (oref state lsp-analyzer)))
1175+
(funcall lsp-analyzer form state method params)))
12521176

12531177
(defun elsa--analyse-body (body scope state)
12541178
(--each body (elsa--analyse-form it scope state)))

elsa-lsp-core.el

Lines changed: 171 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
1+
;;; elsa-lsp-core.el --- LSP implementation with Elsa -*- lexical-binding: t -*-
2+
3+
;; Copyright (C) 2023 Matúš Goljer
4+
5+
;; Author: Matúš Goljer <[email protected]>
6+
;; Maintainer: Matúš Goljer <[email protected]>
7+
;; Version: 0.0.1
8+
;; Created: 12th March 2023
9+
;; Package-requires: ((dash "2.17.0"))
10+
;; Keywords:
11+
12+
;; This program is free software; you can redistribute it and/or
13+
;; modify it under the terms of the GNU General Public License
14+
;; as published by the Free Software Foundation; either version 3
15+
;; of the License, or (at your option) any later version.
16+
17+
;; This program is distributed in the hope that it will be useful,
18+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
;; GNU General Public License for more details.
21+
22+
;; You should have received a copy of the GNU General Public License
23+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
25+
;;; Commentary:
26+
27+
;; LSP methods should be handled in functions called
28+
;; elsa-lsp--handle-CAPABILITY, for example
29+
;; `elsa-lsp--handle-textDocument/hover'.
30+
31+
;; If the handler needs to run analysis of the surrounding form, it
32+
;; needs to pass the current lsp-method, lsp-params and lsp-analyzer
33+
;; to the `elsa-state' which is used for the form analysis. The
34+
;; analyzer should be called elsa-lsp--analyze-CAPABILITY, for example
35+
;; `elsa-lsp--analyze-textDocument/hover'. The analyzer will run in
36+
;; context of `elsa-analyse-form'.
37+
38+
;;; Code:
39+
140
(require 'json)
241
(require 'files)
342

@@ -138,12 +177,13 @@ be re-analysed during textDocument/didOpen handler.")))
138177
(scan-error pos)))))
139178
(list beg end))))
140179

141-
(defun elsa-lsp--list-completion-items (list &optional transform)
180+
(cl-defun elsa-lsp--list-completion-items (list &key transform kind)
142181
(setq transform (or transform #'identity))
143182
(apply #'vector
144183
(mapcar (lambda (item)
145184
(lsp-make-completion-item
146-
:label (funcall transform item) ))
185+
:label (funcall transform item)
186+
:kind (or kind lsp/completion-item-kind-text)))
147187
list)))
148188

149189
(defun elsa-lsp--completions ()
@@ -186,6 +226,33 @@ be re-analysed during textDocument/didOpen handler.")))
186226
(elsa-state-update-global state elsa-global-state)
187227
(apply #'vector (mapcar #'elsa-message-to-lsp (oref state errors)))))
188228

229+
(defun elsa-form-to-lsp-range (form)
230+
"Convert FORM to LSP range."
231+
(lsp-make-range
232+
:start (lsp-make-position
233+
:line (1- (oref form line))
234+
:character (oref form column))
235+
:end (lsp-make-position
236+
:line (1- (oref form end-line))
237+
:character (oref form end-column))))
238+
239+
(defun elsa-lsp--analyze-textDocument/hover (form state method params)
240+
(-let* (((&HoverParams :position (&Position :line :character))
241+
params))
242+
(when (and (= (oref form line) (1+ line))
243+
(<= (oref form column) character)
244+
(or (< (1+ line) (oref form end-line))
245+
(<= character (oref form end-column))))
246+
(throw 'lsp-response
247+
(lsp-make-hover
248+
:contents (lsp-make-markup-content
249+
:kind "plaintext"
250+
:value (format
251+
"%s: %s"
252+
(elsa-form-print form)
253+
(elsa-type-describe (elsa-get-type form))))
254+
:range (elsa-form-to-lsp-range form))))))
255+
189256
(defun elsa-lsp--handle-textDocument/hover (id method params)
190257
(-let* (((&HoverParams :text-document (&TextDocumentIdentifier :uri)
191258
:position (&Position :line :character))
@@ -200,12 +267,108 @@ be re-analysed during textDocument/didOpen handler.")))
200267
(beginning-of-defun)
201268
(let* ((state (elsa-state :global-state elsa-global-state
202269
:lsp-params params
203-
:lsp-method method))
270+
:lsp-method method
271+
:lsp-analyzer #'elsa-lsp--analyze-textDocument/hover))
204272
(value (catch 'lsp-response
205273
(elsa-process-form state))))
206274
(when (and value (hash-table-p value))
207275
(lsp--make-response id value)))))))
208276

277+
(defun elsa-lsp--analyze-textDocument/completion (form state method params)
278+
(-let* ((lgr (lgr-get-logger "elsa.lsp.analyzer"))
279+
((&CompletionParams :position (&Position :line :character))
280+
params))
281+
(when (and (= (oref form line) (1+ line))
282+
(<= (oref form column) character)
283+
(or (< (1+ line) (oref form end-line))
284+
(<= character (oref form end-column))))
285+
(when form (lgr-debug lgr "form %s" (elsa-tostring form)))
286+
(when-let ((call-form (if (elsa-form-function-call-p form)
287+
form
288+
(and (slot-boundp form 'parent)
289+
(oref form parent)
290+
(elsa-form-function-call-p (oref form parent))
291+
(oref form parent)))))
292+
;; special completion inside a function call form
293+
(lgr-debug lgr "call-formadsadasdasdadx %s" (elsa-tostring call-form))
294+
(cond
295+
((eq (elsa-get-name call-form) 'oref)
296+
(lgr-debug lgr "call-form is oref")
297+
(let* ((inst-form (elsa-cadr call-form))
298+
(inst-type (elsa-get-type inst-form)))
299+
(lgr-debug lgr "instance type is %s" (elsa-tostring inst-type))
300+
(when (elsa-class-type-p inst-type)
301+
(lgr-debug lgr "is class type of name %s" (oref inst-type name))
302+
(when-let* ((class (elsa-state-get-defclass state (oref inst-type name))))
303+
(lgr-debug lgr "has class")
304+
(let ((slots (--map
305+
(oref it name)
306+
(elsa-get-slots class))))
307+
(throw 'lsp-response
308+
(lsp-make-completion-list
309+
:is-incomplete json-false
310+
:items (elsa-lsp--list-completion-items
311+
slots :transform #'symbol-name))))))))))
312+
313+
;; Here we complete the function name if the point is at the
314+
;; first position in a list
315+
(when (or (elsa-form-function-call-p form)
316+
(and (elsa-form-symbol-p form)
317+
(eq (elsa-get-name form) 'nil)))
318+
(lgr-debug lgr "completing function name %s" (elsa-tostring form))
319+
(save-excursion
320+
(goto-char (1- (oref form end)))
321+
(when-let ((candidates (elsa-lsp--completions)))
322+
(throw 'lsp-response
323+
(lsp-make-completion-list
324+
:is-incomplete json-false
325+
:items (elsa-lsp--list-completion-items candidates))))))
326+
327+
;; regular symbol, we should use defvars and scope
328+
;; variables
329+
(lgr-debug lgr "variable form %s" (elsa-tostring form))
330+
(throw 'lsp-response
331+
(lsp-make-completion-list
332+
:is-incomplete json-false
333+
:items (elsa-lsp--list-completion-items
334+
(append (hash-table-keys (oref scope vars))
335+
(elsa-state-get-var-symbols state))
336+
:transform #'symbol-name
337+
:kind lsp/completion-item-kind-variable))))))
338+
339+
(defun elsa-lsp--handle-textDocument/completion (id method params)
340+
(-let* (((&CompletionParams :text-document (&TextDocumentIdentifier :uri)
341+
:position (&Position :line :character))
342+
params)
343+
(file (elsa-lsp--uri-to-file uri))
344+
(buffer (elsa-lsp-get-buffer elsa-lsp-state file)))
345+
(when buffer
346+
(with-current-buffer buffer
347+
(goto-char (point-min))
348+
(forward-line line)
349+
(forward-char character)
350+
(beginning-of-defun)
351+
(let* ((state (elsa-state :global-state elsa-global-state
352+
:lsp-params params
353+
:lsp-method method
354+
:lsp-analyzer #'elsa-lsp--analyze-textDocument/completion))
355+
(value (ignore-errors
356+
(catch 'lsp-response
357+
(elsa-process-form state)))))
358+
(if (and value (hash-table-p value))
359+
(lsp--make-response id value)
360+
;; fall back to capf
361+
(lsp--make-response
362+
id
363+
(lsp-make-completion-list
364+
:is-incomplete json-false
365+
:items (apply
366+
#'vector
367+
(mapcar
368+
(lambda (item)
369+
(lsp-make-completion-item :label item))
370+
(elsa-lsp--capf-completions)))))))))))
371+
209372
(defun elsa-lsp--handle-textDocument/didChange (id method params)
210373
(-let* (((&DidChangeTextDocumentParams
211374
:text-document (&VersionedTextDocumentIdentifier :uri :version?)
@@ -254,37 +417,7 @@ be re-analysed during textDocument/didOpen handler.")))
254417
((equal method "textDocument/hover")
255418
(elsa-lsp--handle-textDocument/hover id method params))
256419
((equal method "textDocument/completion")
257-
(-let* (((&CompletionParams :text-document (&TextDocumentIdentifier :uri)
258-
:position (&Position :line :character))
259-
params)
260-
(file (elsa-lsp--uri-to-file uri))
261-
(buffer (elsa-lsp-get-buffer elsa-lsp-state file)))
262-
(when buffer
263-
(with-current-buffer buffer
264-
(goto-char (point-min))
265-
(forward-line line)
266-
(forward-char character)
267-
(let* ((state (elsa-state :global-state elsa-global-state
268-
:lsp-params params
269-
:lsp-method method))
270-
(value (ignore-errors
271-
(catch 'lsp-response
272-
(save-excursion
273-
(beginning-of-defun)
274-
(elsa-process-form state))))))
275-
(if (and value (hash-table-p value))
276-
(lsp--make-response id value)
277-
;; fall back to capf
278-
(lsp--make-response
279-
id
280-
(lsp-make-completion-list
281-
:is-incomplete json-false
282-
:items (apply
283-
#'vector
284-
(mapcar
285-
(lambda (item)
286-
(lsp-make-completion-item :label item))
287-
(elsa-lsp--capf-completions)))))))))))
420+
(elsa-lsp--handle-textDocument/completion id method params))
288421
((equal method "textDocument/didOpen")
289422
(-let* (((&DidOpenTextDocumentParams :text-document (&TextDocumentItem :uri :version)) params)
290423
(file (elsa-lsp--uri-to-file uri)))
@@ -303,6 +436,9 @@ be re-analysed during textDocument/didOpen handler.")))
303436
(-let* (((&DidSaveTextDocumentParams :text-document (&TextDocumentItem :uri :version)) params)
304437
(file (elsa-lsp--uri-to-file uri)))
305438
(elsa-lsp-update-file-buffer elsa-lsp-state file)
439+
(when (string-match-p "/elsa-" file)
440+
(lgr-debug (lgr-get-logger "elsa.lsp") "Reloading file %s" file)
441+
(load file t))
306442
(lsp--make-notification
307443
"textDocument/publishDiagnostics"
308444
(lsp-make-publish-diagnostics-params
@@ -317,3 +453,4 @@ be re-analysed during textDocument/didOpen handler.")))
317453
(elsa-lsp-send-response (lsp--json-serialize res)))))
318454

319455
(provide 'elsa-lsp-core)
456+
;;; elsa-lsp-core.el ends here

elsa-state.el

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ a single file, form or set of forms.")
370370
(scope :initform (elsa-scope))
371371
(lsp-method :initarg :lsp-method :initform nil)
372372
(lsp-params :initarg :lsp-params :initform nil)
373+
(lsp-analyzer :initarg :lsp-analyzer :initform nil)
373374
(global-state :type elsa-global-state
374375
:initarg :global-state
375376
:initform (elsa-global-state))))

0 commit comments

Comments
 (0)