internal/lsp/template: fix processing of multi-line tokens

Aside from the logic error, the root flaw was inadequate
test coverage.

Fixes golang/go#51731

Change-Id: I50787a951ab742700d9890b4b5232e90189cb8ee
Reviewed-on: https://go-review.googlesource.com/c/tools/+/393634
Run-TryBot: Peter Weinberger <pjw@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Trust: Peter Weinberger <pjw@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
diff --git a/gopls/internal/regtest/template/template_test.go b/gopls/internal/regtest/template/template_test.go
index 1846d4b..b0acdfe 100644
--- a/gopls/internal/regtest/template/template_test.go
+++ b/gopls/internal/regtest/template/template_test.go
@@ -17,6 +17,41 @@
 	Main(m, hooks.Options)
 }
 
+func TestMultilineTokens(t *testing.T) {
+	// 51731: panic: runtime error: slice bounds out of range [38:3]
+	const files = `
+-- go.mod --
+module mod.com
+
+go 1.17
+-- hi.tmpl --
+{{if (foÜx .X.Y)}}😀{{$A := 
+	"hi"
+	}}{{.Z $A}}{{else}}
+{{$A.X 12}}
+{{foo (.X.Y) 23 ($A.Z)}}
+{{end}}
+`
+	WithOptions(
+		EditorConfig{
+			Settings: map[string]interface{}{
+				"templateExtensions": []string{"tmpl"},
+				"semanticTokens":     true,
+			},
+		},
+	).Run(t, files, func(t *testing.T, env *Env) {
+		var p protocol.SemanticTokensParams
+		p.TextDocument.URI = env.Sandbox.Workdir.URI("hi.tmpl")
+		toks, err := env.Editor.Server.SemanticTokensFull(env.Ctx, &p)
+		if err != nil {
+			t.Errorf("semantic token failed: %v", err)
+		}
+		if toks == nil || len(toks.Data) == 0 {
+			t.Errorf("got no semantic tokens")
+		}
+	})
+}
+
 func TestTemplatesFromExtensions(t *testing.T) {
 	const files = `
 -- go.mod --
@@ -28,7 +63,6 @@
 Hello {{}} <-- missing body
 {{end}}
 `
-
 	WithOptions(
 		EditorConfig{
 			Settings: map[string]interface{}{
@@ -193,5 +227,4 @@
 	return pieces[j-2] + "/" + pieces[j-1]
 }
 
-// Hover,  SemTok, Diagnose with errors
-// and better coverage
+// Hover needs tests
diff --git a/internal/lsp/template/parse.go b/internal/lsp/template/parse.go
index bf4e1b4..194eeb3 100644
--- a/internal/lsp/template/parse.go
+++ b/internal/lsp/template/parse.go
@@ -291,11 +291,12 @@
 	return ans, nil
 }
 
-// RuneCount counts runes in a line
+// RuneCount counts runes in line l, from col s to e
+// (e==0 for end of line. called only for multiline tokens)
 func (p *Parsed) RuneCount(l, s, e uint32) uint32 {
 	start := p.nls[l] + 1 + int(s)
-	end := int(e)
-	if e == 0 || int(e) >= p.nls[l+1] {
+	end := p.nls[l] + 1 + int(e)
+	if e == 0 || end > p.nls[l+1] {
 		end = p.nls[l+1]
 	}
 	return uint32(utf8.RuneCount(p.buf[start:end]))