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