github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/general.go (about)

     1  // Copyright 2019 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
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"strings"
    16  	"sync"
    17  
    18  	"github.com/april1989/origin-go-tools/internal/event"
    19  	"github.com/april1989/origin-go-tools/internal/jsonrpc2"
    20  	"github.com/april1989/origin-go-tools/internal/lsp/debug"
    21  	"github.com/april1989/origin-go-tools/internal/lsp/debug/tag"
    22  	"github.com/april1989/origin-go-tools/internal/lsp/protocol"
    23  	"github.com/april1989/origin-go-tools/internal/lsp/source"
    24  	"github.com/april1989/origin-go-tools/internal/span"
    25  	errors "golang.org/x/xerrors"
    26  )
    27  
    28  func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
    29  	s.stateMu.Lock()
    30  	if s.state >= serverInitializing {
    31  		defer s.stateMu.Unlock()
    32  		return nil, errors.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
    33  	}
    34  	s.state = serverInitializing
    35  	s.stateMu.Unlock()
    36  
    37  	s.progress.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress
    38  
    39  	options := s.session.Options()
    40  	defer func() { s.session.SetOptions(options) }()
    41  
    42  	if err := s.handleOptionResults(ctx, source.SetOptions(&options, params.InitializationOptions)); err != nil {
    43  		return nil, err
    44  	}
    45  	options.ForClientCapabilities(params.Capabilities)
    46  
    47  	folders := params.WorkspaceFolders
    48  	if len(folders) == 0 {
    49  		if params.RootURI != "" {
    50  			folders = []protocol.WorkspaceFolder{{
    51  				URI:  string(params.RootURI),
    52  				Name: path.Base(params.RootURI.SpanURI().Filename()),
    53  			}}
    54  		}
    55  	}
    56  	for _, folder := range folders {
    57  		uri := span.URIFromURI(folder.URI)
    58  		if !uri.IsFile() {
    59  			continue
    60  		}
    61  		s.pendingFolders = append(s.pendingFolders, folder)
    62  	}
    63  	// gopls only supports URIs with a file:// scheme, so if we have no
    64  	// workspace folders with a supported scheme, fail to initialize.
    65  	if len(folders) > 0 && len(s.pendingFolders) == 0 {
    66  		return nil, fmt.Errorf("unsupported URI schemes: %v (gopls only supports file URIs)", folders)
    67  	}
    68  
    69  	var codeActionProvider interface{} = true
    70  	if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
    71  		// If the client has specified CodeActionLiteralSupport,
    72  		// send the code actions we support.
    73  		//
    74  		// Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
    75  		codeActionProvider = &protocol.CodeActionOptions{
    76  			CodeActionKinds: s.getSupportedCodeActions(),
    77  		}
    78  	}
    79  	var renameOpts interface{} = true
    80  	if r := params.Capabilities.TextDocument.Rename; r.PrepareSupport {
    81  		renameOpts = protocol.RenameOptions{
    82  			PrepareProvider: r.PrepareSupport,
    83  		}
    84  	}
    85  
    86  	goplsVer := &bytes.Buffer{}
    87  	debug.PrintVersionInfo(ctx, goplsVer, true, debug.PlainText)
    88  
    89  	return &protocol.InitializeResult{
    90  		Capabilities: protocol.ServerCapabilities{
    91  			CallHierarchyProvider: true,
    92  			CodeActionProvider:    codeActionProvider,
    93  			CompletionProvider: protocol.CompletionOptions{
    94  				TriggerCharacters: []string{"."},
    95  			},
    96  			DefinitionProvider:         true,
    97  			TypeDefinitionProvider:     true,
    98  			ImplementationProvider:     true,
    99  			DocumentFormattingProvider: true,
   100  			DocumentSymbolProvider:     true,
   101  			WorkspaceSymbolProvider:    true,
   102  			ExecuteCommandProvider: protocol.ExecuteCommandOptions{
   103  				Commands: options.SupportedCommands,
   104  			},
   105  			FoldingRangeProvider:      true,
   106  			HoverProvider:             true,
   107  			DocumentHighlightProvider: true,
   108  			DocumentLinkProvider:      protocol.DocumentLinkOptions{},
   109  			ReferencesProvider:        true,
   110  			RenameProvider:            renameOpts,
   111  			SignatureHelpProvider: protocol.SignatureHelpOptions{
   112  				TriggerCharacters: []string{"(", ","},
   113  			},
   114  			TextDocumentSync: &protocol.TextDocumentSyncOptions{
   115  				Change:    protocol.Incremental,
   116  				OpenClose: true,
   117  				Save: protocol.SaveOptions{
   118  					IncludeText: false,
   119  				},
   120  			},
   121  			Workspace: protocol.WorkspaceGn{
   122  				WorkspaceFolders: protocol.WorkspaceFoldersGn{
   123  					Supported:           true,
   124  					ChangeNotifications: "workspace/didChangeWorkspaceFolders",
   125  				},
   126  			},
   127  		},
   128  		ServerInfo: struct {
   129  			Name    string `json:"name"`
   130  			Version string `json:"version,omitempty"`
   131  		}{
   132  			Name:    "gopls",
   133  			Version: goplsVer.String(),
   134  		},
   135  	}, nil
   136  }
   137  
   138  func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
   139  	s.stateMu.Lock()
   140  	if s.state >= serverInitialized {
   141  		defer s.stateMu.Unlock()
   142  		return errors.Errorf("%w: initalized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
   143  	}
   144  	s.state = serverInitialized
   145  	s.stateMu.Unlock()
   146  
   147  	options := s.session.Options()
   148  	defer func() { s.session.SetOptions(options) }()
   149  
   150  	// TODO: this event logging may be unnecessary.
   151  	// The version info is included in the initialize response.
   152  	buf := &bytes.Buffer{}
   153  	debug.PrintVersionInfo(ctx, buf, true, debug.PlainText)
   154  	event.Log(ctx, buf.String())
   155  
   156  	if err := s.addFolders(ctx, s.pendingFolders); err != nil {
   157  		return err
   158  	}
   159  	s.pendingFolders = nil
   160  
   161  	if options.ConfigurationSupported && options.DynamicConfigurationSupported {
   162  		if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
   163  			Registrations: []protocol.Registration{
   164  				{
   165  					ID:     "workspace/didChangeConfiguration",
   166  					Method: "workspace/didChangeConfiguration",
   167  				},
   168  				{
   169  					ID:     "workspace/didChangeWorkspaceFolders",
   170  					Method: "workspace/didChangeWorkspaceFolders",
   171  				},
   172  			},
   173  		}); err != nil {
   174  			return err
   175  		}
   176  	}
   177  	return nil
   178  }
   179  
   180  func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error {
   181  	originalViews := len(s.session.Views())
   182  	viewErrors := make(map[span.URI]error)
   183  
   184  	var wg sync.WaitGroup
   185  	if s.session.Options().VerboseWorkDoneProgress {
   186  		work := s.progress.start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
   187  		defer func() {
   188  			go func() {
   189  				wg.Wait()
   190  				work.end("Done.")
   191  			}()
   192  		}()
   193  	}
   194  	dirsToWatch := map[span.URI]struct{}{}
   195  	for _, folder := range folders {
   196  		uri := span.URIFromURI(folder.URI)
   197  		// Ignore non-file URIs.
   198  		if !uri.IsFile() {
   199  			continue
   200  		}
   201  		work := s.progress.start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
   202  		view, snapshot, release, err := s.addView(ctx, folder.Name, uri)
   203  		if err != nil {
   204  			viewErrors[uri] = err
   205  			work.end(fmt.Sprintf("Error loading packages: %s", err))
   206  			continue
   207  		}
   208  		go func() {
   209  			view.AwaitInitialized(ctx)
   210  			work.end("Finished loading packages.")
   211  		}()
   212  
   213  		for _, dir := range snapshot.WorkspaceDirectories(ctx) {
   214  			dirsToWatch[dir] = struct{}{}
   215  		}
   216  
   217  		// Print each view's environment.
   218  		buf := &bytes.Buffer{}
   219  		if err := view.WriteEnv(ctx, buf); err != nil {
   220  			event.Error(ctx, "failed to write environment", err, tag.Directory.Of(view.Folder().Filename()))
   221  			continue
   222  		}
   223  		event.Log(ctx, buf.String())
   224  
   225  		// Diagnose the newly created view.
   226  		wg.Add(1)
   227  		go func() {
   228  			s.diagnoseDetached(snapshot)
   229  			release()
   230  			wg.Done()
   231  		}()
   232  	}
   233  	// Register for file watching notifications, if they are supported.
   234  	s.watchedDirectoriesMu.Lock()
   235  	err := s.registerWatchedDirectoriesLocked(ctx, dirsToWatch)
   236  	s.watchedDirectoriesMu.Unlock()
   237  	if err != nil {
   238  		return err
   239  	}
   240  	if len(viewErrors) > 0 {
   241  		errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews)
   242  		for uri, err := range viewErrors {
   243  			errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err)
   244  		}
   245  		return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
   246  			Type:    protocol.Error,
   247  			Message: errMsg,
   248  		})
   249  	}
   250  	return nil
   251  }
   252  
   253  // updateWatchedDirectories compares the current set of directories to watch
   254  // with the previously registered set of directories. If the set of directories
   255  // has changed, we unregister and re-register for file watching notifications.
   256  // updatedSnapshots is the set of snapshots that have been updated.
   257  func (s *Server) updateWatchedDirectories(ctx context.Context, updatedSnapshots []source.Snapshot) error {
   258  	dirsToWatch := map[span.URI]struct{}{}
   259  	seenViews := map[source.View]struct{}{}
   260  
   261  	// Collect all of the workspace directories from the updated snapshots.
   262  	for _, snapshot := range updatedSnapshots {
   263  		seenViews[snapshot.View()] = struct{}{}
   264  		for _, dir := range snapshot.WorkspaceDirectories(ctx) {
   265  			dirsToWatch[dir] = struct{}{}
   266  		}
   267  	}
   268  	// Not all views were necessarily updated, so check the remaining views.
   269  	for _, view := range s.session.Views() {
   270  		if _, ok := seenViews[view]; ok {
   271  			continue
   272  		}
   273  		snapshot, release := view.Snapshot(ctx)
   274  		for _, dir := range snapshot.WorkspaceDirectories(ctx) {
   275  			dirsToWatch[dir] = struct{}{}
   276  		}
   277  		release()
   278  	}
   279  
   280  	s.watchedDirectoriesMu.Lock()
   281  	defer s.watchedDirectoriesMu.Unlock()
   282  
   283  	// Nothing to do if the set of workspace directories is unchanged.
   284  	if equalURISet(s.watchedDirectories, dirsToWatch) {
   285  		return nil
   286  	}
   287  
   288  	// If the set of directories to watch has changed, register the updates and
   289  	// unregister the previously watched directories. This ordering avoids a
   290  	// period where no files are being watched. Still, if a user makes on-disk
   291  	// changes before these updates are complete, we may miss them for the new
   292  	// directories.
   293  	if s.watchRegistrationCount > 0 {
   294  		prevID := s.watchRegistrationCount - 1
   295  		if err := s.registerWatchedDirectoriesLocked(ctx, dirsToWatch); err != nil {
   296  			return err
   297  		}
   298  		return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{
   299  			Unregisterations: []protocol.Unregistration{{
   300  				ID:     watchedFilesCapabilityID(prevID),
   301  				Method: "workspace/didChangeWatchedFiles",
   302  			}},
   303  		})
   304  	}
   305  	return nil
   306  }
   307  
   308  func watchedFilesCapabilityID(id uint64) string {
   309  	return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id)
   310  }
   311  
   312  func equalURISet(m1, m2 map[span.URI]struct{}) bool {
   313  	if len(m1) != len(m2) {
   314  		return false
   315  	}
   316  	for k := range m1 {
   317  		_, ok := m2[k]
   318  		if !ok {
   319  			return false
   320  		}
   321  	}
   322  	return true
   323  }
   324  
   325  // registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles
   326  // registrations to the client and updates s.watchedDirectories.
   327  func (s *Server) registerWatchedDirectoriesLocked(ctx context.Context, dirs map[span.URI]struct{}) error {
   328  	if !s.session.Options().DynamicWatchedFilesSupported {
   329  		return nil
   330  	}
   331  	for k := range s.watchedDirectories {
   332  		delete(s.watchedDirectories, k)
   333  	}
   334  	// Work-around microsoft/vscode#100870 by making sure that we are,
   335  	// at least, watching the user's entire workspace. This will still be
   336  	// applied to every folder in the workspace.
   337  	watchers := []protocol.FileSystemWatcher{{
   338  		GlobPattern: "**/*.{go,mod,sum}",
   339  		Kind:        float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
   340  	}}
   341  	for dir := range dirs {
   342  		filename := dir.Filename()
   343  		// If the directory is within a workspace folder, we're already
   344  		// watching it via the relative path above.
   345  		for _, view := range s.session.Views() {
   346  			if isSubdirectory(view.Folder().Filename(), filename) {
   347  				continue
   348  			}
   349  		}
   350  		// If microsoft/vscode#100870 is resolved before
   351  		// microsoft/vscode#104387, we will need a work-around for Windows
   352  		// drive letter casing.
   353  		watchers = append(watchers, protocol.FileSystemWatcher{
   354  			GlobPattern: fmt.Sprintf("%s/**/*.{go,mod,sum}", filename),
   355  			Kind:        float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
   356  		})
   357  	}
   358  	if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
   359  		Registrations: []protocol.Registration{{
   360  			ID:     watchedFilesCapabilityID(s.watchRegistrationCount),
   361  			Method: "workspace/didChangeWatchedFiles",
   362  			RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
   363  				Watchers: watchers,
   364  			},
   365  		}},
   366  	}); err != nil {
   367  		return err
   368  	}
   369  	s.watchRegistrationCount++
   370  
   371  	for dir := range dirs {
   372  		s.watchedDirectories[dir] = struct{}{}
   373  	}
   374  	return nil
   375  }
   376  
   377  func isSubdirectory(root, leaf string) bool {
   378  	rel, err := filepath.Rel(root, leaf)
   379  	return err == nil && !strings.HasPrefix(rel, "..")
   380  }
   381  
   382  func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error {
   383  	if !s.session.Options().ConfigurationSupported {
   384  		return nil
   385  	}
   386  	v := protocol.ParamConfiguration{
   387  		ConfigurationParams: protocol.ConfigurationParams{
   388  			Items: []protocol.ConfigurationItem{{
   389  				ScopeURI: string(folder),
   390  				Section:  "gopls",
   391  			}, {
   392  				ScopeURI: string(folder),
   393  				Section:  fmt.Sprintf("gopls-%s", name),
   394  			}},
   395  		},
   396  	}
   397  	configs, err := s.client.Configuration(ctx, &v)
   398  	if err != nil {
   399  		return fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err)
   400  	}
   401  	for _, config := range configs {
   402  		if err := s.handleOptionResults(ctx, source.SetOptions(o, config)); err != nil {
   403  			return err
   404  		}
   405  	}
   406  	return nil
   407  }
   408  
   409  func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error {
   410  	for _, result := range results {
   411  		if result.Error != nil {
   412  			if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
   413  				Type:    protocol.Error,
   414  				Message: result.Error.Error(),
   415  			}); err != nil {
   416  				return err
   417  			}
   418  		}
   419  		switch result.State {
   420  		case source.OptionUnexpected:
   421  			if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
   422  				Type:    protocol.Error,
   423  				Message: fmt.Sprintf("unexpected gopls setting %q", result.Name),
   424  			}); err != nil {
   425  				return err
   426  			}
   427  		case source.OptionDeprecated:
   428  			msg := fmt.Sprintf("gopls setting %q is deprecated", result.Name)
   429  			if result.Replacement != "" {
   430  				msg = fmt.Sprintf("%s, use %q instead", msg, result.Replacement)
   431  			}
   432  			if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
   433  				Type:    protocol.Warning,
   434  				Message: msg,
   435  			}); err != nil {
   436  				return err
   437  			}
   438  		}
   439  	}
   440  	return nil
   441  }
   442  
   443  // beginFileRequest checks preconditions for a file-oriented request and routes
   444  // it to a snapshot.
   445  // We don't want to return errors for benign conditions like wrong file type,
   446  // so callers should do if !ok { return err } rather than if err != nil.
   447  func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.VersionedFileHandle, bool, func(), error) {
   448  	uri := pURI.SpanURI()
   449  	if !uri.IsFile() {
   450  		// Not a file URI. Stop processing the request, but don't return an error.
   451  		return nil, nil, false, func() {}, nil
   452  	}
   453  	view, err := s.session.ViewOf(uri)
   454  	if err != nil {
   455  		return nil, nil, false, func() {}, err
   456  	}
   457  	snapshot, release := view.Snapshot(ctx)
   458  	fh, err := snapshot.GetFile(ctx, uri)
   459  	if err != nil {
   460  		release()
   461  		return nil, nil, false, func() {}, err
   462  	}
   463  	if expectKind != source.UnknownKind && fh.Kind() != expectKind {
   464  		// Wrong kind of file. Nothing to do.
   465  		release()
   466  		return nil, nil, false, func() {}, nil
   467  	}
   468  	return snapshot, fh, true, release, nil
   469  }
   470  
   471  func (s *Server) shutdown(ctx context.Context) error {
   472  	s.stateMu.Lock()
   473  	defer s.stateMu.Unlock()
   474  	if s.state < serverInitialized {
   475  		event.Log(ctx, "server shutdown without initialization")
   476  	}
   477  	if s.state != serverShutDown {
   478  		// drop all the active views
   479  		s.session.Shutdown(ctx)
   480  		s.state = serverShutDown
   481  	}
   482  	return nil
   483  }
   484  
   485  func (s *Server) exit(ctx context.Context) error {
   486  	s.stateMu.Lock()
   487  	defer s.stateMu.Unlock()
   488  
   489  	// TODO: We need a better way to find the conn close method.
   490  	s.client.(io.Closer).Close()
   491  
   492  	if s.state != serverShutDown {
   493  		// TODO: We should be able to do better than this.
   494  		os.Exit(1)
   495  	}
   496  	// we don't terminate the process on a normal exit, we just allow it to
   497  	// close naturally if needed after the connection is closed.
   498  	return nil
   499  }