5
5
package lsp
6
6
7
7
import (
8
+ "bytes"
8
9
"context"
9
10
"fmt"
10
11
"go/ast"
@@ -23,11 +24,68 @@ import (
23
24
24
25
func (s * Server ) documentLink (ctx context.Context , params * protocol.DocumentLinkParams ) ([]protocol.DocumentLink , error ) {
25
26
// TODO(golang/go#36501): Support document links for go.mod files.
26
- snapshot , fh , ok , err := s .beginFileRequest (params .TextDocument .URI , source .Go )
27
+ snapshot , fh , ok , err := s .beginFileRequest (params .TextDocument .URI , source .UnknownKind )
27
28
if ! ok {
28
29
return nil , err
29
30
}
31
+ switch fh .Identity ().Kind {
32
+ case source .Mod :
33
+ return modLinks (ctx , snapshot , fh )
34
+ case source .Go :
35
+ return goLinks (ctx , snapshot .View (), fh )
36
+ }
37
+ return nil , nil
38
+ }
39
+
40
+ func modLinks (ctx context.Context , snapshot source.Snapshot , fh source.FileHandle ) ([]protocol.DocumentLink , error ) {
30
41
view := snapshot .View ()
42
+
43
+ file , m , err := snapshot .ModHandle (ctx , fh ).Parse (ctx )
44
+ if err != nil {
45
+ return nil , err
46
+ }
47
+ var links []protocol.DocumentLink
48
+ for _ , req := range file .Require {
49
+ dep := []byte (req .Mod .Path )
50
+ s , e := req .Syntax .Start .Byte , req .Syntax .End .Byte
51
+ i := bytes .Index (m .Content [s :e ], dep )
52
+ if i == - 1 {
53
+ continue
54
+ }
55
+ // Shift the start position to the location of the
56
+ // dependency within the require statement.
57
+ start , end := token .Pos (s + i ), token .Pos (s + i + len (dep ))
58
+ target := fmt .Sprintf ("https://%s/mod/%s" , view .Options ().LinkTarget , req .Mod .String ())
59
+ if l , err := toProtocolLink (view , m , target , start , end , source .Mod ); err == nil {
60
+ links = append (links , l )
61
+ } else {
62
+ log .Error (ctx , "failed to create protocol link" , err )
63
+ }
64
+ }
65
+ // TODO(ridersofrohan): handle links for replace and exclude directives
66
+ if syntax := file .Syntax ; syntax == nil {
67
+ return links , nil
68
+ }
69
+ // Get all the links that are contained in the comments of the file.
70
+ for _ , expr := range file .Syntax .Stmt {
71
+ comments := expr .Comment ()
72
+ if comments == nil {
73
+ continue
74
+ }
75
+ for _ , cmt := range comments .Before {
76
+ links = append (links , findLinksInString (ctx , view , cmt .Token , token .Pos (cmt .Start .Byte ), m , source .Mod )... )
77
+ }
78
+ for _ , cmt := range comments .Suffix {
79
+ links = append (links , findLinksInString (ctx , view , cmt .Token , token .Pos (cmt .Start .Byte ), m , source .Mod )... )
80
+ }
81
+ for _ , cmt := range comments .After {
82
+ links = append (links , findLinksInString (ctx , view , cmt .Token , token .Pos (cmt .Start .Byte ), m , source .Mod )... )
83
+ }
84
+ }
85
+ return links , nil
86
+ }
87
+
88
+ func goLinks (ctx context.Context , view source.View , fh source.FileHandle ) ([]protocol.DocumentLink , error ) {
31
89
phs , err := view .Snapshot ().PackageHandles (ctx , fh )
32
90
if err != nil {
33
91
return nil , err
@@ -52,7 +110,7 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink
52
110
target = fmt .Sprintf ("https://%s/%s" , view .Options ().LinkTarget , target )
53
111
// Account for the quotation marks in the positions.
54
112
start , end := n .Path .Pos ()+ 1 , n .Path .End ()- 1
55
- if l , err := toProtocolLink (view , m , target , start , end ); err == nil {
113
+ if l , err := toProtocolLink (view , m , target , start , end , source . Go ); err == nil {
56
114
links = append (links , l )
57
115
} else {
58
116
log .Error (ctx , "failed to create protocol link" , err )
@@ -62,7 +120,7 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink
62
120
case * ast.BasicLit :
63
121
// Look for links in string literals.
64
122
if n .Kind == token .STRING {
65
- links = append (links , findLinksInString (ctx , view , n .Value , n .Pos (), m )... )
123
+ links = append (links , findLinksInString (ctx , view , n .Value , n .Pos (), m , source . Go )... )
66
124
}
67
125
return false
68
126
}
@@ -71,7 +129,7 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink
71
129
// Look for links in comments.
72
130
for _ , commentGroup := range file .Comments {
73
131
for _ , comment := range commentGroup .List {
74
- links = append (links , findLinksInString (ctx , view , comment .Text , comment .Pos (), m )... )
132
+ links = append (links , findLinksInString (ctx , view , comment .Text , comment .Pos (), m , source . Go )... )
75
133
}
76
134
}
77
135
return links , nil
@@ -96,7 +154,7 @@ func moduleAtVersion(ctx context.Context, target string, ph source.PackageHandle
96
154
return modpath , version , true
97
155
}
98
156
99
- func findLinksInString (ctx context.Context , view source.View , src string , pos token.Pos , m * protocol.ColumnMapper ) []protocol.DocumentLink {
157
+ func findLinksInString (ctx context.Context , view source.View , src string , pos token.Pos , m * protocol.ColumnMapper , fileKind source. FileKind ) []protocol.DocumentLink {
100
158
var links []protocol.DocumentLink
101
159
for _ , index := range view .Options ().URLRegexp .FindAllIndex ([]byte (src ), - 1 ) {
102
160
start , end := index [0 ], index [1 ]
@@ -111,7 +169,7 @@ func findLinksInString(ctx context.Context, view source.View, src string, pos to
111
169
if url .Scheme == "" {
112
170
url .Scheme = "https"
113
171
}
114
- l , err := toProtocolLink (view , m , url .String (), startPos , endPos )
172
+ l , err := toProtocolLink (view , m , url .String (), startPos , endPos , fileKind )
115
173
if err != nil {
116
174
log .Error (ctx , "failed to create protocol link" , err )
117
175
continue
@@ -130,7 +188,7 @@ func findLinksInString(ctx context.Context, view source.View, src string, pos to
130
188
}
131
189
org , repo , number := matches [1 ], matches [2 ], matches [3 ]
132
190
target := fmt .Sprintf ("https://github.com/%s/%s/issues/%s" , org , repo , number )
133
- l , err := toProtocolLink (view , m , target , startPos , endPos )
191
+ l , err := toProtocolLink (view , m , target , startPos , endPos , fileKind )
134
192
if err != nil {
135
193
log .Error (ctx , "failed to create protocol link" , err )
136
194
continue
@@ -152,14 +210,34 @@ var (
152
210
issueRegexp * regexp.Regexp
153
211
)
154
212
155
- func toProtocolLink (view source.View , m * protocol.ColumnMapper , target string , start , end token.Pos ) (protocol.DocumentLink , error ) {
156
- spn , err := span .NewRange (view .Session ().Cache ().FileSet (), start , end ).Span ()
157
- if err != nil {
158
- return protocol.DocumentLink {}, err
159
- }
160
- rng , err := m .Range (spn )
161
- if err != nil {
162
- return protocol.DocumentLink {}, err
213
+ func toProtocolLink (view source.View , m * protocol.ColumnMapper , target string , start , end token.Pos , fileKind source.FileKind ) (protocol.DocumentLink , error ) {
214
+ var rng protocol.Range
215
+ switch fileKind {
216
+ case source .Go :
217
+ spn , err := span .NewRange (view .Session ().Cache ().FileSet (), start , end ).Span ()
218
+ if err != nil {
219
+ return protocol.DocumentLink {}, err
220
+ }
221
+ rng , err = m .Range (spn )
222
+ if err != nil {
223
+ return protocol.DocumentLink {}, err
224
+ }
225
+ case source .Mod :
226
+ s , e := int (start ), int (end )
227
+ line , col , err := m .Converter .ToPosition (s )
228
+ if err != nil {
229
+ return protocol.DocumentLink {}, err
230
+ }
231
+ start := span .NewPoint (line , col , s )
232
+ line , col , err = m .Converter .ToPosition (e )
233
+ if err != nil {
234
+ return protocol.DocumentLink {}, err
235
+ }
236
+ end := span .NewPoint (line , col , e )
237
+ rng , err = m .Range (span .New (m .URI , start , end ))
238
+ if err != nil {
239
+ return protocol.DocumentLink {}, err
240
+ }
163
241
}
164
242
return protocol.DocumentLink {
165
243
Range : rng ,
0 commit comments