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.
6package lsp
7
8import (
9	"context"
10	"fmt"
11	"sync"
12
13	"golang.org/x/tools/internal/jsonrpc2"
14	"golang.org/x/tools/internal/lsp/protocol"
15	"golang.org/x/tools/internal/lsp/source"
16	"golang.org/x/tools/internal/span"
17	errors "golang.org/x/xerrors"
18)
19
20const concurrentAnalyses = 1
21
22// NewServer creates an LSP server and binds it to handle incoming client
23// messages on on the supplied stream.
24func NewServer(session source.Session, client protocol.ClientCloser) *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
38type serverState int
39
40const (
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
47func (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.
62type Server struct {
63	client protocol.ClientCloser
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
113func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
114	return s.progress.cancel(ctx, params.Token)
115}
116
117func (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
150func 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 .
155