Skip to content

Commit

Permalink
Rework and correct major part of xref glue code
Browse files Browse the repository at this point in the history
See comments of joaotavora/eglot#314.  Up to
now, xref-backend-indentifier-completion-table was a gross hack that
only worked sometimes.  It relied on some fugly gymnastics to cache a
response from :textDocument/documentSymbol and somehow used that
information to build a completion table.  But it doesn't work well.

Summarily, LSP doesn't lend itself well to the xref interface of
prompting for an arbitrary identifier and then go look for whichever
type of references of that identifier.  All the LSP
:textDocument/{definition,references,implementation,...} methods
expect to know the exact context of the search the user is about to
perform, in the form of a document location.  That conflicts with the
xref "arbitrary string" requirement.

Therefore, the slightly limited, but much more correct way, for Eglot
to function is to override the user's preference of
xref-prompt-for-identifier, temporarily setting it to nil in
eglot--managed-mode (ideally, though, xref-prompt-for-identifier
should be a function of the backend.)

Later on, a possibly better behaved identifier completion table can be
built on top of the :workspace/symbol LSP method.

* eglot.el (xref-backend-identifier-at-point): Rewrite.
(eglot--lsp-xrefs-for-method): New helper.
(eglot--lsp-xref-helper): Use eglot--lsp-xrefs-for-method.
(eglot--xref-definitions-method): Delete.
(eglot--lsp-xref-refs): New variable.
(xref-backend-references, xref-backend-definitions): Use
eglot--lsp-xrefs-for-method.
(eglot--managed-mode): Set xref-prompt-for-identifier
to nil.
(eglot--xref-reset-known-symbols, eglot--xref-known-symbols): Delete
(xref-backend-identifier-completion-table): Nullify.
(eglot-find-declaration, eglot-find-implementation)
(eglot-find-typeDefinition): Use eglot--lsp-xref-helper.
  • Loading branch information
joaotavora committed Oct 11, 2019
1 parent 4c11b0b commit 7048927
Showing 1 changed file with 48 additions and 95 deletions.
143 changes: 48 additions & 95 deletions lisp/progmodes/eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,7 @@ and just return it. PROMPT shouldn't end with a question mark."
(add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t)
(add-hook 'pre-command-hook 'eglot--pre-command-hook nil t)
(eglot--setq-saving eldoc-documentation-function #'eglot-eldoc-function)
(eglot--setq-saving xref-prompt-for-identifier nil)
(eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend t))
(add-function :around (local 'imenu-create-index-function) #'eglot-imenu)
(flymake-mode 1)
Expand Down Expand Up @@ -1717,16 +1718,6 @@ Calls REPORT-FN maybe if server publishes diagnostics in time."
"EGLOT xref backend."
(when (eglot--server-capable :definitionProvider) 'eglot))

(defvar eglot--xref-known-symbols nil)

(defun eglot--xref-reset-known-symbols (&rest _dummy)
"Reset `eglot--xref-reset-known-symbols'.
DUMMY is ignored."
(setq eglot--xref-known-symbols nil))

(advice-add 'xref-find-definitions :after #'eglot--xref-reset-known-symbols)
(advice-add 'xref-find-references :after #'eglot--xref-reset-known-symbols)

(defvar eglot--temp-location-buffers (make-hash-table :test #'equal)
"Helper variable for `eglot--handling-xrefs'.")

Expand Down Expand Up @@ -1771,102 +1762,64 @@ Try to visit the target file for a richer summary line."
(xref-make summary (xref-make-file-location file line column))))

(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot)))
(when (eglot--server-capable :documentSymbolProvider)
(let ((server (eglot--current-server-or-lose))
(text-id (eglot--TextDocumentIdentifier)))
(completion-table-with-cache
(lambda (string)
(setq eglot--xref-known-symbols
(mapcar
(eglot--lambda
((SymbolInformation) name kind location containerName)
(propertize name
:textDocumentPositionParams
(list :textDocument text-id
:position (plist-get
(plist-get location :range)
:start))
:locations (vector location)
:kind kind
:containerName containerName))
(jsonrpc-request server
:textDocument/documentSymbol
`(:textDocument ,text-id))))
(all-completions string eglot--xref-known-symbols))))))
(eglot--error "cannot (yet) provide reliable completion table for LSP symbols"))

(cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot)))
(when-let ((symatpt (symbol-at-point)))
(propertize (symbol-name symatpt)
:textDocumentPositionParams
(eglot--TextDocumentPositionParams))))

(defvar eglot--xref-definitions-method :textDocument/definition
"The LSP method to map xref-find-definitions call.")
;; JT@19/10/09: This is a totally dummy identifier that isn't even
;; passed to LSP. The reason for this particular wording is to
;; construct a readable message "No references for LSP identifier at
;; point.". See http://github.com/joaotavora/eglot/issues/314
"LSP identifier at point.")

(defvar eglot--lsp-xref-refs nil
"`xref' objects for overriding `xref-backend-references''s.")

(cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability)
"Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY."
(unless (eglot--server-capable
(or capability
(intern
(format ":%sProvider"
(cadr (split-string (symbol-name method)
"/"))))))
(eglot--error "Sorry, this server doesn't do %s" method))
(eglot--handling-xrefs
(mapcar
(eglot--lambda ((Location) uri range)
(eglot--xref-make (symbol-at-point) uri range))
(jsonrpc-request
(eglot--current-server-or-lose) method (append
(eglot--TextDocumentPositionParams)
extra-params)))))

(defun eglot--lsp-xref-helper (method)
"Helper for `eglot-find-declaration' & friends."
(let ((eglot--lsp-xref-refs (eglot--lsp-xrefs-for-method method)))
(xref-find-references "LSP identifier at point.")))

(defun eglot-find-declaration ()
"Find the declaration for the identifier at point.
See `xref-find-definitions' and `xref-prompt-for-identifier'."
"Find declaration for SYM, the identifier at point."
(interactive)
(eglot--find-location 'declaration))
(eglot--lsp-xref-helper :textDocument/declaration))

(defun eglot-find-implementation ()
"Find the implementation for the identifier at point.
See `xref-find-definitions' and `xref-prompt-for-identifier'."
"Find implementation for SYM, the identifier at point."
(interactive)
(eglot--find-location 'implementation))
(eglot--lsp-xref-helper :textDocument/implementation))

(defun eglot-find-typeDefinition ()
"Find the type definition for the identifier at point.
See `xref-find-definitions' and `xref-prompt-for-identifier'."
"Find type definition for SYM, the identifier at point."
(interactive)
(eglot--find-location 'typeDefinition))

(defun eglot--find-location (kind)
(let* ((method-name (symbol-name kind))
(method (intern (concat ":textDocument/" method-name)))
(capability (intern (concat ":" method-name "Provider"))))
(if (eglot--server-capable capability)
(let ((eglot--xref-definitions-method method))
(call-interactively #'xref-find-definitions))
(eglot--error "Server is not a %sProvider" method-name))))

(cl-defmethod xref-backend-definitions ((_backend (eql eglot)) identifier)
(let* ((rich-identifier
(car (member identifier eglot--xref-known-symbols)))
(definitions
(if rich-identifier
(get-text-property 0 :locations rich-identifier)
(jsonrpc-request (eglot--current-server-or-lose)
eglot--xref-definitions-method
(get-text-property
0 :textDocumentPositionParams identifier))))
(locations
(and definitions
(if (vectorp definitions) definitions (vector definitions)))))
(eglot--handling-xrefs
(mapcar (eglot--lambda ((Location) uri range)
(eglot--xref-make identifier uri range))
locations))))

(cl-defmethod xref-backend-references ((_backend (eql eglot)) identifier)
(when (eglot--server-capable :referencesProvider)
(let ((params
(or (get-text-property 0 :textDocumentPositionParams identifier)
(let ((rich (car (member identifier eglot--xref-known-symbols))))
(and rich
(get-text-property 0 :textDocumentPositionParams rich))))))
(unless params
(eglot--error "Don' know where %s is in the workspace!" identifier))
(eglot--handling-xrefs
(mapcar
(eglot--lambda ((Location) uri range)
(eglot--xref-make identifier uri range))
(jsonrpc-request (eglot--current-server-or-lose)
:textDocument/references
(append
params
(list :context
(list :includeDeclaration t)))))))))
(eglot--lsp-xref-helper :textDocument/typeDefinition))

(cl-defmethod xref-backend-definitions ((_backend (eql eglot)) _identifier)
(eglot--lsp-xrefs-for-method :textDocument/definition))

(cl-defmethod xref-backend-references ((_backend (eql eglot)) _identifier)
(or
eglot--lsp-xref-refs
(eglot--lsp-xrefs-for-method
:textDocument/references :extra-params `(:context (:includeDeclaration t)))))

(cl-defmethod xref-backend-apropos ((_backend (eql eglot)) pattern)
(when (eglot--server-capable :workspaceSymbolProvider)
Expand Down

0 comments on commit 7048927

Please sign in to comment.