Skip to content

Commit d00f159

Browse files
committed
feat(vim): add Vim plugin for CODEOWNERS LSP integration
1 parent 4008d48 commit d00f159

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
let s:job = v:null
2+
let s:channel = v:null
3+
let s:request_id = 0
4+
let s:pending_requests = {}
5+
let s:initialized = v:false
6+
let s:root_uri = ''
7+
8+
function! codeinput#init() abort
9+
let s:root_uri = 'file://' . getcwd()
10+
let binary = get(g:, 'codeinput_binary', 'ci')
11+
12+
if !executable(binary)
13+
echoerr 'CodeInput: binary not found: ' . binary
14+
return
15+
endif
16+
17+
let cmd = [binary, 'lsp']
18+
let s:job = job_start(cmd, {
19+
\ 'out_cb': function('s:handle_message'),
20+
\ 'err_cb': function('s:handle_error'),
21+
\ 'out_mode': 'raw',
22+
\ 'noblock': 1,
23+
\ })
24+
25+
if job_status(s:job) !=# 'run'
26+
echoerr 'CodeInput: failed to start LSP server'
27+
let s:job = v:null
28+
return
29+
endif
30+
31+
let s:channel = job_getchannel(s:job)
32+
call s:send_request('initialize', {
33+
\ 'processId': getpid(),
34+
\ 'rootUri': s:root_uri,
35+
\ 'capabilities': {},
36+
\ })
37+
endfunction
38+
39+
function! codeinput#stop() abort
40+
if s:job isnot v:null
41+
call job_stop(s:job)
42+
let s:job = v:null
43+
let s:channel = v:null
44+
let s:initialized = v:false
45+
endif
46+
endfunction
47+
48+
function! codeinput#show_info() abort
49+
if !s:initialized
50+
echo 'CodeInput: not initialized'
51+
return
52+
endif
53+
54+
let uri = 'file://' . expand('%:p')
55+
call s:send_request('textDocument/hover', {
56+
\ 'textDocument': {'uri': uri},
57+
\ 'position': {'line': line('.') - 1, 'character': col('.') - 1},
58+
\ }, function('s:show_hover'))
59+
endfunction
60+
61+
function! codeinput#refresh() abort
62+
if !s:initialized
63+
echo 'CodeInput: not initialized'
64+
return
65+
endif
66+
67+
call s:send_notification('workspace/executeCommand', {
68+
\ 'command': 'codeinput.refresh',
69+
\ })
70+
echo 'CodeInput: cache refreshed'
71+
endfunction
72+
73+
function! s:send_request(method, params, ...) abort
74+
let s:request_id += 1
75+
let request = {
76+
\ 'jsonrpc': '2.0',
77+
\ 'id': s:request_id,
78+
\ 'method': a:method,
79+
\ 'params': a:params,
80+
\ }
81+
82+
if a:0 > 0
83+
let s:pending_requests[s:request_id] = a:1
84+
endif
85+
86+
call s:send_json(request)
87+
endfunction
88+
89+
function! s:send_notification(method, params) abort
90+
let notification = {
91+
\ 'jsonrpc': '2.0',
92+
\ 'method': a:method,
93+
\ 'params': a:params,
94+
\ }
95+
call s:send_json(notification)
96+
endfunction
97+
98+
function! s:send_json(data) abort
99+
if s:channel is v:null
100+
return
101+
endif
102+
103+
let json = json_encode(a:data)
104+
let message = "Content-Length: " . strlen(json) . "\r\n\r\n" . json
105+
call ch_sendraw(s:channel, message)
106+
endfunction
107+
108+
let s:buffer = ''
109+
110+
function! s:handle_message(channel, data) abort
111+
let s:buffer .= a:data
112+
113+
while v:true
114+
let header_end = stridx(s:buffer, "\r\n\r\n")
115+
if header_end == -1
116+
break
117+
endif
118+
119+
let header = s:buffer[:header_end - 1]
120+
let content_length = s:parse_content_length(header)
121+
if content_length == -1
122+
let s:buffer = s:buffer[header_end + 4:]
123+
continue
124+
endif
125+
126+
let body_start = header_end + 4
127+
let body_end = body_start + content_length
128+
if strlen(s:buffer) < body_end
129+
break
130+
endif
131+
132+
let body = s:buffer[body_start : body_end - 1]
133+
let s:buffer = s:buffer[body_end :]
134+
135+
try
136+
let response = json_decode(body)
137+
call s:handle_response(response)
138+
catch
139+
echoerr 'CodeInput: failed to parse LSP message'
140+
endtry
141+
endwhile
142+
endfunction
143+
144+
function! s:parse_content_length(header) abort
145+
let lines = split(a:header, "\r\n")
146+
for line in lines
147+
if line =~? '^Content-Length:'
148+
return str2nr(matchstr(line, '\d\+'))
149+
endif
150+
endfor
151+
return -1
152+
endfunction
153+
154+
function! s:handle_response(response) abort
155+
if has_key(a:response, 'method') && a:response.method ==# 'initialize'
156+
let s:initialized = v:true
157+
call s:send_notification('initialized', {})
158+
return
159+
endif
160+
161+
if has_key(a:response, 'id') && has_key(s:pending_requests, a:response.id)
162+
let Callback = s:pending_requests[a:response.id]
163+
unlet s:pending_requests[a:response.id]
164+
if has_key(a:response, 'result')
165+
call Callback(a:response.result)
166+
endif
167+
endif
168+
endfunction
169+
170+
function! s:handle_error(channel, data) abort
171+
" Ignore progress messages and non-error output
172+
if a:data =~? 'error\|fail' && a:data !~? 'completed successfully'
173+
echoerr 'CodeInput LSP: ' . a:data
174+
endif
175+
endfunction
176+
177+
function! s:show_hover(result) abort
178+
if type(a:result) == type(v:null)
179+
echo 'CodeInput: no ownership info'
180+
return
181+
endif
182+
183+
if has_key(a:result, 'contents')
184+
let contents = a:result.contents
185+
if type(contents) == type('')
186+
echo contents
187+
elseif type(contents) == type([])
188+
echo join(contents, "\n")
189+
endif
190+
endif
191+
endfunction
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
if exists('g:loaded_codeinput')
2+
finish
3+
endif
4+
let g:loaded_codeinput = 1
5+
6+
if !has('job') || !has('channel')
7+
echoerr 'CodeInput requires Vim with +job and +channel features'
8+
finish
9+
endif
10+
11+
command! CodeInputInfo call codeinput#show_info()
12+
command! CodeInputRefresh call codeinput#refresh()
13+
command! CodeInputStop call codeinput#stop()
14+
15+
augroup CodeInput
16+
autocmd!
17+
autocmd VimLeavePre * call codeinput#stop()
18+
augroup END
19+
20+
call codeinput#init()

0 commit comments

Comments
 (0)