1package nbview23import (4 "context"5 "slices"6 "strings"78 "src.rest/cat/012/internal/notebook"9 "src.rest/cat/012/internal/nushell"10)1112// Hover implements Hoverer: for a command, its signature and help from13// nu's --ide-hover; for a flag, the flag in its command's help; for a14// variable, the notebook's word for it ($selection, a linked file), or15// the type nu gives a variable the cell binds itself.16func (n *Nu) Hover(ctx context.Context, src string, at int) (Hover, bool) {17 names, words := n.known()18 from, to := wordBounds(src, at)19 if h, ok := wordHover(words, src, from, to); ok {20 return h, true21 }22 p := prepare(src, names)23 nh, ok := n.hoverAt(ctx, p, from) // the word's start, whichever byte of it the caret is at24 if !ok {25 return Hover{}, false26 }27 from, to = nh.From-p.at, nh.To-p.at28 if from < 0 || to > len(src) || from >= to || p.inHead(from, to) {29 return Hover{}, false30 }31 if nh.Text == "flag" {32 return n.flagHover(ctx, src, p, from, to)33 }34 if help, ok := nushell.ParseHelp(nh.Text); ok {35 return Hover{From: from, To: to, Text: help.Signature(), Brief: help.Brief(), Desc: help.Summary(), Help: &help}, true36 }37 word := src[from:to]38 if strings.HasPrefix(word, "$") && nh.Text != "any" && !strings.ContainsAny(nh.Text, "\n ") {39 return Hover{From: from, To: to, Text: word + ": " + nh.Text}, true40 }41 return Hover{}, false42}4344// hoverAt is nu's hover at byte at of the source p was prepared from.45func (n *Nu) hoverAt(ctx context.Context, p prepared, at int) (h nushell.Hover, ok bool) {46 err := n.s.ask(ctx, func(ctx context.Context) (err error) {47 h, ok, err = nushell.HoverAt(ctx, n.s.Runner, p.text, p.at+at)48 return err49 })50 return h, err == nil && ok51}5253// flagHover is what the help of the command a flag belongs to says of54// the flag at src[from:to].55func (n *Nu) flagHover(ctx context.Context, src string, p prepared, from, to int) (Hover, bool) {56 cmd, ok := commandStart(p.text[p.at:], notebook.Parse(src).Strings, from)57 if !ok {58 return Hover{}, false59 }60 nh, ok := n.hoverAt(ctx, p, cmd)61 if !ok {62 return Hover{}, false63 }64 help, ok := nushell.ParseHelp(nh.Text)65 if !ok {66 return Hover{}, false67 }68 f, ok := help.Flag(src[from:to])69 if !ok {70 return Hover{}, false71 }72 text := help.Name + " " + f.Long73 if f.Arg != "" {74 text += " <" + f.Arg + ">"75 }76 if f.Short != "" {77 text += ", " + f.Short78 }79 return Hover{From: from, To: to, Text: strings.TrimSpace(text), Desc: f.Desc, Help: &help}, true80}8182// commandStart is the first byte of the command whose argument begins83// at byte at of src: after the |, ;, line or bracket opening the84// pipeline's element it's in, strings in strs left alone, and after a85// let's =.86func commandStart(src string, strs [][2]int, at int) (int, bool) {87 depth, i := 0, at88scan:89 for i > 0 {90 i--91 if s, ok := stringAround(strs, i); ok {92 i = s93 continue94 }95 switch src[i] {96 case ')', '}', ']':97 depth++98 case '(', '{', '[':99 if depth == 0 {100 i++101 break scan102 }103 depth--104 case '|', ';', '\n':105 if depth == 0 {106 i++107 break scan108 }109 }110 }111 for i < at && (src[i] == ' ' || src[i] == '\t' || src[i] == '^') {112 i++113 }114 if _, end := wordBounds(src, i); slices.Contains([]string{"let", "mut", "const"}, src[i:end]) {115 if eq := strings.IndexByte(src[end:at], '='); eq >= 0 {116 i = end + eq + 1117 for i < at && (src[i] == ' ' || src[i] == '\t') {118 i++119 }120 }121 }122 return i, i < at123}124125// stringAround is where the string holding byte i starts.126func stringAround(strs [][2]int, i int) (int, bool) {127 for _, s := range strs {128 if i >= s[0] && i < s[1] {129 return s[0], true130 }131 }132 return 0, false133}134135// wordHover is the notebook's own word for the $name at src[from:to]:136// $selection, a linked file, a range of a sheet.137func wordHover(words []Word, src string, from, to int) (Hover, bool) {138 name, end := varName(src, from)139 if name == "" {140 return Hover{}, false141 }142 for _, w := range words {143 switch {144 case w.Text == "$"+name:145 return Hover{From: from, To: end, Text: w.Text + ": " + w.Desc}, true146 case strings.HasSuffix(w.Text, ".") && strings.HasPrefix(src[from:to], w.Text):147 desc, _, _ := strings.Cut(w.Desc, ":") // a range of a sheet: $sheet.A1:C9148 return Hover{From: from, To: to, Text: src[from:to] + ": " + desc}, true149 }150 }151 return Hover{}, false152}