Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lsp): add workspace folder initialization #912

Merged
merged 5 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 44 additions & 19 deletions cmd/templ/lspcmd/lsp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand Down Expand Up @@ -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{
{
Expand All @@ -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 {
Expand All @@ -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{
Expand Down
80 changes: 76 additions & 4 deletions cmd/templ/lspcmd/proxy/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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()

Expand All @@ -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) {
Expand Down Expand Up @@ -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`)
Expand Down
5 changes: 5 additions & 0 deletions cmd/templ/testproject/testdata/remoteChild.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

templ Remote() {
<p>This is remote content</p>
}
9 changes: 9 additions & 0 deletions cmd/templ/testproject/testdata/remoteParent.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

templ RemoteInclusionTest() {
@Remote
}

templ Remote2() {
@Remote
}