Fixed vim and zsh
This commit is contained in:
3
vim/plugins/vim-hug-neovim-rpc/.gitignore
vendored
Normal file
3
vim/plugins/vim-hug-neovim-rpc/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
*.pyc
|
||||
__pycache__
|
158
vim/plugins/vim-hug-neovim-rpc/README.md
Normal file
158
vim/plugins/vim-hug-neovim-rpc/README.md
Normal file
@ -0,0 +1,158 @@
|
||||
|
||||
# vim-hug-neovim-rpc
|
||||
|
||||
This is an **experimental project**, trying to build a compatibility layer for
|
||||
[neovim rpc client](https://github.com/neovim/python-client) working on vim8.
|
||||
I started this project because I want to fix the [vim8
|
||||
support](https://github.com/roxma/nvim-completion-manager/issues/14) issue for
|
||||
[nvim-completion-manager](https://github.com/roxma/nvim-completion-manager).
|
||||
|
||||
Since this is a general purpose module, other plugins needing rpc support may
|
||||
benefit from this project. However, there're many neovim rpc methods I haven't
|
||||
implemented yet, which make this an experimental plugin. **Please fork and
|
||||
open a PR if you get any idea on improving it**.
|
||||
|
||||
***Tip: for porting neovim rplugin to vim8, you might need
|
||||
[roxma/nvim-yarp](https://github.com/roxma/nvim-yarp)***
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
1. vim8
|
||||
2. If `has('pythonx')` and `set pyxversion=3`
|
||||
- same requirements as `4. has('python3')`
|
||||
2. Else if `has('pythonx')` and `set pyxversion=2`
|
||||
- same requirements as `5. has('python')`
|
||||
4. Else if `has('python3')`
|
||||
- [neovim/python-client](https://github.com/neovim/python-client). (`pip3
|
||||
install neovim`). There should be no error when you execute `:python3
|
||||
import neovim`
|
||||
5. Else if `has('python')`
|
||||
- [neovim/python-client](https://github.com/neovim/python-client). (`pip
|
||||
install neovim`). There should be no error when you execute `:python
|
||||
import neovim`
|
||||
6. `set encoding=utf-8` in your vimrc.
|
||||
|
||||
***Use `:echo neovim_rpc#serveraddr()` to test the installation***. It should print
|
||||
something like `127.0.0.1:51359`.
|
||||
|
||||
## API
|
||||
|
||||
| Function | Similar to neovim's |
|
||||
|----------------------------------------------|------------------------------------------------|
|
||||
| `neovim_rpc#serveraddr()` | `v:servername` |
|
||||
| `neovim_rpc#jobstart(cmd,...)` | `jobstart({cmd}[, {opts}])` |
|
||||
| `neovim_rpc#jobstop(jobid)` | `jobstop({job})` |
|
||||
| `neovim_rpc#rpcnotify(channel,event,...)` | `rpcnotify({channel}, {event}[, {args}...])` |
|
||||
| `neovim_rpc#rpcrequest(channel, event, ...)` | `rpcrequest({channel}, {method}[, {args}...])` |
|
||||
|
||||
Note that `neovim_rpc#jobstart` only support these options:
|
||||
|
||||
- `on_stdout`
|
||||
- `on_stderr`
|
||||
- `on_exit`
|
||||
- `detach`
|
||||
|
||||
## Incompatibility issues
|
||||
|
||||
- Cannot pass `Funcref` object to python client. Pass function name instead.
|
||||
- Python `None` will be converted to `''` instead of `v:null` into vimscript.
|
||||
See [vim#2246](https://github.com/vim/vim/issues/2246)
|
||||
- The following neovim-only API will be ignored quietly:
|
||||
- `nvim_buf_add_highlight`
|
||||
- `nvim_buf_clear_highlight`
|
||||
|
||||
## Overall Implementation
|
||||
|
||||
```
|
||||
"vim-hug-neovim-rpc - Sequence Diagram"
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
┌───┐ ┌──────────┐ ┌───────────┐ ┌──────┐
|
||||
│VIM│ │VIM Server│ │NVIM Server│ │Client│
|
||||
└─┬─┘ └────┬─────┘ └─────┬─────┘ └──┬───┘
|
||||
│ Launch thread │ │ │
|
||||
│───────────────────> │ │
|
||||
│ │ │ │
|
||||
│ Launch thread │ │
|
||||
│─────────────────────────────────────────────────>│ │
|
||||
│ │ │ │
|
||||
│ `ch_open` connect │ │ │
|
||||
│───────────────────> │ │
|
||||
│ │ │ │
|
||||
│ │────┐ │ │
|
||||
│ │ │ Launch VimHandler thread│ │
|
||||
│ │<───┘ │ │
|
||||
│ │ │ │
|
||||
│ │ │ Connect │
|
||||
│ │ │<─────────────────────────────
|
||||
│ │ │ │
|
||||
│ │ ────┐
|
||||
│ │ │ Launch NvimHandler thread
|
||||
│ │ <───┘
|
||||
│ │ │ │
|
||||
│ │ │ Request (msgpack rpc) │
|
||||
│ │ │<─────────────────────────────
|
||||
│ │ │ │
|
||||
│ │ ────┐ │
|
||||
│ │ │ Request enqueue │
|
||||
│ │ <───┘ │
|
||||
│ │ │ │
|
||||
│ │ notify (method call) │ │
|
||||
│ │ <────────────────────────────│ │
|
||||
│ │ │ │
|
||||
│ notify (json rpc) │ │ │
|
||||
│<─────────────────── │ │
|
||||
│ │ │ │
|
||||
────┐ │ │
|
||||
│ Request dequeue │ │
|
||||
<───┘ │ │
|
||||
│ │ │ │
|
||||
────┐ │ │ │
|
||||
│ Process │ │ │
|
||||
<───┘ │ │ │
|
||||
│ │ │ │
|
||||
│ │ Send response (msgpack rpc) │
|
||||
│────────────────────────────────────────────────────────────────────────────────>
|
||||
┌─┴─┐ ┌────┴─────┐ ┌─────┴─────┐ ┌──┴───┐
|
||||
│VIM│ │VIM Server│ │NVIM Server│ │Client│
|
||||
└───┘ └──────────┘ └───────────┘ └──────┘
|
||||
```
|
||||
|
||||
<!--
|
||||
@startuml
|
||||
|
||||
title "vim-hug-neovim-rpc - Sequence Diagram"
|
||||
|
||||
VIM -> "VIM Server": Launch thread
|
||||
VIM -> "NVIM Server": Launch thread
|
||||
VIM -> "VIM Server": `ch_open` connect
|
||||
"VIM Server" -> "VIM Server": Launch VimHandler thread
|
||||
|
||||
Client-> "NVIM Server": Connect
|
||||
"NVIM Server" -> "NVIM Server": Launch NvimHandler thread
|
||||
Client -> "NVIM Server": Request (msgpack rpc)
|
||||
"NVIM Server" -> "NVIM Server": Request enqueue
|
||||
"NVIM Server" -> "VIM Server": notify (method call)
|
||||
"VIM Server" -> VIM: notify (json rpc)
|
||||
VIM -> VIM: Request dequeue
|
||||
VIM -> VIM: Process
|
||||
VIM -> Client: Send response (msgpack rpc)
|
||||
|
||||
@enduml
|
||||
-->
|
||||
|
||||
## Debugging
|
||||
|
||||
Add logging settigns to your vimrc. Log files will be generated with prefix
|
||||
`/tmp/nvim_log`. An alternative is to export environment variables before
|
||||
starting vim/nvim.
|
||||
|
||||
```vim
|
||||
let $NVIM_PYTHON_LOG_FILE="/tmp/nvim_log"
|
||||
let $NVIM_PYTHON_LOG_LEVEL="DEBUG"
|
||||
```
|
||||
|
181
vim/plugins/vim-hug-neovim-rpc/autoload/neovim_rpc.vim
Normal file
181
vim/plugins/vim-hug-neovim-rpc/autoload/neovim_rpc.vim
Normal file
@ -0,0 +1,181 @@
|
||||
|
||||
|
||||
if has('pythonx')
|
||||
let s:py = 'pythonx'
|
||||
let s:pyeval = function('pyxeval')
|
||||
elseif has('python3')
|
||||
let s:py = 'python3'
|
||||
let s:pyeval = function('py3eval')
|
||||
else
|
||||
let s:py = 'python'
|
||||
let s:pyeval = function('pyeval')
|
||||
endif
|
||||
|
||||
func! neovim_rpc#serveraddr()
|
||||
if exists('g:_neovim_rpc_nvim_server')
|
||||
return g:_neovim_rpc_nvim_server
|
||||
endif
|
||||
|
||||
" must be utf-8
|
||||
if &encoding !=? "utf-8"
|
||||
throw '[vim-hug-neovim-rpc] requires `:set encoding=utf-8`'
|
||||
endif
|
||||
|
||||
try
|
||||
execute s:py . ' import neovim'
|
||||
catch
|
||||
call neovim_rpc#_error("failed executing: " . s:py . " import neovim")
|
||||
call neovim_rpc#_error(v:exception)
|
||||
throw '[vim-hug-neovim-rpc] requires `:' . s:py . ' import neovim` command to work'
|
||||
endtry
|
||||
|
||||
execute s:py . ' import neovim_rpc_server'
|
||||
let l:servers = s:pyeval('neovim_rpc_server.start()')
|
||||
|
||||
let g:_neovim_rpc_nvim_server = l:servers[0]
|
||||
let g:_neovim_rpc_vim_server = l:servers[1]
|
||||
|
||||
let g:_neovim_rpc_main_channel = ch_open(g:_neovim_rpc_vim_server)
|
||||
|
||||
" close channel before vim exit
|
||||
" au VimLeavePre * let s:leaving = 1 | execute s:py . ' neovim_rpc_server.stop()'
|
||||
|
||||
" identify myself
|
||||
call ch_sendexpr(g:_neovim_rpc_main_channel,'neovim_rpc_setup')
|
||||
|
||||
return g:_neovim_rpc_nvim_server
|
||||
endfunc
|
||||
|
||||
" elegant python function call wrapper
|
||||
func! neovim_rpc#pyxcall(func,...)
|
||||
execute s:py . ' import vim, json'
|
||||
let g:neovim_rpc#_tmp_args = copy(a:000)
|
||||
let l:ret = s:pyeval(a:func . '(*vim.vars["neovim_rpc#_tmp_args"])')
|
||||
unlet g:neovim_rpc#_tmp_args
|
||||
return l:ret
|
||||
endfunc
|
||||
|
||||
" supported opt keys:
|
||||
" - on_stdout
|
||||
" - on_stderr
|
||||
" - on_exit
|
||||
" - detach
|
||||
func! neovim_rpc#jobstart(cmd,...)
|
||||
|
||||
let l:opts = {}
|
||||
if len(a:000)
|
||||
let l:opts = a:1
|
||||
endif
|
||||
|
||||
let l:real_opts = {'mode': 'raw'}
|
||||
if has_key(l:opts,'detach') && l:opts['detach']
|
||||
let l:real_opts['stoponexit'] = ''
|
||||
endif
|
||||
|
||||
if has_key(l:opts,'on_stdout')
|
||||
let l:real_opts['out_cb'] = function('neovim_rpc#_on_stdout')
|
||||
endif
|
||||
if has_key(l:opts,'on_stderr')
|
||||
let l:real_opts['err_cb'] = function('neovim_rpc#_on_stderr')
|
||||
endif
|
||||
let l:real_opts['exit_cb'] = function('neovim_rpc#_on_exit')
|
||||
|
||||
let l:job = job_start(a:cmd, l:real_opts)
|
||||
let l:jobid = ch_info(l:job)['id']
|
||||
|
||||
let g:_neovim_rpc_jobs[l:jobid] = {'cmd':a:cmd, 'opts': l:opts, 'job': l:job}
|
||||
|
||||
return l:jobid
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#jobstop(jobid)
|
||||
let l:job = g:_neovim_rpc_jobs[a:jobid]['job']
|
||||
return job_stop(l:job)
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#rpcnotify(channel,event,...)
|
||||
call neovim_rpc#pyxcall('neovim_rpc_server.rpcnotify',a:channel,a:event,a:000)
|
||||
endfunc
|
||||
|
||||
let s:rspid = 1
|
||||
func! neovim_rpc#rpcrequest(channel, event, ...)
|
||||
let s:rspid = s:rspid + 1
|
||||
|
||||
" a unique key for storing response
|
||||
let rspid = '' . s:rspid
|
||||
|
||||
" neovim's rpcrequest doesn't have timeout
|
||||
let opt = {'timeout': 24 * 60 * 60 * 1000}
|
||||
let args = ['rpcrequest', a:channel, a:event, a:000, rspid]
|
||||
call ch_evalexpr(g:_neovim_rpc_main_channel, args, opt)
|
||||
|
||||
let expr = 'json.dumps(neovim_rpc_server.responses.pop("' . rspid . '"))'
|
||||
|
||||
execute s:py ' import neovim_rpc_server, json'
|
||||
let [err, result] = json_decode(s:pyeval(expr))
|
||||
if err
|
||||
if type(err) == type('')
|
||||
throw err
|
||||
endif
|
||||
throw err[1]
|
||||
endif
|
||||
return result
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_stdout(job,data)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
" convert to neovim style function call
|
||||
call call(l:opts['on_stdout'],[l:jobid,split(a:data,"\n",1),'stdout'],l:opts)
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_stderr(job,data)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
" convert to neovim style function call
|
||||
call call(l:opts['on_stderr'],[l:jobid,split(a:data,"\n",1),'stderr'],l:opts)
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_exit(job,status)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
unlet g:_neovim_rpc_jobs[l:jobid]
|
||||
if has_key(l:opts, 'on_exit')
|
||||
" convert to neovim style function call
|
||||
call call(l:opts['on_exit'],[l:jobid,a:status,'exit'],l:opts)
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_callback()
|
||||
execute s:py . ' neovim_rpc_server.process_pending_requests()'
|
||||
endfunc
|
||||
|
||||
let g:_neovim_rpc_main_channel = -1
|
||||
let g:_neovim_rpc_jobs = {}
|
||||
|
||||
let s:leaving = 0
|
||||
|
||||
func! neovim_rpc#_error(msg)
|
||||
if mode() == 'i'
|
||||
" NOTE: side effect, sorry, but this is necessary
|
||||
set nosmd
|
||||
endif
|
||||
echohl ErrorMsg
|
||||
echom '[vim-hug-neovim-rpc] ' . a:msg
|
||||
echohl None
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_nvim_err_write(msg)
|
||||
if mode() == 'i'
|
||||
" NOTE: side effect, sorry, but this is necessary
|
||||
set nosmd
|
||||
endif
|
||||
echohl ErrorMsg
|
||||
let g:error = a:msg
|
||||
echom a:msg
|
||||
echohl None
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_nvim_out_write(msg)
|
||||
echom a:msg
|
||||
endfunc
|
100
vim/plugins/vim-hug-neovim-rpc/pythonx/neovim_rpc_methods.py
Normal file
100
vim/plugins/vim-hug-neovim-rpc/pythonx/neovim_rpc_methods.py
Normal file
@ -0,0 +1,100 @@
|
||||
# vim:set et sw=4 ts=8:
|
||||
import vim
|
||||
import json
|
||||
import msgpack
|
||||
|
||||
# vim's python binding doesn't have the `call` method, wrap it here
|
||||
def nvim_call_function(method,args):
|
||||
vim.vars['_neovim_rpc_tmp_args'] = args
|
||||
# vim.eval('getcurpos()') return an array of string, it should be an array
|
||||
# of int. Use json_encode to workaround this
|
||||
return vim.bindeval('call("%s",g:_neovim_rpc_tmp_args)' % method)
|
||||
|
||||
def nvim_get_current_buf():
|
||||
return vim.current.buffer
|
||||
|
||||
def nvim_list_bufs():
|
||||
return list(vim.buffers)
|
||||
|
||||
# {'return_type': 'Integer', 'since': 1, 'method': True, 'parameters': [['Buffer', 'buffer']], 'name': 'nvim_buf_get_number'}
|
||||
def nvim_buf_get_number(buf):
|
||||
return buf.number
|
||||
|
||||
def nvim_buf_get_name(buffer):
|
||||
return buffer.name
|
||||
|
||||
def nvim_get_var(name):
|
||||
return vim.vars[name]
|
||||
|
||||
def nvim_set_var(name,val):
|
||||
vim.vars[name] = val
|
||||
return val
|
||||
|
||||
def nvim_buf_get_var(buffer,name):
|
||||
return json.loads(vim.eval('json_encode(getbufvar(%s, "%s"))' % (buffer.number, name)))
|
||||
|
||||
def nvim_buf_set_var(buffer,name,val):
|
||||
buffer.vars[name] = val
|
||||
|
||||
def nvim_buf_get_lines(buffer,start,end,*args):
|
||||
if start < 0:
|
||||
start = len(buffer) + 1 + start
|
||||
if end < 0:
|
||||
end = len(buffer) + 1 + end
|
||||
return buffer[start:end]
|
||||
|
||||
def nvim_eval(expr):
|
||||
return nvim_call_function('eval',[expr])
|
||||
|
||||
def nvim_buf_set_lines(buffer,start,end,err,lines):
|
||||
if start < 0:
|
||||
start = len(buffer) + 1 + start
|
||||
if end < 0:
|
||||
end = len(buffer) + 1 + end
|
||||
buffer[start:end] = lines
|
||||
|
||||
if nvim_call_function('bufwinnr',[buffer.number])!=-1:
|
||||
# vim needs' redraw to update the screen, it seems to be a bug
|
||||
vim.command('redraw')
|
||||
|
||||
buffer_set_lines = nvim_buf_set_lines
|
||||
|
||||
def buffer_line_count(buffer):
|
||||
return len(buffer)
|
||||
|
||||
def nvim_buf_line_count(buffer):
|
||||
return len(buffer)
|
||||
|
||||
def nvim_get_option(name):
|
||||
return vim.options[name]
|
||||
|
||||
def nvim_buf_get_option(buf, name):
|
||||
return buf.options[name]
|
||||
|
||||
def nvim_set_option(name, val):
|
||||
vim.options[name] = val
|
||||
|
||||
def nvim_command(cmd):
|
||||
vim.command(cmd)
|
||||
|
||||
def nvim_get_current_win():
|
||||
return vim.current.window
|
||||
|
||||
def nvim_win_get_cursor(window):
|
||||
return window.cursor
|
||||
|
||||
def nvim_out_write(s):
|
||||
nvim_call_function('neovim_rpc#_nvim_out_write', [s])
|
||||
|
||||
def nvim_err_write(s):
|
||||
nvim_call_function('neovim_rpc#_nvim_err_write', [s])
|
||||
|
||||
# NOTE https://github.com/autozimu/LanguageClient-neovim/pull/151#issuecomment-339198527
|
||||
# TODO
|
||||
def nvim_buf_add_highlight(buf, src_id, *args):
|
||||
return src_id
|
||||
|
||||
# NOTE https://github.com/autozimu/LanguageClient-neovim/pull/151#issuecomment-339198527
|
||||
# TODO
|
||||
def nvim_buf_clear_highlight(*args):
|
||||
pass
|
@ -0,0 +1,56 @@
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import vim
|
||||
import logging
|
||||
import msgpack
|
||||
import neovim_rpc_server_api_info
|
||||
import neovim_rpc_methods
|
||||
import threading
|
||||
import socket
|
||||
import time
|
||||
from neovim.api.common import decode_if_bytes
|
||||
|
||||
BUFFER_TYPE = type(vim.current.buffer)
|
||||
BUFFER_TYPE_ID = neovim_rpc_server_api_info.API_INFO['types']['Buffer']['id']
|
||||
WINDOW_TYPE = type(vim.current.window)
|
||||
WINDOW_TYPE_ID = neovim_rpc_server_api_info.API_INFO['types']['Window']['id']
|
||||
|
||||
def walk(fn, obj):
|
||||
if type(obj) in [list, tuple, vim.List]:
|
||||
return list(walk(fn, o) for o in obj)
|
||||
if type(obj) in [dict, vim.Dictionary]:
|
||||
return dict((walk(fn, k), walk(fn, v)) for k, v in
|
||||
obj.items())
|
||||
return fn(obj)
|
||||
|
||||
def from_client(msg):
|
||||
|
||||
def handler(obj):
|
||||
if type(obj) is msgpack.ExtType:
|
||||
if obj.code == BUFFER_TYPE_ID:
|
||||
return vim.buffers[msgpack.unpackb(obj.data)]
|
||||
if obj.code == WINDOW_TYPE_ID:
|
||||
return vim.windows[msgpack.unpackb(obj.data) - 1]
|
||||
elif obj is None:
|
||||
return ''
|
||||
if sys.version_info.major!=2:
|
||||
# python3 needs decode
|
||||
obj = decode_if_bytes(obj)
|
||||
return obj
|
||||
|
||||
return walk(handler,msg)
|
||||
|
||||
def to_client(msg):
|
||||
def handler( obj):
|
||||
if type(obj) == BUFFER_TYPE:
|
||||
return msgpack.ExtType(BUFFER_TYPE_ID, msgpack.packb(obj.number))
|
||||
if type(obj) == WINDOW_TYPE:
|
||||
return msgpack.ExtType(WINDOW_TYPE_ID, msgpack.packb(obj.number))
|
||||
if type(obj) == vim.Function:
|
||||
return obj.name
|
||||
return obj
|
||||
return walk(handler, msg)
|
||||
|
436
vim/plugins/vim-hug-neovim-rpc/pythonx/neovim_rpc_server.py
Normal file
436
vim/plugins/vim-hug-neovim-rpc/pythonx/neovim_rpc_server.py
Normal file
@ -0,0 +1,436 @@
|
||||
# vim:set et sw=4 ts=8:
|
||||
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import vim
|
||||
import logging
|
||||
import msgpack
|
||||
import neovim_rpc_server_api_info
|
||||
import neovim_rpc_methods
|
||||
import threading
|
||||
import socket
|
||||
import time
|
||||
import subprocess
|
||||
from neovim.api import common as neovim_common
|
||||
import neovim_rpc_protocol
|
||||
|
||||
vim_error = vim.Function('neovim_rpc#_error')
|
||||
|
||||
# protable devnull
|
||||
if sys.version_info.major==2:
|
||||
DEVNULL = open(os.devnull, 'wb')
|
||||
else:
|
||||
from subprocess import DEVNULL
|
||||
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
from Queue import Queue, Empty as QueueEmpty
|
||||
else:
|
||||
from queue import Queue, Empty as QueueEmpty
|
||||
|
||||
# NVIM_PYTHON_LOG_FILE=nvim.log NVIM_PYTHON_LOG_LEVEL=INFO vim test.md
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
import socketserver
|
||||
except ImportError:
|
||||
# Python 2
|
||||
import SocketServer as socketserver
|
||||
|
||||
# globals
|
||||
logger = logging.getLogger(__name__)
|
||||
# supress the annoying error message:
|
||||
# No handlers could be found for logger "neovim_rpc_server"
|
||||
logger.addHandler(logging.NullHandler())
|
||||
|
||||
request_queue = Queue()
|
||||
responses = {}
|
||||
|
||||
def _channel_id_new():
|
||||
with _channel_id_new._lock:
|
||||
_channel_id_new._counter += 1
|
||||
return _channel_id_new._counter
|
||||
# static local
|
||||
_channel_id_new._counter = 0
|
||||
_channel_id_new._lock = threading.Lock()
|
||||
|
||||
|
||||
class VimHandler(socketserver.BaseRequestHandler):
|
||||
|
||||
_lock = threading.Lock()
|
||||
_sock = None
|
||||
|
||||
@classmethod
|
||||
def notify(cls,cmd="call neovim_rpc#_callback()"):
|
||||
try:
|
||||
if not VimHandler._sock:
|
||||
return
|
||||
with VimHandler._lock:
|
||||
encoded = json.dumps(['ex', cmd])
|
||||
logger.info("sending notification: %s",encoded)
|
||||
VimHandler._sock.send(encoded.encode('utf-8'))
|
||||
except Exception as ex:
|
||||
logger.exception('VimHandler notify exception for [%s]: %s', cmd, ex)
|
||||
|
||||
@classmethod
|
||||
def notify_exited(cls,channel):
|
||||
try:
|
||||
cmd = "call neovim_rpc#_on_exit(%s)" % channel
|
||||
cls.notify(cmd)
|
||||
except Exception as ex:
|
||||
logger.exception('notify_exited for channel [%s] exception: %s',channel,ex)
|
||||
|
||||
# each connection is a thread
|
||||
def handle(self):
|
||||
logger.info("=== socket opened ===")
|
||||
data = None
|
||||
while True:
|
||||
try:
|
||||
rcv = self.request.recv(4096)
|
||||
# 16k buffer by default
|
||||
if data:
|
||||
data += rcv
|
||||
else:
|
||||
data = rcv
|
||||
except socket.error:
|
||||
logger.info("=== socket error ===")
|
||||
break
|
||||
except IOError:
|
||||
logger.info("=== socket closed ===")
|
||||
break
|
||||
if len(rcv) == 0:
|
||||
logger.info("=== socket closed ===")
|
||||
break
|
||||
logger.info("received: %s", data)
|
||||
try:
|
||||
decoded = json.loads(data.decode('utf-8'))
|
||||
except ValueError:
|
||||
logger.exception("json decoding failed, wait for more data")
|
||||
continue
|
||||
data = None
|
||||
|
||||
# Send a response if the sequence number is positive.
|
||||
# Negative numbers are used for "eval" responses.
|
||||
if len(decoded)>=2 and decoded[0] >= 0 and decoded[1] == 'neovim_rpc_setup':
|
||||
|
||||
VimHandler._sock = self.request
|
||||
|
||||
# initial setup
|
||||
encoded = json.dumps(['ex', "scall neovim_rpc#_callback()"])
|
||||
logger.info("sending {0}".format(encoded))
|
||||
self.request.send(encoded.encode('utf-8'))
|
||||
|
||||
else:
|
||||
# recognize as rpcrequest
|
||||
reqid = decoded[0]
|
||||
channel = decoded[1][1]
|
||||
event = decoded[1][2]
|
||||
args = decoded[1][3]
|
||||
rspid = decoded[1][4]
|
||||
NvimHandler.request(self.request,
|
||||
channel,
|
||||
reqid,
|
||||
event,
|
||||
args,
|
||||
rspid)
|
||||
|
||||
# wait for response
|
||||
|
||||
class SocketToStream():
|
||||
|
||||
def __init__(self,sock):
|
||||
self._sock = sock
|
||||
|
||||
def read(self,cnt):
|
||||
if cnt>4096:
|
||||
cnt = 4096
|
||||
return self._sock.recv(cnt)
|
||||
|
||||
def write(self,w):
|
||||
return self._sock.send(w)
|
||||
|
||||
class NvimHandler(socketserver.BaseRequestHandler):
|
||||
|
||||
channel_sockets = {}
|
||||
|
||||
def handle(self):
|
||||
|
||||
logger.info("=== socket opened for client ===")
|
||||
|
||||
channel = _channel_id_new()
|
||||
|
||||
sock = self.request
|
||||
chinfo = dict(sock=sock)
|
||||
NvimHandler.channel_sockets[channel] = chinfo
|
||||
|
||||
try:
|
||||
f = SocketToStream(sock)
|
||||
unpacker = msgpack.Unpacker(f)
|
||||
for unpacked in unpacker:
|
||||
logger.info("unpacked: %s", unpacked)
|
||||
|
||||
# response format:
|
||||
# - msg[0]: 1
|
||||
# - msg[1]: the request id
|
||||
# - msg[2]: error(if any), format: [code,str]
|
||||
# - msg[3]: result(if not errored)
|
||||
if int(unpacked[0]) == 1:
|
||||
unpacked = neovim_rpc_protocol.from_client(unpacked)
|
||||
reqid = int(unpacked[1])
|
||||
rspid, vimsock = chinfo[reqid]
|
||||
err = unpacked[2]
|
||||
result = unpacked[3]
|
||||
# VIM fails to parse response when there a sleep in neovim
|
||||
# client. I cannot figure out why. Use global responses to
|
||||
# workaround this issue.
|
||||
responses[rspid] = [err, result]
|
||||
content = [reqid, '']
|
||||
tosend = json.dumps(content)
|
||||
# vimsock.send
|
||||
vimsock.send(tosend.encode('utf-8'))
|
||||
chinfo.pop(reqid)
|
||||
continue
|
||||
|
||||
|
||||
request_queue.put((f,channel,unpacked))
|
||||
# notify vim in order to process request in main thread, and
|
||||
# avoiding the stupid json protocol
|
||||
VimHandler.notify()
|
||||
|
||||
logger.info('channel %s closed.', channel)
|
||||
|
||||
except:
|
||||
logger.exception('unpacker failed.')
|
||||
finally:
|
||||
try:
|
||||
NvimHandler.channel_sockets.pop(channel)
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def notify(cls,channel,event,args):
|
||||
try:
|
||||
channel = int(channel)
|
||||
if channel not in cls.channel_sockets:
|
||||
logger.info("channel[%s] not in NvimHandler", channel)
|
||||
return
|
||||
sock = cls.channel_sockets[channel]['sock']
|
||||
|
||||
# notification format:
|
||||
# - msg[0] type, which is 2
|
||||
# - msg[1] method
|
||||
# - msg[2] arguments
|
||||
content = [2, event, args]
|
||||
|
||||
logger.info("notify channel[%s]: %s", channel, content)
|
||||
packed = msgpack.packb(neovim_rpc_protocol.to_client(content))
|
||||
sock.send(packed)
|
||||
except Exception as ex:
|
||||
logger.exception("notify failed: %s", ex)
|
||||
|
||||
@classmethod
|
||||
def request(cls, vimsock, channel, reqid, event, args, rspid):
|
||||
try:
|
||||
reqid = int(reqid)
|
||||
channel = int(channel)
|
||||
chinfo = cls.channel_sockets[channel]
|
||||
|
||||
if channel not in cls.channel_sockets:
|
||||
logger.info("channel[%s] not in NvimHandler", channel)
|
||||
return
|
||||
|
||||
sock = chinfo['sock']
|
||||
# request format:
|
||||
# - msg[0] type, which is 0
|
||||
# - msg[1] request id
|
||||
# - msg[2] method
|
||||
# - msg[3] arguments
|
||||
content = [0, reqid, event, args]
|
||||
|
||||
chinfo[reqid] = [rspid, vimsock]
|
||||
|
||||
logger.info("request channel[%s]: %s", channel, content)
|
||||
packed = msgpack.packb(neovim_rpc_protocol.to_client(content))
|
||||
sock.send(packed)
|
||||
except Exception as ex:
|
||||
logger.exception("request failed: %s", ex)
|
||||
|
||||
@classmethod
|
||||
def shutdown(cls):
|
||||
# close all sockets
|
||||
for channel in list(cls.channel_sockets.keys()):
|
||||
chinfo = cls.channel_sockets.get(channel,None)
|
||||
if chinfo:
|
||||
sock = chinfo['sock']
|
||||
logger.info("closing client %s", channel)
|
||||
# if don't shutdown the socket, vim will never exit because the
|
||||
# recv thread is still blocking
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
sock.close()
|
||||
|
||||
|
||||
# copied from neovim python-client/neovim/__init__.py
|
||||
def _setup_logging(name):
|
||||
"""Setup logging according to environment variables."""
|
||||
logger = logging.getLogger(__name__)
|
||||
if 'NVIM_PYTHON_LOG_FILE' in os.environ:
|
||||
prefix = os.environ['NVIM_PYTHON_LOG_FILE'].strip()
|
||||
major_version = sys.version_info[0]
|
||||
logfile = '{}_py{}_{}'.format(prefix, major_version, name)
|
||||
handler = logging.FileHandler(logfile, 'w', encoding='utf-8')
|
||||
handler.formatter = logging.Formatter(
|
||||
'%(asctime)s [%(levelname)s @ '
|
||||
'%(filename)s:%(funcName)s:%(lineno)s] %(process)s - %(message)s')
|
||||
logging.root.addHandler(handler)
|
||||
level = logging.INFO
|
||||
if 'NVIM_PYTHON_LOG_LEVEL' in os.environ:
|
||||
l = getattr(logging,
|
||||
os.environ['NVIM_PYTHON_LOG_LEVEL'].strip(),
|
||||
level)
|
||||
if isinstance(l, int):
|
||||
level = l
|
||||
logger.setLevel(level)
|
||||
|
||||
def start():
|
||||
|
||||
_setup_logging('neovim_rpc_server')
|
||||
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
# 0 for random port
|
||||
global _vim_server
|
||||
global _nvim_server
|
||||
_vim_server = ThreadedTCPServer(("127.0.0.1", 0), VimHandler)
|
||||
_nvim_server = ThreadedTCPServer(("127.0.0.1", 0), NvimHandler)
|
||||
_vim_server.daemon_threads = True
|
||||
_nvim_server.daemon_threads = True
|
||||
|
||||
# Start a thread with the server -- that thread will then start one
|
||||
# more thread for each request
|
||||
main_server_thread = threading.Thread(target=_vim_server.serve_forever)
|
||||
clients_server_thread = threading.Thread(target=_nvim_server.serve_forever)
|
||||
|
||||
# Exit the server thread when the main thread terminates
|
||||
main_server_thread.daemon = True
|
||||
main_server_thread.start()
|
||||
clients_server_thread.daemon = True
|
||||
clients_server_thread.start()
|
||||
|
||||
return ["{addr[0]}:{addr[1]}".format(addr=_nvim_server.server_address), "{addr[0]}:{addr[1]}".format(addr=_vim_server.server_address)]
|
||||
|
||||
def process_pending_requests():
|
||||
|
||||
logger.info("process_pending_requests")
|
||||
while True:
|
||||
|
||||
item = None
|
||||
try:
|
||||
|
||||
item = request_queue.get(False)
|
||||
|
||||
f, channel, msg = item
|
||||
|
||||
msg = neovim_rpc_protocol.from_client(msg)
|
||||
|
||||
logger.info("get msg from channel [%s]: %s", channel, msg)
|
||||
|
||||
# request format:
|
||||
# - msg[0] type, which is 0
|
||||
# - msg[1] request id
|
||||
# - msg[2] method
|
||||
# - msg[3] arguments
|
||||
|
||||
# notification format:
|
||||
# - msg[0] type, which is 2
|
||||
# - msg[1] method
|
||||
# - msg[2] arguments
|
||||
|
||||
if msg[0] == 0:
|
||||
#request
|
||||
|
||||
req_typed, req_id, method, args = msg
|
||||
|
||||
try:
|
||||
err=None
|
||||
result = _process_request(channel,method,args)
|
||||
except Exception as ex:
|
||||
logger.exception("process failed: %s", ex)
|
||||
# error uccor
|
||||
err = [1,str(ex)]
|
||||
result = None
|
||||
|
||||
result = [1,req_id,err,result]
|
||||
logger.info("sending result: %s", result)
|
||||
packed = msgpack.packb(neovim_rpc_protocol.to_client(result))
|
||||
f.write(packed)
|
||||
logger.info("sent")
|
||||
if msg[0] == 2:
|
||||
# notification
|
||||
req_typed, method, args = msg
|
||||
try:
|
||||
result = _process_request(channel,method,args)
|
||||
logger.info('notification process result: [%s]', result)
|
||||
except Exception as ex:
|
||||
logger.exception("process failed: %s", ex)
|
||||
|
||||
except QueueEmpty as em:
|
||||
pass
|
||||
except Exception as ex:
|
||||
logger.exception("exception during process: %s", ex)
|
||||
finally:
|
||||
if item:
|
||||
request_queue.task_done()
|
||||
else:
|
||||
# item==None means the queue is empty
|
||||
break
|
||||
|
||||
def _process_request(channel,method,args):
|
||||
if method=='vim_get_api_info':
|
||||
# this is the first request send from neovim client
|
||||
api_info = neovim_rpc_server_api_info.API_INFO
|
||||
return [channel,api_info]
|
||||
if hasattr(neovim_rpc_methods,method):
|
||||
return getattr(neovim_rpc_methods,method)(*args)
|
||||
else:
|
||||
logger.error("method %s not implemented", method)
|
||||
vim_error("rpc method [%s] not implemented in pythonx/neovim_rpc_methods.py. Please send PR or contact the mantainer." % method)
|
||||
raise Exception('%s not implemented' % method)
|
||||
|
||||
def rpcnotify(channel,method,args):
|
||||
NvimHandler.notify(channel,method,args)
|
||||
|
||||
|
||||
def stop():
|
||||
|
||||
logger.info("stop begin")
|
||||
|
||||
# close tcp channel server
|
||||
_nvim_server.shutdown()
|
||||
_nvim_server.server_close()
|
||||
|
||||
# close the main channel
|
||||
try:
|
||||
vim.command('call ch_close(g:_neovim_rpc_main_channel)')
|
||||
except Exception as ex:
|
||||
logger.info("ch_close failed: %s", ex)
|
||||
|
||||
# remove all sockets
|
||||
NvimHandler.shutdown()
|
||||
|
||||
try:
|
||||
# stop the main channel
|
||||
_vim_server.shutdown()
|
||||
except Exception as ex:
|
||||
logger.info("_vim_server shutodwn failed: %s", ex)
|
||||
|
||||
try:
|
||||
_vim_server.server_close()
|
||||
except Exception as ex:
|
||||
logger.info("_vim_server close failed: %s", ex)
|
||||
|
||||
|
2374
vim/plugins/vim-hug-neovim-rpc/pythonx/neovim_rpc_server_api_info.py
Normal file
2374
vim/plugins/vim-hug-neovim-rpc/pythonx/neovim_rpc_server_api_info.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user