mirror of
https://github.com/golang/go
synced 2024-11-18 10:14:45 -07: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:
parent
4b7be70d8a
commit
fc1d57b08d
59
cmd/golsp/forward/main.go
Normal file
59
cmd/golsp/forward/main.go
Normal 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 {
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,12 @@
|
|||||||
"default": [],
|
"default": [],
|
||||||
"description": "Flags to pass to golsp",
|
"description": "Flags to pass to golsp",
|
||||||
"scope": "resource"
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"golsp.command": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "golsp",
|
||||||
|
"description": "Name of the GoLSP binary",
|
||||||
|
"scope": "resource"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,10 @@ import path = require('path');
|
|||||||
export function activate(ctx: vscode.ExtensionContext): void {
|
export function activate(ctx: vscode.ExtensionContext): void {
|
||||||
let document = vscode.window.activeTextEditor.document;
|
let document = vscode.window.activeTextEditor.document;
|
||||||
let config = vscode.workspace.getConfiguration('golsp', document.uri);
|
let config = vscode.workspace.getConfiguration('golsp', document.uri);
|
||||||
|
let golspCommand: string = config['command'];
|
||||||
let golspFlags: string[] = config['flags'];
|
let golspFlags: string[] = config['flags'];
|
||||||
let serverOptions:
|
let serverOptions:
|
||||||
lsp.ServerOptions = {command: getBinPath('golsp'), args: golspFlags};
|
lsp.ServerOptions = {command: getBinPath(golspCommand), args: golspFlags};
|
||||||
let clientOptions: lsp.LanguageClientOptions = {
|
let clientOptions: lsp.LanguageClientOptions = {
|
||||||
initializationOptions: {},
|
initializationOptions: {},
|
||||||
documentSelector: ['go'],
|
documentSelector: ['go'],
|
||||||
|
@ -23,7 +23,7 @@ type Application struct {
|
|||||||
// we also include the server directly for now, so the flags work even without
|
// 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
|
// the verb. We should remove this when we stop allowing the server verb by
|
||||||
// default
|
// default
|
||||||
Server server
|
Server Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements tool.Application returning the binary name.
|
// Name implements tool.Application returning the binary name.
|
||||||
|
@ -21,19 +21,20 @@ import (
|
|||||||
"golang.org/x/tools/internal/tool"
|
"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.
|
// 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"`
|
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"`
|
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) Name() string { return "server" }
|
||||||
func (s *server) Usage() string { return "" }
|
func (s *Server) Usage() string { return "" }
|
||||||
func (s *server) ShortHelp() string {
|
func (s *Server) ShortHelp() string {
|
||||||
return "run a server for Go code using the Language Server Protocol"
|
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(), `
|
fmt.Fprint(f.Output(), `
|
||||||
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
|
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
|
||||||
a child of an editor process.
|
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.
|
// Run configures a server based on the flags, and then runs it.
|
||||||
// It blocks until the server shuts down.
|
// 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 {
|
if len(args) > 0 {
|
||||||
return tool.CommandLineErrorf("server does not take arguments, got %v", args)
|
return tool.CommandLineErrorf("server does not take arguments, got %v", args)
|
||||||
}
|
}
|
||||||
@ -60,52 +61,54 @@ func (s *server) Run(ctx context.Context, args ...string) error {
|
|||||||
log.SetOutput(io.MultiWriter(os.Stderr, f))
|
log.SetOutput(io.MultiWriter(os.Stderr, f))
|
||||||
out = f
|
out = f
|
||||||
}
|
}
|
||||||
return lsp.RunServer(
|
logger := func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
|
||||||
ctx,
|
const eol = "\r\n\r\n\r\n"
|
||||||
jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout),
|
if err != nil {
|
||||||
func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
|
fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
|
||||||
const eol = "\r\n\r\n\r\n"
|
direction, method, id, err, eol)
|
||||||
if err != nil {
|
return
|
||||||
fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
|
}
|
||||||
direction, method, id, err, eol)
|
outx := new(strings.Builder)
|
||||||
return
|
fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
|
||||||
}
|
switch direction {
|
||||||
outx := new(strings.Builder)
|
case jsonrpc2.Send:
|
||||||
fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
|
fmt.Fprint(outx, "Received ")
|
||||||
switch direction {
|
case jsonrpc2.Receive:
|
||||||
case jsonrpc2.Send:
|
fmt.Fprint(outx, "Sending ")
|
||||||
fmt.Fprint(outx, "Received ")
|
}
|
||||||
case jsonrpc2.Receive:
|
switch {
|
||||||
fmt.Fprint(outx, "Sending ")
|
case id == nil:
|
||||||
}
|
fmt.Fprint(outx, "notification ")
|
||||||
switch {
|
case elapsed >= 0:
|
||||||
case id == nil:
|
fmt.Fprint(outx, "response ")
|
||||||
fmt.Fprint(outx, "notification ")
|
default:
|
||||||
case elapsed >= 0:
|
fmt.Fprint(outx, "request ")
|
||||||
fmt.Fprint(outx, "response ")
|
}
|
||||||
default:
|
fmt.Fprintf(outx, "'%s", method)
|
||||||
fmt.Fprint(outx, "request ")
|
switch {
|
||||||
}
|
case id == nil:
|
||||||
fmt.Fprintf(outx, "'%s", method)
|
// do nothing
|
||||||
switch {
|
case id.Name != "":
|
||||||
case id == nil:
|
fmt.Fprintf(outx, " - (%s)", id.Name)
|
||||||
// do nothing
|
default:
|
||||||
case id.Name != "":
|
fmt.Fprintf(outx, " - (%d)", id.Number)
|
||||||
fmt.Fprintf(outx, " - (%s)", id.Name)
|
}
|
||||||
default:
|
fmt.Fprint(outx, "'")
|
||||||
fmt.Fprintf(outx, " - (%d)", id.Number)
|
if elapsed >= 0 {
|
||||||
}
|
msec := int(elapsed.Round(time.Millisecond) / time.Millisecond)
|
||||||
fmt.Fprint(outx, "'")
|
fmt.Fprintf(outx, " in %dms", msec)
|
||||||
if elapsed >= 0 {
|
}
|
||||||
msec := int(elapsed.Round(time.Millisecond) / time.Millisecond)
|
params := string(*payload)
|
||||||
fmt.Fprintf(outx, " in %dms", msec)
|
if params == "null" {
|
||||||
}
|
params = "{}"
|
||||||
params := string(*payload)
|
}
|
||||||
if params == "null" {
|
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
|
||||||
params = "{}"
|
fmt.Fprintf(out, "%s", outx.String())
|
||||||
}
|
}
|
||||||
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
|
// For debugging purposes only.
|
||||||
fmt.Fprintf(out, "%s", outx.String())
|
if s.Port != 0 {
|
||||||
},
|
return lsp.RunServerOnPort(ctx, s.Port, logger)
|
||||||
)
|
}
|
||||||
|
stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
|
||||||
|
return lsp.RunServer(ctx, stream, logger)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@ package lsp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -26,6 +28,28 @@ func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{})
|
|||||||
return conn.Wait(ctx)
|
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 {
|
type server struct {
|
||||||
client protocol.Client
|
client protocol.Client
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user