gopls:  template suffix flags and documentation

Adds 'templateExtensions' (with default ["tmpl", "gotmpl"]) to control
which files gopls considers template files.

Adds template support documentation to features.md.

Fixes golang/go#36911

Change-Id: If0920912bf3748d1c231b3b29e7a008da186bede
Reviewed-on: https://go-review.googlesource.com/c/tools/+/363360
Run-TryBot: Peter Weinberger <pjw@google.com>
Trust: Peter Weinberger <pjw@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/gopls/doc/features.md b/gopls/doc/features.md
index 9cb6864..ccfe138 100644
--- a/gopls/doc/features.md
+++ b/gopls/doc/features.md
@@ -4,10 +4,10 @@
 currently under construction, so, for a comprehensive list, see the
 [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
 
-For now, only special features outside of the LSP are described below.
-
 ## Special features
 
+Here, only special features outside of the LSP are described.
+
 ### Symbol Queries
 
 Gopls supports some extended syntax for `workspace/symbol` requests, when using
@@ -21,4 +21,26 @@
 | `^`       | `^printf` | exact prefix |
 | `$`       | `printf$` | exact suffix |
 
+## Template Files
+
+Gopls provides some support for Go template files, that is, files that
+are parsed by `text/template` or `html/template`.
+Gopls recognizes template files based on their file extension.
+By default it looks for files ending in `.tmpl` or `.gotmpl`,
+but this list may be configured by the
+[`templateExtensions`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#templateextensions-string) setting.
+Making this list empty turns off template support.
+
+In template files, template support works inside
+the default `{{` delimiters. (Go template parsing
+allows the user to specify other delimiters, but
+gopls does not know how to do that.)
+
+Gopls template support includes the following features:
++ **Diagnostics**: if template parsing returns an error,
+it is presented as a diagnostic. (Missing functions do not produce errors.)
++ **Syntax Highlighting**: syntax highlighting is provided for template files.
++  **Definitions**: gopls provides jump-to-definition inside templates, though it does not understand scoping (all templates are considered to be in one global scope).
++  **References**: gopls provides find-references, with the same scoping limitation as definitions.
++ **Completions**: gopls will attempt to suggest completions inside templates.
 <!--TODO(rstambler): Automatically generate a list of supported features.-->
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 375aab3..542eb16 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -72,12 +72,6 @@
 
 Default: `["-node_modules"]`.
 
-#### **templateSupport** *bool*
-
-templateSupport can be used to turn off support for template files.
-
-Default: `true`.
-
 #### **templateExtensions** *[]string*
 
 templateExtensions gives the extensions of file names that are treateed
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 51b00d5..53f97f4 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -159,7 +159,7 @@
 }
 
 func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle {
-	if !s.view.Options().TemplateSupport {
+	if len(s.view.Options().TemplateExtensions) == 0 {
 		return nil
 	}
 
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 881d7f1..b969573 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -347,7 +347,7 @@
 }
 
 func (s *snapshot) locateTemplateFiles(ctx context.Context) {
-	if !s.view.Options().TemplateSupport {
+	if len(s.view.Options().TemplateExtensions) == 0 {
 		return
 	}
 	suffixes := s.view.Options().TemplateExtensions
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index b536df2..b7c5163 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -45,19 +45,6 @@
 				Hierarchy:  "build",
 			},
 			{
-				Name: "templateSupport",
-				Type: "bool",
-				Doc:  "templateSupport can be used to turn off support for template files.\n",
-				EnumKeys: EnumKeys{
-					ValueType: "",
-					Keys:      nil,
-				},
-				EnumValues: nil,
-				Default:    "true",
-				Status:     "",
-				Hierarchy:  "build",
-			},
-			{
 				Name: "templateExtensions",
 				Type: "[]string",
 				Doc:  "templateExtensions gives the extensions of file names that are treateed\nas template files. (The extension\nis the part of the file name after the final dot.)\n",
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 41a0288..147bb9e 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -114,7 +114,6 @@
 					ExperimentalPackageCacheKey: true,
 					MemoryMode:                  ModeNormal,
 					DirectoryFilters:            []string{"-node_modules"},
-					TemplateSupport:             true,
 					TemplateExtensions:          []string{"tmpl", "gotmpl"},
 				},
 				UIOptions: UIOptions{
@@ -233,9 +232,6 @@
 	// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
 	DirectoryFilters []string
 
-	// TemplateSupport can be used to turn off support for template files.
-	TemplateSupport bool
-
 	// TemplateExtensions gives the extensions of file names that are treateed
 	// as template files. (The extension
 	// is the part of the file name after the final dot.)
@@ -940,22 +936,23 @@
 	case "experimentalWorkspaceModule":
 		result.setBool(&o.ExperimentalWorkspaceModule)
 
-	case "experimentalTemplateSupport", // remove after June 2022
-		"templateSupport":
-		if name == "experimentalTemplateSupport" {
-			result.State = OptionDeprecated
-			result.Replacement = "templateSupport"
-		}
-		result.setBool(&o.TemplateSupport)
+	case "experimentalTemplateSupport": // remove after June 2022
+		result.State = OptionDeprecated
 
 	case "templateExtensions":
-		iexts, ok := value.([]string)
-		if !ok {
-			result.errorf("invalid type %T, expect []string", value)
+		if iexts, ok := value.([]interface{}); ok {
+			ans := []string{}
+			for _, x := range iexts {
+				ans = append(ans, fmt.Sprint(x))
+			}
+			o.TemplateExtensions = ans
 			break
 		}
-		o.TemplateExtensions = iexts
-
+		if value == nil {
+			o.TemplateExtensions = nil
+			break
+		}
+		result.errorf(fmt.Sprintf("unexpected type %T not []string", value))
 	case "experimentalDiagnosticsDelay", "diagnosticsDelay":
 		if name == "experimentalDiagnosticsDelay" {
 			result.State = OptionDeprecated
diff --git a/internal/lsp/template/implementations.go b/internal/lsp/template/implementations.go
index 193b973..66dcc4b 100644
--- a/internal/lsp/template/implementations.go
+++ b/internal/lsp/template/implementations.go
@@ -66,7 +66,7 @@
 }
 
 func skipTemplates(s source.Snapshot) bool {
-	return !s.View().Options().TemplateSupport
+	return len(s.View().Options().TemplateExtensions) == 0
 }
 
 // Definition finds the definitions of the symbol at loc. It