diff --git a/cmd/gopls/forward/main.go b/cmd/gopls/forward/main.go deleted file mode 100644 index c437679b..00000000 --- a/cmd/gopls/forward/main.go +++ /dev/null @@ -1,59 +0,0 @@ -// The forward command writes and reads to a gopls server on a network socket. -package main - -import ( - "context" - "flag" - "fmt" - "io" - "log" - "net" - "os" - - "golang.org/x/tools/internal/lsp/cmd" - "golang.org/x/tools/internal/tool" -) - -func main() { - tool.Main(context.Background(), &app{&cmd.Server{}}, os.Args[1:]) -} - -type app struct { - *cmd.Server -} - -func (*app) Name() string { return "forward" } -func (*app) Usage() string { return "[-port=]" } -func (*app) ShortHelp() string { return "An intermediary between an editor and gopls." } -func (*app) DetailedHelp(*flag.FlagSet) {} - -func (a *app) Run(ctx context.Context, args ...string) error { - if a.Server.Port == 0 { - a.ShortHelp() - os.Exit(0) - } - conn, err := net.Dial("tcp", fmt.Sprintf(":%v", a.Server.Port)) - if err != nil { - log.Print(err) - os.Exit(0) - } - - go func(conn net.Conn) { - _, err := io.Copy(conn, os.Stdin) - if err != nil { - log.Print(err) - os.Exit(0) - } - }(conn) - - go func(conn net.Conn) { - _, err := io.Copy(os.Stdout, conn) - if err != nil { - log.Print(err) - os.Exit(0) - } - }(conn) - - for { - } -} diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index 694f8590..9defccd2 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -25,12 +25,16 @@ type Application struct { // Embed the basic profiling flags supported by the tool package tool.Profile - // We include the server directly for now, so the flags work even without the verb. - // TODO: Remove this when we stop allowing the server verb by default. - Server Server + // We include the server configuration directly for now, so the flags work + // even without the verb. + // TODO: Remove this when we stop allowing the serve verb by default. + Serve Serve // An initial, common go/packages configuration Config packages.Config + + // Support for remote lsp server + Remote string `flag:"remote" help:"*EXPERIMENTAL* - forward all commands to a remote lsp"` } // Name implements tool.Application returning the binary name. @@ -65,7 +69,7 @@ gopls flags are: // temporary measure for compatibility. func (app *Application) Run(ctx context.Context, args ...string) error { if len(args) == 0 { - tool.Main(ctx, &app.Server, args) + tool.Main(ctx, &app.Serve, args) return nil } app.Config.Mode = packages.LoadSyntax @@ -87,8 +91,9 @@ func (app *Application) Run(ctx context.Context, args ...string) error { // command line. // The command is specified by the first non flag argument. func (app *Application) commands() []tool.Application { + app.Serve.app = app return []tool.Application{ - &app.Server, + &app.Serve, &query{app: app}, } } diff --git a/internal/lsp/cmd/server.go b/internal/lsp/cmd/serve.go similarity index 76% rename from internal/lsp/cmd/server.go rename to internal/lsp/cmd/serve.go index 72993e6f..67013864 100644 --- a/internal/lsp/cmd/server.go +++ b/internal/lsp/cmd/serve.go @@ -15,26 +15,30 @@ import ( "path/filepath" "strings" "time" + "net" "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/lsp" "golang.org/x/tools/internal/tool" ) -// Server is a struct that exposes the configurable parts of the LSP server as +// 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 Server struct { +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"` + + app *Application } -func (s *Server) Name() string { return "server" } -func (s *Server) Usage() string { return "" } -func (s *Server) ShortHelp() string { +func (s *Serve) Name() string { return "serve" } +func (s *Serve) Usage() string { return "" } +func (s *Serve) ShortHelp() string { return "run a server for Go code using the Language Server Protocol" } -func (s *Server) DetailedHelp(f *flag.FlagSet) { +func (s *Serve) DetailedHelp(f *flag.FlagSet) { fmt.Fprint(f.Output(), ` The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as a child of an editor process. @@ -46,7 +50,7 @@ gopls server flags are: // Run configures a server based on the flags, and then runs it. // It blocks until the server shuts down. -func (s *Server) Run(ctx context.Context, args ...string) error { +func (s *Serve) Run(ctx context.Context, args ...string) error { if len(args) > 0 { return tool.CommandLineErrorf("server does not take arguments, got %v", args) } @@ -64,6 +68,9 @@ func (s *Server) Run(ctx context.Context, args ...string) error { log.SetOutput(io.MultiWriter(os.Stderr, f)) out = f } + if s.app.Remote != "" { + return s.forward() + } logger := func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) { const eol = "\r\n\r\n\r\n" if err != nil { @@ -112,9 +119,33 @@ func (s *Server) Run(ctx context.Context, args ...string) error { fmt.Fprintf(out, "%s", outx.String()) } // For debugging purposes only. + if s.Address != "" { + return lsp.RunServerOnAddress(ctx, s.Address, logger) + } if s.Port != 0 { return lsp.RunServerOnPort(ctx, s.Port, logger) } stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout) return lsp.RunServer(ctx, stream, logger) } + + +func (s *Serve) forward() error { + conn, err := net.Dial("tcp", s.app.Remote) + if err != nil { + return err + } + errc := make(chan error) + + go func(conn net.Conn) { + _, err := io.Copy(conn, os.Stdin) + errc <- err + }(conn) + + go func(conn net.Conn) { + _, err := io.Copy(os.Stdout, conn) + errc <- err + }(conn) + + return <-errc +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index b70fec61..4a1d8b3d 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -31,8 +31,14 @@ func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) // RunServerOnPort starts an LSP server on the given port and does not exit. // This function exists for debugging purposes. func RunServerOnPort(ctx context.Context, port int, opts ...interface{}) error { + return RunServerOnAddress(ctx, fmt.Sprintf(":%v", port)) +} + +// RunServerOnPort starts an LSP server on the given port and does not exit. +// This function exists for debugging purposes. +func RunServerOnAddress(ctx context.Context, addr string, opts ...interface{}) error { s := &server{} - ln, err := net.Listen("tcp", fmt.Sprintf(":%v", port)) + ln, err := net.Listen("tcp", addr) if err != nil { return err }