1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package lsp 6 7import ( 8 "context" 9 "go/token" 10 "net/url" 11 12 "golang.org/x/tools/internal/lsp/cache" 13 "golang.org/x/tools/internal/lsp/protocol" 14 "golang.org/x/tools/internal/lsp/source" 15) 16 17// fromProtocolURI converts a string to a source.URI. 18// TODO(rstambler): Add logic here to support Windows. 19func fromProtocolURI(uri string) (source.URI, error) { 20 unescaped, err := url.PathUnescape(string(uri)) 21 if err != nil { 22 return "", err 23 } 24 return source.URI(unescaped), nil 25} 26 27// fromProtocolLocation converts from a protocol location to a source range. 28// It will return an error if the file of the location was not valid. 29// It uses fromProtocolRange to convert the start and end positions. 30func fromProtocolLocation(ctx context.Context, v *cache.View, loc protocol.Location) (source.Range, error) { 31 sourceURI, err := fromProtocolURI(loc.URI) 32 if err != nil { 33 return source.Range{}, err 34 } 35 f, err := v.GetFile(ctx, sourceURI) 36 if err != nil { 37 return source.Range{}, err 38 } 39 tok := f.GetToken(ctx) 40 return fromProtocolRange(tok, loc.Range), nil 41} 42 43// toProtocolLocation converts from a source range back to a protocol location. 44func toProtocolLocation(fset *token.FileSet, r source.Range) protocol.Location { 45 tok := fset.File(r.Start) 46 uri := source.ToURI(tok.Name()) 47 return protocol.Location{ 48 URI: string(uri), 49 Range: toProtocolRange(tok, r), 50 } 51} 52 53// fromProtocolRange converts a protocol range to a source range. 54// It uses fromProtocolPosition to convert the start and end positions, which 55// requires the token file the positions belongs to. 56func fromProtocolRange(f *token.File, r protocol.Range) source.Range { 57 start := fromProtocolPosition(f, r.Start) 58 var end token.Pos 59 switch { 60 case r.End == r.Start: 61 end = start 62 case r.End.Line < 0: 63 end = token.NoPos 64 default: 65 end = fromProtocolPosition(f, r.End) 66 } 67 return source.Range{ 68 Start: start, 69 End: end, 70 } 71} 72 73// toProtocolRange converts from a source range back to a protocol range. 74func toProtocolRange(f *token.File, r source.Range) protocol.Range { 75 return protocol.Range{ 76 Start: toProtocolPosition(f, r.Start), 77 End: toProtocolPosition(f, r.End), 78 } 79} 80 81// fromProtocolPosition converts a protocol position (0-based line and column 82// number) to a token.Pos (byte offset value). 83// It requires the token file the pos belongs to in order to do this. 84func fromProtocolPosition(f *token.File, pos protocol.Position) token.Pos { 85 line := lineStart(f, int(pos.Line)+1) 86 return line + token.Pos(pos.Character) // TODO: this is wrong, bytes not characters 87} 88 89// toProtocolPosition converts from a token pos (byte offset) to a protocol 90// position (0-based line and column number) 91// It requires the token file the pos belongs to in order to do this. 92func toProtocolPosition(f *token.File, pos token.Pos) protocol.Position { 93 if !pos.IsValid() { 94 return protocol.Position{Line: -1.0, Character: -1.0} 95 } 96 p := f.Position(pos) 97 return protocol.Position{ 98 Line: float64(p.Line - 1), 99 Character: float64(p.Column - 1), 100 } 101} 102 103// fromTokenPosition converts a token.Position (1-based line and column 104// number) to a token.Pos (byte offset value). 105// It requires the token file the pos belongs to in order to do this. 106func fromTokenPosition(f *token.File, pos token.Position) token.Pos { 107 line := lineStart(f, pos.Line) 108 return line + token.Pos(pos.Column-1) // TODO: this is wrong, bytes not characters 109} 110 111// this functionality was borrowed from the analysisutil package 112func lineStart(f *token.File, line int) token.Pos { 113 // Use binary search to find the start offset of this line. 114 // 115 // TODO(rstambler): eventually replace this function with the 116 // simpler and more efficient (*go/token.File).LineStart, added 117 // in go1.12. 118 119 min := 0 // inclusive 120 max := f.Size() // exclusive 121 for { 122 offset := (min + max) / 2 123 pos := f.Pos(offset) 124 posn := f.Position(pos) 125 if posn.Line == line { 126 return pos - (token.Pos(posn.Column) - 1) 127 } 128 129 if min+1 >= max { 130 return token.NoPos 131 } 132 133 if posn.Line < line { 134 min = offset 135 } else { 136 max = offset 137 } 138 } 139} 140