mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
internal/lsp: refactor the command line handling
This switched the golsp binary to support a sub-command model so it can grow some guru like command line query capabilites Change-Id: I1a7a49bb17701e62004bba636d6bee9de2481ffd Reviewed-on: https://go-review.googlesource.com/c/154559 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
71d3d868db
commit
a072e66104
@ -10,148 +10,12 @@ package main // import "golang.org/x/tools/cmd/golsp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/internal/jsonrpc2"
|
||||
"golang.org/x/tools/internal/lsp"
|
||||
)
|
||||
|
||||
var (
|
||||
cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file")
|
||||
memprofile = flag.String("memprofile", "", "write memory profile to this file")
|
||||
traceFlag = flag.String("trace", "", "write trace log to this file")
|
||||
logfile = flag.String("logfile", "", "filename to log to. if value is \"auto\", then logging to a default output file is enabled")
|
||||
|
||||
// Flags for compatitibility with VSCode.
|
||||
mode = flag.String("mode", "", "no effect")
|
||||
"golang.org/x/tools/internal/lsp/cmd"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "usage: golsp [flags]\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() > 0 {
|
||||
flag.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if *cpuprofile != "" {
|
||||
f, err := os.Create(*cpuprofile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// NB: profile won't be written in case of error.
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
if *traceFlag != "" {
|
||||
f, err := os.Create(*traceFlag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := trace.Start(f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// NB: trace log won't be written in case of error.
|
||||
defer func() {
|
||||
trace.Stop()
|
||||
log.Printf("To view the trace, run:\n$ go tool trace view %s", *traceFlag)
|
||||
}()
|
||||
}
|
||||
|
||||
if *memprofile != "" {
|
||||
f, err := os.Create(*memprofile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// NB: memprofile won't be written in case of error.
|
||||
defer func() {
|
||||
runtime.GC() // get up-to-date statistics
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
log.Fatalf("Writing memory profile: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
out := os.Stderr
|
||||
if *logfile != "" {
|
||||
filename := *logfile
|
||||
if filename == "auto" {
|
||||
filename = filepath.Join(os.TempDir(), fmt.Sprintf("golsp-%d.log", os.Getpid()))
|
||||
}
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create log file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
log.SetOutput(io.MultiWriter(os.Stderr, f))
|
||||
out = f
|
||||
}
|
||||
if err := lsp.RunServer(
|
||||
context.Background(),
|
||||
jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout),
|
||||
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"),
|
||||
direction, method, id, err, eol)
|
||||
return
|
||||
}
|
||||
outx := new(strings.Builder)
|
||||
fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
|
||||
switch direction {
|
||||
case jsonrpc2.Send:
|
||||
fmt.Fprint(outx, "Received ")
|
||||
case jsonrpc2.Receive:
|
||||
fmt.Fprint(outx, "Sending ")
|
||||
}
|
||||
switch {
|
||||
case id == nil:
|
||||
fmt.Fprint(outx, "notification ")
|
||||
case elapsed >= 0:
|
||||
fmt.Fprint(outx, "response ")
|
||||
default:
|
||||
fmt.Fprint(outx, "request ")
|
||||
}
|
||||
fmt.Fprintf(outx, "'%s", method)
|
||||
switch {
|
||||
case id == nil:
|
||||
// do nothing
|
||||
case id.Name != "":
|
||||
fmt.Fprintf(outx, " - (%s)", id.Name)
|
||||
default:
|
||||
fmt.Fprintf(outx, " - (%d)", id.Number)
|
||||
}
|
||||
fmt.Fprint(outx, "'")
|
||||
if elapsed >= 0 {
|
||||
fmt.Fprintf(outx, " in %vms", elapsed.Nanoseconds()/1000)
|
||||
}
|
||||
params := string(*payload)
|
||||
if params == "null" {
|
||||
params = "{}"
|
||||
}
|
||||
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
|
||||
fmt.Fprintf(out, "%s", outx.String())
|
||||
},
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tool.Main(context.Background(), &cmd.Application{}, os.Args[1:])
|
||||
}
|
||||
|
81
internal/lsp/cmd/cmd.go
Normal file
81
internal/lsp/cmd/cmd.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cmd handles the golsp command line.
|
||||
// It contains a handler for each of the modes, along with all the flag handling
|
||||
// and the command line output format.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
// Application is the main application as passed to tool.Main
|
||||
// It handles the main command line parsing and dispatch to the sub commands.
|
||||
type Application struct {
|
||||
// Embed the basic profiling flags supported by the tool package
|
||||
tool.Profile
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Name implements tool.Application returning the binary name.
|
||||
func (app *Application) Name() string { return "golsp" }
|
||||
|
||||
// Usage implements tool.Application returning empty extra argument usage.
|
||||
func (app *Application) Usage() string { return "<mode> [mode-flags] [mode-args]" }
|
||||
|
||||
// ShortHelp implements tool.Application returning the main binary help.
|
||||
func (app *Application) ShortHelp() string {
|
||||
return "The Go Language Smartness Provider."
|
||||
}
|
||||
|
||||
// DetailedHelp implements tool.Application returning the main binary help.
|
||||
// This includes the short help for all the sub commands.
|
||||
func (app *Application) DetailedHelp(f *flag.FlagSet) {
|
||||
fmt.Fprint(f.Output(), `
|
||||
Available modes are:
|
||||
`)
|
||||
for _, c := range app.modes() {
|
||||
fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
|
||||
}
|
||||
fmt.Fprint(f.Output(), `
|
||||
golsp flags are:
|
||||
`)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
// Run takes the args after top level flag processing, and invokes the correct
|
||||
// sub command as specified by the first argument.
|
||||
// If no arguments are passed it will invoke the server sub command, as a
|
||||
// temporary measure for compatability.
|
||||
func (app *Application) Run(ctx context.Context, args ...string) error {
|
||||
if len(args) == 0 {
|
||||
tool.Main(ctx, &app.Server, args)
|
||||
return nil
|
||||
}
|
||||
mode, args := args[0], args[1:]
|
||||
for _, m := range app.modes() {
|
||||
if m.Name() == mode {
|
||||
tool.Main(ctx, m, args)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return tool.CommandLineErrorf("Unknown mode %v", mode)
|
||||
}
|
||||
|
||||
// modes returns the set of command modes supported by the golsp tool on the
|
||||
// command line.
|
||||
// The mode is specified by the first non flag argument.
|
||||
func (app *Application) modes() []tool.Application {
|
||||
return []tool.Application{
|
||||
&app.Server,
|
||||
}
|
||||
}
|
110
internal/lsp/cmd/server.go
Normal file
110
internal/lsp/cmd/server.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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
|
||||
// flags, in the right form for tool.Main to consume.
|
||||
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"`
|
||||
}
|
||||
|
||||
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) {
|
||||
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.
|
||||
`)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if len(args) > 0 {
|
||||
return tool.CommandLineErrorf("server does not take arguments, got %v", args)
|
||||
}
|
||||
out := os.Stderr
|
||||
if s.Logfile != "" {
|
||||
filename := s.Logfile
|
||||
if filename == "auto" {
|
||||
filename = filepath.Join(os.TempDir(), fmt.Sprintf("golsp-%d.log", os.Getpid()))
|
||||
}
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create log file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
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) {
|
||||
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"),
|
||||
direction, method, id, err, eol)
|
||||
return
|
||||
}
|
||||
outx := new(strings.Builder)
|
||||
fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
|
||||
switch direction {
|
||||
case jsonrpc2.Send:
|
||||
fmt.Fprint(outx, "Received ")
|
||||
case jsonrpc2.Receive:
|
||||
fmt.Fprint(outx, "Sending ")
|
||||
}
|
||||
switch {
|
||||
case id == nil:
|
||||
fmt.Fprint(outx, "notification ")
|
||||
case elapsed >= 0:
|
||||
fmt.Fprint(outx, "response ")
|
||||
default:
|
||||
fmt.Fprint(outx, "request ")
|
||||
}
|
||||
fmt.Fprintf(outx, "'%s", method)
|
||||
switch {
|
||||
case id == nil:
|
||||
// do nothing
|
||||
case id.Name != "":
|
||||
fmt.Fprintf(outx, " - (%s)", id.Name)
|
||||
default:
|
||||
fmt.Fprintf(outx, " - (%d)", id.Number)
|
||||
}
|
||||
fmt.Fprint(outx, "'")
|
||||
if elapsed >= 0 {
|
||||
fmt.Fprintf(outx, " in %vms", elapsed.Nanoseconds()/1000)
|
||||
}
|
||||
params := string(*payload)
|
||||
if params == "null" {
|
||||
params = "{}"
|
||||
}
|
||||
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
|
||||
fmt.Fprintf(out, "%s", outx.String())
|
||||
},
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user