golang.org/x/tools/gopls@v0.15.3/internal/server/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 server defines gopls' implementation of the LSP server
     6  // interface, [protocol.Server]. Call [New] to create an instance.
     7  package server
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"os"
    13  	"sync"
    14  
    15  	"golang.org/x/tools/gopls/internal/cache"
    16  	"golang.org/x/tools/gopls/internal/progress"
    17  	"golang.org/x/tools/gopls/internal/protocol"
    18  	"golang.org/x/tools/gopls/internal/settings"
    19  	"golang.org/x/tools/internal/event"
    20  )
    21  
    22  // New creates an LSP server and binds it to handle incoming client
    23  // messages on the supplied stream.
    24  func New(session *cache.Session, client protocol.ClientCloser, options *settings.Options) protocol.Server {
    25  	const concurrentAnalyses = 1
    26  	// If this assignment fails to compile after a protocol
    27  	// upgrade, it means that one or more new methods need new
    28  	// stub declarations in unimplemented.go.
    29  	return &server{
    30  		diagnostics:         make(map[protocol.DocumentURI]*fileDiagnostics),
    31  		watchedGlobPatterns: nil, // empty
    32  		changedFiles:        make(map[protocol.DocumentURI]unit),
    33  		session:             session,
    34  		client:              client,
    35  		diagnosticsSema:     make(chan unit, concurrentAnalyses),
    36  		progress:            progress.NewTracker(client),
    37  		options:             options,
    38  		viewsToDiagnose:     make(map[*cache.View]uint64),
    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 *cache.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[protocol.DocumentURI]unit
    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  	// Each has a valid, non-empty 'file'-scheme URI.
    85  	pendingFolders []protocol.WorkspaceFolder
    86  
    87  	// watchedGlobPatterns is the set of glob patterns that we have requested
    88  	// the client watch on disk. It will be updated as the set of directories
    89  	// that the server should watch changes.
    90  	// The map field may be reassigned but the map is immutable.
    91  	watchedGlobPatternsMu  sync.Mutex
    92  	watchedGlobPatterns    map[protocol.RelativePattern]unit
    93  	watchRegistrationCount int
    94  
    95  	diagnosticsMu sync.Mutex
    96  	diagnostics   map[protocol.DocumentURI]*fileDiagnostics
    97  
    98  	// diagnosticsSema limits the concurrency of diagnostics runs, which can be
    99  	// expensive.
   100  	diagnosticsSema chan unit
   101  
   102  	progress *progress.Tracker
   103  
   104  	// When the workspace fails to load, we show its status through a progress
   105  	// report with an error message.
   106  	criticalErrorStatusMu sync.Mutex
   107  	criticalErrorStatus   *progress.WorkDone
   108  
   109  	// Track an ongoing CPU profile created with the StartProfile command and
   110  	// terminated with the StopProfile command.
   111  	ongoingProfileMu sync.Mutex
   112  	ongoingProfile   *os.File // if non-nil, an ongoing profile is writing to this file
   113  
   114  	// Track most recently requested options.
   115  	optionsMu sync.Mutex
   116  	options   *settings.Options
   117  
   118  	// # Modification tracking and diagnostics
   119  	//
   120  	// For the purpose of tracking diagnostics, we need a monotonically
   121  	// increasing clock. Each time a change occurs on the server, this clock is
   122  	// incremented and the previous diagnostics pass is cancelled. When the
   123  	// changed is processed, the Session (via DidModifyFiles) determines which
   124  	// Views are affected by the change and these views are added to the
   125  	// viewsToDiagnose set. Then the server calls diagnoseChangedViews
   126  	// in a separate goroutine. Any Views that successfully complete their
   127  	// diagnostics are removed from the viewsToDiagnose set, provided they haven't
   128  	// been subsequently marked for re-diagnosis (as determined by the latest
   129  	// modificationID referenced by viewsToDiagnose).
   130  	//
   131  	// In this way, we enforce eventual completeness of the diagnostic set: any
   132  	// views requiring diagnosis are diagnosed, though possibly at a later point
   133  	// in time. Notably, the logic in Session.DidModifyFiles to determines if a
   134  	// view needs diagnosis considers whether any packages in the view were
   135  	// invalidated. Consider the following sequence of snapshots for a given view
   136  	// V:
   137  	//
   138  	//     C1    C2
   139  	//  S1 -> S2 -> S3
   140  	//
   141  	// In this case, suppose that S1 was fully type checked, and then two changes
   142  	// C1 and C2 occur in rapid succession, to a file in their package graph but
   143  	// perhaps not enclosed by V's root.  In this case, the logic of
   144  	// DidModifyFiles will detect that V needs to be reloaded following C1. In
   145  	// order for our eventual consistency to be sound, we need to avoid the race
   146  	// where S2 is being diagnosed, C2 arrives, and S3 is not detected as needing
   147  	// diagnosis because the relevant package has not yet been computed in S2. To
   148  	// achieve this, we only remove V from viewsToDiagnose if the diagnosis of S2
   149  	// completes before C2 is processed, which we can confirm by checking
   150  	// S2.BackgroundContext().
   151  	modificationMu        sync.Mutex
   152  	cancelPrevDiagnostics func()
   153  	viewsToDiagnose       map[*cache.View]uint64 // View -> modification at which it last required diagnosis
   154  	lastModificationID    uint64                 // incrementing clock
   155  }
   156  
   157  func (s *server) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
   158  	ctx, done := event.Start(ctx, "lsp.Server.workDoneProgressCancel")
   159  	defer done()
   160  
   161  	return s.progress.Cancel(params.Token)
   162  }