github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/server.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package lsp implements LSP for gopls.
     6  package lsp
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"sync"
    12  
    13  	"github.com/jhump/golang-x-tools/internal/jsonrpc2"
    14  	"github.com/jhump/golang-x-tools/internal/lsp/progress"
    15  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    16  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    17  	"github.com/jhump/golang-x-tools/internal/span"
    18  	errors "golang.org/x/xerrors"
    19  )
    20  
    21  const concurrentAnalyses = 1
    22  
    23  // NewServer creates an LSP server and binds it to handle incoming client
    24  // messages on on the supplied stream.
    25  func NewServer(session source.Session, client protocol.ClientCloser) *Server {
    26  	tracker := progress.NewTracker(client)
    27  	session.SetProgressTracker(tracker)
    28  	return &Server{
    29  		diagnostics:           map[span.URI]*fileReports{},
    30  		gcOptimizationDetails: make(map[string]struct{}),
    31  		watchedGlobPatterns:   make(map[string]struct{}),
    32  		changedFiles:          make(map[span.URI]struct{}),
    33  		session:               session,
    34  		client:                client,
    35  		diagnosticsSema:       make(chan struct{}, concurrentAnalyses),
    36  		progress:              tracker,
    37  		diagDebouncer:         newDebouncer(),
    38  		watchedFileDebouncer:  newDebouncer(),
    39  	}
    40  }
    41  
    42  type serverState int
    43  
    44  const (
    45  	serverCreated      = serverState(iota)
    46  	serverInitializing // set once the server has received "initialize" request
    47  	serverInitialized  // set once the server has received "initialized" request
    48  	serverShutDown
    49  )
    50  
    51  func (s serverState) String() string {
    52  	switch s {
    53  	case serverCreated:
    54  		return "created"
    55  	case serverInitializing:
    56  		return "initializing"
    57  	case serverInitialized:
    58  		return "initialized"
    59  	case serverShutDown:
    60  		return "shutDown"
    61  	}
    62  	return fmt.Sprintf("(unknown state: %d)", int(s))
    63  }
    64  
    65  // Server implements the protocol.Server interface.
    66  type Server struct {
    67  	client protocol.ClientCloser
    68  
    69  	stateMu sync.Mutex
    70  	state   serverState
    71  	// notifications generated before serverInitialized
    72  	notifications []*protocol.ShowMessageParams
    73  
    74  	session source.Session
    75  
    76  	tempDir string
    77  
    78  	// changedFiles tracks files for which there has been a textDocument/didChange.
    79  	changedFilesMu sync.Mutex
    80  	changedFiles   map[span.URI]struct{}
    81  
    82  	// folders is only valid between initialize and initialized, and holds the
    83  	// set of folders to build views for when we are ready
    84  	pendingFolders []protocol.WorkspaceFolder
    85  
    86  	// watchedGlobPatterns is the set of glob patterns that we have requested
    87  	// the client watch on disk. It will be updated as the set of directories
    88  	// that the server should watch changes.
    89  	watchedGlobPatternsMu  sync.Mutex
    90  	watchedGlobPatterns    map[string]struct{}
    91  	watchRegistrationCount int
    92  
    93  	diagnosticsMu sync.Mutex
    94  	diagnostics   map[span.URI]*fileReports
    95  
    96  	// gcOptimizationDetails describes the packages for which we want
    97  	// optimization details to be included in the diagnostics. The key is the
    98  	// ID of the package.
    99  	gcOptimizationDetailsMu sync.Mutex
   100  	gcOptimizationDetails   map[string]struct{}
   101  
   102  	// diagnosticsSema limits the concurrency of diagnostics runs, which can be
   103  	// expensive.
   104  	diagnosticsSema chan struct{}
   105  
   106  	progress *progress.Tracker
   107  
   108  	// diagDebouncer is used for debouncing diagnostics.
   109  	diagDebouncer *debouncer
   110  
   111  	// watchedFileDebouncer is used for batching didChangeWatchedFiles notifications.
   112  	watchedFileDebouncer *debouncer
   113  	fileChangeMu         sync.Mutex
   114  	pendingOnDiskChanges []*pendingModificationSet
   115  
   116  	// When the workspace fails to load, we show its status through a progress
   117  	// report with an error message.
   118  	criticalErrorStatusMu sync.Mutex
   119  	criticalErrorStatus   *progress.WorkDone
   120  }
   121  
   122  type pendingModificationSet struct {
   123  	diagnoseDone chan struct{}
   124  	changes      []source.FileModification
   125  }
   126  
   127  func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
   128  	return s.progress.Cancel(ctx, params.Token)
   129  }
   130  
   131  func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
   132  	switch method {
   133  	case "gopls/diagnoseFiles":
   134  		paramMap := params.(map[string]interface{})
   135  		for _, file := range paramMap["files"].([]interface{}) {
   136  			snapshot, fh, ok, release, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind)
   137  			defer release()
   138  			if !ok {
   139  				return nil, err
   140  			}
   141  
   142  			fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.URI())
   143  			if err != nil {
   144  				return nil, err
   145  			}
   146  			if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
   147  				URI:         protocol.URIFromSpanURI(fh.URI()),
   148  				Diagnostics: toProtocolDiagnostics(diagnostics),
   149  				Version:     fileID.Version,
   150  			}); err != nil {
   151  				return nil, err
   152  			}
   153  		}
   154  		if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
   155  			URI: "gopls://diagnostics-done",
   156  		}); err != nil {
   157  			return nil, err
   158  		}
   159  		return struct{}{}, nil
   160  	}
   161  	return nil, notImplemented(method)
   162  }
   163  
   164  func notImplemented(method string) error {
   165  	return errors.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
   166  }
   167  
   168  //go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .