Eglot: rework eglot-imenu

Most newer servers return a vector of 'DocumentSymbol' as a response
to 'textDocument/documentSymbol'.  It's not worth trying to dumb this
down to imenu format of 'SymbolInformation' vectors.

This lays groundwork for the forthcoming "breadcrumb" feature of
bug#58431.

* lisp/progmodes/eglot.el
(eglot--imenu-SymbolInformation, eglot--imenu-DocumentSymbol): New
helpers.
(eglot-imenu): Rework.
This commit is contained in:
João Távora 2023-04-07 12:54:39 +01:00
parent 5e5f5b28e9
commit b1f8d98a11
2 changed files with 43 additions and 43 deletions

View file

@ -409,9 +409,7 @@ If multiple rules match, only first one is executed.")
(goto-char pos)
(funcall probe last-command-event))))
(when res (throw 'done res))))))))))
(when (and rule
;; Not in a string or comment.
(not (nth 8 (save-excursion (syntax-ppss pos)))))
(when rule
(goto-char pos)
(when (functionp rule) (setq rule (funcall rule)))
(dolist (sym (if (symbolp rule) (list rule) rule))

View file

@ -486,9 +486,7 @@ This can be useful when using docker to run a language server.")
(SymbolInformation (:name :kind :location)
(:deprecated :containerName))
(DocumentSymbol (:name :range :selectionRange :kind)
;; `:containerName' isn't really allowed , but
;; it simplifies the impl of `eglot-imenu'.
(:detail :deprecated :children :containerName))
(:detail :deprecated :children))
(TextDocumentEdit (:textDocument :edits) ())
(TextEdit (:range :newText))
(VersionedTextDocumentIdentifier (:uri :version) ())
@ -3235,49 +3233,53 @@ for which LSP on-type-formatting should be requested."
:deferred :textDocument/documentHighlight)
nil)))
(defun eglot--imenu-SymbolInformation (res)
"Compute `imenu--index-alist' for RES vector of SymbolInformation."
(mapcar
(pcase-lambda (`(,kind . ,objs))
(cons
(alist-get kind eglot--symbol-kind-names "Unknown")
(mapcan
(pcase-lambda (`(,container . ,objs))
(let ((elems (mapcar
(eglot--lambda ((SymbolInformation) kind name location)
(let ((reg (eglot--range-region
(plist-get location :range))))
(cons (propertize name
'breadcrumb-region reg
'breadcrumb-kind kind)
(car reg))))
objs)))
(if container (list (cons container elems)) elems)))
(seq-group-by
(eglot--lambda ((SymbolInformation) containerName) containerName) objs))))
(seq-group-by (eglot--lambda ((SymbolInformation) kind) kind) res)))
(defun eglot--imenu-DocumentSymbol (res)
"Compute `imenu--index-alist' for RES vector of DocumentSymbol."
(cl-labels ((dfs (&key name children range kind &allow-other-keys)
(let* ((reg (eglot--range-region range))
(name (propertize name
'breadcrumb-region reg
'breadcrumb-kind kind)))
(if children
(cons name
(mapcar (lambda (c) (apply #'dfs c)) children))
(cons name (car reg))))))
(mapcar (lambda (s) (apply #'dfs s)) res)))
(defun eglot-imenu ()
"Eglot's `imenu-create-index-function'.
Returns a list as described in docstring of `imenu--index-alist'."
(cl-labels
((unfurl (obj)
(eglot--dcase obj
(((SymbolInformation)) (list obj))
(((DocumentSymbol) name children)
(cons obj
(mapcar
(lambda (c)
(plist-put
c :containerName
(let ((existing (plist-get c :containerName)))
(if existing (format "%s::%s" name existing)
name))))
(mapcan #'unfurl children)))))))
(mapcar
(pcase-lambda (`(,kind . ,objs))
(cons
(alist-get kind eglot--symbol-kind-names "Unknown")
(mapcan (pcase-lambda (`(,container . ,objs))
(let ((elems (mapcar
(lambda (obj)
(cons (plist-get obj :name)
(car (eglot--range-region
(eglot--dcase obj
(((SymbolInformation) location)
(plist-get location :range))
(((DocumentSymbol) selectionRange)
selectionRange))))))
objs)))
(if container (list (cons container elems)) elems)))
(seq-group-by
(lambda (e) (plist-get e :containerName)) objs))))
(seq-group-by
(lambda (obj) (plist-get obj :kind))
(mapcan #'unfurl
(eglot--request (eglot--current-server-or-lose)
(let* ((res (eglot--request (eglot--current-server-or-lose)
:textDocument/documentSymbol
`(:textDocument
,(eglot--TextDocumentIdentifier))
:cancel-on-input non-essential))))))
:cancel-on-input non-essential))
(head (and res (elt res 0))))
(eglot--dcase head
(((SymbolInformation)) (eglot--imenu-SymbolInformation res))
(((DocumentSymbol)) (eglot--imenu-DocumentSymbol res)))))
(cl-defun eglot--apply-text-edits (edits &optional version)
"Apply EDITS for current buffer if at VERSION, or if it's nil."