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