// Copyright 2018 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" "go/token" "golang.org/x/tools/internal/lsp/cache" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" ) // fromProtocolURI converts a protocol.DocumentURI to a source.URI. // TODO(rstambler): Add logic here to support Windows. func fromProtocolURI(uri protocol.DocumentURI) source.URI { return source.URI(uri) } // fromProtocolLocation converts from a protocol location to a source range. // It will return an error if the file of the location was not valid. // It uses fromProtocolRange to convert the start and end positions. func fromProtocolLocation(ctx context.Context, v *cache.View, loc protocol.Location) (source.Range, error) { f, err := v.GetFile(ctx, fromProtocolURI(loc.URI)) if err != nil { return source.Range{}, err } tok, err := f.GetToken() if err != nil { return source.Range{}, err } return fromProtocolRange(tok, loc.Range), nil } // toProtocolLocation converts from a source range back to a protocol location. func toProtocolLocation(fset *token.FileSet, r source.Range) protocol.Location { tokFile := fset.File(r.Start) uri := source.ToURI(tokFile.Name()) return protocol.Location{ URI: protocol.DocumentURI(uri), Range: toProtocolRange(tokFile, r), } } // fromProtocolRange converts a protocol range to a source range. // It uses fromProtocolPosition to convert the start and end positions, which // requires the token file the positions belongs to. func fromProtocolRange(f *token.File, r protocol.Range) source.Range { start := fromProtocolPosition(f, r.Start) var end token.Pos switch { case r.End == r.Start: end = start case r.End.Line < 0: end = token.NoPos default: end = fromProtocolPosition(f, r.End) } return source.Range{ Start: start, End: end, } } // toProtocolRange converts from a source range back to a protocol range. func toProtocolRange(f *token.File, r source.Range) protocol.Range { return protocol.Range{ Start: toProtocolPosition(f, r.Start), End: toProtocolPosition(f, r.End), } } // fromProtocolPosition converts a protocol position (0-based line and column // number) to a token.Pos (byte offset value). // It requires the token file the pos belongs to in order to do this. func fromProtocolPosition(f *token.File, pos protocol.Position) token.Pos { line := lineStart(f, int(pos.Line)+1) return line + token.Pos(pos.Character) // TODO: this is wrong, bytes not characters } // toProtocolPosition converts from a token pos (byte offset) to a protocol // position (0-based line and column number) // It requires the token file the pos belongs to in order to do this. func toProtocolPosition(f *token.File, pos token.Pos) protocol.Position { if !pos.IsValid() { return protocol.Position{Line: -1.0, Character: -1.0} } p := f.Position(pos) return protocol.Position{ Line: float64(p.Line - 1), Character: float64(p.Column - 1), } } // fromTokenPosition converts a token.Position (1-based line and column // number) to a token.Pos (byte offset value). // It requires the token file the pos belongs to in order to do this. func fromTokenPosition(f *token.File, pos token.Position) token.Pos { line := lineStart(f, pos.Line) return line + token.Pos(pos.Column-1) // TODO: this is wrong, bytes not characters } // this functionality was borrowed from the analysisutil package func lineStart(f *token.File, line int) token.Pos { // Use binary search to find the start offset of this line. // // TODO(adonovan): eventually replace this function with the // simpler and more efficient (*go/token.File).LineStart, added // in go1.12. min := 0 // inclusive max := f.Size() // exclusive for { offset := (min + max) / 2 pos := f.Pos(offset) posn := f.Position(pos) if posn.Line == line { return pos - (token.Pos(posn.Column) - 1) } if min+1 >= max { return token.NoPos } if posn.Line < line { min = offset } else { max = offset } } }