blob: 5977b4c4d6f856d0a6fad37549d37093b885be78 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package langserver
import (
"fmt"
"github.com/sourcegraph/go-lsp"
"fidl-lsp/analysis"
"fidl-lsp/state"
)
// handleHover asks the FileSystem for the symbol at the location specified in
// `params`, and then asks the Analyzer for the type of that symbol.
//
// It then converts that type to a human-readable string representation that
// will be displayed as a tooltip, including doc comments, other attributes on
// the type, and the type itself. For example, given this FIDL:
//
// library example;
//
// /// Foo is a struct.
// [Attribute = "Value"]
// struct Foo {};
//
// protocol Bar {
// Method(Foo foo);
// ~~~
// }
//
// Hovering over the `~~~` would give you this tooltip:
//
// Foo is a struct. // In plain text
// [Attribute = "Value"] // In monospace font
// struct example/Foo
//
func (h *LangHandler) handleHover(params lsp.TextDocumentPositionParams) (lsp.Hover, error) {
sym, err := h.fs.SymbolAtPos(
state.FileID(params.TextDocument.URI),
state.Position{Line: params.Position.Line, Character: params.Position.Character},
)
if err != nil {
h.log.Printf(
"could not find symbol at position `%#v` in document `%s`\n",
params.Position,
params.TextDocument.URI,
)
return lsp.Hover{}, err
}
symType, err := h.analyzer.TypeOfSymbol(h.fs, sym)
if err != nil {
h.log.Printf("could not get type of symbol `%s`: %s", sym.Name, err)
return lsp.Hover{}, err
}
// Convert SymbolType --> []lsp.MarkedString
typeDescription, err := symbolTypeToMarkedStrings(sym.Name, symType)
if err != nil {
h.log.Printf("could not get string representation of symbol `%s`'s type: %s", sym.Name, err)
return lsp.Hover{}, err
}
return lsp.Hover{
Contents: typeDescription,
Range: &lsp.Range{
Start: lsp.Position{
Line: sym.Location.Range.Start.Line,
Character: sym.Location.Range.Start.Character,
},
End: lsp.Position{
Line: sym.Location.Range.End.Line,
Character: sym.Location.Range.End.Character,
},
},
}, nil
}
func newFIDLString(s string) lsp.MarkedString {
return lsp.MarkedString{
Language: "fidl",
Value: s,
}
}
func symbolTypeToMarkedStrings(name string, symType analysis.Type) ([]lsp.MarkedString, error) {
if symType.IsLib {
return []lsp.MarkedString{{
Language: "fidl",
Value: fmt.Sprintf("library %s", name),
}}, nil
}
res := []lsp.MarkedString{}
var docStr *lsp.MarkedString
attrList := "["
firstAttr := true
// Add attributes as a marked string
for _, attr := range symType.Attrs {
if attr.Name == "Doc" {
rawMarkedStr := lsp.RawMarkedString(attr.Value)
docStr = &rawMarkedStr
} else {
if !firstAttr {
attrList += ", "
}
firstAttr = false
if attr.Value != "" {
attrList += fmt.Sprintf("%s = \"%s\"", attr.Name, attr.Value)
} else {
attrList += attr.Name
}
}
}
attrList += "]"
// Prepend doc string if there is one
if docStr != nil {
res = append([]lsp.MarkedString{*docStr}, res...)
}
if !firstAttr {
res = append(res, lsp.MarkedString{
Language: "fidl",
Value: attrList,
})
}
switch symType.Kind {
default:
return nil, fmt.Errorf("unknown kind of symbol: %s", symType.Kind)
case analysis.ArrayType:
if symType.Array == nil {
goto TypeInfoNotSet
}
res = append(res, newFIDLString(shortName(symType)))
case analysis.VectorType:
if symType.Vector == nil {
goto TypeInfoNotSet
}
res = append(res, newFIDLString(shortName(symType)))
case analysis.StringType:
if symType.String == nil {
goto TypeInfoNotSet
}
res = append(res, newFIDLString(shortName(symType)))
case analysis.HandleType:
if symType.Handle == nil {
goto TypeInfoNotSet
}
res = append(res, newFIDLString(shortName(symType)))
case analysis.RequestType:
if symType.Request == nil {
goto TypeInfoNotSet
}
res = append(res, newFIDLString(shortName(symType)))
case analysis.PrimitiveType:
if symType.Primitive == nil {
goto TypeInfoNotSet
}
res = append(res, newFIDLString(shortName(symType)))
case analysis.IdentifierType:
if symType.Identifier == nil {
goto TypeInfoNotSet
}
if !symType.Identifier.IsDecl {
res = append(res, newFIDLString(shortName(symType)))
break
}
switch symType.Identifier.Kind {
default:
return nil, fmt.Errorf("unknown identifier kind: %s", symType.Identifier.Kind)
case analysis.BitsKind:
if symType.Identifier.Bits == nil {
goto IdentifierTypeInfoNotSet
}
res = append(res, newFIDLString(
fmt.Sprintf(
"bits %s: %s",
symType.Identifier.Name,
shortName(symType.Identifier.Bits.Type),
),
))
case analysis.EnumKind:
if symType.Identifier.Enum == nil {
goto IdentifierTypeInfoNotSet
}
res = append(res, newFIDLString(
fmt.Sprintf(
"enum %s: %s",
symType.Identifier.Name,
symType.Identifier.Enum.Type,
),
))
case analysis.ConstKind:
if symType.Identifier.Const == nil {
goto IdentifierTypeInfoNotSet
}
res = append(res, newFIDLString(
fmt.Sprintf(
"const %s %s = `%s`",
shortName(symType.Identifier.Const.Type),
symType.Identifier.Name,
symType.Identifier.Const.Value,
),
))
case analysis.TypeAliasKind:
if symType.Identifier.TypeAlias == nil {
goto IdentifierTypeInfoNotSet
}
res = append(res, newFIDLString(
fmt.Sprintf(
"%s: alias to %s",
symType.Identifier.Name,
shortName(symType.Identifier.TypeAlias.Type),
),
))
case analysis.ProtocolKind,
analysis.ServiceKind,
analysis.StructKind,
analysis.TableKind,
analysis.UnionKind:
res = append(res, newFIDLString(
fmt.Sprintf("%s %s", symType.Identifier.Kind, symType.Identifier.Name),
))
}
}
return res, nil
TypeInfoNotSet:
return nil, fmt.Errorf(
"symbol is tagged as a `%s` but `%s` type info not set",
symType.Kind,
symType.Kind,
)
IdentifierTypeInfoNotSet:
return nil, fmt.Errorf(
"symbol is tagged as a `%s` but `%s` type info not set",
symType.Identifier.Kind,
symType.Identifier.Kind,
)
}
func shortName(t analysis.Type) string {
switch t.Kind {
default:
return "unknown type"
case analysis.ArrayType:
return fmt.Sprintf("array<%s>:%d", shortName(t.Array.ElementType), t.Array.ElementCount)
case analysis.VectorType:
str := fmt.Sprintf("vector<%s>", shortName(t.Vector.ElementType))
if t.Vector.ElementCount != nil {
str += fmt.Sprintf(":%d", *t.Vector.ElementCount)
}
if t.Vector.Nullable {
str += "?"
}
return str
case analysis.StringType:
str := "string"
if t.String.ElementCount != nil {
str += fmt.Sprintf(":%d", *t.String.ElementCount)
}
if t.String.Nullable {
str += "?"
}
return str
case analysis.RequestType:
str := fmt.Sprintf("request<%s>", t.Request.Subtype)
if t.Request.Nullable {
str += "?"
}
return str
case analysis.HandleType:
str := fmt.Sprintf("handle<%s>", t.Handle.Subtype)
if t.Handle.Nullable {
str += "?"
}
return str
case analysis.PrimitiveType:
return string(t.Primitive.Subtype)
case analysis.IdentifierType:
var str string
if t.Identifier.IsDecl {
str = t.Identifier.Name
} else {
str = t.Identifier.Identifier
}
if t.Identifier.Nullable {
str += "?"
}
return str
}
}