1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
// 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 cmd
import (
"context"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"time"
"cuelang.org/go/internal/golangorgx/gopls/cache"
"cuelang.org/go/internal/golangorgx/gopls/debug"
"cuelang.org/go/internal/golangorgx/gopls/lsprpc"
"cuelang.org/go/internal/golangorgx/gopls/protocol"
"cuelang.org/go/internal/golangorgx/gopls/telemetry"
"cuelang.org/go/internal/golangorgx/tools/fakenet"
"cuelang.org/go/internal/golangorgx/tools/jsonrpc2"
"cuelang.org/go/internal/golangorgx/tools/tool"
)
// Serve is a struct that exposes the configurable parts of the LSP server as
// flags, in the right form for tool.Main to consume.
type Serve struct {
Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
Mode string `flag:"mode" help:"no effect"`
Port int `flag:"port" help:"port on which to run gopls for debugging purposes"`
Address string `flag:"listen" help:"address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used."`
IdleTimeout time.Duration `flag:"listen.timeout" help:"when used with -listen, shut down the server when there are no connected clients for this duration"`
Trace bool `flag:"rpc.trace" help:"print the full rpc trace in lsp inspector format"`
Debug string `flag:"debug" help:"serve debug information on the supplied address"`
RemoteListenTimeout time.Duration `flag:"remote.listen.timeout" help:"when used with -remote=auto, the -listen.timeout value used to start the daemon"`
RemoteDebug string `flag:"remote.debug" help:"when used with -remote=auto, the -debug value used to start the daemon"`
RemoteLogfile string `flag:"remote.logfile" help:"when used with -remote=auto, the -logfile value used to start the daemon"`
app *Application
}
func (s *Serve) Name() string { return "serve" }
func (s *Serve) Parent() string { return s.app.Name() }
func (s *Serve) Usage() string { return "[server-flags]" }
func (s *Serve) ShortHelp() string {
return "run a server for Go code using the Language Server Protocol"
}
func (s *Serve) DetailedHelp(f *flag.FlagSet) {
fmt.Fprint(f.Output(), ` gopls [flags] [server-flags]
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
a child of an editor process.
server-flags:
`)
printFlagDefaults(f)
}
func (s *Serve) remoteArgs(network, address string) []string {
args := []string{"serve",
"-listen", fmt.Sprintf(`%s;%s`, network, address),
}
if s.RemoteDebug != "" {
args = append(args, "-debug", s.RemoteDebug)
}
if s.RemoteListenTimeout != 0 {
args = append(args, "-listen.timeout", s.RemoteListenTimeout.String())
}
if s.RemoteLogfile != "" {
args = append(args, "-logfile", s.RemoteLogfile)
}
return args
}
// Run configures a server based on the flags, and then runs it.
// It blocks until the server shuts down.
func (s *Serve) Run(ctx context.Context, args ...string) error {
telemetry.Upload()
if len(args) > 0 {
return tool.CommandLineErrorf("server does not take arguments, got %v", args)
}
di := debug.GetInstance(ctx)
isDaemon := s.Address != "" || s.Port != 0
if di != nil {
closeLog, err := di.SetLogFile(s.Logfile, isDaemon)
if err != nil {
return err
}
defer closeLog()
di.ServerAddress = s.Address
di.Serve(ctx, s.Debug)
}
var ss jsonrpc2.StreamServer
if s.app.Remote != "" {
var err error
ss, err = lsprpc.NewForwarder(s.app.Remote, s.remoteArgs)
if err != nil {
return fmt.Errorf("creating forwarder: %w", err)
}
} else {
ss = lsprpc.NewStreamServer(cache.New(nil), isDaemon, s.app.options)
}
var network, addr string
if s.Address != "" {
network, addr = lsprpc.ParseAddr(s.Address)
}
if s.Port != 0 {
network = "tcp"
// TODO(adonovan): should gopls ever be listening on network
// sockets, or only local ones?
//
// Ian says this was added in anticipation of
// something related to "VS Code remote" that turned
// out to be unnecessary. So I propose we limit it to
// localhost, if only so that we avoid the macOS
// firewall prompt.
//
// Hana says: "s.Address is for the remote access (LSP)
// and s.Port is for debugging purpose (according to
// the Server type documentation). I am not sure why the
// existing code here is mixing up and overwriting addr.
// For debugging endpoint, I think localhost makes perfect sense."
//
// TODO(adonovan): disentangle Address and Port,
// and use only localhost for the latter.
addr = fmt.Sprintf(":%v", s.Port)
}
if addr != "" {
log.Printf("Gopls daemon: listening on %s network, address %s...", network, addr)
defer log.Printf("Gopls daemon: exiting")
return jsonrpc2.ListenAndServe(ctx, network, addr, ss, s.IdleTimeout)
}
stream := jsonrpc2.NewHeaderStream(fakenet.NewConn("stdio", os.Stdin, os.Stdout))
if s.Trace && di != nil {
stream = protocol.LoggingStream(stream, di.LogWriter)
}
conn := jsonrpc2.NewConn(stream)
err := ss.ServeStream(ctx, conn)
if errors.Is(err, io.EOF) {
return nil
}
return err
}
|