blob: 03eb4520ff05f1c150c7067146b36f8eafbdcdd5 [file] [log] [blame]
Robert Findleyb15dac22022-08-30 14:40:12 -04001// 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
5package cmd
6
7import (
8 "context"
9 "errors"
10 "flag"
11 "fmt"
12 "io"
13 "log"
14 "os"
15 "time"
16
Alan Donovanc21f2672023-11-30 11:03:30 -050017 "golang.org/x/tools/gopls/internal/debug"
Robert Findleyb15dac22022-08-30 14:40:12 -040018 "golang.org/x/tools/gopls/internal/lsp/cache"
Robert Findleyb15dac22022-08-30 14:40:12 -040019 "golang.org/x/tools/gopls/internal/lsp/lsprpc"
20 "golang.org/x/tools/gopls/internal/lsp/protocol"
Hana (Hyang-Ah) Kim18968792023-11-28 11:12:29 -050021 "golang.org/x/tools/gopls/internal/telemetry"
Robert Findleyfe83ddb2022-11-28 16:52:57 -050022 "golang.org/x/tools/internal/fakenet"
23 "golang.org/x/tools/internal/jsonrpc2"
Robert Findleyb15dac22022-08-30 14:40:12 -040024 "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.
29type 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
45func (s *Serve) Name() string { return "serve" }
46func (s *Serve) Parent() string { return s.app.Name() }
47func (s *Serve) Usage() string { return "[server-flags]" }
48func (s *Serve) ShortHelp() string {
49 return "run a server for Go code using the Language Server Protocol"
50}
51func (s *Serve) DetailedHelp(f *flag.FlagSet) {
52 fmt.Fprint(f.Output(), ` gopls [flags] [server-flags]
53
54The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
55a child of an editor process.
56
57server-flags:
58`)
59 printFlagDefaults(f)
60}
61
62func (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.
80func (s *Serve) Run(ctx context.Context, args ...string) error {
Hana (Hyang-Ah) Kim18968792023-11-28 11:12:29 -050081 telemetry.Upload()
82
Robert Findleyb15dac22022-08-30 14:40:12 -040083 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 Findleyb15dac22022-08-30 14:40:12 -040096 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 Findley21d22562023-02-21 12:26:27 -0500106 ss = lsprpc.NewStreamServer(cache.New(nil), isDaemon, s.app.options)
Robert Findleyb15dac22022-08-30 14:40:12 -0400107 }
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 Donovan8f077822023-06-23 17:58:45 -0400115 // 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 Findleyb15dac22022-08-30 14:40:12 -0400132 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}