github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/cmd/rename.go (about)

     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  
     5  package cmd
     6  
     7  import (
     8  	"context"
     9  	"flag"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"sort"
    15  
    16  	"github.com/powerman/golang-tools/internal/lsp/diff"
    17  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    18  	"github.com/powerman/golang-tools/internal/lsp/source"
    19  	"github.com/powerman/golang-tools/internal/span"
    20  	"github.com/powerman/golang-tools/internal/tool"
    21  	errors "golang.org/x/xerrors"
    22  )
    23  
    24  // rename implements the rename verb for gopls.
    25  type rename struct {
    26  	Diff     bool `flag:"d,diff" help:"display diffs instead of rewriting files"`
    27  	Write    bool `flag:"w,write" help:"write result to (source) file instead of stdout"`
    28  	Preserve bool `flag:"preserve" help:"preserve original files"`
    29  
    30  	app *Application
    31  }
    32  
    33  func (r *rename) Name() string      { return "rename" }
    34  func (r *rename) Parent() string    { return r.app.Name() }
    35  func (r *rename) Usage() string     { return "[rename-flags] <position> <name>" }
    36  func (r *rename) ShortHelp() string { return "rename selected identifier" }
    37  func (r *rename) DetailedHelp(f *flag.FlagSet) {
    38  	fmt.Fprint(f.Output(), `
    39  Example:
    40  
    41  	$ # 1-based location (:line:column or :#position) of the thing to change
    42  	$ gopls rename helper/helper.go:8:6 Foo
    43  	$ gopls rename helper/helper.go:#53 Foo
    44  
    45  rename-flags:
    46  `)
    47  	printFlagDefaults(f)
    48  }
    49  
    50  // Run renames the specified identifier and either;
    51  // - if -w is specified, updates the file(s) in place;
    52  // - if -d is specified, prints out unified diffs of the changes; or
    53  // - otherwise, prints the new versions to stdout.
    54  func (r *rename) Run(ctx context.Context, args ...string) error {
    55  	if len(args) != 2 {
    56  		return tool.CommandLineErrorf("definition expects 2 arguments (position, new name)")
    57  	}
    58  	conn, err := r.app.connect(ctx)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	defer conn.terminate(ctx)
    63  
    64  	from := span.Parse(args[0])
    65  	file := conn.AddFile(ctx, from.URI())
    66  	if file.err != nil {
    67  		return file.err
    68  	}
    69  	loc, err := file.mapper.Location(from)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	p := protocol.RenameParams{
    74  		TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
    75  		Position:     loc.Range.Start,
    76  		NewName:      args[1],
    77  	}
    78  	edit, err := conn.Rename(ctx, &p)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	var orderedURIs []string
    83  	edits := map[span.URI][]protocol.TextEdit{}
    84  	for _, c := range edit.DocumentChanges {
    85  		uri := fileURI(c.TextDocument.URI)
    86  		edits[uri] = append(edits[uri], c.Edits...)
    87  		orderedURIs = append(orderedURIs, string(uri))
    88  	}
    89  	sort.Strings(orderedURIs)
    90  	changeCount := len(orderedURIs)
    91  
    92  	for _, u := range orderedURIs {
    93  		uri := span.URIFromURI(u)
    94  		cmdFile := conn.AddFile(ctx, uri)
    95  		filename := cmdFile.uri.Filename()
    96  
    97  		// convert LSP-style edits to []diff.TextEdit cuz Spans are handy
    98  		renameEdits, err := source.FromProtocolEdits(cmdFile.mapper, edits[uri])
    99  		if err != nil {
   100  			return errors.Errorf("%v: %v", edits, err)
   101  		}
   102  		newContent := diff.ApplyEdits(string(cmdFile.mapper.Content), renameEdits)
   103  
   104  		switch {
   105  		case r.Write:
   106  			fmt.Fprintln(os.Stderr, filename)
   107  			if r.Preserve {
   108  				if err := os.Rename(filename, filename+".orig"); err != nil {
   109  					return errors.Errorf("%v: %v", edits, err)
   110  				}
   111  			}
   112  			ioutil.WriteFile(filename, []byte(newContent), 0644)
   113  		case r.Diff:
   114  			diffs := diff.ToUnified(filename+".orig", filename, string(cmdFile.mapper.Content), renameEdits)
   115  			fmt.Print(diffs)
   116  		default:
   117  			if len(orderedURIs) > 1 {
   118  				fmt.Printf("%s:\n", filepath.Base(filename))
   119  			}
   120  			fmt.Print(string(newContent))
   121  			if changeCount > 1 { // if this wasn't last change, print newline
   122  				fmt.Println()
   123  			}
   124  			changeCount -= 1
   125  		}
   126  	}
   127  	return nil
   128  }