// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package lsp implements LSP for gopls. package lsp import ( "context" "fmt" "net" "sync" "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) // NewClientServer func NewClientServer(ctx context.Context, session source.Session, client protocol.Client) (context.Context, *Server) { ctx = protocol.WithClient(ctx, client) return ctx, &Server{ client: client, session: session, delivered: make(map[span.URI]sentDiagnostics), } } // NewServer creates an LSP server and binds it to handle incoming client // messages on on the supplied stream. func NewServer(ctx context.Context, session source.Session, stream jsonrpc2.Stream) (context.Context, *Server) { s := &Server{ delivered: make(map[span.URI]sentDiagnostics), session: session, } ctx, s.Conn, s.client = protocol.NewServer(ctx, stream, s) return ctx, s } // RunServerOnPort starts an LSP server on the given port and does not exit. // This function exists for debugging purposes. func RunServerOnPort(ctx context.Context, cache source.Cache, port int, h func(ctx context.Context, s *Server)) error { return RunServerOnAddress(ctx, cache, fmt.Sprintf(":%v", port), h) } // RunServerOnAddress starts an LSP server on the given address and does not // exit. This function exists for debugging purposes. func RunServerOnAddress(ctx context.Context, cache source.Cache, addr string, h func(ctx context.Context, s *Server)) error { ln, err := net.Listen("tcp", addr) if err != nil { return err } for { conn, err := ln.Accept() if err != nil { return err } h(NewServer(ctx, cache.NewSession(), jsonrpc2.NewHeaderStream(conn, conn))) } } func (s *Server) Run(ctx context.Context) error { return s.Conn.Run(ctx) } type serverState int const ( serverCreated = serverState(iota) serverInitializing // set once the server has received "initialize" request serverInitialized // set once the server has received "initialized" request serverShutDown ) type Server struct { Conn *jsonrpc2.Conn client protocol.Client stateMu sync.Mutex state serverState session source.Session // changedFiles tracks files for which there has been a textDocument/didChange. changedFiles map[span.URI]struct{} // folders is only valid between initialize and initialized, and holds the // set of folders to build views for when we are ready pendingFolders []protocol.WorkspaceFolder // delivered is a cache of the diagnostics that the server has sent. deliveredMu sync.Mutex delivered map[span.URI]sentDiagnostics } // sentDiagnostics is used to cache diagnostics that have been sent for a given file. type sentDiagnostics struct { version float64 identifier string sorted []source.Diagnostic withAnalysis bool snapshotID uint64 } func (s *Server) cancelRequest(ctx context.Context, params *protocol.CancelParams) error { return nil } func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) { return nil, nil } func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { paramMap := params.(map[string]interface{}) if method == "gopls/diagnoseFiles" { for _, file := range paramMap["files"].([]interface{}) { uri := span.NewURI(file.(string)) view, err := s.session.ViewOf(uri) if err != nil { return nil, err } fileID, diagnostics, err := source.FileDiagnostics(ctx, view.Snapshot(), uri) if err != nil { return nil, err } if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{ URI: protocol.NewURI(uri), Diagnostics: toProtocolDiagnostics(diagnostics), Version: fileID.Version, }); err != nil { return nil, err } } if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{ URI: "gopls://diagnostics-done", }); err != nil { return nil, err } return struct{}{}, nil } return nil, notImplemented(method) } func notImplemented(method string) *jsonrpc2.Error { return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method) } //go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .