mirror of
https://github.com/golang/go
synced 2024-11-05 15:06:09 -07:00
6b78e25f47
There were a few merge conflict-related issues in the GC optimization details CL. Also fixed a few things I noticed after the fact, like separating out a new mutex. Staticcheck caught a few things, and I also fixed a bug I noticed in the cache package. Change-Id: I3fc519373253418586dca08fdec3114b30a247ea Reviewed-on: https://go-review.googlesource.com/c/tools/+/245399 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Peter Weinberger <pjw@google.com>
168 lines
4.8 KiB
Go
168 lines
4.8 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package lsp implements LSP for gopls.
|
|
package lsp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/span"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
const concurrentAnalyses = 1
|
|
|
|
// NewServer creates an LSP server and binds it to handle incoming client
|
|
// messages on on the supplied stream.
|
|
func NewServer(session source.Session, client protocol.Client) *Server {
|
|
return &Server{
|
|
delivered: make(map[span.URI]sentDiagnostics),
|
|
gcOptimizatonDetails: make(map[span.URI]struct{}),
|
|
session: session,
|
|
client: client,
|
|
diagnosticsSema: make(chan struct{}, concurrentAnalyses),
|
|
}
|
|
}
|
|
|
|
type serverState int
|
|
|
|
const (
|
|
serverCreated = serverState(iota)
|
|
serverInitializing // set once the server has received "initialize" request
|
|
serverInitialized // set once the server has received "initialized" request
|
|
serverShutDown
|
|
)
|
|
|
|
func (s serverState) String() string {
|
|
switch s {
|
|
case serverCreated:
|
|
return "created"
|
|
case serverInitializing:
|
|
return "initializing"
|
|
case serverInitialized:
|
|
return "initialized"
|
|
case serverShutDown:
|
|
return "shutDown"
|
|
}
|
|
return fmt.Sprintf("(unknown state: %d)", int(s))
|
|
}
|
|
|
|
// Server implements the protocol.Server interface.
|
|
type Server struct {
|
|
client protocol.Client
|
|
|
|
stateMu sync.Mutex
|
|
state serverState
|
|
|
|
session source.Session
|
|
|
|
// changedFiles tracks files for which there has been a textDocument/didChange.
|
|
changedFiles map[span.URI]struct{}
|
|
|
|
// folders is only valid between initialize and initialized, and holds the
|
|
// set of folders to build views for when we are ready
|
|
pendingFolders []protocol.WorkspaceFolder
|
|
|
|
// delivered is a cache of the diagnostics that the server has sent.
|
|
deliveredMu sync.Mutex
|
|
delivered map[span.URI]sentDiagnostics
|
|
|
|
// gcOptimizationDetails describes the packages for which we want
|
|
// optimization details to be included in the diagnostics. The key is the
|
|
// directory of the package.
|
|
gcOptimizationDetailsMu sync.Mutex
|
|
gcOptimizatonDetails map[span.URI]struct{}
|
|
|
|
// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
|
|
diagnosticsSema chan struct{}
|
|
|
|
// supportsWorkDoneProgress is set in the initializeRequest
|
|
// to determine if the client can support progress notifications
|
|
supportsWorkDoneProgress bool
|
|
inProgressMu sync.Mutex
|
|
inProgress map[string]*WorkDone
|
|
}
|
|
|
|
// sentDiagnostics is used to cache diagnostics that have been sent for a given file.
|
|
type sentDiagnostics struct {
|
|
version float64
|
|
identifier string
|
|
sorted []*source.Diagnostic
|
|
withAnalysis bool
|
|
snapshotID uint64
|
|
}
|
|
|
|
func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
|
|
paramMap := params.(map[string]interface{})
|
|
if method == "gopls/diagnoseFiles" {
|
|
for _, file := range paramMap["files"].([]interface{}) {
|
|
snapshot, fh, ok, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind)
|
|
if !ok {
|
|
return nil, err
|
|
}
|
|
|
|
fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.URI())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
URI: protocol.URIFromSpanURI(fh.URI()),
|
|
Diagnostics: toProtocolDiagnostics(diagnostics),
|
|
Version: fileID.Version,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
URI: "gopls://diagnostics-done",
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return struct{}{}, nil
|
|
}
|
|
return nil, notImplemented(method)
|
|
}
|
|
|
|
func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
|
|
token, ok := params.Token.(string)
|
|
if !ok {
|
|
return errors.Errorf("expected params.Token to be string but got %T", params.Token)
|
|
}
|
|
s.inProgressMu.Lock()
|
|
defer s.inProgressMu.Unlock()
|
|
wd, ok := s.inProgress[token]
|
|
if !ok {
|
|
return errors.Errorf("token %q not found in progress", token)
|
|
}
|
|
if wd.cancel == nil {
|
|
return errors.Errorf("work %q is not cancellable", token)
|
|
}
|
|
wd.cancel()
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) addInProgress(wd *WorkDone) {
|
|
s.inProgressMu.Lock()
|
|
s.inProgress[wd.token] = wd
|
|
s.inProgressMu.Unlock()
|
|
}
|
|
|
|
func (s *Server) removeInProgress(token string) {
|
|
s.inProgressMu.Lock()
|
|
delete(s.inProgress, token)
|
|
s.inProgressMu.Unlock()
|
|
}
|
|
|
|
func notImplemented(method string) error {
|
|
return fmt.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
|
|
}
|
|
|
|
//go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .
|