Skip to content

Commit

Permalink
Add a new boolean-action-arguments client param
Browse files Browse the repository at this point in the history
This lets us fix emacs-lsp#4184 by selectively changing `nil` code action
argument values to `:json-false`. Once this is merged, HLS needs to set
the field to `'(:withSig)` (and possibly some other values).
  • Loading branch information
robbert-vdh committed Oct 30, 2023
1 parent 0f5723f commit 54b971e
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.org
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
* Add [https://github.com/rubocop/rubocop][RuboCop built-in language server]] for linting and formatting Ruby code.
* Add Move language server support.
* Add mdx support using [[https://github.com/mdx-js/mdx-analyzer/tree/main/packages/language-server][mdx-language-server]]
* Added a new optional ~:boolean-action-arguments~ argument when defining LSP clients. This allows clients to work around an lsp-mode parsing quirk that incorrectly sends ~null~ values instead of ~false~ in code action requests. An example of a code action that was broken because of this is the 'Fill placeholders for ...' code action in Haskell Language Server.
** Release 8.0.0
* Add ~lsp-clients-angular-node-get-prefix-command~ to get the Angular server from another location which is still has ~/lib/node_modules~ in it.
* Set ~lsp-clients-angular-language-server-command~ after the first connection to speed up subsequent connections.
Expand Down
52 changes: 51 additions & 1 deletion lsp-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,22 @@ return value of `body' or nil if interrupted."
;; to the server.
(action-handlers (make-hash-table :test 'equal))

;; `boolean-action-arguments' is a list of code action argument names that
;; represent non-nullable booleans.
;;
;; The values of this list can be either symbols, or lists of symbols for
;; deeper nested values. For example:
;;
;; > '(:foo :bar (:some :nested :boolean))
;;
;; When there are available code actions, the server sends `lsp-mode' a list
;; of possible command names and arguments as JSON. `lsp-mode' parses all
;; boolean false values as `nil'. As a result code action arguments containing
;; falsy values don't roundtrip correctly because `lsp-mode' will end up
;; sending null values back to the client. This list makes it possible to
;; selectively transform `nil' values back into `:json-false'.
(boolean-action-arguments '())

;; major modes supported by the client.
major-modes
;; Function that will be called to decide if this language client
Expand Down Expand Up @@ -5920,7 +5936,41 @@ Request codeAction/resolve for more info if server supports."

(cond
((stringp command?) (lsp--execute-command action))
((lsp-command? command?) (lsp--execute-command command?))))
((lsp-command? command?) (progn
(lsp--fix-code-action-booleans command?)
(lsp--execute-command command?)))))

(lsp-defun lsp--fix-code-action-booleans ((&Command :arguments?))
"Patch incorrect boolean argument values in the provided `CodeAction' command
in place, based on the BOOLEAN-ACTION-ARGUMENTS LSP client
parameter. lsp-mode parses JSON false values `nil' values, which are then
encoded back to JSON as null values. This may not match the language server's
schema, and these values thus need to be changed patched back to false values
for the affected requests to work correctly."
(let ((boolean-action-arguments (->> (lsp-workspaces)
(cl-first)
(or lsp--cur-workspace)
(lsp--workspace-client)
(lsp--client-boolean-action-arguments))))
(seq-doseq (path boolean-action-arguments)
(seq-doseq (args arguments?)
(lsp--fix-nested-boolean args (if (listp path) path (list path)))))))

(defun lsp--fix-nested-boolean (structure path)
"Traverse STRUCTURE using the paths from the PATH list, changing the value to
`:json-false' if it was `nil'. PATH should be a list containing
one or more symbols, and STRUCTURE should be compatible with
`lsp-member?', `lsp-get', and `lsp-put'."
(let ((key (car path))
(rest (cdr path)))
(if (null rest)
;; `lsp-put' returns `nil' both when the key doesn't exist and when the
;; value is `nil', so we need to explicitly check its presence here
(when (and (lsp-member? structure key) (not (lsp-get structure key)))
(lsp-put structure key :json-false))
;; If `key' does not exist, then we'll silently ignore it
(when-let ((child (lsp-get structure key)))
(lsp--fix-nested-boolean child rest)))))

(defvar lsp--formatting-indent-alist
;; Taken from `dtrt-indent-mode'
Expand Down

0 comments on commit 54b971e

Please sign in to comment.