File: execute.go

package info (click to toggle)
golang-golang-x-tools 1%3A0.25.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 22,724 kB
  • sloc: javascript: 2,027; asm: 1,645; sh: 166; yacc: 155; makefile: 49; ansic: 8
file content (135 lines) | stat: -rw-r--r-- 3,970 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2023 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"
	"log"
	"os"

	"golang.org/x/tools/gopls/internal/protocol"
	"golang.org/x/tools/gopls/internal/protocol/command"
	"golang.org/x/tools/gopls/internal/server"
	"golang.org/x/tools/gopls/internal/util/slices"
	"golang.org/x/tools/internal/tool"
)

// execute implements the LSP ExecuteCommand verb for gopls.
type execute struct {
	EditFlags
	app *Application
}

func (e *execute) Name() string      { return "execute" }
func (e *execute) Parent() string    { return e.app.Name() }
func (e *execute) Usage() string     { return "[flags] command argument..." }
func (e *execute) ShortHelp() string { return "Execute a gopls custom LSP command" }
func (e *execute) DetailedHelp(f *flag.FlagSet) {
	fmt.Fprint(f.Output(), `
The execute command sends an LSP ExecuteCommand request to gopls,
with a set of optional JSON argument values.
Some commands return a result, also JSON.

Gopls' command set is defined by the command.Interface type; see
https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol/command#Interface.
It is not a stable interface: commands may change or disappear without notice.

Examples:

	$ gopls execute gopls.add_import '{"ImportPath": "fmt", "URI": "file:///hello.go"}'
	$ gopls execute gopls.run_tests '{"URI": "file:///a_test.go", "Tests": ["Test"]}'
	$ gopls execute gopls.list_known_packages '{"URI": "file:///hello.go"}'

execute-flags:
`)
	printFlagDefaults(f)
}

func (e *execute) Run(ctx context.Context, args ...string) error {
	if len(args) == 0 {
		return tool.CommandLineErrorf("execute requires a command name")
	}
	cmd := args[0]
	if !slices.Contains(command.Commands, command.Command(cmd)) {
		return tool.CommandLineErrorf("unrecognized command: %s", cmd)
	}

	// A command may have multiple arguments, though the only one
	// that currently does so is the "legacy" gopls.test,
	// so we don't show an example of it.
	var jsonArgs []json.RawMessage
	for i, arg := range args[1:] {
		var dummy any
		if err := json.Unmarshal([]byte(arg), &dummy); err != nil {
			return fmt.Errorf("argument %d is not valid JSON: %v", i+1, err)
		}
		jsonArgs = append(jsonArgs, json.RawMessage(arg))
	}

	e.app.editFlags = &e.EditFlags // in case command performs an edit

	conn, err := e.app.connect(ctx)
	if err != nil {
		return err
	}
	defer conn.terminate(ctx)

	res, err := conn.executeCommand(ctx, &protocol.Command{
		Command:   cmd,
		Arguments: jsonArgs,
	})
	if err != nil {
		return err
	}
	if res != nil {
		data, err := json.MarshalIndent(res, "", "\t")
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", data)
	}
	return nil
}

// executeCommand executes a protocol.Command, displaying progress
// messages and awaiting completion of asynchronous commands.
func (conn *connection) executeCommand(ctx context.Context, cmd *protocol.Command) (any, error) {
	endStatus := make(chan string, 1)
	token, unregister := conn.client.registerProgressHandler(func(params *protocol.ProgressParams) {
		switch v := params.Value.(type) {
		case *protocol.WorkDoneProgressReport:
			fmt.Fprintln(os.Stderr, v.Message) // combined std{out,err}

		case *protocol.WorkDoneProgressEnd:
			endStatus <- v.Message // = canceled | failed | completed
		}
	})
	defer unregister()

	res, err := conn.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
		Command:   cmd.Command,
		Arguments: cmd.Arguments,
		WorkDoneProgressParams: protocol.WorkDoneProgressParams{
			WorkDoneToken: token,
		},
	})
	if err != nil {
		return nil, err
	}

	// Some commands are asynchronous, so clients
	// must wait for the "end" progress notification.
	if command.Command(cmd.Command).IsAsync() {
		status := <-endStatus
		if status != server.CommandCompleted {
			return nil, fmt.Errorf("command %s", status)
		}
	}

	return res, nil
}