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
+
1
40
(require 'json )
2
41
(require 'files )
3
42
@@ -138,12 +177,13 @@ be re-analysed during textDocument/didOpen handler.")))
138
177
(scan-error pos)))))
139
178
(list beg end))))
140
179
141
- (defun elsa-lsp--list-completion-items (list &optional transform )
180
+ (cl- defun elsa-lsp--list-completion-items (list &key transform kind )
142
181
(setq transform (or transform #'identity ))
143
182
(apply #'vector
144
183
(mapcar (lambda (item )
145
184
(lsp-make-completion-item
146
- :label (funcall transform item) ))
185
+ :label (funcall transform item)
186
+ :kind (or kind lsp/completion-item-kind-text)))
147
187
list )))
148
188
149
189
(defun elsa-lsp--completions ()
@@ -186,6 +226,33 @@ be re-analysed during textDocument/didOpen handler.")))
186
226
(elsa-state-update-global state elsa-global-state)
187
227
(apply #'vector (mapcar #'elsa-message-to-lsp (oref state errors)))))
188
228
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
+
189
256
(defun elsa-lsp--handle-textDocument/hover (id method params )
190
257
(-let* (((&HoverParams :text-document (&TextDocumentIdentifier :uri )
191
258
:position (&Position :line :character ))
@@ -200,12 +267,108 @@ be re-analysed during textDocument/didOpen handler.")))
200
267
(beginning-of-defun )
201
268
(let* ((state (elsa-state :global-state elsa-global-state
202
269
:lsp-params params
203
- :lsp-method method))
270
+ :lsp-method method
271
+ :lsp-analyzer #'elsa-lsp--analyze-textDocument/hover ))
204
272
(value (catch 'lsp-response
205
273
(elsa-process-form state))))
206
274
(when (and value (hash-table-p value))
207
275
(lsp--make-response id value)))))))
208
276
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
+
209
372
(defun elsa-lsp--handle-textDocument/didChange (id method params )
210
373
(-let* (((&DidChangeTextDocumentParams
211
374
:text-document (&VersionedTextDocumentIdentifier :uri :version? )
@@ -254,37 +417,7 @@ be re-analysed during textDocument/didOpen handler.")))
254
417
((equal method " textDocument/hover" )
255
418
(elsa-lsp--handle-textDocument/hover id method params))
256
419
((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))
288
421
((equal method " textDocument/didOpen" )
289
422
(-let* (((&DidOpenTextDocumentParams :text-document (&TextDocumentItem :uri :version )) params)
290
423
(file (elsa-lsp--uri-to-file uri)))
@@ -303,6 +436,9 @@ be re-analysed during textDocument/didOpen handler.")))
303
436
(-let* (((&DidSaveTextDocumentParams :text-document (&TextDocumentItem :uri :version )) params)
304
437
(file (elsa-lsp--uri-to-file uri)))
305
438
(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 ))
306
442
(lsp--make-notification
307
443
" textDocument/publishDiagnostics"
308
444
(lsp-make-publish-diagnostics-params
@@ -317,3 +453,4 @@ be re-analysed during textDocument/didOpen handler.")))
317
453
(elsa-lsp-send-response (lsp--json-serialize res)))))
318
454
319
455
(provide 'elsa-lsp-core )
456
+ ; ;; elsa-lsp-core.el ends here
0 commit comments