github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/cmd/serve.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 cmd 6 7 import ( 8 "context" 9 "flag" 10 "fmt" 11 "io" 12 "log" 13 "os" 14 "time" 15 16 "github.com/jhump/golang-x-tools/internal/fakenet" 17 "github.com/jhump/golang-x-tools/internal/jsonrpc2" 18 "github.com/jhump/golang-x-tools/internal/lsp/cache" 19 "github.com/jhump/golang-x-tools/internal/lsp/debug" 20 "github.com/jhump/golang-x-tools/internal/lsp/lsprpc" 21 "github.com/jhump/golang-x-tools/internal/lsp/protocol" 22 "github.com/jhump/golang-x-tools/internal/tool" 23 errors "golang.org/x/xerrors" 24 ) 25 26 // Serve is a struct that exposes the configurable parts of the LSP server as 27 // flags, in the right form for tool.Main to consume. 28 type Serve struct { 29 Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"` 30 Mode string `flag:"mode" help:"no effect"` 31 Port int `flag:"port" help:"port on which to run gopls for debugging purposes"` 32 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."` 33 IdleTimeout time.Duration `flag:"listen.timeout" help:"when used with -listen, shut down the server when there are no connected clients for this duration"` 34 Trace bool `flag:"rpc.trace" help:"print the full rpc trace in lsp inspector format"` 35 Debug string `flag:"debug" help:"serve debug information on the supplied address"` 36 37 RemoteListenTimeout time.Duration `flag:"remote.listen.timeout" help:"when used with -remote=auto, the -listen.timeout value used to start the daemon"` 38 RemoteDebug string `flag:"remote.debug" help:"when used with -remote=auto, the -debug value used to start the daemon"` 39 RemoteLogfile string `flag:"remote.logfile" help:"when used with -remote=auto, the -logfile value used to start the daemon"` 40 41 app *Application 42 } 43 44 func (s *Serve) Name() string { return "serve" } 45 func (s *Serve) Parent() string { return s.app.Name() } 46 func (s *Serve) Usage() string { return "[server-flags]" } 47 func (s *Serve) ShortHelp() string { 48 return "run a server for Go code using the Language Server Protocol" 49 } 50 func (s *Serve) DetailedHelp(f *flag.FlagSet) { 51 fmt.Fprint(f.Output(), ` gopls [flags] [server-flags] 52 53 The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as 54 a child of an editor process. 55 56 server-flags: 57 `) 58 printFlagDefaults(f) 59 } 60 61 func (s *Serve) remoteArgs(network, address string) []string { 62 args := []string{"serve", 63 "-listen", fmt.Sprintf(`%s;%s`, network, address), 64 } 65 if s.RemoteDebug != "" { 66 args = append(args, "-debug", s.RemoteDebug) 67 } 68 if s.RemoteListenTimeout != 0 { 69 args = append(args, "-listen.timeout", s.RemoteListenTimeout.String()) 70 } 71 if s.RemoteLogfile != "" { 72 args = append(args, "-logfile", s.RemoteLogfile) 73 } 74 return args 75 } 76 77 // Run configures a server based on the flags, and then runs it. 78 // It blocks until the server shuts down. 79 func (s *Serve) Run(ctx context.Context, args ...string) error { 80 if len(args) > 0 { 81 return tool.CommandLineErrorf("server does not take arguments, got %v", args) 82 } 83 84 di := debug.GetInstance(ctx) 85 isDaemon := s.Address != "" || s.Port != 0 86 if di != nil { 87 closeLog, err := di.SetLogFile(s.Logfile, isDaemon) 88 if err != nil { 89 return err 90 } 91 defer closeLog() 92 di.ServerAddress = s.Address 93 di.MonitorMemory(ctx) 94 di.Serve(ctx, s.Debug) 95 } 96 var ss jsonrpc2.StreamServer 97 if s.app.Remote != "" { 98 var err error 99 ss, err = lsprpc.NewForwarder(s.app.Remote, s.remoteArgs) 100 if err != nil { 101 return errors.Errorf("creating forwarder: %w", err) 102 } 103 } else { 104 ss = lsprpc.NewStreamServer(cache.New(s.app.options), isDaemon) 105 } 106 107 var network, addr string 108 if s.Address != "" { 109 network, addr = lsprpc.ParseAddr(s.Address) 110 } 111 if s.Port != 0 { 112 network = "tcp" 113 addr = fmt.Sprintf(":%v", s.Port) 114 } 115 if addr != "" { 116 log.Printf("Gopls daemon: listening on %s network, address %s...", network, addr) 117 defer log.Printf("Gopls daemon: exiting") 118 return jsonrpc2.ListenAndServe(ctx, network, addr, ss, s.IdleTimeout) 119 } 120 stream := jsonrpc2.NewHeaderStream(fakenet.NewConn("stdio", os.Stdin, os.Stdout)) 121 if s.Trace && di != nil { 122 stream = protocol.LoggingStream(stream, di.LogWriter) 123 } 124 conn := jsonrpc2.NewConn(stream) 125 err := ss.ServeStream(ctx, conn) 126 if errors.Is(err, io.EOF) { 127 return nil 128 } 129 return err 130 }