templ
templ copied to clipboard
lsp: support textDocument/documentSymbol
Hi, I am missing a feature of the symbol list(the 'Ctrl+Shift+O' menu) in your vscode extension. The result could look like this:
It would only have to contain the top-level
templ
, css
, script
and type
declarations.
I agree, this would be a convenient improvement 🙂
This feature is driven by the following LSP capability: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol
Implementing this should unlock this feature for both vscode and other clients supporting this capability (nvim).
I took a look at how to implement this.
The feature is supposed to list useful stuff in the file - variables etc., and then you can navigate the file using those symbols.
But at the moment, the types for templ's templ
(parser.HTMLTemplate), css
and other top level elements don't store their position in the templ object model, so first step is to add a field to store that information.
type HTMLTemplate struct {
+ Position Range
Expression Expression
Children []Node
}
Then, populate it during parsing like this:
var template = parse.Func(func(pi *parse.Input) (r HTMLTemplate, ok bool, err error) {
// templ FuncName(p Person, other Other) {
+ r.Position.From = NewPosition(int64(pi.Index()), uint32(pi.Position().Line), uint32(pi.Position().Col))
var te templateExpression
if te, ok, err = templateExpressionParser.Parse(pi); err != nil || !ok {
return
}
r.Expression = te.Expression
+ r.Position.To = NewPosition(int64(pi.Index()), uint32(pi.Position().Line), uint32(pi.Position().Col))
Next, the LSP proxy (in templ/proxy/server.go
) has a parseTemplate
function. This function doesn't currently store the object model of templ files in the struct, like it does with the source map, or diagnostics (DiagnosticCache
), so the next step is to get the server to store a map of file URI to list of symbols in the struct so that we can grab them when DocumentSymbol
is called. It shouldn't be an lsp.DocumentSymbol
that's stored in the cache, because they're in different packages and we'd end up with a circular dependency, but will be close enough structurally.
Finally, when DocumentSymbol is called, need to call the Go LSP first, then do the rest. Something like this.
func (p *Server) DocumentSymbol(ctx context.Context, params *lsp.DocumentSymbolParams) (result []interface{} /* []SymbolInformation | []DocumentSymbol */, err error) {
p.Log.Info("client -> server: DocumentSymbol")
defer p.Log.Info("client -> server: DocumentSymbol end")
//TODO: Convert the templ URI to a Go URI.
//result, err = p.Target.DocumentSymbol(ctx, params)
//if err != nil {
//return
//}
//TODO: Rewrite the positions that came back, and remove any items that are not in the source map cache.
//TODO: Grab the symbol data from the `*Server` symbol cache, and populate the results.
result = append(result, lsp.SymbolInformation{
Name: "templ",
Kind: lsp.SymbolKindFunction,
Tags: []lsp.SymbolTag{},
Deprecated: false,
Location: lsp.Location{},
ContainerName: "",
})
result = append(result, lsp.SymbolInformation{
Name: "css",
Kind: lsp.SymbolKindFunction,
Tags: []lsp.SymbolTag{},
Deprecated: false,
Location: lsp.Location{},
ContainerName: "",
})
result = append(result, lsp.SymbolInformation{
Name: "script",
Kind: lsp.SymbolKindFunction,
Tags: []lsp.SymbolTag{},
Deprecated: false,
Location: lsp.Location{},
ContainerName: "",
})
return
}
If someone wants to have a crack at it, let me know. With tests etc. it's probably a day's work.