diff --git a/internal/lsp/template/completion.go b/internal/lsp/template/completion.go
index 4ec7623ba9a..55b59f82152 100644
--- a/internal/lsp/template/completion.go
+++ b/internal/lsp/template/completion.go
@@ -78,13 +78,15 @@ func filterSyms(syms map[string]symbol, ns []symbol) {
// return the starting position of the enclosing token, or -1 if none
func inTemplate(fc *Parsed, pos protocol.Position) int {
+ // pos is the pos-th character. if the cursor is at the beginning
+ // of the file, pos is 0. That is, we've only seen characters before pos
// 1. pos might be in a Token, return tk.Start
// 2. pos might be after an elided but before a Token, return elided
// 3. return -1 for false
offset := fc.FromPosition(pos)
// this could be a binary search, as the tokens are ordered
for _, tk := range fc.tokens {
- if tk.Start <= offset && offset < tk.End {
+ if tk.Start < offset && offset <= tk.End {
return tk.Start
}
}
@@ -244,7 +246,7 @@ func scan(buf []byte) []string {
ans[len(ans)-1] = "." + lit
} else if tok == token.IDENT && len(ans) > 0 && ans[len(ans)-1] == "$" {
ans[len(ans)-1] = "$" + lit
- } else {
+ } else if lit != "" {
ans = append(ans, lit)
}
}
diff --git a/internal/lsp/template/completion_test.go b/internal/lsp/template/completion_test.go
index 7d17ab1ebab..b7ef89fbeb9 100644
--- a/internal/lsp/template/completion_test.go
+++ b/internal/lsp/template/completion_test.go
@@ -23,40 +23,41 @@ type tparse struct {
}
// Test completions in templates that parse enough (if completion needs symbols)
+// Seen characters up to the ^
func TestParsed(t *testing.T) {
var tests = []tparse{
- {"{{^if}}", []string{"index", "if"}},
- {"{{if .}}{{^e {{end}}", []string{"eq", "end}}", "else", "end"}},
- {"{{foo}}{{^f", []string{"foo"}},
- {"{{^$}}", []string{"$"}},
- {"{{$x:=4}}{{^$", []string{"$x"}},
- {"{{$x:=4}}{{$^ ", []string{}},
- {"{{len .Modified}}{{^.Mo", []string{"Modified"}},
- {"{{len .Modified}}{{.m^f", []string{"Modified"}},
- {"{{^$ }}", []string{"$"}},
- {"{{$a =3}}{{^$", []string{"$a"}},
+ {`
`, nil},
+ {"{{i^f}}", []string{"index", "if"}},
+ {"{{if .}}{{e^ {{end}}", []string{"eq", "end}}", "else", "end"}},
+ {"{{foo}}{{f^", []string{"foo"}},
+ {"{{$^}}", []string{"$"}},
+ {"{{$x:=4}}{{$^", []string{"$x"}},
+ {"{{$x:=4}}{{$ ^ ", []string{}},
+ {"{{len .Modified}}{{.^Mo", []string{"Modified"}},
+ {"{{len .Modified}}{{.mf^", []string{"Modified"}},
+ {"{{$^ }}", []string{"$"}},
+ {"{{$a =3}}{{$^", []string{"$a"}},
// .two is not good here: fix someday
- {`{{.Modified}}{{^.{{if $.one.two}}xxx{{end}}`, []string{"Modified", "one", "two"}},
- {`{{.Modified}}{{.^o{{if $.one.two}}xxx{{end}}`, []string{"one"}},
- {"{{.Modiifed}}{{.one.^t{{if $.one.two}}xxx{{end}}", []string{"two"}},
- {`{{block "foo" .}}{{^i`, []string{"index", "if"}},
- {"{{i^n{{Internal}}", []string{"index", "Internal", "if"}},
+ {`{{.Modified}}{{.^{{if $.one.two}}xxx{{end}}`, []string{"Modified", "one", "two"}},
+ {`{{.Modified}}{{.o^{{if $.one.two}}xxx{{end}}`, []string{"one"}},
+ {"{{.Modiifed}}{{.one.t^{{if $.one.two}}xxx{{end}}", []string{"two"}},
+ {`{{block "foo" .}}{{i^`, []string{"index", "if"}},
+ {"{{in^{{Internal}}", []string{"index", "Internal", "if"}},
// simple number has no completions
{"{{4^e", []string{}},
// simple string has no completions
- {"{{`^e", []string{}},
- {"{{`No ^i", []string{}}, // example of why go/scanner is used
- {"{{xavier}}{{12. ^x", []string{"xavier"}},
+ {"{{`e^", []string{}},
+ {"{{`No i^", []string{}}, // example of why go/scanner is used
+ {"{{xavier}}{{12. x^", []string{"xavier"}},
}
for _, tx := range tests {
c := testCompleter(t, tx)
- ans, err := c.complete()
- if err != nil {
- t.Fatal(err)
- }
var v []string
- for _, a := range ans.Items {
- v = append(v, a.Label)
+ if c != nil {
+ ans, _ := c.complete()
+ for _, a := range ans.Items {
+ v = append(v, a.Label)
+ }
}
if len(v) != len(tx.wanted) {
t.Errorf("%q: got %v, wanted %v", tx.marked, v, tx.wanted)
@@ -75,16 +76,18 @@ func TestParsed(t *testing.T) {
func testCompleter(t *testing.T, tx tparse) *completer {
t.Helper()
- col := strings.Index(tx.marked, "^") + 1
- offset := strings.LastIndex(tx.marked[:col], string(Left))
- if offset < 0 {
- t.Fatalf("no {{ before ^: %q", tx.marked)
- }
+ // seen chars up to ^
+ col := strings.Index(tx.marked, "^")
buf := strings.Replace(tx.marked, "^", "", 1)
p := parseBuffer([]byte(buf))
+ pos := protocol.Position{Line: 0, Character: uint32(col)}
if p.ParseErr != nil {
log.Printf("%q: %v", tx.marked, p.ParseErr)
}
+ offset := inTemplate(p, pos)
+ if offset == -1 {
+ return nil
+ }
syms := make(map[string]symbol)
filterSyms(syms, p.symbols)
c := &completer{