Skip to content

Commit a618923

Browse files
committed
internal/lsp/template: fix error that causes crashes
When LSP asks for a completion it sends a position, which is the position of the first character after the cursor. The old code mistakenly thought it was the position of the last character seen, and sometimes produced an impossible slice to parse. The example at line 29 of completion_test.go used to cause a panic. Fixes: golang/go#49600 Change-Id: I772fe38a1a12d3876ef866819e6a878b297bb92f Reviewed-on: https://go-review.googlesource.com/c/tools/+/366036 Run-TryBot: Peter Weinberger <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Peter Weinberger <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent cb80a01 commit a618923

File tree

2 files changed

+36
-31
lines changed

2 files changed

+36
-31
lines changed

internal/lsp/template/completion.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,15 @@ func filterSyms(syms map[string]symbol, ns []symbol) {
7878

7979
// return the starting position of the enclosing token, or -1 if none
8080
func inTemplate(fc *Parsed, pos protocol.Position) int {
81+
// pos is the pos-th character. if the cursor is at the beginning
82+
// of the file, pos is 0. That is, we've only seen characters before pos
8183
// 1. pos might be in a Token, return tk.Start
8284
// 2. pos might be after an elided but before a Token, return elided
8385
// 3. return -1 for false
8486
offset := fc.FromPosition(pos)
8587
// this could be a binary search, as the tokens are ordered
8688
for _, tk := range fc.tokens {
87-
if tk.Start <= offset && offset < tk.End {
89+
if tk.Start < offset && offset <= tk.End {
8890
return tk.Start
8991
}
9092
}
@@ -244,7 +246,7 @@ func scan(buf []byte) []string {
244246
ans[len(ans)-1] = "." + lit
245247
} else if tok == token.IDENT && len(ans) > 0 && ans[len(ans)-1] == "$" {
246248
ans[len(ans)-1] = "$" + lit
247-
} else {
249+
} else if lit != "" {
248250
ans = append(ans, lit)
249251
}
250252
}

internal/lsp/template/completion_test.go

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,40 +23,41 @@ type tparse struct {
2323
}
2424

2525
// Test completions in templates that parse enough (if completion needs symbols)
26+
// Seen characters up to the ^
2627
func TestParsed(t *testing.T) {
2728
var tests = []tparse{
28-
{"{{^if}}", []string{"index", "if"}},
29-
{"{{if .}}{{^e {{end}}", []string{"eq", "end}}", "else", "end"}},
30-
{"{{foo}}{{^f", []string{"foo"}},
31-
{"{{^$}}", []string{"$"}},
32-
{"{{$x:=4}}{{^$", []string{"$x"}},
33-
{"{{$x:=4}}{{$^ ", []string{}},
34-
{"{{len .Modified}}{{^.Mo", []string{"Modified"}},
35-
{"{{len .Modified}}{{.m^f", []string{"Modified"}},
36-
{"{{^$ }}", []string{"$"}},
37-
{"{{$a =3}}{{^$", []string{"$a"}},
29+
{`<table class="chroma" data-new-comment-url="{{if $.PageIsPullFiles}}{{$.Issue.HTMLURL}}/files/reviews/new_comment{{else}}{{$.CommitHTML}}/new_comment^{{end}}">`, nil},
30+
{"{{i^f}}", []string{"index", "if"}},
31+
{"{{if .}}{{e^ {{end}}", []string{"eq", "end}}", "else", "end"}},
32+
{"{{foo}}{{f^", []string{"foo"}},
33+
{"{{$^}}", []string{"$"}},
34+
{"{{$x:=4}}{{$^", []string{"$x"}},
35+
{"{{$x:=4}}{{$ ^ ", []string{}},
36+
{"{{len .Modified}}{{.^Mo", []string{"Modified"}},
37+
{"{{len .Modified}}{{.mf^", []string{"Modified"}},
38+
{"{{$^ }}", []string{"$"}},
39+
{"{{$a =3}}{{$^", []string{"$a"}},
3840
// .two is not good here: fix someday
39-
{`{{.Modified}}{{^.{{if $.one.two}}xxx{{end}}`, []string{"Modified", "one", "two"}},
40-
{`{{.Modified}}{{.^o{{if $.one.two}}xxx{{end}}`, []string{"one"}},
41-
{"{{.Modiifed}}{{.one.^t{{if $.one.two}}xxx{{end}}", []string{"two"}},
42-
{`{{block "foo" .}}{{^i`, []string{"index", "if"}},
43-
{"{{i^n{{Internal}}", []string{"index", "Internal", "if"}},
41+
{`{{.Modified}}{{.^{{if $.one.two}}xxx{{end}}`, []string{"Modified", "one", "two"}},
42+
{`{{.Modified}}{{.o^{{if $.one.two}}xxx{{end}}`, []string{"one"}},
43+
{"{{.Modiifed}}{{.one.t^{{if $.one.two}}xxx{{end}}", []string{"two"}},
44+
{`{{block "foo" .}}{{i^`, []string{"index", "if"}},
45+
{"{{in^{{Internal}}", []string{"index", "Internal", "if"}},
4446
// simple number has no completions
4547
{"{{4^e", []string{}},
4648
// simple string has no completions
47-
{"{{`^e", []string{}},
48-
{"{{`No ^i", []string{}}, // example of why go/scanner is used
49-
{"{{xavier}}{{12. ^x", []string{"xavier"}},
49+
{"{{`e^", []string{}},
50+
{"{{`No i^", []string{}}, // example of why go/scanner is used
51+
{"{{xavier}}{{12. x^", []string{"xavier"}},
5052
}
5153
for _, tx := range tests {
5254
c := testCompleter(t, tx)
53-
ans, err := c.complete()
54-
if err != nil {
55-
t.Fatal(err)
56-
}
5755
var v []string
58-
for _, a := range ans.Items {
59-
v = append(v, a.Label)
56+
if c != nil {
57+
ans, _ := c.complete()
58+
for _, a := range ans.Items {
59+
v = append(v, a.Label)
60+
}
6061
}
6162
if len(v) != len(tx.wanted) {
6263
t.Errorf("%q: got %v, wanted %v", tx.marked, v, tx.wanted)
@@ -75,16 +76,18 @@ func TestParsed(t *testing.T) {
7576

7677
func testCompleter(t *testing.T, tx tparse) *completer {
7778
t.Helper()
78-
col := strings.Index(tx.marked, "^") + 1
79-
offset := strings.LastIndex(tx.marked[:col], string(Left))
80-
if offset < 0 {
81-
t.Fatalf("no {{ before ^: %q", tx.marked)
82-
}
79+
// seen chars up to ^
80+
col := strings.Index(tx.marked, "^")
8381
buf := strings.Replace(tx.marked, "^", "", 1)
8482
p := parseBuffer([]byte(buf))
83+
pos := protocol.Position{Line: 0, Character: uint32(col)}
8584
if p.ParseErr != nil {
8685
log.Printf("%q: %v", tx.marked, p.ParseErr)
8786
}
87+
offset := inTemplate(p, pos)
88+
if offset == -1 {
89+
return nil
90+
}
8891
syms := make(map[string]symbol)
8992
filterSyms(syms, p.symbols)
9093
c := &completer{

0 commit comments

Comments
 (0)