From fb6a5c25b4694c5d3d8679deda63965b3bd2c8c2 Mon Sep 17 00:00:00 2001 From: Tristan Knight Date: Mon, 23 Sep 2024 16:34:09 +0100 Subject: [PATCH] feat(lsp): add workspace folder initialization (#912) --- cmd/templ/lspcmd/lsp_test.go | 63 ++++++++++----- cmd/templ/lspcmd/proxy/server.go | 80 ++++++++++++++++++- .../testproject/testdata/remoteChild.templ | 5 ++ .../testproject/testdata/remoteParent.templ | 9 +++ 4 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 cmd/templ/testproject/testdata/remoteChild.templ create mode 100644 cmd/templ/testproject/testdata/remoteParent.templ diff --git a/cmd/templ/lspcmd/lsp_test.go b/cmd/templ/lspcmd/lsp_test.go index 2331e49df..041605a55 100644 --- a/cmd/templ/lspcmd/lsp_test.go +++ b/cmd/templ/lspcmd/lsp_test.go @@ -335,35 +335,19 @@ func TestReferences(t *testing.T) { defer teardown(t) defer cancel() - templFile, err := os.ReadFile(appDir + "/templates.templ") - if err != nil { - t.Fatalf("failed to read file %q: %v", appDir+"/templates.templ", err) - return - - } - err = server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{ - TextDocument: protocol.TextDocumentItem{ - URI: uri.URI("file://" + appDir + "/templates.templ"), - LanguageID: "templ", - Version: 1, - Text: string(templFile), - }, - }) - if err != nil { - t.Errorf("failed to register open file: %v", err) - return - } log.Info("Calling References") tests := []struct { line int character int + filename string assert func(t *testing.T, l []protocol.Location) (msg string, ok bool) }{ { // this is the definition of the templ function in the templates.templ file. line: 5, character: 9, + filename: "/templates.templ", assert: func(t *testing.T, actual []protocol.Location) (msg string, ok bool) { expectedReference := []protocol.Location{ { @@ -391,6 +375,7 @@ func TestReferences(t *testing.T) { // this is the definition of the struct in the templates.templ file. line: 21, character: 9, + filename: "/templates.templ", assert: func(t *testing.T, actual []protocol.Location) (msg string, ok bool) { expectedReference := []protocol.Location{ { @@ -414,6 +399,46 @@ func TestReferences(t *testing.T) { return "", true }, }, + { + // this test is for inclusions from a remote file that has not been explicitly called with didOpen + line: 3, + character: 9, + filename: "/remoteChild.templ", + assert: func(t *testing.T, actual []protocol.Location) (msg string, ok bool) { + expectedReference := []protocol.Location{ + { + URI: uri.URI("file://" + appDir + "/remoteParent.templ"), + Range: protocol.Range{ + Start: protocol.Position{ + Line: uint32(3), + Character: uint32(2), + }, + End: protocol.Position{ + Line: uint32(3), + Character: uint32(8), + }, + }, + }, + { + URI: uri.URI("file://" + appDir + "/remoteParent.templ"), + Range: protocol.Range{ + Start: protocol.Position{ + Line: uint32(7), + Character: uint32(2), + }, + End: protocol.Position{ + Line: uint32(7), + Character: uint32(8), + }, + }, + }, + } + if diff := lspdiff.References(expectedReference, actual); diff != "" { + return fmt.Sprintf("Expected: %+v\nActual: %+v", expectedReference, actual), false + } + return "", true + }, + }, } for i, test := range tests { @@ -429,7 +454,7 @@ func TestReferences(t *testing.T) { actual, err := server.References(ctx, &protocol.ReferenceParams{ TextDocumentPositionParams: protocol.TextDocumentPositionParams{ TextDocument: protocol.TextDocumentIdentifier{ - URI: uri.URI("file://" + appDir + "/templates.templ"), + URI: uri.URI("file://" + appDir + test.filename), }, // Positions are zero indexed. Position: protocol.Position{ diff --git a/cmd/templ/lspcmd/proxy/server.go b/cmd/templ/lspcmd/proxy/server.go index fe6689454..02fc83571 100644 --- a/cmd/templ/lspcmd/proxy/server.go +++ b/cmd/templ/lspcmd/proxy/server.go @@ -3,17 +3,20 @@ package proxy import ( "context" "fmt" + "os" + "path/filepath" "regexp" "strings" "github.com/a-h/parse" lsp "github.com/a-h/protocol" + "go.lsp.dev/uri" + "go.uber.org/zap" + "github.com/a-h/templ" "github.com/a-h/templ/cmd/templ/imports" "github.com/a-h/templ/generator" "github.com/a-h/templ/parser/v2" - "go.lsp.dev/uri" - "go.uber.org/zap" ) // Server is responsible for rewriting messages that are @@ -37,6 +40,7 @@ type Server struct { DiagnosticCache *DiagnosticCache TemplSource *DocumentContents GoSource map[string]string + preLoadURIs []*lsp.DidOpenTextDocumentParams } func NewServer(log *zap.Logger, target lsp.Server, cache *SourceMapCache, diagnosticCache *DiagnosticCache) (s *Server) { @@ -81,6 +85,7 @@ func (p *Server) convertTemplRangeToGoRange(templURI lsp.DocumentURI, input lsp. var sourceMap *parser.SourceMap sourceMap, ok = p.SourceMapCache.Get(string(templURI)) if !ok { + p.Log.Warn("templ->go: sourcemap not found in cache") return } // Map from the source position to target Go position. @@ -101,6 +106,7 @@ func (p *Server) convertGoRangeToTemplRange(templURI lsp.DocumentURI, input lsp. output = input sourceMap, ok := p.SourceMapCache.Get(string(templURI)) if !ok { + p.Log.Warn("go->templ: sourcemap not found in cache") return } // Map from the source position to target Go position. @@ -228,6 +234,62 @@ func (p *Server) Initialize(ctx context.Context, params *lsp.InitializeParams) ( Save: &lsp.SaveOptions{IncludeText: true}, } + for _, c := range params.WorkspaceFolders { + path := strings.TrimPrefix(c.URI, "file://") + werr := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + p.Log.Info("found file", zap.String("path", path)) + uri := uri.URI("file://" + path) + isTemplFile, goURI := convertTemplToGoURI(uri) + + if !isTemplFile { + p.Log.Info("not a templ file", zap.String("uri", string(uri))) + return nil + } + + b, err := os.ReadFile(path) + if err != nil { + return err + } + p.TemplSource.Set(string(uri), NewDocument(p.Log, string(b))) + // Parse the template. + template, ok, err := p.parseTemplate(ctx, uri, string(b)) + if err != nil { + p.Log.Error("parseTemplate failure", zap.Error(err)) + } + if !ok { + p.Log.Info("parsing template did not succeed", zap.String("uri", string(uri))) + return nil + } + w := new(strings.Builder) + sm, _, err := generator.Generate(template, w) + if err != nil { + return fmt.Errorf("generate failure: %w", err) + } + p.Log.Info("setting source map cache contents", zap.String("uri", string(uri))) + p.SourceMapCache.Set(string(uri), sm) + // Set the Go contents. + p.GoSource[string(uri)] = w.String() + + didOpenParams := &lsp.DidOpenTextDocumentParams{ + TextDocument: lsp.TextDocumentItem{ + URI: goURI, + Text: w.String(), + Version: 1, + LanguageID: "go", + }, + } + + p.preLoadURIs = append(p.preLoadURIs, didOpenParams) + return nil + }) + if werr != nil { + p.Log.Error("walk error", zap.Error(werr)) + } + } + result.ServerInfo.Name = "templ-lsp" result.ServerInfo.Version = templ.Version() @@ -237,7 +299,17 @@ func (p *Server) Initialize(ctx context.Context, params *lsp.InitializeParams) ( func (p *Server) Initialized(ctx context.Context, params *lsp.InitializedParams) (err error) { p.Log.Info("client -> server: Initialized") defer p.Log.Info("client -> server: Initialized end") - return p.Target.Initialized(ctx, params) + goInitErr := p.Target.Initialized(ctx, params) + + for i, doParams := range p.preLoadURIs { + doErr := p.Target.DidOpen(ctx, doParams) + if doErr != nil { + return doErr + } + p.preLoadURIs[i] = nil + } + + return goInitErr } func (p *Server) Shutdown(ctx context.Context) (err error) { @@ -464,8 +536,8 @@ func getPackageFromItemDetail(pkg string) string { } type importInsert struct { - LineIndex int Text string + LineIndex int } var nonImportKeywordRegexp = regexp.MustCompile(`^(?:templ|func|css|script|var|const|type)\s`) diff --git a/cmd/templ/testproject/testdata/remoteChild.templ b/cmd/templ/testproject/testdata/remoteChild.templ new file mode 100644 index 000000000..17366eaa4 --- /dev/null +++ b/cmd/templ/testproject/testdata/remoteChild.templ @@ -0,0 +1,5 @@ +package main + +templ Remote() { +

This is remote content

+} diff --git a/cmd/templ/testproject/testdata/remoteParent.templ b/cmd/templ/testproject/testdata/remoteParent.templ new file mode 100644 index 000000000..bf66800ce --- /dev/null +++ b/cmd/templ/testproject/testdata/remoteParent.templ @@ -0,0 +1,9 @@ +package main + +templ RemoteInclusionTest() { + @Remote +} + +templ Remote2() { + @Remote +}