syl20bnr / spacemacs

A community-driven Emacs distribution - The best editor is neither Emacs nor Vim, it's Emacs *and* Vim!
http://spacemacs.org
GNU General Public License v3.0
23.69k stars 4.9k forks source link

`spacemacs//lsp-avy-document-symbol` errors out for some language servers #15938

Closed rihardsk closed 2 months ago

rihardsk commented 1 year ago

Description :octocat:

There's a bug in spacemacs//lsp-avy-document-symbol which makes it fail for some language servers. As a result, the lsp-avy-goto-word and lsp-avy-goto-symbol functions don't work for those language servers.

Here's the code of the function

(defun spacemacs//lsp-avy-document-symbol (all)
  (interactive)
  (let ((line 0) (col 0) (w (selected-window))
        (ccls (and (memq major-mode '(c-mode c++-mode objc-mode)) (eq c-c++-backend 'lsp-ccls)))
        (start-line (1- (line-number-at-pos (window-start))))
        (end-line (1- (line-number-at-pos (window-end))))
        ranges point0 point1
        candidates)
    (save-excursion
      (goto-char 1)
      (cl-loop for loc in
               (lsp--send-request
                (lsp--make-request
                 "textDocument/documentSymbol"
                 `(:textDocument ,(lsp--text-document-identifier)
                                 :all ,(if all t :json-false)
                                 :startLine ,start-line :endLine ,end-line)))
               for range = (if ccls
                               loc
                             (->> loc (gethash "location") (gethash "range")))
               for range_start = (gethash "start" range)
               for range_end = (gethash "end" range)
               for l0 = (gethash "line" range_start)
               for c0 = (gethash "character" range_start)
               for l1 = (gethash "line" range_end)
               for c1 = (gethash "character" range_end)
               while (<= l0 end-line)
               when (>= l0 start-line)
               do
               (forward-line (- l0 line))
               (forward-char c0)
               (setq point0 (point))
               (forward-line (- l1 l0))
               (forward-char c1)
               (setq point1 (point))
               (setq line l1 col c1)
               (push `((,point0 . ,point1) . ,w) candidates)))
    (avy-with avy-document-symbol
              (avy--process candidates
                            (avy--style-fn avy-style)))))

The issue is with this expression (line 20):

(->> loc (gethash "location") (gethash "range"))

As per the spec https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol, a response to the textDocument/documentSymbol request can contain either a SymbolInformation[] or a DocumentSymbol[]. Of these two only SymbolInformation contains a location field so the above code fails if a DocumentSymbol[] is returned instead. Moreover, the docs state that using the later response type is preferred, making spacemacs//lsp-avy-document-symbol fail for the most common case.

I've tested that rust-analyzer and jdtls (the Eclipse JDT Language Server) both return DocumentSymbol[] making them not supported by this function. When I try to invoke it, I get an error:

if: Wrong type argument: hash-table-p, nil

Reproduction guide :beetle:

Observed behaviour: :eyes: :broken_heart: This message appears

if: Wrong type argument: hash-table-p, nil

Expected behaviour: :heart: :smile: I get to navigate to some symbols or something

System Info :computer:

Backtrace :paw_prints:

Debugger entered--Lisp error: (wrong-type-argument hash-table-p nil)
  gethash("range" nil)
  (->> (->> loc (gethash "location")) (gethash "range"))
  (->> loc (gethash "location") (gethash "range"))
  (if ccls loc (->> loc (gethash "location") (gethash "range")))
  (setq range (if ccls loc (->> loc (gethash "location") (gethash "range"))))
  (progn (setq loc (car --cl-var--)) (setq range (if ccls loc (->> loc (gethash "location") (gethash "range")))) (setq range_start (gethash "start" range)) (setq range_end (gethash "end" range)) (setq l0 (gethash "line" range_start)) (setq c0 (gethash "character" range_start)) (setq l1 (gethash "line" range_end)) (setq c1 (gethash "character" range_end)) (<= l0 end-line))
  (and (consp --cl-var--) (progn (setq loc (car --cl-var--)) (setq range (if ccls loc (->> loc (gethash "location") (gethash "range")))) (setq range_start (gethash "start" range)) (setq range_end (gethash "end" range)) (setq l0 (gethash "line" range_start)) (setq c0 (gethash "character" range_start)) (setq l1 (gethash "line" range_end)) (setq c1 (gethash "character" range_end)) (<= l0 end-line)))
  (while (and (consp --cl-var--) (progn (setq loc (car --cl-var--)) (setq range (if ccls loc (->> loc (gethash "location") (gethash "range")))) (setq range_start (gethash "start" range)) (setq range_end (gethash "end" range)) (setq l0 (gethash "line" range_start)) (setq c0 (gethash "character" range_start)) (setq l1 (gethash "line" range_end)) (setq c1 (gethash "character" range_end)) (<= l0 end-line))) (if (>= l0 start-line) (progn (forward-line (- l0 line)) (forward-char c0) (setq point0 (point)) (forward-line (- l1 l0)) (forward-char c1) (setq point1 (point)) (setq line l1 col c1) (setq candidates (cons (cons (cons point0 point1) w) candidates)))) (setq --cl-var-- (cdr --cl-var--)) (setq --cl-var-- nil))
  (let* ((--cl-var-- (lsp--send-request (lsp--make-request "textDocument/documentSymbol" (list ':textDocument (lsp--text-document-identifier) ':all (if all t :json-false) ':startLine start-line ':endLine end-line)))) (loc nil) (range nil) (range_start nil) (range_end nil) (l0 nil) (c0 nil) (l1 nil) (c1 nil) (--cl-var-- t)) (while (and (consp --cl-var--) (progn (setq loc (car --cl-var--)) (setq range (if ccls loc (->> loc (gethash "location") (gethash "range")))) (setq range_start (gethash "start" range)) (setq range_end (gethash "end" range)) (setq l0 (gethash "line" range_start)) (setq c0 (gethash "character" range_start)) (setq l1 (gethash "line" range_end)) (setq c1 (gethash "character" range_end)) (<= l0 end-line))) (if (>= l0 start-line) (progn (forward-line (- l0 line)) (forward-char c0) (setq point0 (point)) (forward-line (- l1 l0)) (forward-char c1) (setq point1 (point)) (setq line l1 col c1) (setq candidates (cons (cons (cons point0 point1) w) candidates)))) (setq --cl-var-- (cdr --cl-var--)) (setq --cl-var-- nil)) nil)
  (save-excursion (goto-char 1) (let* ((--cl-var-- (lsp--send-request (lsp--make-request "textDocument/documentSymbol" (list ':textDocument (lsp--text-document-identifier) ':all (if all t :json-false) ':startLine start-line ':endLine end-line)))) (loc nil) (range nil) (range_start nil) (range_end nil) (l0 nil) (c0 nil) (l1 nil) (c1 nil) (--cl-var-- t)) (while (and (consp --cl-var--) (progn (setq loc (car --cl-var--)) (setq range (if ccls loc (->> loc ... ...))) (setq range_start (gethash "start" range)) (setq range_end (gethash "end" range)) (setq l0 (gethash "line" range_start)) (setq c0 (gethash "character" range_start)) (setq l1 (gethash "line" range_end)) (setq c1 (gethash "character" range_end)) (<= l0 end-line))) (if (>= l0 start-line) (progn (forward-line (- l0 line)) (forward-char c0) (setq point0 (point)) (forward-line (- l1 l0)) (forward-char c1) (setq point1 (point)) (setq line l1 col c1) (setq candidates (cons (cons ... w) candidates)))) (setq --cl-var-- (cdr --cl-var--)) (setq --cl-var-- nil)) nil))
  (let ((line 0) (col 0) (w (selected-window)) (ccls (and (memq major-mode '(c-mode c++-mode objc-mode)) (eq c-c++-backend 'lsp-ccls))) (start-line (1- (line-number-at-pos (window-start)))) (end-line (1- (line-number-at-pos (window-end)))) ranges point0 point1 candidates) (save-excursion (goto-char 1) (let* ((--cl-var-- (lsp--send-request (lsp--make-request "textDocument/documentSymbol" (list ... ... ... ... ... start-line ... end-line)))) (loc nil) (range nil) (range_start nil) (range_end nil) (l0 nil) (c0 nil) (l1 nil) (c1 nil) (--cl-var-- t)) (while (and (consp --cl-var--) (progn (setq loc (car --cl-var--)) (setq range (if ccls loc ...)) (setq range_start (gethash "start" range)) (setq range_end (gethash "end" range)) (setq l0 (gethash "line" range_start)) (setq c0 (gethash "character" range_start)) (setq l1 (gethash "line" range_end)) (setq c1 (gethash "character" range_end)) (<= l0 end-line))) (if (>= l0 start-line) (progn (forward-line (- l0 line)) (forward-char c0) (setq point0 (point)) (forward-line (- l1 l0)) (forward-char c1) (setq point1 (point)) (setq line l1 col c1) (setq candidates (cons ... candidates)))) (setq --cl-var-- (cdr --cl-var--)) (setq --cl-var-- nil)) nil)) (avy-with avy-document-symbol (avy--process candidates (avy--style-fn avy-style))))
  spacemacs//lsp-avy-document-symbol(t)
  spacemacs/lsp-avy-goto-word()
  funcall-interactively(spacemacs/lsp-avy-goto-word)
  call-interactively(spacemacs/lsp-avy-goto-word nil nil)
  command-execute(spacemacs/lsp-avy-goto-word)
lebensterben commented 1 year ago

could this be removed and replaced by lsp-ivy-workspace-symbol

rihardsk commented 1 year ago

I haven't used ivy, but it seems that lsp-ivy-workspace-symbol is the equivalent of helm-lsp-workspace-symbol which lets you search through workspace symbols. spacemacs//lsp-avy-document-symbol does a different thing – it lets you jump to a position in the current document.

smile13241324 commented 1 year ago

I have made a first commit to fix our function, it will not longer break however we still need to obtain all ranges, for now we only get the first one. If someone can help with this it would be very much appreciated. If not I will have a look later.

smile13241324 commented 2 months ago

I don't think that we have the resources to maintain specific functions like these. Instead it must be moved into a specific emacs package and integrated in one of the existing layers. Therefore I have removed the mostly broken spacemacs specific implementation today.