internal/lsp/completion: move postfix completions behind option

Move postfix completion functionality behind an experimental option
flag. For now users can enable it by setting
"experimentalPostfixCompletions" or "allExperiments".

I added a RunnerOption so regtest tests can tweak *source.Options. I
didn't refactor the "Experimental" mode to use the new RunnerOption
because I didn't fully understand its purpose.

Change-Id: I75ed748710cae7fa99f4ea6ea117ce245a4e9749
Reviewed-on: https://go-review.googlesource.com/c/tools/+/296109
Run-TryBot: Muir Manders <muir@mnd.rs>
gopls-CI: kokoro <noreply+kokoro@google.com>
Trust: Heschi Kreinick <heschi@google.com>
Trust: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Muir Manders 2021-02-24 13:37:02 -08:00 committed by Rebecca Stambler
parent 09058ab085
commit 94a19427f1
8 changed files with 79 additions and 34 deletions

View File

@ -209,6 +209,15 @@ Must be one of:
* `"Fuzzy"`
Default: `"Fuzzy"`.
##### **experimentalPostfixCompletions** *bool*
**This setting is experimental and may be deleted.**
experimentalPostfixCompletions enables artifical method snippets
such as "someSlice.sort!".
Default: `false`.
#### Diagnostic
##### **analyses** *map[string]bool*

View File

@ -9,6 +9,7 @@ import (
"testing"
. "golang.org/x/tools/gopls/internal/regtest"
"golang.org/x/tools/internal/lsp/source"
)
func TestPostfixSnippetCompletion(t *testing.T) {
@ -373,7 +374,10 @@ func _() {
},
}
Run(t, mod, func(t *testing.T, env *Env) {
r := WithOptions(Options(func(o *source.Options) {
o.ExperimentalPostfixCompletions = true
}))
r.Run(t, mod, func(t *testing.T, env *Env) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
c.before = strings.Trim(c.before, "\n")

View File

@ -70,19 +70,21 @@ type Runner struct {
}
type runConfig struct {
editor fake.EditorConfig
sandbox fake.SandboxConfig
modes Mode
timeout time.Duration
debugAddr string
skipLogs bool
skipHooks bool
editor fake.EditorConfig
sandbox fake.SandboxConfig
modes Mode
timeout time.Duration
debugAddr string
skipLogs bool
skipHooks bool
optionsHook func(*source.Options)
}
func (r *Runner) defaultConfig() *runConfig {
return &runConfig{
modes: r.DefaultModes,
timeout: r.Timeout,
modes: r.DefaultModes,
timeout: r.Timeout,
optionsHook: hooks.Options,
}
}
@ -118,6 +120,19 @@ func Modes(modes Mode) RunOption {
})
}
// Options configures the various server and user options.
func Options(hook func(*source.Options)) RunOption {
return optionSetter(func(opts *runConfig) {
old := opts.optionsHook
opts.optionsHook = func(o *source.Options) {
if old != nil {
old(o)
}
hook(o)
}
})
}
func SendPID() RunOption {
return optionSetter(func(opts *runConfig) {
opts.editor.SendPID = true
@ -216,7 +231,7 @@ func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOptio
tests := []struct {
name string
mode Mode
getServer func(context.Context, *testing.T) jsonrpc2.StreamServer
getServer func(context.Context, *testing.T, func(*source.Options)) jsonrpc2.StreamServer
}{
{"singleton", Singleton, singletonServer},
{"forwarded", Forwarded, r.forwardedServer},
@ -268,7 +283,7 @@ func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOptio
// better solution to ensure that all Go processes started by gopls have
// exited before we clean up.
r.AddCloser(sandbox)
ss := tc.getServer(ctx, t)
ss := tc.getServer(ctx, t, config.optionsHook)
framer := jsonrpc2.NewRawStream
ls := &loggingFramer{}
if !config.skipLogs {
@ -367,38 +382,38 @@ func (s *loggingFramer) printBuffers(testname string, w io.Writer) {
fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs for %q\n", testname)
}
func singletonServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
return lsprpc.NewStreamServer(cache.New(hooks.Options), false)
func singletonServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
return lsprpc.NewStreamServer(cache.New(optsHook), false)
}
func experimentalWorkspaceModule(_ context.Context, t *testing.T) jsonrpc2.StreamServer {
func experimentalWorkspaceModule(_ context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
options := func(o *source.Options) {
hooks.Options(o)
optsHook(o)
o.ExperimentalWorkspaceModule = true
}
return lsprpc.NewStreamServer(cache.New(options), false)
}
func (r *Runner) forwardedServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
ts := r.getTestServer()
func (r *Runner) forwardedServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
ts := r.getTestServer(optsHook)
return lsprpc.NewForwarder("tcp", ts.Addr)
}
// getTestServer gets the shared test server instance to connect to, or creates
// one if it doesn't exist.
func (r *Runner) getTestServer() *servertest.TCPServer {
func (r *Runner) getTestServer(optsHook func(*source.Options)) *servertest.TCPServer {
r.mu.Lock()
defer r.mu.Unlock()
if r.ts == nil {
ctx := context.Background()
ctx = debug.WithInstance(ctx, "", "off")
ss := lsprpc.NewStreamServer(cache.New(hooks.Options), false)
ss := lsprpc.NewStreamServer(cache.New(optsHook), false)
r.ts = servertest.NewTCPServer(ctx, ss, nil)
}
return r.ts
}
func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
// TODO(rfindley): can we use the autostart behavior here, instead of
// pre-starting the remote?
socket := r.getRemoteSocket(t)

View File

@ -21,7 +21,7 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion,
opts.CompleteUnimported = false
opts.InsertTextFormat = protocol.SnippetTextFormat
opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
opts.PostfixCompletions = strings.Contains(string(src.URI()), "postfix")
opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
})
got = tests.FilterBuiltins(src, got)
want := expected(t, test, items)
@ -100,7 +100,7 @@ func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completi
opts.Matcher = source.Fuzzy
opts.CompleteUnimported = false
opts.LiteralCompletions = true
opts.PostfixCompletions = true
opts.ExperimentalPostfixCompletions = true
})
want := expected(t, test, items)
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {

View File

@ -221,6 +221,19 @@ var GeneratedAPIJSON = &APIJSON{
Status: "advanced",
Hierarchy: "ui.completion",
},
{
Name: "experimentalPostfixCompletions",
Type: "bool",
Doc: "experimentalPostfixCompletions enables artifical method snippets\nsuch as \"someSlice.sort!\".\n",
EnumKeys: EnumKeys{
ValueType: "",
Keys: nil,
},
EnumValues: nil,
Default: "false",
Status: "experimental",
Hierarchy: "ui.completion",
},
{
Name: "importShortcut",
Type: "enum",

View File

@ -522,7 +522,7 @@ func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHan
literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
budget: opts.CompletionBudget,
snippets: opts.InsertTextFormat == protocol.SnippetTextFormat,
postfix: opts.PostfixCompletions,
postfix: opts.ExperimentalPostfixCompletions,
},
// default to a matcher that always matches
matcher: prefixMatcher(""),

View File

@ -129,8 +129,9 @@ func DefaultOptions() *Options {
SymbolStyle: DynamicSymbols,
},
CompletionOptions: CompletionOptions{
Matcher: Fuzzy,
CompletionBudget: 100 * time.Millisecond,
Matcher: Fuzzy,
CompletionBudget: 100 * time.Millisecond,
ExperimentalPostfixCompletions: false,
},
Codelenses: map[string]bool{
string(command.Generate): true,
@ -144,7 +145,6 @@ func DefaultOptions() *Options {
},
InternalOptions: InternalOptions{
LiteralCompletions: true,
PostfixCompletions: true,
TempModfile: true,
CompleteUnimported: true,
CompletionDocumentation: true,
@ -294,6 +294,10 @@ type CompletionOptions struct {
// Matcher sets the algorithm that is used when calculating completion
// candidates.
Matcher Matcher `status:"advanced"`
// ExperimentalPostfixCompletions enables artifical method snippets
// such as "someSlice.sort!".
ExperimentalPostfixCompletions bool `status:"experimental"`
}
type DocumentationOptions struct {
@ -437,11 +441,6 @@ type InternalOptions struct {
// their expected values.
LiteralCompletions bool
// PostfixCompletions enables pseudo method snippets such as
// "someSlice.sort!". Tests disable this flag to simplify their
// expected values.
PostfixCompletions bool
// VerboseWorkDoneProgress controls whether the LSP server should send
// progress reports for all work done outside the scope of an RPC.
// Used by the regression tests.
@ -687,6 +686,7 @@ func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer) {
// should be enabled in enableAllExperimentMaps.
func (o *Options) enableAllExperiments() {
o.SemanticTokens = true
o.ExperimentalPostfixCompletions = true
}
func (o *Options) enableAllExperimentMaps() {
@ -858,6 +858,9 @@ func (o *Options) set(name string, value interface{}, seen map[string]struct{})
case "expandWorkspaceToModule":
result.setBool(&o.ExpandWorkspaceToModule)
case "experimentalPostfixCompletions":
result.setBool(&o.ExperimentalPostfixCompletions)
case "experimentalWorkspaceModule":
result.setBool(&o.ExperimentalWorkspaceModule)

View File

@ -178,7 +178,7 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion,
opts.CompleteUnimported = false
opts.InsertTextFormat = protocol.SnippetTextFormat
opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
opts.PostfixCompletions = strings.Contains(string(src.URI()), "postfix")
opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
})
got = tests.FilterBuiltins(src, got)
if diff := tests.DiffCompletionItems(want, got); diff != "" {
@ -277,7 +277,7 @@ func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completi
_, got := r.callCompletion(t, src, func(opts *source.Options) {
opts.DeepCompletion = true
opts.Matcher = source.Fuzzy
opts.PostfixCompletions = true
opts.ExperimentalPostfixCompletions = true
})
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
t.Errorf("%s: %s", src, msg)
@ -931,6 +931,7 @@ func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.Signa
// These are pure LSP features, no source level functionality to be tested.
func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {}
func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) {
}
func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {}