Skip to content

Commit

Permalink
feat(lsp): add workspace folder initialization (#912)
Browse files Browse the repository at this point in the history
  • Loading branch information
tris203 authored Sep 23, 2024
1 parent ff29e12 commit fb6a5c2
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 23 deletions.
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
}

2 comments on commit fb6a5c2

@a-h
Copy link
Owner

@a-h a-h commented on fb6a5c2 Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are incorrectly cased files in this commit. They will need to be fixed to remotechild.templ and remoteparent.templ.

@joerdav
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I missed that one

Please sign in to comment.