1// Copyright 2019 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
5package cmd
6
7import (
8	"context"
9	"flag"
10	"fmt"
11	"io/ioutil"
12
13	"golang.org/x/tools/internal/lsp/diff"
14	"golang.org/x/tools/internal/lsp/protocol"
15	"golang.org/x/tools/internal/lsp/source"
16	"golang.org/x/tools/internal/span"
17	errors "golang.org/x/xerrors"
18)
19
20// format implements the format verb for gopls.
21type format struct {
22	Diff  bool `flag:"d" help:"display diffs instead of rewriting files"`
23	Write bool `flag:"w" help:"write result to (source) file instead of stdout"`
24	List  bool `flag:"l" help:"list files whose formatting differs from gofmt's"`
25
26	app *Application
27}
28
29func (c *format) Name() string      { return "format" }
30func (c *format) Usage() string     { return "<filerange>" }
31func (c *format) ShortHelp() string { return "format the code according to the go standard" }
32func (c *format) DetailedHelp(f *flag.FlagSet) {
33	fmt.Fprint(f.Output(), `
34The arguments supplied may be simple file names, or ranges within files.
35
36Example: reformat this file:
37
38  $ gopls format -w internal/lsp/cmd/check.go
39
40	gopls format flags are:
41`)
42	f.PrintDefaults()
43}
44
45// Run performs the check on the files specified by args and prints the
46// results to stdout.
47func (c *format) Run(ctx context.Context, args ...string) error {
48	if len(args) == 0 {
49		// no files, so no results
50		return nil
51	}
52	// now we ready to kick things off
53	conn, err := c.app.connect(ctx)
54	if err != nil {
55		return err
56	}
57	defer conn.terminate(ctx)
58	for _, arg := range args {
59		spn := span.Parse(arg)
60		file := conn.AddFile(ctx, spn.URI())
61		if file.err != nil {
62			return file.err
63		}
64		filename := spn.URI().Filename()
65		loc, err := file.mapper.Location(spn)
66		if err != nil {
67			return err
68		}
69		if loc.Range.Start != loc.Range.End {
70			return errors.Errorf("only full file formatting supported")
71		}
72		p := protocol.DocumentFormattingParams{
73			TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
74		}
75		edits, err := conn.Formatting(ctx, &p)
76		if err != nil {
77			return errors.Errorf("%v: %v", spn, err)
78		}
79		sedits, err := source.FromProtocolEdits(file.mapper, edits)
80		if err != nil {
81			return errors.Errorf("%v: %v", spn, err)
82		}
83		formatted := diff.ApplyEdits(string(file.mapper.Content), sedits)
84		printIt := true
85		if c.List {
86			printIt = false
87			if len(edits) > 0 {
88				fmt.Println(filename)
89			}
90		}
91		if c.Write {
92			printIt = false
93			if len(edits) > 0 {
94				ioutil.WriteFile(filename, []byte(formatted), 0644)
95			}
96		}
97		if c.Diff {
98			printIt = false
99			u := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits)
100			fmt.Print(u)
101		}
102		if printIt {
103			fmt.Print(formatted)
104		}
105	}
106	return nil
107}
108