joaotavora / eglot

A client for Language Server Protocol servers
GNU General Public License v3.0
2.25k stars 202 forks source link

Flymake getting clobbered by doclets/types in Eldoc #889

Closed trev-dev closed 2 years ago

trev-dev commented 2 years ago

The issue

I've seen other issues here regarding Eldoc showing doclets/types in favor of error diagnostics from Flymake. It seems to me that the common solution is to set Eldoc's documentation strategy to compose. This is not working for me.

Before I eat up too much of anyone else's time: yes, I am using a git build of Emacs. I can, and will, go to the trouble of swapping back to stable if that is required. This may just be an Eldoc issue. Ultimately my goal is to rule out Eglot and decide what to do from there.

At the very end of this issue there are additional files/configurations to help re-produce this issue.

Refs to issues I looked into before submitting:

Visual examples of what is going on:

Given the following intentional errors:

Screenshot from 2022-03-18 10-02-00

Eldoc's output is only:

Screenshot from 2022-03-18 10-54-34

Flymake's error buffer: Screenshot from 2022-03-18 10-02-13

The issue template

LSP transcript - M-x eglot-events-buffer (mandatory unless Emacs inoperable)

... Paste the events transcript here ...  Try to start from the line that says
[stderr] Process EGLOT (eglot/js-mode) stderr finished
[client-request] (id:1) Fri Mar 18 10:27:40 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
      (:processId 137517 :rootPath "/home/trevdev/Projects/eglot/" :rootUri "file:///home/trevdev/Projects/eglot" :initializationOptions #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                                                                               ())
              :capabilities
              (:workspace
               (:applyEdit t :executeCommand
                   (:dynamicRegistration :json-false)
                   :workspaceEdit
                   (:documentChanges t)
                   :didChangeWatchedFiles
                   (:dynamicRegistration t)
                   :symbol
                   (:dynamicRegistration :json-false)
                   :configuration t)
               :textDocument
               (:synchronization
            (:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
            :completion
            (:dynamicRegistration :json-false :completionItem
                          (:snippetSupport :json-false :deprecatedSupport t :tagSupport
                                   (:valueSet
                                [1]))
                          :contextSupport t)
            :hover
            (:dynamicRegistration :json-false :contentFormat
                          ["plaintext"])
            :signatureHelp
            (:dynamicRegistration :json-false :signatureInformation
                          (:parameterInformation
                           (:labelOffsetSupport t)
                           :activeParameterSupport t))
            :references
            (:dynamicRegistration :json-false)
            :definition
            (:dynamicRegistration :json-false :linkSupport t)
            :declaration
            (:dynamicRegistration :json-false :linkSupport t)
            :implementation
            (:dynamicRegistration :json-false :linkSupport t)
            :typeDefinition
            (:dynamicRegistration :json-false :linkSupport t)
            :documentSymbol
            (:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
                          (:valueSet
                           [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
            :documentHighlight
            (:dynamicRegistration :json-false)
            :codeAction
            (:dynamicRegistration :json-false :codeActionLiteralSupport
                          (:codeActionKind
                           (:valueSet
                        ["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
                          :isPreferredSupport t)
            :formatting
            (:dynamicRegistration :json-false)
            :rangeFormatting
            (:dynamicRegistration :json-false)
            :rename
            (:dynamicRegistration :json-false)
            :publishDiagnostics
            (:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
                         (:valueSet
                          [1 2])))
               :experimental #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                           ()))))
[server-notification] Fri Mar 18 10:27:40 2022:
(:jsonrpc "2.0" :method "window/logMessage" :params
      (:type 3 :message "[lspserver] Using Typescript version (bundled) 4.5.5 from path \"/home/trevdev/.local/npm/lib/node_modules/typescript/lib/tsserver.js\""))
[server-reply] (id:1) Fri Mar 18 10:27:40 2022:
(:jsonrpc "2.0" :id 1 :result
      (:capabilities
       (:textDocumentSync 2 :completionProvider
                  (:triggerCharacters
                   ["." "\"" "'" "/" "@" "<"]
                   :resolveProvider t)
                  :codeActionProvider
                  (:codeActionKinds
                   ["source.fixAll.ts" "source.removeUnused.ts" "source.addMissingImports.ts" "source.organizeImports.ts"])
                  :definitionProvider t :documentFormattingProvider t :documentRangeFormattingProvider t :documentHighlightProvider t :documentSymbolProvider t :executeCommandProvider
                  (:commands
                   ["_typescript.applyWorkspaceEdit" "_typescript.applyCodeAction" "_typescript.applyRefactoring" "_typescript.organizeImports" "_typescript.applyRenameFile"])
                  :hoverProvider t :renameProvider t :referencesProvider t :signatureHelpProvider
                  (:triggerCharacters
                   ["(" "," "<"])
                  :workspaceSymbolProvider t :implementationProvider t :typeDefinitionProvider t :foldingRangeProvider t :semanticTokensProvider
                  (:documentSelector nil :legend
                         (:tokenTypes
                          ["class" "enum" "interface" "namespace" "typeParameter" "type" "parameter" "variable" "enumMember" "property" "function" "member"]
                          :tokenModifiers
                          ["declaration" "static" "async" "readonly" "defaultLibrary" "local"])
                         :full t :range t)
                  :callsProvider t)))
[client-notification] Fri Mar 18 10:27:40 2022:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
                                ()))
[client-notification] Fri Mar 18 10:27:40 2022:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js" :version 0 :languageId "js" :text "/**\n * @file An example module.\n * @module example\n */\n\n/**\n * Programmer age reference by keyof name.\n */\nconst programmerAges = {\n    tom: 24,\n    dick: 36,\n    harry: 48\n}\n\n/**\n * A superflous example function which guards against invalid key references.\n * Though it may be less likely that an incorrect key could be passed in, there's\n * still a chance the return value could be undefined. TypeScript would have\n * you believe that this is type safe.\n *\n * @function\n * @param {keyof programmerAges} name - A programmer's name.\n * @see {@link module:example~programmerAges}\n * @returns {number} An age number\n */\nconst getAge = name => programmers[name];\n\n/** @type {keyof programmerAges} */\nconst tom = \"tom\";\n\nconst age = getAge(\"carl\");\n")))
[client-notification] Fri Mar 18 10:27:40 2022:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
      (:settings nil))
[server-notification] Fri Mar 18 10:27:42 2022:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
      (:uri "file:///home/trevdev/Projects/eglot/test.js" :diagnostics
        [(:range
          (:start
           (:line 25 :character 23)
           :end
           (:line 25 :character 34))
          :message "Cannot find name 'programmers'." :severity 1 :code 2304 :source "typescript" :tags
          [])
         (:range
          (:start
           (:line 30 :character 19)
           :end
           (:line 30 :character 25))
          :message "Argument of type '\"carl\"' is not assignable to parameter of type '\"tom\" | \"dick\" | \"harry\"'." :severity 1 :code 2345 :source "typescript" :tags
          [])]))
[client-request] (id:2) Fri Mar 18 10:27:59 2022:
(:jsonrpc "2.0" :id 2 :method "textDocument/signatureHelp" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 25 :character 26)))
[client-request] (id:3) Fri Mar 18 10:27:59 2022:
(:jsonrpc "2.0" :id 3 :method "textDocument/hover" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 25 :character 26)))
[client-request] (id:4) Fri Mar 18 10:27:59 2022:
(:jsonrpc "2.0" :id 4 :method "textDocument/documentHighlight" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 25 :character 26)))
[server-reply] (id:2) Fri Mar 18 10:27:59 2022:
(:jsonrpc "2.0" :id 2 :result nil)
[server-reply] (id:3) Fri Mar 18 10:27:59 2022:
(:jsonrpc "2.0" :id 3 :result
      (:contents
       [(:language "typescript" :value "any")
        ""]
       :range
       (:start
        (:line 25 :character 23)
        :end
        (:line 25 :character 34))))
[server-reply] (id:4) Fri Mar 18 10:27:59 2022:
(:jsonrpc "2.0" :id 4 :result
      [])
[client-request] (id:5) Fri Mar 18 10:28:03 2022:
(:jsonrpc "2.0" :id 5 :method "textDocument/signatureHelp" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 25 :character 27)))
[client-request] (id:6) Fri Mar 18 10:28:03 2022:
(:jsonrpc "2.0" :id 6 :method "textDocument/hover" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 25 :character 27)))
[client-request] (id:7) Fri Mar 18 10:28:03 2022:
(:jsonrpc "2.0" :id 7 :method "textDocument/documentHighlight" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 25 :character 27)))
[server-reply] (id:5) Fri Mar 18 10:28:03 2022:
(:jsonrpc "2.0" :id 5 :result nil)
[server-reply] (id:6) Fri Mar 18 10:28:03 2022:
(:jsonrpc "2.0" :id 6 :result
      (:contents
       [(:language "typescript" :value "any")
        ""]
       :range
       (:start
        (:line 25 :character 23)
        :end
        (:line 25 :character 34))))
[server-reply] (id:7) Fri Mar 18 10:28:03 2022:
(:jsonrpc "2.0" :id 7 :result
      [])
[client-request] (id:8) Fri Mar 18 10:28:25 2022:
(:jsonrpc "2.0" :id 8 :method "textDocument/signatureHelp" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 26 :character 0)))
[client-request] (id:9) Fri Mar 18 10:28:25 2022:
(:jsonrpc "2.0" :id 9 :method "textDocument/hover" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 26 :character 0)))
[client-request] (id:10) Fri Mar 18 10:28:25 2022:
(:jsonrpc "2.0" :id 10 :method "textDocument/documentHighlight" :params
      (:textDocument
       (:uri "file:///home/trevdev/Projects/eglot/test.js")
       :position
       (:line 26 :character 0)))
[server-reply] (id:8) Fri Mar 18 10:28:25 2022:
(:jsonrpc "2.0" :id 8 :result nil)
[server-reply] (id:9) Fri Mar 18 10:28:25 2022:
(:jsonrpc "2.0" :id 9 :result
      (:contents
       []))
[server-reply] (id:10) Fri Mar 18 10:28:25 2022:
(:jsonrpc "2.0" :id 10 :result
      [])

Backtrace (mandatory, unless no error message seen or heard):

No errors.

Minimal configuration (mandatory)

Note how this is different from the instructions, but still good.

 # Type this in a shell to start an Emacs with Eglot configured
 $ /path/to/a/certain/version/of/emacs -q --load config.test.el
 ;; Example of a minimal configuration
 ;;
(load-file "~/.emacs.d/elpa/eglot-20220315.1021/eglot.el")
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs '(php-mode . ("intelephense" "--stdio")))
  (add-to-list 'eglot-server-programs '(svelte-mode . ("svelteserver" "--stdio")))
  (add-to-list 'eglot-server-programs '(shopify-mode
                                        . ("theme-check-language-server" "--stdio")))
  (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename)
  (define-key eglot-mode-map (kbd "C-c h") 'eldoc)
  (define-key eglot-mode-map (kbd "C-c r") 'xref-find-definitions))

(defun td/start-eglot ()
  "Start eglot with the following local preferences."
  (setq-local eldoc-documentation-strategy #'eldoc-documentation-compose)
  (eglot-ensure))
;; Set-up programming modes to use Eglot
(dolist (mode '(css-mode-hook
                scss-mode-hook
                php-mode-hook
                js-mode-hook
                rjsx-mode-hook
                typescript-mode-hook
                svelte-mode-hook))
  (add-hook mode #'td/start-eglot))

Example JS file

/**
 * @file An example module.
 * @module example
 */

/**
 * Programmer age reference by keyof name.
 */
const programmerAges = {
    tom: 24,
    dick: 36,
    harry: 48
}

/**
 * A superflous example function which guards against invalid key references.
 * Though it may be less likely that an incorrect key could be passed in, there's
 * still a chance the return value could be undefined. TypeScript would have
 * you believe that this is type safe.
 *
 * @function
 * @param {keyof programmerAges} name - A programmer's name.
 * @see {@link module:example~programmerAges}
 * @returns {number} An age number
 */
const getAge = name => programmers[name];

/** @type {keyof programmerAges} */
const tom = "tom";

const age = getAge("carl");

Required jsconfig.json

You will need this if you expect TypeScript to check types in JavaScript. Put this in your project root as jsconfig.json

{
    "compilerOptions": {
    "checkJs": true
    }
}
joaotavora commented 2 years ago

Thanks, this seems like a good valid issue with all that's needed. But I've exhausted my Eglot budget for the day (and maybe for the week :grimacing: ).

joaotavora commented 2 years ago

Can you double check that the eldoc-documentation-strategy is actually being set to the eldoc-documentation-compose? Maybe it's not. You can try to set it in eglot-managed-mode-hook instead of your setq-local, which -- I think -- would be overwritten by Eglot's management of that extension.

nemethf commented 2 years ago

@trev-dev, I've only read the minimal configuration section of the bug report. (It is not minimal because the following lines can surely be omitted.)

(add-to-list 'eglot-server-programs '(php-mode . ("intelephense" "--stdio")))

It seems you are using a php lsp server. So can you take a look at #537, and contribute to it?

(define-key eglot-mode-map (kbd "C-c r") 'eglot-rename) (define-key eglot-mode-map (kbd "C-c r") 'xref-find-definitions))

The lines above bind to the same key.

trev-dev commented 2 years ago

@trev-dev, I've only read the minimal configuration section of the bug report. (It is not minimal because the following lines can surely be omitted.)

Apologies for that. I do not see how js-mode and php-mode could be getting confused in this context, but if need be, I can re-test without additional server configs and re-submit.

@trev-dev, I've only read the minimal configuration section of the bug report. (It is not minimal because the following lines can surely be omitted.)

(add-to-list 'eglot-server-programs '(php-mode . ("intelephense" "--stdio")))

It seems you are using a php lsp server. So can you take a look at #537, and contribute to it?

(define-key eglot-mode-map (kbd "C-c r") 'eglot-rename) (define-key eglot-mode-map (kbd "C-c r") 'xref-find-definitions))

The lines above bind to the same key.

This just shows how often I use these keybinds (rarely, if ever). Thanks for the catch! I would like to contribute to 537, but if I'm being honest, I don't use PHP frequently enough to call my feedback on LSP servers credible.

trev-dev commented 2 years ago

Can you double check that the eldoc-documentation-strategy is actually being set to the eldoc-documentation-compose? Maybe it's not. You can try to set it in eglot-managed-mode-hook instead of your setq-local, which -- I think -- would be overwritten by Eglot's management of that extension.

There was definitely some ambiguity as to what was going on here when I had looked at described-variable. I tried binding it several ways and this is what I ended up with in the original, minimal config:

eldoc-documentation-strategy is a variable defined in ‘eldoc.el’.

Its value is ‘eldoc-documentation-enthusiast’
Original value was 
‘eldoc-documentation-default’
Local in buffer test.js; global value is 
eldoc-documentation-default

Hold up, that's not ambiguous at all. Let me try this again.

trev-dev commented 2 years ago

Ok, yep. I must have gotten my eyes crossed here. I thought the buffer-local value was compose. I must've read it incorrectly.

To the satisfaction of @nemethf and myself I also further minimized the config to this:

(load-file "~/.emacs.d/elpa/eglot-20220315.1021/eglot.el")
;; Set-up programming modes to use Eglot
(add-hook 'js-mode-hook #'eglot-ensure)
(add-hook 'eglot-managed-mode-hook
          #'(lambda ()
              (setq-local eldoc-documentation-strategy
                          #'eldoc-documentation-compose)))

And voila, eldoc is giving me all of my feedback now:

Screenshot from 2022-03-18 12-09-35

Thanks for talking this out with me. I will re-work my config to use the managed-mode hook instead of sequential function calls.