internal/tool: implement structured help command
'gopls help cmd...' is redirected to 'gopls cmd... -h'.
The tool.Run operation for each subcommand already has
logic to show the usage message when it parses a -h flag.
Examples:
$ gopls help
$ gopls help remote
$ gopls help remote sessions
Fixes golang/go#52598
Change-Id: I5135c6e7b130d839f880d0613534ac08de199bcf
Reviewed-on: https://go-review.googlesource.com/c/tools/+/403677
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 06e1d5f..64b0703 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -245,6 +245,7 @@
&app.Serve,
&version{app: app},
&bug{app: app},
+ &help{app: app},
&apiJSON{app: app},
&licenses{app: app},
}
diff --git a/internal/lsp/cmd/info.go b/internal/lsp/cmd/info.go
index 09f453e..8e581a3 100644
--- a/internal/lsp/cmd/info.go
+++ b/internal/lsp/cmd/info.go
@@ -17,8 +17,57 @@
"golang.org/x/tools/internal/lsp/browser"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/source"
+ "golang.org/x/tools/internal/tool"
)
+// help implements the help command.
+type help struct {
+ app *Application
+}
+
+func (h *help) Name() string { return "help" }
+func (h *help) Parent() string { return h.app.Name() }
+func (h *help) Usage() string { return "" }
+func (h *help) ShortHelp() string { return "print usage information for subcommands" }
+func (h *help) DetailedHelp(f *flag.FlagSet) {
+ fmt.Fprint(f.Output(), `
+
+Examples:
+$ gopls help # main gopls help message
+$ gopls help remote # help on 'remote' command
+$ gopls help remote sessions # help on 'remote sessions' subcommand
+`)
+ printFlagDefaults(f)
+}
+
+// Run prints help information about a subcommand.
+func (h *help) Run(ctx context.Context, args ...string) error {
+ find := func(cmds []tool.Application, name string) tool.Application {
+ for _, cmd := range cmds {
+ if cmd.Name() == name {
+ return cmd
+ }
+ }
+ return nil
+ }
+
+ // Find the subcommand denoted by args (empty => h.app).
+ var cmd tool.Application = h.app
+ for i, arg := range args {
+ cmd = find(getSubcommands(cmd), arg)
+ if cmd == nil {
+ return tool.CommandLineErrorf(
+ "no such subcommand: %s", strings.Join(args[:i+1], " "))
+ }
+ }
+
+ // 'gopls help cmd subcmd' is equivalent to 'gopls cmd subcmd -h'.
+ // The flag package prints the usage information (defined by tool.Run)
+ // when it sees the -h flag.
+ fs := flag.NewFlagSet(cmd.Name(), flag.ExitOnError)
+ return tool.Run(ctx, fs, h.app, append(args[:len(args):len(args)], "-h"))
+}
+
// version implements the version command.
type version struct {
JSON bool `flag:"json" help:"outputs in json format."`
diff --git a/internal/lsp/cmd/subcommands.go b/internal/lsp/cmd/subcommands.go
index deac5c8..e30c42b 100644
--- a/internal/lsp/cmd/subcommands.go
+++ b/internal/lsp/cmd/subcommands.go
@@ -42,3 +42,18 @@
}
return tool.CommandLineErrorf("unknown subcommand %v", command)
}
+
+func (s subcommands) Commands() []tool.Application { return s }
+
+// getSubcommands returns the subcommands of a given Application.
+func getSubcommands(a tool.Application) []tool.Application {
+ // This interface is satisfied both by tool.Applications
+ // that embed subcommands, and by *cmd.Application.
+ type hasCommands interface {
+ Commands() []tool.Application
+ }
+ if sub, ok := a.(hasCommands); ok {
+ return sub.Commands()
+ }
+ return nil
+}
diff --git a/internal/lsp/cmd/usage/help.hlp b/internal/lsp/cmd/usage/help.hlp
new file mode 100644
index 0000000..f0ff44a
--- /dev/null
+++ b/internal/lsp/cmd/usage/help.hlp
@@ -0,0 +1,10 @@
+print usage information for subcommands
+
+Usage:
+ gopls [flags] help
+
+
+Examples:
+$ gopls help # main gopls help message
+$ gopls help remote # help on 'remote' command
+$ gopls help remote sessions # help on 'remote sessions' subcommand
diff --git a/internal/lsp/cmd/usage/usage.hlp b/internal/lsp/cmd/usage/usage.hlp
index 1d0fb8d..eaa05c5 100644
--- a/internal/lsp/cmd/usage/usage.hlp
+++ b/internal/lsp/cmd/usage/usage.hlp
@@ -14,6 +14,7 @@
serve run a server for Go code using the Language Server Protocol
version print the gopls version information
bug report a bug in gopls
+ help print usage information for subcommands
api-json print json describing gopls API
licenses print licenses of included software
diff --git a/internal/tool/tool.go b/internal/tool/tool.go
index 526b6b7..ec3a6bb 100644
--- a/internal/tool/tool.go
+++ b/internal/tool/tool.go
@@ -92,6 +92,10 @@
if err := Run(ctx, s, app, args); err != nil {
fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err)
if _, printHelp := err.(commandLineError); printHelp {
+ // TODO(adonovan): refine this. It causes
+ // any command-line error to result in the full
+ // usage message, which typically obscures
+ // the actual error.
s.Usage()
}
os.Exit(2)