I am trying to make a language server to create a code lens that opens an overlay to other files in VS Code. I tried the SO answer to How to implement overlay in VS Code Extension? (using the peekLocations
command documented here) but in my own attempts I keep getting this unhelpful error when I click on the code lens:
argument does not match one of these constraints: arg instanceof constraint, arg.constructor === constraint, nor constraint(arg) === true
Perhaps there is an issue in LSP implementation I use (the OCaml lsp library), but to make this question language-agnostic, here is the relevant JSON response from my server that creates the code lens:
[Trace - 7:50:16 PM] Received response 'textDocument/codeLens - (12)' in 10ms.
[
{
"command": {
"arguments": [
"file:///home/test/testfile.test",
{
"character": 3,
"line": 9
},
[
{
"range": {
"end": {
"character": 2,
"line": 1
},
"start": {
"character": 1,
"line": 1
}
},
"uri": "file:///home/test/testfile2.test"
},
{
"range": {
"end": {
"character": 2,
"line": 1
},
"start": {
"character": 1,
"line": 1
}
},
"uri": "file:///home/test/testfile2.test"
},
{
"range": {
"end": {
"character": 2,
"line": 1
},
"start": {
"character": 1,
"line": 1
}
},
"uri": "file:///home/test/testfile2.test"
}
],
"gotoAndPeek"
],
"command": "editor.action.peekLocations",
"title": "test"
},
"range": {
"end": {
"character": 9,
"line": 9
},
"start": {
"character": 3,
"line": 9
}
}
}
]
What is wrong with the "arguments"
field, or anything else in this response?
The answer is that the peekLocations
function expects objects of specific types (Uri
, Position
, Location[]
), whereas when an LSP puts a command in a code lens, the arguments are JSON values (strings, arrays, and dictionaries) which are thus the wrong type. The workaround I found is to define my own command as a wrapper that decodes the JSON values into the expected objects before passing them to peekLocations
.
type RawPosition = {line: number, character: number}
type RawRange = {start: RawPosition, end: RawPosition}
type RawLocation = {uri: string, range: RawRange}
const mkPosition = (raw : RawPosition) => new vscode.Position(raw.line, raw.character)
const mkRange = (raw : RawRange) => new vscode.Range(mkPosition(raw.start), mkPosition(raw.end))
const mkLocation = (raw : RawLocation) => new vscode.Location(Uri.parse(raw.uri), mkRange(raw.range))
// New command "myextension.peekLocations" as a wrapper around "editor.action.peekLocations"
const disposable = vscode.commands.registerCommand(context, "myextension.peekLocations", async (rawUri, rawPosition, rawLocations) => {
const uri = Uri.parse(rawUri)
const position = mkPosition(rawPosition)
const locations = rawLocations.map(mkLocation)
await vscode.commands.executeCommand("editor.action.peekLocations", uri, position, locations, "peek")
});
context.subscriptions.push(disposable);