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)