github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/build_langserver/langserver/hover.go (about) 1 package langserver 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/thought-machine/please/src/parse/asp" 9 "github.com/thought-machine/please/tools/build_langserver/lsp" 10 11 "github.com/sourcegraph/jsonrpc2" 12 ) 13 14 const hoverMethod = "textDocument/hover" 15 16 func (h *LsHandler) handleHover(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) { 17 params, err := h.getParamFromTDPositionReq(req, hoverMethod) 18 if err != nil { 19 return nil, err 20 } 21 documentURI := params.TextDocument.URI 22 23 h.mu.Lock() 24 defer h.mu.Unlock() 25 content, err := h.getHoverContent(ctx, documentURI, params.Position) 26 27 if err != nil { 28 return nil, err 29 } 30 31 markedString := lsp.MarkedString{ 32 Language: "build", 33 Value: content, 34 } 35 markedStrings := []lsp.MarkedString{markedString} 36 37 log.Info("hover content: %s", markedStrings) 38 39 return &lsp.Hover{ 40 Contents: markedStrings, 41 // TODO(bnmetrics): we can add range here later 42 }, nil 43 } 44 45 func (h *LsHandler) getHoverContent(ctx context.Context, uri lsp.DocumentURI, pos lsp.Position) (content string, err error) { 46 // Get the content of the line from the position 47 fileContent := h.workspace.documents[uri].textInEdit 48 if pos.Line >= len(fileContent) { 49 return "", &jsonrpc2.Error{ 50 Code: jsonrpc2.CodeInvalidParams, 51 Message: fmt.Sprintf("Invalid file line: requested %d, but we think file is only %d long", pos.Line, len(fileContent)), 52 } 53 } 54 lineContent := fileContent[pos.Line] 55 56 if err != nil { 57 return "", &jsonrpc2.Error{ 58 Code: jsonrpc2.CodeParseError, 59 Message: fmt.Sprintf("fail to read file %s: %s", uri, err), 60 } 61 } 62 63 // Return empty string if the hovered content is blank 64 if isEmpty(lineContent, pos) { 65 return "", nil 66 } 67 stmts := h.analyzer.AspStatementFromContent(JoinLines(fileContent, true)) 68 69 call := h.analyzer.CallFromAST(stmts, pos) 70 label := h.analyzer.BuildLabelFromAST(ctx, stmts, uri, pos) 71 var contentString string 72 var contentErr error 73 74 if call != nil { 75 subincludes := h.analyzer.GetSubinclude(ctx, stmts, uri) 76 rule := h.analyzer.GetBuildRuleByName(call.Name, subincludes) 77 78 if rule != nil { 79 contentString, contentErr = contentFromCall(call.Arguments, rule, lineContent, pos) 80 } 81 } 82 83 if label != nil { 84 if label.BuildDef != nil && label.BuildDef.Content != "" { 85 contentString = label.BuildDef.Content 86 } else { 87 contentString = label.Definition 88 } 89 } 90 91 if contentErr != nil { 92 log.Warning("fail to get content from Build file %s: %s", uri, contentErr) 93 return "", nil 94 } 95 return contentString, nil 96 } 97 98 func contentFromCall(args []asp.CallArgument, ruleDef *RuleDef, 99 lineContent string, pos lsp.Position) (string, error) { 100 101 // check if the hovered content is on the name of the ident 102 if strings.Contains(lineContent, ruleDef.Name) { 103 // adding the trailing open paren to the identName ensure it's a call, 104 // prevent inaccuracy for cases like so: replace_str = x.replace('-', '_') 105 identNameIndex := strings.Index(lineContent, ruleDef.Name+"(") 106 107 if pos.Character >= identNameIndex && 108 pos.Character <= identNameIndex+len(ruleDef.Name)-1 { 109 return contentFromRuleDef(ruleDef), nil 110 } 111 } 112 113 // Check arguments of the IdentStatement, and return the appropriate content if any 114 return contentFromArgs(args, ruleDef, pos) 115 } 116 117 func contentFromArgs(args []asp.CallArgument, ruleDef *RuleDef, pos lsp.Position) (string, error) { 118 119 for i, identArg := range args { 120 argNameEndPos := asp.Position{ 121 Line: identArg.Pos.Line, 122 Column: identArg.Pos.Column + len(identArg.Name), 123 } 124 if withInRange(identArg.Pos, argNameEndPos, pos) { 125 // This is to prevent cases like str.format(), 126 // When the positional args are not exactly stored in ArgMap 127 arg, okay := ruleDef.ArgMap[identArg.Name] 128 if okay { 129 return arg.Definition, nil 130 } 131 // Return definition if the hovered content is a positional argument 132 } else if identArg.Name == "" && withInRange(identArg.Value.Pos, identArg.Value.EndPos, pos) { 133 argInd := i 134 if ruleDef.Arguments[0].Name == "self" { 135 argInd++ 136 } 137 return ruleDef.ArgMap[ruleDef.Arguments[argInd].Name].Definition, nil 138 } 139 140 } 141 return "", nil 142 } 143 144 // contentFromRuleDef returns the content from when hovering over the name of a function call 145 // return value consist of a string containing the header and the docstring of a build rule 146 func contentFromRuleDef(ruleDef *RuleDef) string { 147 148 header := ruleDef.Header 149 docString := ruleDef.Docstring 150 151 if docString != "" { 152 return header + "\n\n" + docString 153 } 154 return header 155 }