diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index ad7cf1295f..375aab3700 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -72,6 +72,20 @@ Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-pro 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 +as template files. (The extension +is the part of the file name after the final dot.) + +Default: `["tmpl","gotmpl"]`. + #### **memoryMode** *enum* **This setting is experimental and may be deleted.** @@ -112,15 +126,6 @@ for multi-module workspaces. Default: `false`. -#### **experimentalTemplateSupport** *bool* - -**This setting is experimental and may be deleted.** - -experimentalTemplateSupport opts into the experimental support -for template files. - -Default: `false`. - #### **experimentalPackageCacheKey** *bool* **This setting is experimental and may be deleted.** diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go index 90304cb924..486a2287cd 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -159,7 +159,7 @@ func (s *snapshot) ModFiles() []span.URI { } func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle { - if !s.view.Options().ExperimentalTemplateSupport { + if !s.view.Options().TemplateSupport { return nil } @@ -783,12 +783,15 @@ func (s *snapshot) getWorkspacePkgPath(id PackageID) PackagePath { const fileExtensions = "go,mod,sum,work,tmpl" func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} { + extensions := fileExtensions + for _, ext := range s.View().Options().TemplateExtensions { + extensions += "," + ext + } // Work-around microsoft/vscode#100870 by making sure that we are, // at least, watching the user's entire workspace. This will still be // applied to every folder in the workspace. patterns := map[string]struct{}{ - fmt.Sprintf("**/*.{%s}", fileExtensions): {}, - "**/*.*tmpl": {}, + fmt.Sprintf("**/*.{%s}", extensions): {}, } dirs := s.workspace.dirs(ctx, s) for _, dir := range dirs { @@ -802,7 +805,7 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru // TODO(rstambler): If microsoft/vscode#3025 is resolved before // microsoft/vscode#101042, we will need a work-around for Windows // drive letter casing. - patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, fileExtensions)] = struct{}{} + patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{} } // Some clients do not send notifications for changes to directories that diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index db99b775f3..881d7f12b1 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -332,10 +332,25 @@ func (s *snapshot) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Optio return s.view.importsState.runProcessEnvFunc(ctx, s, fn) } +// separated out from its sole use in locateTemplatFiles for testability +func fileHasExtension(path string, suffixes []string) bool { + ext := filepath.Ext(path) + if ext != "" && ext[0] == '.' { + ext = ext[1:] + } + for _, s := range suffixes { + if s != "" && ext == s { + return true + } + } + return false +} + func (s *snapshot) locateTemplateFiles(ctx context.Context) { - if !s.view.Options().ExperimentalTemplateSupport { + if !s.view.Options().TemplateSupport { return } + suffixes := s.view.Options().TemplateExtensions dir := s.workspace.root.Filename() searched := 0 // Change to WalkDir when we move up to 1.16 @@ -343,7 +358,7 @@ func (s *snapshot) locateTemplateFiles(ctx context.Context) { if err != nil { return err } - if strings.HasSuffix(filepath.Ext(path), "tmpl") && !pathExcludedByFilter(path, dir, s.view.gomodcache, s.view.options) && + if fileHasExtension(path, suffixes) && !pathExcludedByFilter(path, dir, s.view.gomodcache, s.view.options) && !fi.IsDir() { k := span.URIFromPath(path) fh, err := s.GetVersionedFile(ctx, k) diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go index f0923d4617..ecd7e84ed7 100644 --- a/internal/lsp/cache/view_test.go +++ b/internal/lsp/cache/view_test.go @@ -172,3 +172,44 @@ func TestFilters(t *testing.T) { } } } + +func TestSuffixes(t *testing.T) { + type file struct { + path string + want bool + } + type cases struct { + option []string + files []file + } + tests := []cases{ + {[]string{"tmpl", "gotmpl"}, []file{ // default + {"foo", false}, + {"foo.tmpl", true}, + {"foo.gotmpl", true}, + {"tmpl", false}, + {"tmpl.go", false}}, + }, + {[]string{"tmpl", "gotmpl", "html", "gohtml"}, []file{ + {"foo.gotmpl", true}, + {"foo.html", true}, + {"foo.gohtml", true}, + {"html", false}}, + }, + {[]string{"tmpl", "gotmpl", ""}, []file{ // possible user mistake + {"foo.gotmpl", true}, + {"foo.go", false}, + {"foo", false}}, + }, + } + for _, a := range tests { + suffixes := a.option + for _, b := range a.files { + got := fileHasExtension(b.path, suffixes) + if got != b.want { + t.Errorf("got %v, want %v, option %q, file %q (%+v)", + got, b.want, a.option, b.path, b) + } + } + } +} diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go index 0c328071ad..b536df28b2 100755 --- a/internal/lsp/source/api_json.go +++ b/internal/lsp/source/api_json.go @@ -44,6 +44,32 @@ var GeneratedAPIJSON = &APIJSON{ Status: "", 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", + EnumKeys: EnumKeys{ + ValueType: "", + Keys: nil, + }, + EnumValues: nil, + Default: "[\"tmpl\",\"gotmpl\"]", + Status: "", + Hierarchy: "build", + }, { Name: "memoryMode", Type: "enum", @@ -92,19 +118,6 @@ var GeneratedAPIJSON = &APIJSON{ Status: "experimental", Hierarchy: "build", }, - { - Name: "experimentalTemplateSupport", - Type: "bool", - Doc: "experimentalTemplateSupport opts into the experimental support\nfor template files.\n", - EnumKeys: EnumKeys{ - ValueType: "", - Keys: nil, - }, - EnumValues: nil, - Default: "false", - Status: "experimental", - Hierarchy: "build", - }, { Name: "experimentalPackageCacheKey", Type: "bool", diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index 4875111ac6..41a02884d6 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -114,6 +114,8 @@ func DefaultOptions() *Options { ExperimentalPackageCacheKey: true, MemoryMode: ModeNormal, DirectoryFilters: []string{"-node_modules"}, + TemplateSupport: true, + TemplateExtensions: []string{"tmpl", "gotmpl"}, }, UIOptions: UIOptions{ DiagnosticOptions: DiagnosticOptions{ @@ -231,6 +233,14 @@ type BuildOptions struct { // 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.) + TemplateExtensions []string + // MemoryMode controls the tradeoff `gopls` makes between memory usage and // correctness. // @@ -249,10 +259,6 @@ type BuildOptions struct { // for multi-module workspaces. ExperimentalWorkspaceModule bool `status:"experimental"` - // ExperimentalTemplateSupport opts into the experimental support - // for template files. - ExperimentalTemplateSupport bool `status:"experimental"` - // ExperimentalPackageCacheKey controls whether to use a coarser cache key // for package type information to increase cache hits. This setting removes // the user's environment, build flags, and working directory from the cache @@ -747,7 +753,6 @@ func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer, enabled bool, sev func (o *Options) EnableAllExperiments() { o.SemanticTokens = true o.ExperimentalPostfixCompletions = true - o.ExperimentalTemplateSupport = true o.ExperimentalUseInvalidMetadata = true o.ExperimentalWatchedFileDelay = 50 * time.Millisecond } @@ -935,8 +940,21 @@ func (o *Options) set(name string, value interface{}, seen map[string]struct{}) case "experimentalWorkspaceModule": result.setBool(&o.ExperimentalWorkspaceModule) - case "experimentalTemplateSupport": - result.setBool(&o.ExperimentalTemplateSupport) + case "experimentalTemplateSupport", // remove after June 2022 + "templateSupport": + if name == "experimentalTemplateSupport" { + result.State = OptionDeprecated + result.Replacement = "templateSupport" + } + result.setBool(&o.TemplateSupport) + + case "templateExtensions": + iexts, ok := value.([]string) + if !ok { + result.errorf("invalid type %T, expect []string", value) + break + } + o.TemplateExtensions = iexts case "experimentalDiagnosticsDelay", "diagnosticsDelay": if name == "experimentalDiagnosticsDelay" { diff --git a/internal/lsp/template/implementations.go b/internal/lsp/template/implementations.go index 6c57a6875b..193b973814 100644 --- a/internal/lsp/template/implementations.go +++ b/internal/lsp/template/implementations.go @@ -66,7 +66,7 @@ func Diagnose(f source.VersionedFileHandle) []*source.Diagnostic { } func skipTemplates(s source.Snapshot) bool { - return !s.View().Options().ExperimentalTemplateSupport + return !s.View().Options().TemplateSupport } // Definition finds the definitions of the symbol at loc. It