Skip to content

Commit a072e66

Browse files
committed
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 <[email protected]>
1 parent 71d3d86 commit a072e66

File tree

3 files changed

+194
-139
lines changed

3 files changed

+194
-139
lines changed

cmd/golsp/main.go

+3-139
Original file line numberDiff line numberDiff line change
@@ -10,148 +10,12 @@ package main // import "golang.org/x/tools/cmd/golsp"
1010

1111
import (
1212
"context"
13-
"encoding/json"
14-
"flag"
15-
"fmt"
16-
"io"
17-
"log"
1813
"os"
19-
"path/filepath"
20-
"runtime"
21-
"runtime/pprof"
22-
"runtime/trace"
23-
"strings"
24-
"time"
2514

26-
"golang.org/x/tools/internal/jsonrpc2"
27-
"golang.org/x/tools/internal/lsp"
28-
)
29-
30-
var (
31-
cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file")
32-
memprofile = flag.String("memprofile", "", "write memory profile to this file")
33-
traceFlag = flag.String("trace", "", "write trace log to this file")
34-
logfile = flag.String("logfile", "", "filename to log to. if value is \"auto\", then logging to a default output file is enabled")
35-
36-
// Flags for compatitibility with VSCode.
37-
mode = flag.String("mode", "", "no effect")
15+
"golang.org/x/tools/internal/lsp/cmd"
16+
"golang.org/x/tools/internal/tool"
3817
)
3918

4019
func main() {
41-
flag.Usage = func() {
42-
fmt.Fprintf(os.Stderr, "usage: golsp [flags]\n")
43-
flag.PrintDefaults()
44-
}
45-
flag.Parse()
46-
if flag.NArg() > 0 {
47-
flag.Usage()
48-
os.Exit(2)
49-
}
50-
51-
if *cpuprofile != "" {
52-
f, err := os.Create(*cpuprofile)
53-
if err != nil {
54-
log.Fatal(err)
55-
}
56-
if err := pprof.StartCPUProfile(f); err != nil {
57-
log.Fatal(err)
58-
}
59-
// NB: profile won't be written in case of error.
60-
defer pprof.StopCPUProfile()
61-
}
62-
63-
if *traceFlag != "" {
64-
f, err := os.Create(*traceFlag)
65-
if err != nil {
66-
log.Fatal(err)
67-
}
68-
if err := trace.Start(f); err != nil {
69-
log.Fatal(err)
70-
}
71-
// NB: trace log won't be written in case of error.
72-
defer func() {
73-
trace.Stop()
74-
log.Printf("To view the trace, run:\n$ go tool trace view %s", *traceFlag)
75-
}()
76-
}
77-
78-
if *memprofile != "" {
79-
f, err := os.Create(*memprofile)
80-
if err != nil {
81-
log.Fatal(err)
82-
}
83-
// NB: memprofile won't be written in case of error.
84-
defer func() {
85-
runtime.GC() // get up-to-date statistics
86-
if err := pprof.WriteHeapProfile(f); err != nil {
87-
log.Fatalf("Writing memory profile: %v", err)
88-
}
89-
f.Close()
90-
}()
91-
}
92-
93-
out := os.Stderr
94-
if *logfile != "" {
95-
filename := *logfile
96-
if filename == "auto" {
97-
filename = filepath.Join(os.TempDir(), fmt.Sprintf("golsp-%d.log", os.Getpid()))
98-
}
99-
f, err := os.Create(filename)
100-
if err != nil {
101-
log.Fatalf("Unable to create log file: %v", err)
102-
}
103-
defer f.Close()
104-
log.SetOutput(io.MultiWriter(os.Stderr, f))
105-
out = f
106-
}
107-
if err := lsp.RunServer(
108-
context.Background(),
109-
jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout),
110-
func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
111-
112-
const eol = "\r\n\r\n\r\n"
113-
if err != nil {
114-
fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
115-
direction, method, id, err, eol)
116-
return
117-
}
118-
outx := new(strings.Builder)
119-
fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
120-
switch direction {
121-
case jsonrpc2.Send:
122-
fmt.Fprint(outx, "Received ")
123-
case jsonrpc2.Receive:
124-
fmt.Fprint(outx, "Sending ")
125-
}
126-
switch {
127-
case id == nil:
128-
fmt.Fprint(outx, "notification ")
129-
case elapsed >= 0:
130-
fmt.Fprint(outx, "response ")
131-
default:
132-
fmt.Fprint(outx, "request ")
133-
}
134-
fmt.Fprintf(outx, "'%s", method)
135-
switch {
136-
case id == nil:
137-
// do nothing
138-
case id.Name != "":
139-
fmt.Fprintf(outx, " - (%s)", id.Name)
140-
default:
141-
fmt.Fprintf(outx, " - (%d)", id.Number)
142-
}
143-
fmt.Fprint(outx, "'")
144-
if elapsed >= 0 {
145-
fmt.Fprintf(outx, " in %vms", elapsed.Nanoseconds()/1000)
146-
}
147-
params := string(*payload)
148-
if params == "null" {
149-
params = "{}"
150-
}
151-
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
152-
fmt.Fprintf(out, "%s", outx.String())
153-
},
154-
); err != nil {
155-
log.Fatal(err)
156-
}
20+
tool.Main(context.Background(), &cmd.Application{}, os.Args[1:])
15721
}

internal/lsp/cmd/cmd.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package cmd handles the golsp command line.
6+
// It contains a handler for each of the modes, along with all the flag handling
7+
// and the command line output format.
8+
package cmd
9+
10+
import (
11+
"context"
12+
"flag"
13+
"fmt"
14+
"golang.org/x/tools/internal/tool"
15+
)
16+
17+
// Application is the main application as passed to tool.Main
18+
// It handles the main command line parsing and dispatch to the sub commands.
19+
type Application struct {
20+
// Embed the basic profiling flags supported by the tool package
21+
tool.Profile
22+
23+
// we also include the server directly for now, so the flags work even without
24+
// the verb. We should remove this when we stop allowing the server verb by
25+
// default
26+
Server server
27+
}
28+
29+
// Name implements tool.Application returning the binary name.
30+
func (app *Application) Name() string { return "golsp" }
31+
32+
// Usage implements tool.Application returning empty extra argument usage.
33+
func (app *Application) Usage() string { return "<mode> [mode-flags] [mode-args]" }
34+
35+
// ShortHelp implements tool.Application returning the main binary help.
36+
func (app *Application) ShortHelp() string {
37+
return "The Go Language Smartness Provider."
38+
}
39+
40+
// DetailedHelp implements tool.Application returning the main binary help.
41+
// This includes the short help for all the sub commands.
42+
func (app *Application) DetailedHelp(f *flag.FlagSet) {
43+
fmt.Fprint(f.Output(), `
44+
Available modes are:
45+
`)
46+
for _, c := range app.modes() {
47+
fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
48+
}
49+
fmt.Fprint(f.Output(), `
50+
golsp flags are:
51+
`)
52+
f.PrintDefaults()
53+
}
54+
55+
// Run takes the args after top level flag processing, and invokes the correct
56+
// sub command as specified by the first argument.
57+
// If no arguments are passed it will invoke the server sub command, as a
58+
// temporary measure for compatability.
59+
func (app *Application) Run(ctx context.Context, args ...string) error {
60+
if len(args) == 0 {
61+
tool.Main(ctx, &app.Server, args)
62+
return nil
63+
}
64+
mode, args := args[0], args[1:]
65+
for _, m := range app.modes() {
66+
if m.Name() == mode {
67+
tool.Main(ctx, m, args)
68+
return nil
69+
}
70+
}
71+
return tool.CommandLineErrorf("Unknown mode %v", mode)
72+
}
73+
74+
// modes returns the set of command modes supported by the golsp tool on the
75+
// command line.
76+
// The mode is specified by the first non flag argument.
77+
func (app *Application) modes() []tool.Application {
78+
return []tool.Application{
79+
&app.Server,
80+
}
81+
}

internal/lsp/cmd/server.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cmd
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"flag"
11+
"fmt"
12+
"io"
13+
"log"
14+
"os"
15+
"path/filepath"
16+
"strings"
17+
"time"
18+
19+
"golang.org/x/tools/internal/jsonrpc2"
20+
"golang.org/x/tools/internal/lsp"
21+
"golang.org/x/tools/internal/tool"
22+
)
23+
24+
// server is a struct that exposes the configurable parts of the LSP server as
25+
// flags, in the right form for tool.Main to consume.
26+
type server struct {
27+
Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
28+
Mode string `flag:"mode" help:"no effect"`
29+
}
30+
31+
func (s *server) Name() string { return "server" }
32+
func (s *server) Usage() string { return "" }
33+
func (s *server) ShortHelp() string {
34+
return "run a server for Go code using the Language Server Protocol"
35+
}
36+
func (s *server) DetailedHelp(f *flag.FlagSet) {
37+
fmt.Fprint(f.Output(), `
38+
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
39+
a child of an editor process.
40+
`)
41+
}
42+
43+
// Run configures a server based on the flags, and then runs it.
44+
// It blocks until the server shuts down.
45+
func (s *server) Run(ctx context.Context, args ...string) error {
46+
if len(args) > 0 {
47+
return tool.CommandLineErrorf("server does not take arguments, got %v", args)
48+
}
49+
out := os.Stderr
50+
if s.Logfile != "" {
51+
filename := s.Logfile
52+
if filename == "auto" {
53+
filename = filepath.Join(os.TempDir(), fmt.Sprintf("golsp-%d.log", os.Getpid()))
54+
}
55+
f, err := os.Create(filename)
56+
if err != nil {
57+
return fmt.Errorf("Unable to create log file: %v", err)
58+
}
59+
defer f.Close()
60+
log.SetOutput(io.MultiWriter(os.Stderr, f))
61+
out = f
62+
}
63+
return lsp.RunServer(
64+
ctx,
65+
jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout),
66+
func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
67+
const eol = "\r\n\r\n\r\n"
68+
if err != nil {
69+
fmt.Fprintf(out, "[Error - %v] %s %s%s %v%s", time.Now().Format("3:04:05 PM"),
70+
direction, method, id, err, eol)
71+
return
72+
}
73+
outx := new(strings.Builder)
74+
fmt.Fprintf(outx, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
75+
switch direction {
76+
case jsonrpc2.Send:
77+
fmt.Fprint(outx, "Received ")
78+
case jsonrpc2.Receive:
79+
fmt.Fprint(outx, "Sending ")
80+
}
81+
switch {
82+
case id == nil:
83+
fmt.Fprint(outx, "notification ")
84+
case elapsed >= 0:
85+
fmt.Fprint(outx, "response ")
86+
default:
87+
fmt.Fprint(outx, "request ")
88+
}
89+
fmt.Fprintf(outx, "'%s", method)
90+
switch {
91+
case id == nil:
92+
// do nothing
93+
case id.Name != "":
94+
fmt.Fprintf(outx, " - (%s)", id.Name)
95+
default:
96+
fmt.Fprintf(outx, " - (%d)", id.Number)
97+
}
98+
fmt.Fprint(outx, "'")
99+
if elapsed >= 0 {
100+
fmt.Fprintf(outx, " in %vms", elapsed.Nanoseconds()/1000)
101+
}
102+
params := string(*payload)
103+
if params == "null" {
104+
params = "{}"
105+
}
106+
fmt.Fprintf(outx, ".\r\nParams: %s%s", params, eol)
107+
fmt.Fprintf(out, "%s", outx.String())
108+
},
109+
)
110+
}

0 commit comments

Comments
 (0)