1
0
mirror of https://github.com/golang/go synced 2024-09-30 16:28:32 -06:00

cmd/golsp: add a debugging tool to connect with golsp on a port

This change allows golsp to be run on a port, with an intermediary
command passing the data through. This allows for improved logging.
Also, add necessary changes to VSCode integration to allow changing the
name of the command for golsp.

Change-Id: I20dca1a50296636e57e022342ee70f0610ad1531
Reviewed-on: https://go-review.googlesource.com/c/157497
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-01-10 20:42:25 -05:00
parent 4b7be70d8a
commit fc1d57b08d
6 changed files with 150 additions and 57 deletions

59
cmd/golsp/forward/main.go Normal file
View File

@ -0,0 +1,59 @@
// The forward command writes and reads to a golsp 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 GoLSP." }
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 {
}
}

View File

@ -45,6 +45,12 @@
"default": [],
"description": "Flags to pass to golsp",
"scope": "resource"
},
"golsp.command": {
"type": "string",
"default": "golsp",
"description": "Name of the GoLSP binary",
"scope": "resource"
}
}
}

View File

@ -12,9 +12,10 @@ import path = require('path');
export function activate(ctx: vscode.ExtensionContext): void {
let document = vscode.window.activeTextEditor.document;
let config = vscode.workspace.getConfiguration('golsp', document.uri);
let golspCommand: string = config['command'];
let golspFlags: string[] = config['flags'];
let serverOptions:
lsp.ServerOptions = {command: getBinPath('golsp'), args: golspFlags};
lsp.ServerOptions = {command: getBinPath(golspCommand), args: golspFlags};
let clientOptions: lsp.LanguageClientOptions = {
initializationOptions: {},
documentSelector: ['go'],

View File

@ -23,7 +23,7 @@ type Application struct {
// we also include the server directly for now, so the flags work even without
// the verb. We should remove this when we stop allowing the server verb by
// default
Server server
Server Server
}
// Name implements tool.Application returning the binary name.

View File

@ -21,19 +21,20 @@ import (
"golang.org/x/tools/internal/tool"
)
// server is a struct that exposes the configurable parts of the LSP server as
// Server 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 Server 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 golsp for debugging purposes"`
}
func (s *server) Name() string { return "server" }
func (s *server) Usage() string { return "" }
func (s *server) ShortHelp() string {
func (s *Server) Name() string { return "server" }
func (s *Server) Usage() string { return "" }
func (s *Server) ShortHelp() string {
return "run a server for Go code using the Language Server Protocol"
}
func (s *server) DetailedHelp(f *flag.FlagSet) {
func (s *Server) 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.
@ -42,7 +43,7 @@ a child of an editor process.
// 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 *Server) Run(ctx context.Context, args ...string) error {
if len(args) > 0 {
return tool.CommandLineErrorf("server does not take arguments, got %v", args)
}
@ -60,10 +61,7 @@ func (s *server) Run(ctx context.Context, args ...string) error {
log.SetOutput(io.MultiWriter(os.Stderr, f))
out = f
}
return lsp.RunServer(
ctx,
jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout),
func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
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 {
fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
@ -106,6 +104,11 @@ func (s *server) Run(ctx context.Context, args ...string) error {
}
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
fmt.Fprintf(out, "%s", outx.String())
},
)
}
// For debugging purposes only.
if s.Port != 0 {
return lsp.RunServerOnPort(ctx, s.Port, logger)
}
stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
return lsp.RunServer(ctx, stream, logger)
}

View File

@ -6,7 +6,9 @@ package lsp
import (
"context"
"fmt"
"go/token"
"net"
"os"
"sync"
@ -26,6 +28,28 @@ func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{})
return conn.Wait(ctx)
}
// 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 {
s := &server{}
ln, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return err
}
for {
conn, err := ln.Accept()
if err != nil {
return err
}
stream := jsonrpc2.NewHeaderStream(conn, conn)
go func() {
conn, client := protocol.RunServer(ctx, stream, s, opts...)
s.client = client
conn.Wait(ctx)
}()
}
}
type server struct {
client protocol.Client