Skip to content

Commit ef99d0e

Browse files
committed
testing vim channels
0 parents  commit ef99d0e

File tree

6 files changed

+174
-0
lines changed

6 files changed

+174
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
chtest.log
2+
chtest.err
3+
server/__pycache__

plugin/chtest.vim

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function TestStart()
2+
let server = expand('<sfile>:p:h') . '/test.py'
3+
let err_log = expand('<sfile>:p:h') . '/chtest.err'
4+
let cmd = ['python3', server]
5+
return job_start(cmd, { 'in_mode': 'json',
6+
\ 'out_mode': 'json',
7+
\ 'out_cb': 'MyHandler',
8+
\ 'err_mode': 'raw',
9+
\ 'err_io': 'file',
10+
\ 'err_name': err_log})
11+
endfun
12+
13+
func MyHandler(ch, msg)
14+
echo "server says: " . a:msg
15+
endfunc
16+
17+
let g:my_job = TestStart()
18+
let g:my_ch = job_getchannel(g:my_job)

server.py

Whitespace-only changes.

server/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import (absolute_import, division, print_function)
2+
3+
import logging
4+
from multiprocessing import Queue
5+
from select import select
6+
7+
__metaclass__ = type # pylint: disable=invalid-name
8+
9+
10+
class Middleman():
11+
12+
def __init__(self, vimx):
13+
self.vimx = vimx
14+
self.queue = Queue(maxsize=2)
15+
self.logger = logging.getLogger(__name__)
16+
17+
def safe_exec(self, fn):
18+
""" fn should take a VimX object as the only argument. """
19+
self.queue.put(['exec', fn])
20+
21+
def loop(self):
22+
x = 1
23+
while True:
24+
ready, _, _ = select([self.vimx.ch_in, self.queue._reader], [], [], 2)
25+
for ev in ready:
26+
if ev == self.queue._reader:
27+
req = self.queue.get()
28+
if req[0] == 'exec':
29+
fn = req[1]
30+
fn(self.vimx)
31+
elif req[0] == 'exit':
32+
break
33+
elif ev == self.vimx.ch_in:
34+
vi = self.vimx.wait()
35+
self.logger.info("got: %s", vi)
36+
self.vimx.send('hello {}'.format(x))
37+
x += 1

server/vimx.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import logging
2+
import json
3+
4+
class VimX:
5+
6+
def __init__(self, ch_in, ch_out):
7+
self.ch_in = ch_in
8+
self.ch_out = ch_out
9+
self.counter = -1
10+
self.buffer = [] # buffer for 'positive' objects
11+
self.logger = logging.getLogger(__name__)
12+
13+
def wait(self, expect=0):
14+
""" Blocking function. Use with care!
15+
`expect` should either be 0 or a negative number. If `expect == 0`, any positive
16+
indexed object is returned. Otherwise, it will queue any positive objects until the
17+
first negative object is received. If the received negative object does not match
18+
`expect`, then a ValueError is raised.
19+
"""
20+
if expect > 0:
21+
raise AssertionError('expect <= 0')
22+
if expect == 0 and len(self.buffer) > 0:
23+
return self.pop()
24+
while True:
25+
s = self.ch_in.readline()
26+
self.logger.info("read: %s", s)
27+
ind, obj = json.loads(s)
28+
if (expect == 0 and ind < 0) or (expect < 0 and expect != ind):
29+
raise ValueError('Incorrect index received! {} != {}', expect, ind)
30+
elif expect < 0 and ind > 0:
31+
self.buffer.insert(0, obj)
32+
else:
33+
break
34+
return obj
35+
36+
def write(self, obj):
37+
s = json.dumps(obj)
38+
print(s, file=self.ch_out) # line break
39+
self.ch_out.flush()
40+
self.logger.info("write: %s", s)
41+
42+
def send(self, obj):
43+
self.write([0, obj])
44+
45+
def call(self, fname, *args, reply=True):
46+
obj = ['call', fname, args]
47+
if reply:
48+
obj += [self.counter]
49+
self.counter -= 1
50+
self.write(obj)
51+
if reply:
52+
re = self.wait(expect=self.counter)
53+
return re
54+
55+
def eval(self, expr, reply=True):
56+
obj = ['expr', expr]
57+
if reply:
58+
obj += [self.counter]
59+
self.counter -= 1
60+
self.write(obj)
61+
if reply:
62+
re = self.wait(expect=self.counter)
63+
return re
64+
65+
def command(self, cmd):
66+
obj = ['ex', expr]
67+
self.write(obj)
68+
69+
def echox(self, msg, level=1):
70+
""" Execute echom in vim using appropriate highlighting. """
71+
level_map = ['None', 'WarningMsg', 'ErrorMsg']
72+
msg = msg.strip().replace('"', '\\"').replace('\n', '\\n')
73+
self.command('echohl {} | echom "{}" | echohl None'.format(level_map[level], msg))
74+
75+
def buffer_add(self, name):
76+
""" Create a buffer (if it doesn't exist) and return its number. """
77+
bufnr = self.call('bufnr', name, 1)
78+
self.call('setbufvar', bufnr, '&bl', 1, reply=False)
79+
return bufnr
80+
81+
def sign_place(self, sign_id, name, bufnr, line):
82+
""" Place a sign at the specified location. """
83+
self.command("sign place {} name={} line={} buffer={}".format(sign_id, name, line, bufnr))
84+
85+
def sign_unplace(self, sign_id):
86+
""" Hide a sign with specified id. """
87+
self.command("sign unplace {}".format(sign_id))
88+
89+
def get_buffer_name(self, nr):
90+
""" Get the buffer name given its number. """
91+
return self.call('bufname', nr)
92+
93+
def abspath(self, relpath):
94+
vim_cwd = self.call("getcwd")
95+
return path.join(vim_cwd, relpath)

test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import logging
2+
from server.vimx import VimX
3+
from server import Middleman
4+
import sys
5+
6+
def main():
7+
print('hello from the other side', file=sys.stderr)
8+
ch_in = sys.stdin
9+
ch_out = sys.stdout
10+
vimx = VimX(ch_in, ch_out)
11+
12+
Middleman(vimx).loop()
13+
14+
if __name__ == '__main__':
15+
handler = logging.FileHandler('chtest.log', 'w')
16+
handler.formatter = logging.Formatter(
17+
'%(asctime)s [%(levelname)s @ '
18+
'%(filename)s:%(funcName)s:%(lineno)s] - %(message)s')
19+
logging.root.addHandler(handler)
20+
logging.root.setLevel(logging.DEBUG)
21+
main()

0 commit comments

Comments
 (0)