github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/build_langserver/langserver/rename.go (about) 1 package langserver 2 3 import ( 4 "context" 5 "core" 6 "encoding/json" 7 8 "github.com/sourcegraph/jsonrpc2" 9 10 "github.com/thought-machine/please/src/parse/asp" 11 "github.com/thought-machine/please/tools/build_langserver/lsp" 12 ) 13 14 const renameMethod = "textDocument/rename" 15 16 func (h *LsHandler) handleRename(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) { 17 if req.Params == nil { 18 return nil, nil 19 } 20 21 var params lsp.RenameParams 22 if err := json.Unmarshal(*req.Params, ¶ms); err != nil { 23 return nil, err 24 } 25 26 documentURI, err := getURIAndHandleErrors(params.TextDocument.URI, renameMethod) 27 if err != nil { 28 return nil, err 29 } 30 31 edits, err := h.getRenameEdits(ctx, params.NewName, documentURI, params.Position) 32 if err != nil { 33 log.Warning("error occurred trying to get the rename edits from %s", documentURI) 34 } 35 36 return edits, nil 37 } 38 39 func (h *LsHandler) getRenameEdits(ctx context.Context, newName string, 40 uri lsp.DocumentURI, pos lsp.Position) (*lsp.WorkspaceEdit, error) { 41 42 renamingLabel, err := h.getRenamingLabel(ctx, uri, pos) 43 if err != nil { 44 return nil, err 45 } 46 47 h.mu.Lock() 48 defer h.mu.Unlock() 49 50 doc, ok := h.workspace.documents[uri] 51 if !ok { 52 log.Info("document %s is not opened", uri) 53 return nil, nil 54 } 55 workSpaceEdit := &lsp.WorkspaceEdit{ 56 Changes: make(map[lsp.DocumentURI][]lsp.TextEdit), 57 } 58 // Fill in workSpaceEdit.Changes first 59 for _, label := range renamingLabel.RevDeps { 60 buildLabel, err := h.analyzer.BuildLabelFromCoreBuildLabel(ctx, label) 61 if err != nil { 62 // In the case of error, we still return the current available locs 63 return workSpaceEdit, nil 64 } 65 labelURI := lsp.DocumentURI("file://" + buildLabel.Path) 66 edits := getEditsFromLabel(buildLabel, renamingLabel, newName) 67 68 _, ok := workSpaceEdit.Changes[labelURI] 69 if ok { 70 workSpaceEdit.Changes[uri] = append(workSpaceEdit.Changes[uri], edits...) 71 } else { 72 workSpaceEdit.Changes[uri] = edits 73 } 74 } 75 76 for k, v := range workSpaceEdit.Changes { 77 docChange := lsp.TextDocumentEdit{ 78 TextDocument: lsp.VersionedTextDocumentIdentifier{ 79 TextDocumentIdentifier: &lsp.TextDocumentIdentifier{URI: k}, 80 Version: doc.version, 81 }, 82 Edits: v, 83 } 84 workSpaceEdit.DocumentChanges = append(workSpaceEdit.DocumentChanges, docChange) 85 } 86 87 return workSpaceEdit, nil 88 } 89 90 // getRenamingLabel returns a BuildLabel object with RevDeps being defined 91 func (h *LsHandler) getRenamingLabel(ctx context.Context, uri lsp.DocumentURI, pos lsp.Position) (*BuildLabel, error) { 92 93 h.mu.Lock() 94 defer h.mu.Unlock() 95 96 def, err := h.analyzer.BuildDefsFromPos(ctx, uri, pos) 97 if def == nil || err != nil || !isPosAtNameArg(def, pos) { 98 return nil, err 99 } 100 101 coreLabel, err := getCoreBuildLabel(def, uri) 102 if err != nil { 103 return nil, err 104 } 105 renamingLabel, err := h.analyzer.BuildLabelFromCoreBuildLabel(ctx, coreLabel) 106 if err != nil { 107 return nil, err 108 } 109 110 revDeps, err := h.analyzer.RevDepsFromCoreBuildLabel(coreLabel, uri) 111 if err != nil { 112 log.Info("error occurred computing the reverse dependency of %s: %s", coreLabel.String(), err) 113 return nil, err 114 } 115 116 renamingLabel.RevDeps = revDeps 117 118 return renamingLabel, nil 119 } 120 121 func getEditsFromLabel(depLabel *BuildLabel, renaminglabel *BuildLabel, newName string) (edits []lsp.TextEdit) { 122 if !isBuildDefValid(depLabel.BuildDef) { 123 return nil 124 } 125 126 // Get the label string based on whether it's relative 127 newLabelStr := core.NewBuildLabel(renaminglabel.PackageName, newName).String() 128 oldLabelStr := renaminglabel.String() 129 if renaminglabel.Path == depLabel.Path { 130 newLabelStr = ":" + newName 131 oldLabelStr = ":" + renaminglabel.Name 132 } 133 134 // Walk through the all the arguments from the call, and find string values that is the same as the old label string 135 callback := func(astStruct interface{}) interface{} { 136 if expr, ok := astStruct.(asp.Expression); ok && expr.Val != nil { 137 if expr.Val.String != "" && TrimQuotes(expr.Val.String) == oldLabelStr { 138 pos := aspPositionToLsp(expr.Pos) 139 // Add 1 to the character, as we would be changing from inside of the quotes 140 pos.Character = pos.Character + 1 141 edit := lsp.TextEdit{ 142 Range: getNameRange(pos, oldLabelStr), 143 NewText: newLabelStr, 144 } 145 edits = append(edits, edit) 146 } 147 } 148 149 return nil 150 } 151 152 asp.WalkAST(depLabel.BuildDef.Action.Call.Arguments, callback) 153 154 return edits 155 } 156 157 func isPosAtNameArg(def *BuildDef, pos lsp.Position) bool { 158 159 if !isBuildDefValid(def) { 160 return false 161 } 162 163 args := def.Action.Call.Arguments 164 165 for _, arg := range args { 166 if arg.Name == "name" && withInRange(arg.Value.Pos, arg.Value.EndPos, pos) { 167 return true 168 } 169 } 170 171 return false 172 } 173 174 func isBuildDefValid(def *BuildDef) bool { 175 if def.Action == nil || def.Action.Call == nil || 176 def.Action.Call.Arguments == nil || len(def.Action.Call.Arguments) == 0 { 177 178 return false 179 } 180 181 return true 182 }