Skip to content

Commit bbfd039

Browse files
committed
lsp: use FullDocumentation hover response
Use the FullDocumentation hover response instead of Structured, because the latter is deprecated and will be removed soon. * Parse FullDocumentation format for use in a hover message, following the same basic format was was previously provided by gopls in its Structured format. * Refactor handlers that relied on the hover response to expect the structured output from the new function that provides it. * Refactor functions that expected documentation links to first rely on a new request, documentLink via go#lsp#DocLink, and then falling back to trusty `go doc` to get what's needed.
1 parent 6adc82b commit bbfd039

File tree

3 files changed

+118
-28
lines changed

3 files changed

+118
-28
lines changed

autoload/go/doc.vim

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ function! s:docURL() abort
2525
" will strip any version information from the URL.
2626
let [l:out, l:err] = go#lsp#DocLink()
2727
if !(l:err || len(l:out) is 0)
28-
let l:url = printf('%s/%s', go#config#DocUrl(), l:out)
28+
let l:url = l:out
2929
else
30-
let l:url = ''
30+
let l:url = call('s:docURLFor', a:000)
3131
endif
3232
else
3333
let l:url = call('s:docURLFor', a:000)

autoload/go/lsp.vim

+97-25
Original file line numberDiff line numberDiff line change
@@ -1004,21 +1004,22 @@ function! s:hoverHandler(next, diagnostic, msg) abort dict
10041004
endif
10051005

10061006
try
1007-
let l:value = json_decode(a:msg.contents.value)
1008-
10091007
let l:msg = []
10101008
if len(a:diagnostic) > 0
1011-
let l:msg = split(a:diagnostic, "\n")
1009+
let l:msg = split(a:diagnostic, '\n')
10121010
let l:msg = add(l:msg, '')
10131011
endif
1014-
let l:signature = split(l:value.signature, "\n")
1015-
let l:msg = extend(l:msg, l:signature)
1012+
1013+
let l:value = s:structuredHoverResult(a:msg.contents)
1014+
1015+
let l:msg = extend(l:msg, l:value.signature)
10161016
if go#config#DocBalloon()
10171017
" use synopsis instead of fullDocumentation to keep the hover window
10181018
" small.
10191019
let l:doc = l:value.synopsis
10201020
if len(l:doc) isnot 0
1021-
let l:msg = extend(l:msg, ['', l:doc])
1021+
let l:msg = add(l:msg, '')
1022+
let l:msg = extend(l:msg, l:doc)
10221023
endif
10231024
endif
10241025

@@ -1065,51 +1066,122 @@ function! s:docFromHoverResult(msg) abort dict
10651066
return ['Undocumented', 0]
10661067
endif
10671068

1068-
let l:value = json_decode(a:msg.contents.value)
1069-
let l:doc = l:value.fullDocumentation
1069+
let l:value = s:structuredHoverResult(a:msg.contents)
1070+
let l:doc = join(l:value.fullDocumentation, "\n")
10701071
if len(l:doc) is 0
10711072
let l:doc = 'Undocumented'
10721073
endif
1073-
let l:content = printf("%s\n\n%s", l:value.signature, l:doc)
1074+
let l:content = printf("%s\n\n%s", join(l:value.signature, "\n"), l:doc)
10741075
return [l:content, 0]
10751076
endfunction
10761077

1078+
function! s:structuredHoverResult(contents) abort
1079+
let l:value = {
1080+
\ 'fullDocumentation': [],
1081+
\ 'synopsis': '',
1082+
\ 'signature': [],
1083+
\ 'singleLine': '',
1084+
\ 'symbolName': '',
1085+
\ 'linkPath': '',
1086+
\ 'linkAnchor': ''
1087+
\ }
1088+
1089+
if !has_key(a:contents, 'value')
1090+
return l:value
1091+
endif
1092+
1093+
let l:contents = a:contents.value
1094+
1095+
" The signature is either a multiline identifier (e.g. a struct or an
1096+
" interface and will therefore end with a } on a line by itself or it's a
1097+
" single line identifier. Check for the former first and fallback to the
1098+
" latter.
1099+
let l:parts = split(l:contents, '\n}\zs\n')
1100+
if len(l:parts) is 1
1101+
let l:parts = split(l:contents, '\n')
1102+
else
1103+
let l:parts = extend([l:parts[0]], split(l:parts[1], '\n'))
1104+
endif
1105+
1106+
" The first part will have been the signature. If it was a multiline
1107+
" signature, then it was split on '\n}\zs\n' and needs to be split again
1108+
" on '\n'. If it was a single line signature, then there's no harm in
1109+
" splitting on '\n' again. Either way, a list is returned from split.
1110+
let l:value.signature = split(l:parts[0], '\n')
1111+
1112+
if len(l:value.signature) is 1
1113+
let l:value.singleLine = s:prepareSingleLineFromSignature(0, l:value.signature[0])
1114+
else
1115+
let l:value.singleLine = join(map(copy(l:value.signature), function('s:prepareSingleLineFromSignature')), '')
1116+
endif
1117+
if l:value.singleLine[-1:] is ';'
1118+
let l:value.singleLine = l:value.singleLine[0:-2]
1119+
endif
1120+
1121+
let l:fullDocumentation = l:parts[1:]
1122+
let l:value.fullDocumentation = l:fullDocumentation
1123+
1124+
let l:idx= 0
1125+
for l:line in l:fullDocumentation
1126+
if l:line is ''
1127+
break
1128+
end
1129+
let l:idx += 1
1130+
endfor
1131+
1132+
let l:value.synopsis = l:fullDocumentation[0:(l:idx)]
1133+
1134+
return l:value
1135+
endfunction
1136+
1137+
function s:prepareSingleLineFromSignature(key, val) abort
1138+
let l:line = a:val
1139+
" remove comments
1140+
let l:line = substitute(l:line, '\s\+//.*','','g')
1141+
" end field lines that aren't the beginning of a struct with a semicolon
1142+
if l:line !~ '^\t\+{$'
1143+
let l:line = substitute(l:line, '$',';','')
1144+
endif
1145+
" replace leading tabs with a single space on field lines
1146+
let l:line = substitute(l:line,'^\t\+\ze[{}]',' ','g')
1147+
1148+
return l:line
1149+
endfunction
1150+
10771151
function! go#lsp#DocLink() abort
10781152
let l:fname = expand('%:p')
10791153
let [l:line, l:col] = go#lsp#lsp#Position()
10801154

10811155
call go#lsp#DidChange(l:fname)
10821156

10831157
let l:lsp = s:lspfactory.get()
1084-
let l:msg = go#lsp#message#Hover(l:fname, l:line, l:col)
1158+
let l:msg = go#lsp#message#DocLink(l:fname)
10851159
let l:state = s:newHandlerState('doc url')
1086-
let l:resultHandler = go#promise#New(function('s:docLinkFromHoverResult', [], l:state), 10000, '')
1160+
let l:resultHandler = go#promise#New(function('s:handleDocLink', [l:line, l:col], l:state), 10000, '')
10871161
let l:state.handleResult = l:resultHandler.wrapper
10881162
let l:state.error = l:resultHandler.wrapper
10891163
call l:lsp.sendMessage(l:msg, l:state)
10901164
return l:resultHandler.await()
10911165
endfunction
10921166

1093-
function! s:docLinkFromHoverResult(msg) abort dict
1167+
function! s:handleDocLink(line, character, msg) abort dict
10941168
if type(a:msg) is type('')
10951169
return [a:msg, 1]
10961170
endif
10971171

1098-
if a:msg is v:null || !has_key(a:msg, 'contents')
1172+
if a:msg is v:null || (type(a:msg) is type([]) && len(a:msg) == 0)
10991173
return ['', 0]
11001174
endif
1101-
let l:doc = json_decode(a:msg.contents.value)
11021175

1103-
" for backward compatibility with older gopls
1104-
if has_key(l:doc, 'link')
1105-
let l:link = l:doc.link
1106-
return [l:doc.link, 0]
1107-
endif
1176+
let l:link = ''
1177+
for l:item in a:msg
1178+
if !s:within(l:item.range, a:line, a:character)
1179+
continue
1180+
endif
1181+
let l:link = l:item.target
1182+
break
1183+
endfor
11081184

1109-
if !has_key(l:doc, 'linkPath') || empty(l:doc.linkPath)
1110-
return ['', 0]
1111-
endif
1112-
let l:link = l:doc.linkPath . "#" . l:doc.linkAnchor
11131185
return [l:link, 0]
11141186
endfunction
11151187

@@ -1183,7 +1255,7 @@ function! s:info(show, msg) abort dict
11831255
return
11841256
endif
11851257

1186-
let l:value = json_decode(a:msg.contents.value)
1258+
let l:value = s:structuredHoverResult(a:msg.contents)
11871259
let l:content = [l:value.singleLine]
11881260
let l:content = s:infoFromHoverContent(l:content)
11891261

@@ -1678,7 +1750,7 @@ function! go#lsp#FillStruct() abort
16781750
endfunction
16791751

16801752
" Extract executes the refactor.extract code action for the current buffer
1681-
" and configures the handler to only apply the fillstruct command for the
1753+
" and configures the handler to only apply the extract_function command for the
16821754
" current location.
16831755
function! go#lsp#Extract(line1, line2) abort
16841756
let l:fname = expand('%:p')

autoload/go/lsp/message.vim

+19-1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,15 @@ function! go#lsp#message#Hover(file, line, col) abort
269269
\ }
270270
endfunction
271271

272+
function! go#lsp#message#DocLink(file) abort
273+
let l:params = s:textDocumentParams(a:file)
274+
return {
275+
\ 'notification': 0,
276+
\ 'method': 'textDocument/documentLink',
277+
\ 'params': l:params,
278+
\ }
279+
endfunction
280+
272281
function! go#lsp#message#Rename(file, line, col, newName) abort
273282
let l:params = s:textDocumentPositionParams(a:file, a:line, a:col)
274283
let l:params.newName = a:newName
@@ -304,7 +313,8 @@ function! go#lsp#message#ConfigurationResult(items) abort
304313
let l:workspace = go#path#FromURI(l:item.scopeUri)
305314
let l:config = {
306315
\ 'buildFlags': [],
307-
\ 'hoverKind': 'Structured',
316+
\ 'hoverKind': 'FullDocumentation',
317+
\ 'linkTarget': substitute(go#config#DocUrl(), 'https\?://', '', ''),
308318
\ }
309319
let l:buildtags = go#config#BuildTags()
310320
if buildtags isnot ''
@@ -429,6 +439,14 @@ function! s:position(line, col) abort
429439
return {'line': a:line, 'character': a:col}
430440
endfunction
431441

442+
function! s:textDocumentParams(fname) abort
443+
return {
444+
\ 'textDocument': {
445+
\ 'uri': go#path#ToURI(a:fname)
446+
\ },
447+
\ }
448+
endfunction
449+
432450
function! s:textDocumentPositionParams(fname, line, col) abort
433451
return {
434452
\ 'textDocument': {

0 commit comments

Comments
 (0)