internal/lsp: changing server noun to serve verb

Also adding in --remote support and using it to implement the equivalent
functionality of the external forward command
Also adding in --listen as a replacement for --port as it is more flexible,
specifically it allows localhost:port which
is helpful in environments where opening remotely accesible ports is
problematic.

Change-Id: I5de1cea7dd6f1ee46e7423f3be2a4caca6f040b2
Reviewed-on: https://go-review.googlesource.com/c/161658
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/cmd/gopls/forward/main.go b/cmd/gopls/forward/main.go
deleted file mode 100644
index c437679..0000000
--- 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=<value>]" }
-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 694f859..9defccd 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -25,12 +25,16 @@
 	// 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 @@
 // 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 @@
 // 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 72993e6..6701386 100644
--- a/internal/lsp/cmd/server.go
+++ b/internal/lsp/cmd/serve.go
@@ -15,26 +15,30 @@
 	"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 @@
 
 // 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 @@
 		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 @@
 		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 b70fec6..4a1d8b3 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -31,8 +31,14 @@
 // 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
 	}