Skip to content

Commit 9e76cda

Browse files
committed
works
1 parent 59ec0c1 commit 9e76cda

22 files changed

+5721
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib

.npmignore

Whitespace-only changes.

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -1 +1,43 @@
11
# vim-node-rpc
2+
3+
## How it works
4+
5+
vim <-> node-rpc <-> neovim-client
6+
7+
## Not supported methods
8+
9+
* `nvim_execute_lua`
10+
* `nvim_input`
11+
* `nvim_buf_get_keymap`
12+
* `nvim_buf_get_commands`
13+
* `nvim_buf_get_mark`
14+
* `nvim_buf_add_highlight`
15+
* `nvim_buf_clear_highlight`
16+
* `nvim_replace_termcodes`
17+
* `nvim_subscribe`
18+
* `nvim_unsubscribe`
19+
* `nvim_get_color_by_name`
20+
* `nvim_get_color_map`
21+
* `nvim_get_keymap`
22+
* `nvim_get_commands`
23+
* `nvim_set_client_info`
24+
* `nvim_get_chan_info`
25+
* `nvim_list_chans`
26+
* `nvim_call_atomic`
27+
* `nvim_parse_expression`
28+
* `nvim_get_proc_children`
29+
* `nvim_get_proc`
30+
31+
## vim
32+
33+
* `requestId > 0` for request, use `ch_evalexpr`
34+
* `requestId = 0` for notification, use `ch_sendraw`
35+
* `requestId < 0` for response, send by vim
36+
37+
Vim use new line character for the end of JSON text.
38+
39+
Avoid use request for vim that not response, except `redraw`
40+
41+
## TODO
42+
43+
* Checkout what happens on function error

autoload/nvim/api.vim

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
scriptencoding utf-8
2+
let s:save_cpo = &cpo
3+
set cpo&vim
4+
5+
let s:funcs = {}
6+
7+
function! s:buf_line_count(bufnr) abort
8+
if bufnr('%') == a:bufnr
9+
return line('$')
10+
endif
11+
let lines = getbufline(a:bufnr, 1, '$')
12+
return len(lines)
13+
endfunction
14+
15+
function! s:switch_tab(tabnr)
16+
pyx << EOF
17+
tabnr = int(vim.eval('a:tabnr'))
18+
for t in vim.tabpages:
19+
if t.number == tabnr:
20+
tab = t
21+
break
22+
EOF
23+
endfunction
24+
25+
function! s:switch_win(win_id)
26+
let [tabnr, winnr] = win_id2tabwin(a:win_id)
27+
if tabnr == 0 | return | endif
28+
execute 'pyx windows = vim.tabpages['.(tabnr - 1).'].windows'
29+
execute 'pyx win = windows['.(winnr - 1).']'
30+
return 1
31+
endfunction
32+
33+
function! s:funcs.feedkeys(keys, mode, escape_csi)
34+
call feedkeys(a:keys, a:mode)
35+
endfunction
36+
37+
function! s:funcs.win_get_position(win_id) abort
38+
if !s:switch_win(a:win_id) | return v:null | endif
39+
return pyxeval('[win.row, win.col]')
40+
endfunction
41+
42+
function! s:funcs.win_get_cursor(win_id) abort
43+
if !s:switch_win(a:win_id) | return v:null | endif
44+
return pyxeval('win.cursor')
45+
endfunction
46+
47+
function! s:funcs.win_get_height(win_id) abort
48+
if !s:switch_win(a:win_id) | return v:null | endif
49+
return pyxeval('win.height')
50+
endfunction
51+
52+
function! s:funcs.win_get_width(win_id) abort
53+
if !s:switch_win(a:win_id) | return v:null | endif
54+
return pyxeval('win.width')
55+
endfunction
56+
57+
function! s:funcs.win_get_var(win_id, name) abort
58+
if !s:switch_win(a:win_id) | return v:null | endif
59+
return pyxeval('win.vars["'.a:name.'"]')
60+
endfunction
61+
62+
function! s:funcs.win_set_width(win_id, width) abort
63+
if !s:switch_win(a:win_id) | return v:null | endif
64+
execute 'pyx win.width='.a:width
65+
redraw
66+
endfunction
67+
68+
function! s:funcs.win_get_option(win_id, name) abort
69+
if !s:switch_win(a:win_id) | return v:null | endif
70+
return pyxeval('win.options["'.a:name.'"]')
71+
endfunction
72+
73+
function! s:funcs.win_set_height(win_id, height) abort
74+
if !s:switch_win(a:win_id) | return v:null | endif
75+
execute 'pyx win.height='.a:height
76+
redraw
77+
endfunction
78+
79+
function! s:funcs.win_set_option(win_id, name, value) abort
80+
if !s:switch_win(a:win_id) | return v:null | endif
81+
if type(a:value) == 0
82+
execute 'pyx win.options["'.a:name.'"] = '.a:value.
83+
else
84+
execute 'pyx win.options["'.a:name.'"] = vim.eval("a:value")'
85+
endif
86+
endfunction
87+
88+
function! s:funcs.win_set_var(win_id, name, value) abort
89+
if !s:switch_win(a:win_id) | return v:null | endif
90+
if type(a:value) == 0
91+
execute 'pyx value = '.a:value
92+
else
93+
execute 'pyx value = vim.eval("a:value")'
94+
endif
95+
execute 'pyx win.vars["'.a:name.'"] = value'
96+
endfunction
97+
98+
function! s:funcs.win_del_var(win_id, name) abort
99+
if !s:switch_win(a:win_id) | return v:null | endif
100+
execute 'pyx win.vars["'.a:name.'"] = None'
101+
endfunction
102+
103+
function! s:funcs.win_is_valid(win_id) abort
104+
if !s:switch_win(a:win_id) | return v:null | endif
105+
return pyxeval('win.valid')
106+
endfunction
107+
108+
function! s:funcs.win_get_number(win_id) abort
109+
return win_id2win(a:win_id)
110+
endfunction
111+
112+
function! s:funcs.win_set_cursor(win_id, pos) abort
113+
if !s:switch_win(a:win_id) | return v:null | endif
114+
let [lnum, col] = a:pos
115+
execute 'pyx win.cursor = ('.lnum.','.col.')'
116+
endfunction
117+
118+
function! s:funcs.buf_line_count(bufnr) abort
119+
return s:buf_line_count(a:bufnr)
120+
endfunction
121+
122+
function! s:funcs.buf_attach(...)
123+
" not supported
124+
return 0
125+
endfunction
126+
127+
function! s:funcs.buf_detach()
128+
" not supported
129+
return 0
130+
endfunction
131+
132+
function! s:funcs.buf_get_lines(bufnr, start, end, strict) abort
133+
let lines = getbufline(a:bufnr, 1, '$')
134+
let start = a:start < 0 ? a:start + 1 : a:start
135+
let end = a:end < 0 ? a:end + 1 : a:end
136+
if a:strict && end > len(lines)
137+
throw 'line number out of range: '. end
138+
endif
139+
return lines[start : end - 1]
140+
endfunction
141+
142+
function! s:funcs.buf_set_lines(bufnr, start, end, strict, ...) abort
143+
let replacement = get(a:, 1, [])
144+
let lineCount = s:buf_line_count(a:bufnr)
145+
let startLnum = a:start >= 0 ? a:start + 1 : lineCount + a:start + 1
146+
let end = a:end >= 0 ? a:end : lineCount + a:end + 1
147+
let delCount = end - (startLnum - 1)
148+
" TODO strict check
149+
if delCount == len(replacement)
150+
call setbufline(a:bufnr, startLnum, replacement)
151+
elseif delCount > 0
152+
call deletebufline(a:bufnr, startLnum, startLnum + delCount - 1)
153+
let len = len(replacement)
154+
if len > 0
155+
if startLnum == 1
156+
call setbufline(a:bufnr, 1, replacement[0])
157+
if len > 1
158+
call appendbufline(a:bufnr, 1, replacement[1:])
159+
endif
160+
else
161+
call appendbufline(a:bufnr, startLnum - 1, replacement)
162+
endif
163+
endif
164+
endif
165+
redraw
166+
endfunction
167+
168+
function! s:funcs.buf_set_name(bufnr, name) abort
169+
pyx << EOF
170+
name = vim.eval('a:name')
171+
bufnr = int(vim.eval('a:bufnr'))
172+
for b in vim.buffers:
173+
if b.number = bufnr:
174+
b.name = name
175+
break
176+
EOF
177+
endfunction
178+
179+
function! s:funcs.command(command) abort
180+
execute a:command
181+
endfunction
182+
183+
function! s:funcs.set_current_dir(dir) abort
184+
execute 'cd '.a:dir
185+
endfunction
186+
187+
function! s:funcs.set_var(name, value) abort
188+
execute 'let g:'.a:name.'= a:value'
189+
endfunction
190+
191+
function! s:funcs.del_var(name) abort
192+
execute 'unlet g:'.a:name
193+
endfunction
194+
195+
function! s:funcs.set_option(name, value) abort
196+
execute 'let &'.a:name.' = a:value'
197+
endfunction
198+
199+
function! s:funcs.set_current_buf(bufnr) abort
200+
if !bufexists(a:bufnr) | return | endif
201+
execute 'buffer '.a:bufnr
202+
endfunction
203+
204+
function! s:funcs.set_current_win(win_id) abort
205+
let [tabnr, winnr] = win_id2tabwin(a:win_id)
206+
if tabnr == 0 | return | endif
207+
execute 'normal! '.tabnr.'gt'
208+
execute winnr.' wincmd w'
209+
endfunction
210+
211+
function! s:funcs.set_current_tabpage(tabnr) abort
212+
execute 'normal! '.a:tabnr.'gt'
213+
endfunction
214+
215+
function! s:funcs.tabpage_list_wins(tabnr)
216+
call s:switch_tab(a:tabnr)
217+
pyx << EOF
218+
res = []
219+
for w in tab.windows:
220+
winid = int(vim.eval('win_getid(%d,%d)' % (w.number, tab.number)))
221+
res.append(winid)
222+
EOF
223+
return pyxeval('res')
224+
endfunction
225+
226+
function! s:funcs.tabpage_get_var(tabnr, name)
227+
call s:switch_tab(a:tabnr)
228+
return pyxeval('tab.vars["'.a:name.'"]')
229+
endfunction
230+
231+
function! s:funcs.tabpage_set_var(tabnr, name, value)
232+
call s:switch_tab(a:tabnr)
233+
if type(a:value) == 0
234+
execute 'pyx tab.vars["'.a:name.'"] = '.a:value
235+
else
236+
execute 'pyx tab.vars["'.a:name.'"] = vim.eval("a:value")'
237+
endif
238+
endfunction
239+
240+
function! s:funcs.tabpage_del_var(tabnr, name)
241+
call s:switch_tab(a:tabnr)
242+
execute 'pyx tab.vars["'.a:name.'"] = None'
243+
endfunction
244+
245+
function! s:funcs.tabpage_is_valid()
246+
call s:switch_tab(a:tabnr)
247+
return pyxeval('tab.valid')
248+
endfunction
249+
250+
"Get the current window in a tabpage
251+
function! s:funcs.tabpage_get_win(tabnr)
252+
call s:switch_tab(a:tabnr)
253+
let wnr = pyxeval('tab.window.number')
254+
return win_getid(wnr, a:tabnr)
255+
endfunction
256+
257+
function! s:funcs.win_get_tabpage(win_id) abort
258+
if !s:switch_win(a:win_id) | return v:null | endif
259+
return pyxeval('win.tabpage.number')
260+
endfunction
261+
262+
function! s:funcs.list_wins() abort
263+
pyx << EOF
264+
wins = []
265+
for t in vim.tabpages:
266+
for w in t.windows:
267+
winid = int(vim.eval('win_getid(%d,%d)' % (w.number, t.number)))
268+
wins.append(winid)
269+
EOF
270+
return pyxeval('wins')
271+
endfunction
272+
273+
function! nvim#api#func_names() abort
274+
return keys(s:funcs)
275+
endfunction
276+
277+
function! nvim#api#call(method, ...) abort
278+
let args = get(a:, 1, [])
279+
return call(s:funcs[a:method], args)
280+
endfunction
281+
282+
let &cpo = s:save_cpo
283+
unlet s:save_cpo

autoload/nvim/rpc.vim

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
scriptencoding utf-8
2+
let s:save_cpo = &cpo
3+
set cpo&vim
4+
5+
let s:script = expand('<sfile>:h:h:h').'/lib/index.js'
6+
let s:channel = v:null
7+
" used for socket path
8+
call delete('/tmp/nvim')
9+
let s:tempname = '/tmp/nvim'
10+
11+
function! s:on_error(channel, msg)
12+
echohl Error | echom a:msg | echohl None
13+
endfunction
14+
15+
let s:start = 0
16+
17+
function! s:on_notify(channel, result)
18+
if a:result ==# 'ready'
19+
call system('echo "'.s:tempname.'" | pbcopy')
20+
echo s:tempname
21+
endif
22+
endfunction
23+
24+
function! nvim#rpc#start_server()
25+
let s:start = reltime()
26+
let job = job_start('node '.s:script, {
27+
\ 'in_mode': 'json',
28+
\ 'out_mode': 'json',
29+
\ 'err_mode': 'nl',
30+
\ 'callback': function('s:on_notify'),
31+
\ 'err_cb': function('s:on_error'),
32+
\ 'timeout': 3000
33+
\})
34+
let s:channel = job_getchannel(job)
35+
" send channel id to backend
36+
let info = ch_info(s:channel)
37+
let fns = nvim#api#func_names()
38+
let data = json_encode([0, ['ready', [info.id, fns, s:tempname]]])
39+
call ch_sendraw(s:channel, data."\n")
40+
endfunction
41+
42+
function! nvim#rpc#request(method, ...) abort
43+
let args = get(a:, 1, [])
44+
return ch_evalexpr(s:channel, [a:method, args])
45+
endfunction
46+
47+
function! nvim#rpc#notify(method, ...) abort
48+
" use 0 as vim request id
49+
let args = get(a:, 1, [])
50+
let data = json_encode([0, [a:method, args]])
51+
call ch_sendraw(s:channel, data."\n")
52+
endfunction
53+
54+
let &cpo = s:save_cpo
55+
unlet s:save_cpo

0 commit comments

Comments
 (0)