Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 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 | |
Alan Donovan | c21f267 | 2023-11-30 11:03:30 -0500 | [diff] [blame] | 17 | "golang.org/x/tools/gopls/internal/debug" |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 18 | "golang.org/x/tools/gopls/internal/lsp/cache" |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 19 | "golang.org/x/tools/gopls/internal/lsp/lsprpc" |
| 20 | "golang.org/x/tools/gopls/internal/lsp/protocol" |
Hana (Hyang-Ah) Kim | 1896879 | 2023-11-28 11:12:29 -0500 | [diff] [blame] | 21 | "golang.org/x/tools/gopls/internal/telemetry" |
Robert Findley | fe83ddb | 2022-11-28 16:52:57 -0500 | [diff] [blame] | 22 | "golang.org/x/tools/internal/fakenet" |
| 23 | "golang.org/x/tools/internal/jsonrpc2" |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 24 | "golang.org/x/tools/internal/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 { |
Hana (Hyang-Ah) Kim | 1896879 | 2023-11-28 11:12:29 -0500 | [diff] [blame] | 81 | telemetry.Upload() |
| 82 | |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 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 |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 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 { |
Robert Findley | 21d2256 | 2023-02-21 12:26:27 -0500 | [diff] [blame] | 106 | ss = lsprpc.NewStreamServer(cache.New(nil), isDaemon, s.app.options) |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 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" |
Alan Donovan | 8f07782 | 2023-06-23 17:58:45 -0400 | [diff] [blame] | 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. |
Robert Findley | b15dac2 | 2022-08-30 14:40:12 -0400 | [diff] [blame] | 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 | } |