Skip to content

Commit 025b812

Browse files
committed
gopls/internal/server: don't call window/showDocument if unsupported
Don't unconditionally call window/showDocument; check the client capability. In the case of opening a browser window, use showMessage if showDocument is unsupported. Also somewhat clean up the way that we check for shown documents. Fixes golang/go#68904 Change-Id: I29b281b615185f2bbda2f00e7c17575678177cd0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/626616 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 288b9cb commit 025b812

File tree

6 files changed

+185
-31
lines changed

6 files changed

+185
-31
lines changed

gopls/internal/server/command.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ func (c *commandHandler) Doc(ctx context.Context, args command.DocArgs) (protoco
719719
// Direct the client to open the /pkg page.
720720
result = web.PkgURL(deps.snapshot.View().ID(), pkgpath, fragment)
721721
if args.ShowDocument {
722-
openClientBrowser(ctx, c.s.client, result)
722+
openClientBrowser(ctx, c.s.client, "Doc", result, c.s.Options())
723723
}
724724

725725
return nil
@@ -1158,7 +1158,7 @@ func (c *commandHandler) StartDebugging(ctx context.Context, args command.Debugg
11581158
return result, fmt.Errorf("starting debug server: %w", err)
11591159
}
11601160
result.URLs = []string{"http://" + listenedAddr}
1161-
openClientBrowser(ctx, c.s.client, result.URLs[0])
1161+
openClientBrowser(ctx, c.s.client, "Debug", result.URLs[0], c.s.Options())
11621162
return result, nil
11631163
}
11641164

@@ -1560,20 +1560,39 @@ func showMessage(ctx context.Context, cli protocol.Client, typ protocol.MessageT
15601560

15611561
// openClientBrowser causes the LSP client to open the specified URL
15621562
// in an external browser.
1563-
func openClientBrowser(ctx context.Context, cli protocol.Client, url protocol.URI) {
1564-
showDocumentImpl(ctx, cli, url, nil)
1563+
//
1564+
// If the client does not support window/showDocument, a window/showMessage
1565+
// request is instead used, with the format "$title: open your browser to $url".
1566+
func openClientBrowser(ctx context.Context, cli protocol.Client, title string, url protocol.URI, opts *settings.Options) {
1567+
if opts.ShowDocumentSupported {
1568+
showDocumentImpl(ctx, cli, url, nil, opts)
1569+
} else {
1570+
params := &protocol.ShowMessageParams{
1571+
Type: protocol.Info,
1572+
Message: fmt.Sprintf("%s: open your browser to %s", title, url),
1573+
}
1574+
if err := cli.ShowMessage(ctx, params); err != nil {
1575+
event.Error(ctx, "failed to show brower url", err)
1576+
}
1577+
}
15651578
}
15661579

15671580
// openClientEditor causes the LSP client to open the specified document
15681581
// and select the indicated range.
15691582
//
15701583
// Note that VS Code 1.87.2 doesn't currently raise the window; this is
15711584
// https://github.com/microsoft/vscode/issues/207634
1572-
func openClientEditor(ctx context.Context, cli protocol.Client, loc protocol.Location) {
1573-
showDocumentImpl(ctx, cli, protocol.URI(loc.URI), &loc.Range)
1585+
func openClientEditor(ctx context.Context, cli protocol.Client, loc protocol.Location, opts *settings.Options) {
1586+
if !opts.ShowDocumentSupported {
1587+
return // no op
1588+
}
1589+
showDocumentImpl(ctx, cli, protocol.URI(loc.URI), &loc.Range, opts)
15741590
}
15751591

1576-
func showDocumentImpl(ctx context.Context, cli protocol.Client, url protocol.URI, rangeOpt *protocol.Range) {
1592+
func showDocumentImpl(ctx context.Context, cli protocol.Client, url protocol.URI, rangeOpt *protocol.Range, opts *settings.Options) {
1593+
if !opts.ShowDocumentSupported {
1594+
return // no op
1595+
}
15771596
// In principle we shouldn't send a showDocument request to a
15781597
// client that doesn't support it, as reported by
15791598
// ShowDocumentClientCapabilities. But even clients that do
@@ -1690,7 +1709,7 @@ func (c *commandHandler) FreeSymbols(ctx context.Context, viewID string, loc pro
16901709
return err
16911710
}
16921711
url := web.freesymbolsURL(viewID, loc)
1693-
openClientBrowser(ctx, c.s.client, url)
1712+
openClientBrowser(ctx, c.s.client, "Free symbols", url, c.s.Options())
16941713
return nil
16951714
}
16961715

@@ -1700,12 +1719,14 @@ func (c *commandHandler) Assembly(ctx context.Context, viewID, packageID, symbol
17001719
return err
17011720
}
17021721
url := web.assemblyURL(viewID, packageID, symbol)
1703-
openClientBrowser(ctx, c.s.client, url)
1722+
openClientBrowser(ctx, c.s.client, "Assembly", url, c.s.Options())
17041723
return nil
17051724
}
17061725

17071726
func (c *commandHandler) ClientOpenURL(ctx context.Context, url string) error {
1708-
openClientBrowser(ctx, c.s.client, url)
1727+
// Fall back to "Gopls: open your browser..." if we must send a showMessage
1728+
// request, since we don't know the context of this command.
1729+
openClientBrowser(ctx, c.s.client, "Gopls", url, c.s.Options())
17091730
return nil
17101731
}
17111732

gopls/internal/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func (s *server) initWeb() (*web, error) {
303303
openClientEditor(req.Context(), s.client, protocol.Location{
304304
URI: uri,
305305
Range: protocol.Range{Start: posn, End: posn},
306-
})
306+
}, s.Options())
307307
})
308308

309309
// The /pkg/PATH&view=... handler shows package documentation for PATH.

gopls/internal/settings/settings.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ type ClientOptions struct {
7676
CompletionDeprecated bool
7777
SupportedResourceOperations []protocol.ResourceOperationKind
7878
CodeActionResolveOptions []string
79+
ShowDocumentSupported bool
7980
}
8081

8182
// ServerOptions holds LSP-specific configuration that is provided by the
@@ -872,6 +873,9 @@ func (o *Options) ForClientCapabilities(clientInfo *protocol.ClientInfo, caps pr
872873
o.InsertTextFormat = protocol.SnippetTextFormat
873874
}
874875
o.InsertReplaceSupported = caps.TextDocument.Completion.CompletionItem.InsertReplaceSupport
876+
if caps.Window.ShowDocument != nil {
877+
o.ShowDocumentSupported = caps.Window.ShowDocument.Support
878+
}
875879
// Check if the client supports configuration messages.
876880
o.ConfigurationSupported = caps.Workspace.Configuration
877881
o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration

gopls/internal/test/integration/env.go

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"strings"
1111
"sync"
12+
"sync/atomic"
1213
"testing"
1314

1415
"golang.org/x/tools/gopls/internal/protocol"
@@ -34,6 +35,10 @@ type Env struct {
3435
Awaiter *Awaiter
3536
}
3637

38+
// nextAwaiterRegistration is used to create unique IDs for various Awaiter
39+
// registrations.
40+
var nextAwaiterRegistration atomic.Uint64
41+
3742
// An Awaiter keeps track of relevant LSP state, so that it may be asserted
3843
// upon with Expectations.
3944
//
@@ -46,9 +51,13 @@ type Awaiter struct {
4651

4752
mu sync.Mutex
4853
// For simplicity, each waiter gets a unique ID.
49-
nextWaiterID int
50-
state State
51-
waiters map[int]*condition
54+
state State
55+
waiters map[uint64]*condition
56+
57+
// collectors map a registration to the collection of messages that have been
58+
// received since the registration was created.
59+
docCollectors map[uint64][]*protocol.ShowDocumentParams
60+
messageCollectors map[uint64][]*protocol.ShowMessageParams
5261
}
5362

5463
func NewAwaiter(workdir *fake.Workdir) *Awaiter {
@@ -60,7 +69,7 @@ func NewAwaiter(workdir *fake.Workdir) *Awaiter {
6069
startedWork: make(map[string]uint64),
6170
completedWork: make(map[string]uint64),
6271
},
63-
waiters: make(map[int]*condition),
72+
waiters: make(map[uint64]*condition),
6473
}
6574
}
6675

@@ -79,9 +88,6 @@ func (a *Awaiter) Hooks() fake.ClientHooks {
7988
}
8089
}
8190

82-
// ResetShownDocuments resets the set of accumulated ShownDocuments seen so far.
83-
func (a *Awaiter) ResetShownDocuments() { a.state.showDocument = nil }
84-
8591
// State encapsulates the server state TODO: explain more
8692
type State struct {
8793
// diagnostics are a map of relative path->diagnostics params
@@ -171,20 +177,78 @@ func (a *Awaiter) onShowDocument(_ context.Context, params *protocol.ShowDocumen
171177
a.mu.Lock()
172178
defer a.mu.Unlock()
173179

180+
// Update any outstanding listeners.
181+
for id, s := range a.docCollectors {
182+
a.docCollectors[id] = append(s, params)
183+
}
184+
174185
a.state.showDocument = append(a.state.showDocument, params)
175186
a.checkConditionsLocked()
176187
return nil
177188
}
178189

179-
func (a *Awaiter) onShowMessage(_ context.Context, m *protocol.ShowMessageParams) error {
190+
// ListenToShownDocuments registers a listener to incoming showDocument
191+
// notifications. Call the resulting func to deregister the listener and
192+
// receive all notifications that have occurred since the listener was
193+
// registered.
194+
func (a *Awaiter) ListenToShownDocuments() func() []*protocol.ShowDocumentParams {
195+
id := nextAwaiterRegistration.Add(1)
196+
180197
a.mu.Lock()
181198
defer a.mu.Unlock()
182199

183-
a.state.showMessage = append(a.state.showMessage, m)
200+
if a.docCollectors == nil {
201+
a.docCollectors = make(map[uint64][]*protocol.ShowDocumentParams)
202+
}
203+
a.docCollectors[id] = nil
204+
205+
return func() []*protocol.ShowDocumentParams {
206+
a.mu.Lock()
207+
defer a.mu.Unlock()
208+
params := a.docCollectors[id]
209+
delete(a.docCollectors, id)
210+
return params
211+
}
212+
}
213+
214+
func (a *Awaiter) onShowMessage(_ context.Context, params *protocol.ShowMessageParams) error {
215+
a.mu.Lock()
216+
defer a.mu.Unlock()
217+
218+
// Update any outstanding listeners.
219+
for id, s := range a.messageCollectors {
220+
a.messageCollectors[id] = append(s, params)
221+
}
222+
223+
a.state.showMessage = append(a.state.showMessage, params)
184224
a.checkConditionsLocked()
185225
return nil
186226
}
187227

228+
// ListenToShownDocuments registers a listener to incoming showDocument
229+
// notifications. Call the resulting func to deregister the listener and
230+
// receive all notifications that have occurred since the listener was
231+
// registered.
232+
func (a *Awaiter) ListenToShownMessages() func() []*protocol.ShowMessageParams {
233+
id := nextAwaiterRegistration.Add(1)
234+
235+
a.mu.Lock()
236+
defer a.mu.Unlock()
237+
238+
if a.messageCollectors == nil {
239+
a.messageCollectors = make(map[uint64][]*protocol.ShowMessageParams)
240+
}
241+
a.messageCollectors[id] = nil
242+
243+
return func() []*protocol.ShowMessageParams {
244+
a.mu.Lock()
245+
defer a.mu.Unlock()
246+
params := a.messageCollectors[id]
247+
delete(a.messageCollectors, id)
248+
return params
249+
}
250+
}
251+
188252
func (a *Awaiter) onShowMessageRequest(_ context.Context, m *protocol.ShowMessageRequestParams) error {
189253
a.mu.Lock()
190254
defer a.mu.Unlock()
@@ -332,8 +396,7 @@ func (a *Awaiter) Await(ctx context.Context, expectations ...Expectation) error
332396
expectations: expectations,
333397
verdict: make(chan Verdict),
334398
}
335-
a.waiters[a.nextWaiterID] = cond
336-
a.nextWaiterID++
399+
a.waiters[nextAwaiterRegistration.Add(1)] = cond
337400
a.mu.Unlock()
338401

339402
var err error

gopls/internal/test/integration/fake/editor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,8 @@ func clientCapabilities(cfg EditorConfig) (protocol.ClientCapabilities, error) {
342342
capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
343343
capabilities.TextDocument.Completion.CompletionItem.InsertReplaceSupport = true
344344
capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true}
345-
capabilities.Window.WorkDoneProgress = true // support window/workDoneProgress
345+
capabilities.Window.WorkDoneProgress = true // support window/workDoneProgress
346+
capabilities.Window.ShowDocument = &protocol.ShowDocumentClientCapabilities{Support: true} // support window/showDocument
346347
capabilities.TextDocument.SemanticTokens.TokenTypes = []string{
347348
"namespace", "type", "class", "enum", "interface",
348349
"struct", "typeParameter", "parameter", "variable", "property", "enumMember",

0 commit comments

Comments
 (0)