Please consider adding ability to configure path to Godot project subfolder within Zed project folder
Hopefully the gibberish in the title above makes some sense. Can you please consider adding an optional GDScript extension setting to specify the relative path to Godot project subfolder if not the same as Zed project root folder?
Basically, when your Zed project root and your Godot project root is the same folder everything appears to be working fine. However when your Godot project is a subfolder buried somewhere down the hierarchy of your Zed project folder structure this is where things start to happen.
Most notably, when you open the Zed project you are always greeted by a toast saying "The GDScript Language Server might not work correctly with other projects than the one opened in Godot".
Another issue is that Godot LSP appears to start parsing incorrect file types with GDScript syntax, even its own types. For example, if say you have a .gdshader file open in one of Zed buffers with something like this:
shader_type spatial;
render_mode unshaded, skip_vertex_transform;
Re-saving this buffer without any changes will trigger new LSP errors all over the place in it clearly todo with shader code being treated as .gdscript instead of .gdshader:
None of this appears to happen when both Zed and Godot project roots are the same folder. Potentially also related to https://github.com/GDQuest/zed-gdscript/issues/6.
The debugger appears to work fine regardless of whether Zed project root == Godot Project root or is down the hierarchy. It looks like only Godot LSP is affected.
Now if you ask me why do I even need to have Godot project in a subfolder of Zed project it is just common sense when organising your project structure. Any game will most likely have other related resources that need to live side-by-side with your Godot project but not a direct part of it. You may have a Documents folder for notes and docs, a Blender/Houdini/Whatever folder for modelling, a Gimp/Affinity/Whatever folder for imaging, server backend source code, you name it. Everything sitting in a common project root dir under source control in a single git repo. At least this is how I'm grouping project stuff together where it makes sense.
This way you can hop between your notes, godot code, server backend code, etc related stuff in a single instance of Zed. As well use Zed's built-in Git support for source control.
I don't think this is possible right now: as far as I know we don't have access to the paths passed by Zed to the language server. In an editor like visual studio code, you do have a lot more API for extensions but in Zed right now it's very limited and Zed expects/assumes the language server follows the specification well and handles the specifics of its own files.
Note: The issue you mentioned is unrelated to the path configuration: The Godot LSP may try to compile anything as Godot project files/GDScript that are not GDScript files. Being able to give the language server a different project root could help work around that for some people, but you'd still e.g. get GDScript parse errors on any Rust or C++ file in your project (GDExtension files for example)
Well it took a while but I managed to cobble together a (somewhat hacky) solution which works for me.
I had a bit deeper look into it and figured the actual issues are:
- Godot LSP server gets the project root path from LSP "initialize" method call, from the "rootUri" JSON field to be precise. This is the first method request sent by Zed to LSP server after connecting which is NOT logged on Zed's LSP log console btw! Had to jump through some hoops to monitor it.
/godot-4.5/modules/gdscript/language_server/gdscript_language_protocol.cpp =>
GDScriptLanguageProtocol::initialize() =>
Variant root_uri_var = p_params["rootUri"];
- Godot LSP server cannot handle any syntax types other than GDScript. It is treating all LSP document/* method calls as GDScript code, basically ignoring the source file extension altogether:
/godot-4.5/modules/gdscript/language_server/gdscript_text_document.cpp =>
GDScriptTextDocument::didOpen()
GDScriptTextDocument::didChange()
...
- As you guys discovered here #6, for whatever stupid reason Zed will send LSP requests for non GDScript buffers to GDScript LSP server. Resulting in tons of LSP diagnostics errors everywhere from code inline errors to Project Diagnostic view errors and even Project Panel file icons.
So, given the above, my first thought was I'll just grab Zed sources and quickly patch these few annoyances. But hell was I wrong. One can easily break legs in Zed code 😒 Literally wasn't able to figure out what is where with LSP handling. Rust is generally mostly unreadable gibberish to me but Zed code is on another level altogether! Furthermore, it looks like a lot of that LSP/DAP logic is in third party crates.
So I gave up with modding Zed sources in the end. Took an easier albeit shady approach and swapped Apple's uc from XCode toolchain with my own custom uc variant to doctor LSP requests in the fly. It wasn't that bad since LSP is just plain text JSONRPC message exchange over basic TCP stream socket. So I'm now automatically patching initialize.params.rootUri and initialize.workspaceFolders.uri fields depending on where project.godot is found in the Zed project tree. I'm also ignoring all of LSP document/* method requests that are not for .gd files.
With this hack I can now have my Godot project anywhere inside Zed project folder structure with fully functional project scope GDScript LSP and debugging. As well as other language buffers not getting messed up by bogus requests to Godot LSP server.
There is just one annoying issue left: On Macs there is no way to easily override system / dirs with custom locations. Apple deliberately not allowing to change order for system / dirs in $PATH variable. So if say you do something like this:
export PATH = /path/to/my/override/uc/tool:$PATH
On a Mac this will still end up as:
PATH = <system_dirs_go_first> : /path/to/my/override/uc/tool : <other_paths>
The only way to override Apple's stock /usr/bin/uc with my own build for Zed / GDScript extension is to put it under /usr/local/bin (/usr/bin not writable unless you disable Mac SIP). This works fine for Zed but then you can't get XCode to use its own native /usr/bin/uc which has a great deal of extra functionality vs my quick hack custom uc version. So it breaks things for XCode and hell knows what other Apple tools.
Can you please perhaps consider exposing uc binary path as an optional extension config setting rather than relying on "which" lookup alone:
let nc_command = if os == zed::Os::Windows {
worktree.which("ncat").or_else(|| worktree.which("nc"))
} else {
worktree.which("nc").or_else(|| worktree.which("ncat"))
};
Or perhaps somehow support Zed's built-in way of configuring server binary via settings.json like you can do with other LSP servers:
"lsp": {
"rust-analyzer": {
"binary": {
"path": "/path/to/langserver/bin",
"arguments": ["--option", "value"],
"env": {
"FOO": "BAR"
}
}
}
}
This way it would be possible to use custom uc for Zed / GDScript extension and other tools XCode etc can still use the stock one under /usr/bin/uc?
Thanks!
Or perhaps somehow support Zed's built-in way of configuring server binary via settings.json like you can do with other LSP servers
Yes, I will add it tomorrow.
Thanks a ton @fstxz!
@iamvoidcoder I implemented it in https://github.com/GDQuest/zed-gdscript/pull/66. If you don't want to wait until the next release, you can build the extension locally, see here how to do it.
I can fully migrate to Zed + zed-gdscript for Godot dev now. Thank you @fstxz!
Godot LSP server gets the project root path from LSP "initialize" method call, from the "rootUri" JSON field to be precise. This is the first method request sent by Zed to LSP server after connecting which is NOT logged on Zed's LSP log console btw! Had to jump through some hoops to monitor it.
Can you please check if Zed actually sends rootUri? I am trying to debug another issue, but Zed's logs aren't helpful.
Yes, it definitely does. I haven't spent much time investigating Zed side of things. Only had a very brief dig through the sources and concluded the LSP initialize request is prepared elsewhere in one of 3rd party dependency crates, not in the actual Zed code. At which point I gave up on patching rootUri field on Zed side and resorted to doctoring it in the fly in netcat instead.
This is a dump of initialize method call in current build 0.209.6 on a Mac:
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"processId": null,
"rootUri": "file:///Users/voidcoder/Projects/TestProject",
"capabilities": {
"workspace": {
"applyEdit": true,
"workspaceEdit": {
"documentChanges": true,
"resourceOperations": [
"create",
"rename",
"delete"
],
"snippetEditSupport": true
},
"didChangeConfiguration": {
"dynamicRegistration": true
},
"didChangeWatchedFiles": {
"dynamicRegistration": true,
"relativePatternSupport": true
},
"symbol": {
"dynamicRegistration": true
},
"executeCommand": {
"dynamicRegistration": true
},
"workspaceFolders": true,
"configuration": true,
"codeLens": {
"refreshSupport": true
},
"fileOperations": {
"dynamicRegistration": true,
"didRename": true,
"willRename": true
},
"inlayHint": {
"refreshSupport": true
},
"diagnostics": {
"refreshSupport": true
}
},
"textDocument": {
"synchronization": {
"dynamicRegistration": true,
"didSave": true
},
"completion": {
"dynamicRegistration": true,
"completionItem": {
"snippetSupport": true,
"documentationFormat": [
"markdown",
"plaintext"
],
"insertReplaceSupport": true,
"resolveSupport": {
"properties": [
"additionalTextEdits",
"command",
"documentation"
]
},
"insertTextModeSupport": {
"valueSet": [
1,
2
]
},
"labelDetailsSupport": true
},
"contextSupport": true,
"insertTextMode": 2,
"completionList": {
"itemDefaults": [
"commitCharacters",
"editRange",
"insertTextMode",
"insertTextFormat",
"data"
]
}
},
"hover": {
"dynamicRegistration": true,
"contentFormat": [
"markdown"
]
},
"signatureHelp": {
"dynamicRegistration": true,
"signatureInformation": {
"documentationFormat": [
"markdown",
"plaintext"
],
"parameterInformation": {
"labelOffsetSupport": true
},
"activeParameterSupport": true
}
},
"documentSymbol": {
"dynamicRegistration": true,
"hierarchicalDocumentSymbolSupport": true
},
"formatting": {
"dynamicRegistration": true
},
"rangeFormatting": {
"dynamicRegistration": true
},
"onTypeFormatting": {
"dynamicRegistration": true
},
"definition": {
"dynamicRegistration": true,
"linkSupport": true
},
"codeAction": {
"dynamicRegistration": true,
"codeActionLiteralSupport": {
"codeActionKind": {
"valueSet": [
"refactor",
"quickfix",
"source"
]
}
},
"dataSupport": true,
"resolveSupport": {
"properties": [
"kind",
"diagnostics",
"isPreferred",
"disabled",
"edit",
"command"
]
}
},
"codeLens": {
"dynamicRegistration": true
},
"colorProvider": {
"dynamicRegistration": true
},
"rename": {
"dynamicRegistration": true,
"prepareSupport": true,
"prepareSupportDefaultBehavior": 1
},
"publishDiagnostics": {
"relatedInformation": true,
"tagSupport": {
"valueSet": [
1,
2
]
},
"versionSupport": true,
"codeDescriptionSupport": true,
"dataSupport": true
},
"inlayHint": {
"dynamicRegistration": true,
"resolveSupport": {
"properties": [
"textEdits",
"tooltip",
"label.tooltip",
"label.location",
"label.command"
]
}
},
"diagnostic": {
"dynamicRegistration": true,
"relatedDocumentSupport": true
}
},
"window": {
"workDoneProgress": true,
"showMessage": {}
},
"general": {
"positionEncodings": [
"utf-16"
]
},
"experimental": {
"serverStatusNotification": true,
"localDocs": true
}
},
"workspaceFolders": [
{
"uri": "file:///Users/voidcoder/Projects/TestProject",
"name": ""
}
],
"clientInfo": {
"name": "Zed",
"version": "0.209.6"
}
}
}
Thanks a lot!