cuelang.org/go@v0.13.0/internal/golangorgx/gopls/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  	"cuelang.org/go/internal/golangorgx/gopls/cache"
    16  	"cuelang.org/go/internal/golangorgx/gopls/progress"
    17  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    18  	"cuelang.org/go/internal/golangorgx/gopls/settings"
    19  	"cuelang.org/go/internal/golangorgx/tools/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  	//
    86  	// TODO(myitcv): it doesn't feel clean having this state at the "same level"
    87  	// as other server state. This field is only relevant for the time between
    88  	// the call to Initialize and notification of Initialized.
    89  	pendingFolders []protocol.WorkspaceFolder
    90  
    91  	// watchedGlobPatterns is the set of glob patterns that we have requested
    92  	// the client watch on disk. It will be updated as the set of directories
    93  	// that the server should watch changes.
    94  	// The map field may be reassigned but the map is immutable.
    95  	watchedGlobPatternsMu  sync.Mutex
    96  	watchedGlobPatterns    map[protocol.RelativePattern]unit
    97  	watchRegistrationCount int
    98  
    99  	diagnosticsMu sync.Mutex
   100  	diagnostics   map[protocol.DocumentURI]*fileDiagnostics
   101  
   102  	// diagnosticsSema limits the concurrency of diagnostics runs, which can be
   103  	// expensive.
   104  	diagnosticsSema chan unit
   105  
   106  	progress *progress.Tracker
   107  
   108  	// When the workspace fails to load, we show its status through a progress
   109  	// report with an error message.
   110  	criticalErrorStatusMu sync.Mutex
   111  	criticalErrorStatus   *progress.WorkDone
   112  
   113  	// Track an ongoing CPU profile created with the StartProfile command and
   114  	// terminated with the StopProfile command.
   115  	ongoingProfileMu sync.Mutex
   116  	ongoingProfile   *os.File // if non-nil, an ongoing profile is writing to this file
   117  
   118  	// Track most recently requested options.
   119  	optionsMu sync.Mutex
   120  	options   *settings.Options
   121  
   122  	// # Modification tracking and diagnostics
   123  	//
   124  	// For the purpose of tracking diagnostics, we need a monotonically
   125  	// increasing clock. Each time a change occurs on the server, this clock is
   126  	// incremented and the previous diagnostics pass is cancelled. When the
   127  	// changed is processed, the Session (via DidModifyFiles) determines which
   128  	// Views are affected by the change and these views are added to the
   129  	// viewsToDiagnose set. Then the server calls diagnoseChangedViews
   130  	// in a separate goroutine. Any Views that successfully complete their
   131  	// diagnostics are removed from the viewsToDiagnose set, provided they haven't
   132  	// been subsequently marked for re-diagnosis (as determined by the latest
   133  	// modificationID referenced by viewsToDiagnose).
   134  	//
   135  	// In this way, we enforce eventual completeness of the diagnostic set: any
   136  	// views requiring diagnosis are diagnosed, though possibly at a later point
   137  	// in time. Notably, the logic in Session.DidModifyFiles to determines if a
   138  	// view needs diagnosis considers whether any packages in the view were
   139  	// invalidated. Consider the following sequence of snapshots for a given view
   140  	// V:
   141  	//
   142  	//     C1    C2
   143  	//  S1 -> S2 -> S3
   144  	//
   145  	// In this case, suppose that S1 was fully type checked, and then two changes
   146  	// C1 and C2 occur in rapid succession, to a file in their package graph but
   147  	// perhaps not enclosed by V's root.  In this case, the logic of
   148  	// DidModifyFiles will detect that V needs to be reloaded following C1. In
   149  	// order for our eventual consistency to be sound, we need to avoid the race
   150  	// where S2 is being diagnosed, C2 arrives, and S3 is not detected as needing
   151  	// diagnosis because the relevant package has not yet been computed in S2. To
   152  	// achieve this, we only remove V from viewsToDiagnose if the diagnosis of S2
   153  	// completes before C2 is processed, which we can confirm by checking
   154  	// S2.BackgroundContext().
   155  	modificationMu        sync.Mutex
   156  	cancelPrevDiagnostics func()
   157  	viewsToDiagnose       map[*cache.View]uint64 // View -> modification at which it last required diagnosis
   158  	lastModificationID    uint64                 // incrementing clock
   159  }
   160  
   161  func (s *server) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
   162  	ctx, done := event.Start(ctx, "lsp.Server.workDoneProgressCancel")
   163  	defer done()
   164  
   165  	return s.progress.Cancel(params.Token)
   166  }