-
-
Notifications
You must be signed in to change notification settings - Fork 399
Description
Your environment
Which OS do you use?
MacOS and Arch Linux, the issue appears on both.
Which version of GHC do you use and how did you install it?
GHC 9.10.1 using ghcup
How is your project built (alternative: link to the project)?
My main reproduction case is a simple haskell script with embedded cabal metadata:
#!/usr/bin/env cabal
{- cabal:
build-depends: turtle, base
-}
main :: IO ()
main = putStrLn "foo"
However, the issue also appears in full cabal projects.
Which LSP client (editor/plugin) do you use?
neovim 0.10.0 using the haskell-tools plugin, lsp-config and nvim-cmp.
My configuration can be found here.
Which version of HLS do you use and how did you install it?
HLS 2.9.0.1 using ghcup
Have you configured HLS in any way (especially: a hie.yaml
file)?
No
Steps to reproduce
- Create a new file, test.hs
- Start typing the code provided in the above example.
Expected behaviour
Autocompletion should work correctly
Actual behaviour
At some point, every time a character is typed, the following error message appears:
Error executing vim.schedule lua callback: ...local/share/nvim/lazy/nvim-cmp/lua/cmp/utils/snippet.lua:422
: snippet parsing failed
stack traceback:
[C]: in function 'error'
...local/share/nvim/lazy/nvim-cmp/lua/cmp/utils/snippet.lua:422: in function 'parse'
...s/ryan/.local/share/nvim/lazy/nvim-cmp/lua/cmp/entry.lua:130: in function 'callback'
.../.local/share/nvim/lazy/nvim-cmp/lua/cmp/utils/cache.lua:38: in function 'get_word'
...s/ryan/.local/share/nvim/lazy/nvim-cmp/lua/cmp/entry.lua:81: in function 'callback'
.../.local/share/nvim/lazy/nvim-cmp/lua/cmp/utils/cache.lua:38: in function 'get_offset'
.../ryan/.local/share/nvim/lazy/nvim-cmp/lua/cmp/source.lua:353: in function ''
vim/_editor.lua: in function <vim/_editor.lua:0>
Debug information
I've spent a bit of time diving into this issue and I think I understand what the root cause is. There's a few components at work:
- nvim-cmp has recently updated their codebase to perform validation on code snippets: see this commit
- I modified the error message of nvim-cmp to see what the snippet was that it was failing to parse. The snippet it showed was
$!
- Given that
$!
is a standard operator in Haskell, I began to suspect that the issue is actually coming from HLS - Sure enough, looking at the debug logs for the lsp, I see this, in the response from method "textDocument/completion". I've only copied the relevant bit:
itemName = { "GHC.Internal.Base", "ghc-internal", "v", "$!" }, itemNeedsType = true } },
detail = "from Prelude", documentation = { kind = "markdown", value = "*Imported from 'Prelude'*\n" },
insertText = "$!", insertTextFormat = 2, kind = 3, label = "$!", sortText = "01" }, { data = {
resolvePlugin = "ghcide-completions", resolveURI = "file:///Users/ryan/test3.hs", resolveValue = { itemFile =
"file:///Users/ryan/test3.hs", itemName = { "GHC.Internal.Base", "ghc-internal", "v", "." }, itemNeedsType = true }
}, detail = "from Prelude", documentation = { kind = "markdown", value = "*Imported from 'Prelude'*\n" },
So HLS is responding with a completion item of kind Snippet, and with an insertText of "$!".
5. Here's the documentation for the snippet syntax specification from vscode, which nvim-cmp also uses. Specifically, the section describing how $ characters must be escaped using \, otherwise they will be interpreted as a placeholder variable: link.
6. Looking at the HLS source code, specifically ghcide/src/Development/IDE/Plugin/Completions/Logic.hs line 210:
let ci = CompletionItem
{_label = label,
_kind = kind,
_tags = Nothing,
_detail =
case (typeText, provenance) of
(Just t,_) | not(T.null t) -> Just $ ":: " <> t
(_, ImportedFrom mod) -> Just $ "from " <> mod
(_, DefinedIn mod) -> Just $ "from " <> mod
_ -> Nothing,
_documentation = documentation,
_deprecated = Nothing,
_preselect = Nothing,
_sortText = Nothing,
_filterText = Nothing,
_insertText = Just insertText,
_insertTextFormat = Just InsertTextFormat_Snippet,
_insertTextMode = Nothing,
_textEdit = Nothing,
_additionalTextEdits = Nothing,
_commitCharacters = Nothing,
_command = mbCommand,
_data_ = toJSON <$> fmap (CompletionResolveData uri (isNothing typeText)) nameDetails,
_labelDetails = Nothing,
_textEditText = Nothing}
It appears that the language server hardcodes every response to be of kind Snippet, and does not use PlainText responses, except in the case that snippets are disabled or the symbol being types is infix (e.g. foo `on` bar
).
- All put together, since $ is a valid character in Haskell but is not handled or escaped by HLS, it responds with invalid snippets, and nvim-cmp correctly fails.
There's two potential solutions here:
- Continue returning all responses as snippets, but ensure that all $ characters outside of actual snippet placeholders are escaped as \$
- Tag non-snippet items as _insertTextFormat = InsertTextFormat_PlainText. Save InsertTextFormat_Snippet for actual snippet items.
Currently the only workaround is to disable snippets in the language server, or role back to a version of nvim-cmp that doesn't perform validation, otherwise the functionality will be broken. The following code in init.lua should work as a temporary fix:
vim.g.haskell_tools = {
hls = {
settings = {
plugin = {
['ghcide-completions'] = {
config = {
snippetsOn = false,
autoExtendOn = true
}
}
}
}
}
}