blob: 71dd32060f66b5bfd93343aa738232693c30cb7d [file] [log] [blame]
// Copyright 2020 The Go 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 lsp
import (
"context"
"fmt"
"go/ast"
"go/types"
"log"
"sort"
"strings"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
errors "golang.org/x/xerrors"
)
func (s *Server) semanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
now := time.Now()
ret, err := s.computeSemanticTokens(ctx, p.TextDocument, nil)
if err == nil {
event.Log(ctx, fmt.Sprintf("Full(%v): %d items for %s in %s",
s.session.Options().SemanticTokens, len(ret.Data)/5, p.TextDocument.URI.SpanURI().Filename(), time.Since(now)))
} else {
event.Error(ctx, fmt.Sprintf("Full failed for %s in %s", p.TextDocument.URI.SpanURI().Filename(), time.Since(now)), err)
}
return ret, err
}
func (s *Server) semanticTokensFullDelta(ctx context.Context, p *protocol.SemanticTokensDeltaParams) (interface{}, error) {
return nil, errors.Errorf("implement SemanticTokensFullDelta")
}
func (s *Server) semanticTokensRange(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
now := time.Now()
ret, err := s.computeSemanticTokens(ctx, p.TextDocument, &p.Range)
if err == nil {
event.Log(ctx, fmt.Sprintf("Range(%v): %d items for %s %s in %s",
s.session.Options().SemanticTokens, len(ret.Data)/5, p.TextDocument.URI.SpanURI().Filename(),
p.Range, time.Since(now)))
} else {
event.Error(ctx, "semanticTokensRange failed", err)
}
return ret, err
}
func (s *Server) semanticTokensRefresh(ctx context.Context) error {
// in the code, but not in the protocol spec
return errors.Errorf("implement SemanticTokensRefresh")
}
func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocumentIdentifier, rng *protocol.Range) (*protocol.SemanticTokens, error) {
ans := protocol.SemanticTokens{
Data: []float64{},
}
snapshot, _, ok, release, err := s.beginFileRequest(ctx, td.URI, source.Go)
defer release()
if !ok {
return nil, err
}
vv, err := s.session.ViewOf(td.URI.SpanURI())
if err != nil {
return nil, err
}
if !vv.Options().SemanticTokens {
// return an error, so if the option changes
// the client won't remember the wrong answer
return nil, errors.Errorf("semantictokens are disabled")
}
pkg, err := snapshot.PackageForFile(ctx, td.URI.SpanURI(), source.TypecheckFull, source.WidestPackage)
if err != nil {
return nil, err
}
info := pkg.GetTypesInfo()
pgf, err := pkg.File(td.URI.SpanURI())
if err != nil {
return nil, err
}
e := &encoded{
pgf: pgf,
rng: rng,
ti: info,
}
if err := e.init(); err != nil {
return nil, err
}
e.semantics()
ans.Data, err = e.Data()
if err != nil {
// this is an internal error, likely caused by a typo
// for a token or modifier
return nil, err
}
// for small cache, some day. for now, the client ignores this
ans.ResultID = fmt.Sprintf("%v", time.Now())
return &ans, nil
}
func (e *encoded) semantics() {
f := e.pgf.File
inspect := func(n ast.Node) bool {
return e.inspector(n)
}
for _, d := range f.Decls {
// only look at the decls that overlap the range
start, end := d.Pos(), d.End()
if int(end) <= e.start || int(start) >= e.end {
continue
}
ast.Inspect(d, inspect)
}
}
// semItem represents a token found walking the parse tree
type semItem struct {
line, start, len int
typeStr string
mods []string
}
type encoded struct {
// the generated data
items []semItem
pgf *source.ParsedGoFile
rng *protocol.Range
ti *types.Info
// allowed starting and ending token.Pos, set by init
// used to avoid looking at declarations not in range
start, end int
// path from the root of the parse tree, used for debugging
stack []ast.Node
}
// convert the stack to a string, for debugging
func (e *encoded) strStack() string {
msg := []string{"["}
for _, s := range e.stack {
msg = append(msg, fmt.Sprintf("%T", s)[5:])
}
if len(e.stack) > 0 {
loc := e.stack[len(e.stack)-1].Pos()
add := e.pgf.Tok.PositionFor(loc, false)
msg = append(msg, fmt.Sprintf("(%d:%d)", add.Line, add.Column))
}
msg = append(msg, "]")
return strings.Join(msg, " ")
}
func (e *encoded) inspector(n ast.Node) bool {
// this will be filled in, in the next CL
pop := func() {
e.stack = e.stack[:len(e.stack)-1]
}
if n == nil {
pop()
return true
}
e.stack = append(e.stack, n)
switch x := n.(type) {
case *ast.ArrayType:
case *ast.AssignStmt:
case *ast.BasicLit:
case *ast.BinaryExpr:
case *ast.BlockStmt:
case *ast.BranchStmt:
case *ast.CallExpr:
case *ast.CaseClause:
case *ast.ChanType:
case *ast.CommClause:
case *ast.CompositeLit:
case *ast.DeclStmt:
case *ast.DeferStmt:
case *ast.Ellipsis:
case *ast.EmptyStmt:
case *ast.ExprStmt:
case *ast.Field:
case *ast.FieldList:
case *ast.ForStmt:
case *ast.FuncDecl:
case *ast.FuncLit:
case *ast.FuncType:
case *ast.GenDecl:
case *ast.GoStmt:
case *ast.Ident:
case *ast.IfStmt:
case *ast.ImportSpec:
pop()
return false
case *ast.IncDecStmt:
case *ast.IndexExpr:
case *ast.InterfaceType:
case *ast.KeyValueExpr:
case *ast.LabeledStmt:
case *ast.MapType:
case *ast.ParenExpr:
case *ast.RangeStmt:
case *ast.ReturnStmt:
case *ast.SelectStmt:
case *ast.SelectorExpr:
case *ast.SendStmt:
case *ast.SliceExpr:
case *ast.StarExpr:
case *ast.StructType:
case *ast.SwitchStmt:
case *ast.TypeAssertExpr:
case *ast.TypeSpec:
case *ast.TypeSwitchStmt:
case *ast.UnaryExpr:
case *ast.ValueSpec:
// things we won't see
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt,
*ast.File, *ast.Package:
log.Printf("implement %T %s", x, e.pgf.Tok.PositionFor(x.Pos(), false))
// things we knowingly ignore
case *ast.Comment, *ast.CommentGroup:
pop()
return false
default: // just to be super safe.
panic(fmt.Sprintf("failed to implement %T", x))
}
return true
}
func (e *encoded) init() error {
e.start = e.pgf.Tok.Base()
e.end = e.start + e.pgf.Tok.Size()
if e.rng == nil {
return nil
}
span, err := e.pgf.Mapper.RangeSpan(*e.rng)
if err != nil {
return errors.Errorf("range span error for %s", e.pgf.File.Name)
}
e.end = e.start + span.End().Offset()
e.start += span.Start().Offset()
return nil
}
func (e *encoded) Data() ([]float64, error) {
// binary operators, at least, will be out of order
sort.Slice(e.items, func(i, j int) bool {
if e.items[i].line != e.items[j].line {
return e.items[i].line < e.items[j].line
}
return e.items[i].start < e.items[j].start
})
// each semantic token needs five values
// (see Integer Encoding for Tokens in the LSP spec)
x := make([]int, 5*len(e.items))
for i := 0; i < len(e.items); i++ {
j := 5 * i
if i == 0 {
x[0] = e.items[0].line
} else {
x[j] = e.items[i].line - e.items[i-1].line
}
x[j+1] = e.items[i].start
if i > 0 && e.items[i].line == e.items[i-1].line {
x[j+1] = e.items[i].start - e.items[i-1].start
}
x[j+2] = e.items[i].len
x[j+3] = SemanticMemo.typeMap[e.items[i].typeStr]
for _, s := range e.items[i].mods {
x[j+4] |= SemanticMemo.modMap[s]
}
}
// As the client never sends these, we could fix the generated
// type to want []int rather than []float64.
ans := make([]float64, len(x))
for i, y := range x {
ans[i] = float64(y)
}
return ans, nil
}
// SemMemo supports semantic token translations between numbers and strings
type SemMemo struct {
tokTypes, tokMods []string
typeMap map[string]int
modMap map[string]int
}
var SemanticMemo *SemMemo
// save what the client sent
func rememberToks(toks []string, mods []string) ([]string, []string) {
SemanticMemo = &SemMemo{
tokTypes: toks,
tokMods: mods,
typeMap: make(map[string]int),
modMap: make(map[string]int),
}
for i, t := range toks {
SemanticMemo.typeMap[t] = i
}
for i, m := range mods {
SemanticMemo.modMap[m] = 1 << uint(i)
}
// we could have pruned or rearranged them.
// But then change the list in cmd.go too
return SemanticMemo.tokTypes, SemanticMemo.tokMods
}