1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
" Author: w0rp <devw0rp@gmail.com>
" Description: APIs for working with asynchronous sockets, with an API
" normalised between Vim 8 and NeoVim. Socket connections only work in NeoVim
" 0.3+, and silently do nothing in earlier NeoVim versions.
"
" Important functions are described below. They are:
"
" ale#socket#Open(address, options) -> channel_id (>= 0 if successful)
" ale#socket#IsOpen(channel_id) -> 1 if open, 0 otherwise
" ale#socket#Close(channel_id)
" ale#socket#Send(channel_id, data)
" ale#socket#GetAddress(channel_id) -> Return the address for a job
let s:channel_map = get(s:, 'channel_map', {})
function! s:VimOutputCallback(channel, data) abort
let l:channel_id = ch_info(a:channel).id
" Only call the callbacks for jobs which are valid.
if l:channel_id >= 0 && has_key(s:channel_map, l:channel_id)
call ale#util#GetFunction(s:channel_map[l:channel_id].callback)(l:channel_id, a:data)
endif
endfunction
function! s:NeoVimOutputCallback(channel_id, data, event) abort
let l:info = s:channel_map[a:channel_id]
if a:event is# 'data'
let l:info.last_line = ale#util#JoinNeovimOutput(
\ a:channel_id,
\ l:info.last_line,
\ a:data,
\ l:info.mode,
\ ale#util#GetFunction(l:info.callback),
\)
endif
endfunction
" Open a socket for a given address. The following options are accepted:
"
" callback - A callback for receiving input. (required)
"
" A non-negative number representing a channel ID will be returned is the
" connection was successful. 0 is a valid channel ID in Vim, so test if the
" connection ID is >= 0.
function! ale#socket#Open(address, options) abort
let l:mode = get(a:options, 'mode', 'raw')
let l:Callback = a:options.callback
let l:channel_info = {
\ 'address': a:address,
\ 'mode': l:mode,
\ 'callback': a:options.callback,
\}
if !has('nvim')
" Vim
let l:channel_options = {
\ 'mode': l:mode,
\ 'waittime': 0,
\ 'callback': function('s:VimOutputCallback'),
\}
" Use non-blocking writes for Vim versions that support the option.
if has('patch-8.1.350')
let l:channel_options.noblock = 1
endif
let l:channel_info.channel = ch_open(a:address, l:channel_options)
let l:vim_info = ch_info(l:channel_info.channel)
let l:channel_id = !empty(l:vim_info) ? l:vim_info.id : -1
elseif exists('*chansend') && exists('*sockconnect')
" NeoVim 0.3+
try
let l:channel_id = sockconnect(stridx(a:address, ':') != -1 ? 'tcp' : 'pipe',
\ a:address, {'on_data': function('s:NeoVimOutputCallback')})
let l:channel_info.last_line = ''
catch /connection failed/
let l:channel_id = -1
endtry
" 0 means the connection failed some times in NeoVim, so make the ID
" invalid to match Vim.
if l:channel_id is 0
let l:channel_id = -1
endif
let l:channel_info.channel = l:channel_id
else
" Other Vim versions.
let l:channel_id = -1
endif
if l:channel_id >= 0
let s:channel_map[l:channel_id] = l:channel_info
endif
return l:channel_id
endfunction
" Return 1 is a channel is open, 0 otherwise.
function! ale#socket#IsOpen(channel_id) abort
if !has_key(s:channel_map, a:channel_id)
return 0
endif
if has('nvim')
" In NeoVim, we have to check if this channel is in the global list.
return index(map(nvim_list_chans(), 'v:val.id'), a:channel_id) >= 0
endif
let l:channel = s:channel_map[a:channel_id].channel
return ch_status(l:channel) is# 'open'
endfunction
" Close a socket, if it's still open.
function! ale#socket#Close(channel_id) abort
" IsRunning isn't called here, so we don't check nvim_list_chans()
if !has_key(s:channel_map, a:channel_id)
return 0
endif
let l:channel = remove(s:channel_map, a:channel_id).channel
if has('nvim')
silent! call chanclose(l:channel)
elseif ch_status(l:channel) is# 'open'
call ch_close(l:channel)
endif
endfunction
" Send some data to a socket.
function! ale#socket#Send(channel_id, data) abort
if !has_key(s:channel_map, a:channel_id)
return
endif
let l:channel = s:channel_map[a:channel_id].channel
if has('nvim')
call chansend(l:channel, a:data)
else
call ch_sendraw(l:channel, a:data)
endif
endfunction
" Get an address for a channel, or an empty string.
function! ale#socket#GetAddress(channel_id) abort
return get(get(s:channel_map, a:channel_id, {}), 'address', '')
endfunction
|