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