Skip to content

Commit 5c43a09

Browse files
authored
Add more types of hovers to LSP (#3486)
This PR adds three new hoverable things in the LSP: 1. You can hover over the path of an import to show the contents of that file. 2. You can hover over the tag of a field to show its varint encoding. 3. You can hover over the number of an enum value to show its varint encoding and hex/bin values.
1 parent 326f8d3 commit 5c43a09

File tree

1 file changed

+90
-3
lines changed

1 file changed

+90
-3
lines changed

private/buf/buflsp/symbol.go

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/bufbuild/buf/private/pkg/slicesext"
3232
"github.com/bufbuild/protocompile/ast"
3333
"go.lsp.dev/protocol"
34+
"google.golang.org/protobuf/encoding/protowire"
3435
)
3536

3637
// symbol represents a named symbol inside of a buflsp.file
@@ -96,10 +97,16 @@ type builtin struct {
9697
name string
9798
}
9899

100+
type fieldTag struct {
101+
tag ast.IntValueNode
102+
def *symbol
103+
}
104+
99105
func (*definition) isSymbolKind() {}
100106
func (*reference) isSymbolKind() {}
101107
func (*import_) isSymbolKind() {}
102108
func (*builtin) isSymbolKind() {}
109+
func (*fieldTag) isSymbolKind() {}
103110

104111
// Range constructs an LSP protocol code range for this symbol.
105112
func (s *symbol) Range() protocol.Range {
@@ -403,6 +410,72 @@ func (s *symbol) FormatDocs(ctx context.Context) string {
403410
node = kind.node
404411
path = kind.path
405412

413+
case *import_:
414+
return fmt.Sprintf("```proto\n%s\n```", kind.file.text)
415+
416+
case *fieldTag:
417+
var value uint64
418+
if v, ok := kind.tag.AsInt64(); ok {
419+
value = uint64(v)
420+
} else {
421+
value, _ = kind.tag.AsUint64()
422+
}
423+
424+
plural := func(i int) string {
425+
if i == 1 {
426+
return ""
427+
}
428+
return "s"
429+
}
430+
431+
var ty protowire.Type
432+
var packed bool
433+
// Only definition symbols are placed into the def field of fieldTag, so
434+
// doing an unchecked assertion here is ok.
435+
switch def := kind.def.kind.(*definition).node.(type) {
436+
case *ast.EnumValueNode:
437+
varint := protowire.AppendVarint(nil, value)
438+
return fmt.Sprintf(
439+
"`0x%x`, `0b%b`\n\nencoded (hex): `%X` (%d byte%s)",
440+
value, value, varint, len(varint), plural(len(varint)),
441+
)
442+
case *ast.MapFieldNode:
443+
ty = protowire.BytesType
444+
case *ast.GroupNode:
445+
ty = protowire.StartGroupType
446+
case *ast.FieldNode:
447+
switch def.FldType.AsIdentifier() {
448+
case "bool", "int32", "int64", "uint32", "uint64", "sint32", "sint64":
449+
ty = protowire.VarintType
450+
packed = def.Label.Repeated
451+
case "fixed32", "sfixed32", "float":
452+
ty = protowire.Fixed32Type
453+
packed = def.Label.Repeated
454+
case "fixed64", "sfixed64", "double":
455+
ty = protowire.Fixed64Type
456+
packed = def.Label.Repeated
457+
default:
458+
ty = protowire.BytesType
459+
}
460+
}
461+
462+
// Don't use AppendTag because that wants to truncate value to int32.
463+
varint := protowire.AppendVarint(nil, value<<3|uint64(ty))
464+
doc := fmt.Sprintf(
465+
"encoded (hex): `%X` (%d byte%s)",
466+
varint, len(varint), plural(len(varint)),
467+
)
468+
469+
if packed {
470+
packed := protowire.AppendVarint(nil, value<<3|uint64(protowire.BytesType))
471+
return doc + fmt.Sprintf(
472+
"\n\npacked (hex): `%X` (%d byte%s)",
473+
packed, len(packed), plural(len(varint)),
474+
)
475+
}
476+
477+
return doc
478+
406479
default:
407480
return ""
408481
}
@@ -597,25 +670,29 @@ func (w *symbolWalker) Walk(node, parent ast.Node) {
597670
}
598671

599672
case *ast.GroupNode:
673+
def := w.newDef(node, node.Name)
600674
w.newDef(node, node.Name)
675+
w.newTag(node.Tag, def)
601676
// TODO: also do the name of the generated field.
602677
for _, decl := range node.Decls {
603678
w.Walk(decl, node)
604679
}
605680

606681
case *ast.FieldNode:
607-
w.newDef(node, node.Name)
682+
def := w.newDef(node, node.Name)
608683
w.newRef(node.FldType)
684+
w.newTag(node.Tag, def)
609685
if node.Options != nil {
610686
for _, option := range node.Options.Options {
611687
w.Walk(option, node)
612688
}
613689
}
614690

615691
case *ast.MapFieldNode:
616-
w.newDef(node, node.Name)
692+
def := w.newDef(node, node.Name)
617693
w.newRef(node.MapType.KeyType)
618694
w.newRef(node.MapType.ValueType)
695+
w.newTag(node.Tag, def)
619696
if node.Options != nil {
620697
for _, option := range node.Options.Options {
621698
w.Walk(option, node)
@@ -639,7 +716,8 @@ func (w *symbolWalker) Walk(node, parent ast.Node) {
639716
}
640717

641718
case *ast.EnumValueNode:
642-
w.newDef(node, node.Name)
719+
def := w.newDef(node, node.Name)
720+
w.newTag(node.Number, def)
643721
if node.Options != nil {
644722
for _, option := range node.Options.Options {
645723
w.Walk(option, node)
@@ -713,6 +791,15 @@ func (w *symbolWalker) newDef(node ast.Node, name *ast.IdentNode) *symbol {
713791
return symbol
714792
}
715793

794+
// newTag creates a new symbol for a field tag, and adds it to the running list.
795+
//
796+
// Returns a new symbol for that tag.
797+
func (w *symbolWalker) newTag(tag ast.IntValueNode, def *symbol) *symbol {
798+
symbol := w.newSymbol(tag)
799+
symbol.kind = &fieldTag{tag, def}
800+
return symbol
801+
}
802+
716803
// newDef creates a new symbol for a name reference, and adds it to the running list.
717804
//
718805
// newRef performs same-file Protobuf name resolution. It searches for a partial package

0 commit comments

Comments
 (0)