mirror of
https://github.com/golang/go.git
synced 2025-05-06 08:03:03 +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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
"runtime/trace"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/tools/internal/jsonrpc2"
|
"golang.org/x/tools/internal/lsp/cmd"
|
||||||
"golang.org/x/tools/internal/lsp"
|
"golang.org/x/tools/internal/tool"
|
||||||
)
|
|
||||||
|
|
||||||
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")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() {
|
tool.Main(context.Background(), &cmd.Application{}, os.Args[1:])
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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