summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
authorw0rp <devw0rp@gmail.com>2018-07-02 23:49:47 +0100
committerw0rp <devw0rp@gmail.com>2018-07-02 23:49:47 +0100
commit01c68fedd63ccb9360134dba1cd2c722bc9f7006 (patch)
tree086ad02a035a20acc12cfe2a693cb8c9db901466 /autoload
parentb637b35ea89a5a756477b9e2545a2f36299ac5fa (diff)
downloadale-01c68fedd63ccb9360134dba1cd2c722bc9f7006.zip
#830 Implement a socket wrapper API for use with LSP connections
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale/job.vim27
-rw-r--r--autoload/ale/socket.vim137
-rw-r--r--autoload/ale/util.vim27
3 files changed, 166 insertions, 25 deletions
diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim
index 6ffc2a06..e0266cba 100644
--- a/autoload/ale/job.vim
+++ b/autoload/ale/job.vim
@@ -26,34 +26,11 @@ function! s:KillHandler(timer) abort
call job_stop(l:job, 'kill')
endfunction
-" Note that jobs and IDs are the same thing on NeoVim.
-function! ale#job#JoinNeovimOutput(job, last_line, data, mode, callback) abort
- if a:mode is# 'raw'
- call a:callback(a:job, join(a:data, "\n"))
- return ''
- endif
-
- let l:lines = a:data[:-2]
-
- if len(a:data) > 1
- let l:lines[0] = a:last_line . l:lines[0]
- let l:new_last_line = a:data[-1]
- else
- let l:new_last_line = a:last_line . get(a:data, 0, '')
- endif
-
- for l:line in l:lines
- call a:callback(a:job, l:line)
- endfor
-
- return l:new_last_line
-endfunction
-
function! s:NeoVimCallback(job, data, event) abort
let l:info = s:job_map[a:job]
if a:event is# 'stdout'
- let l:info.out_cb_line = ale#job#JoinNeovimOutput(
+ let l:info.out_cb_line = ale#util#JoinNeovimOutput(
\ a:job,
\ l:info.out_cb_line,
\ a:data,
@@ -61,7 +38,7 @@ function! s:NeoVimCallback(job, data, event) abort
\ ale#util#GetFunction(l:info.out_cb),
\)
elseif a:event is# 'stderr'
- let l:info.err_cb_line = ale#job#JoinNeovimOutput(
+ let l:info.err_cb_line = ale#util#JoinNeovimOutput(
\ a:job,
\ l:info.err_cb_line,
\ a:data,
diff --git a/autoload/ale/socket.vim b/autoload/ale/socket.vim
new file mode 100644
index 00000000..78eba737
--- /dev/null
+++ b/autoload/ale/socket.vim
@@ -0,0 +1,137 @@
+" 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)
+
+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 = {
+ \ 'mode': l:mode,
+ \ 'callback': a:options.callback,
+ \}
+
+ if !has('nvim')
+ " Vim
+ let l:channel_info.channel = ch_open(a:address, {
+ \ 'mode': l:mode,
+ \ 'waittime': 0,
+ \ 'callback': function('s:VimOutputCallback'),
+ \})
+ 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('tcp', 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
diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim
index 28ab8231..d7b6904c 100644
--- a/autoload/ale/util.vim
+++ b/autoload/ale/util.vim
@@ -46,6 +46,33 @@ if !exists('g:ale#util#nul_file')
endif
endif
+" Given a job, a buffered line of data, a list of parts of lines, a mode data
+" is being read in, and a callback, join the lines of output for a NeoVim job
+" or socket together, and call the callback with the joined output.
+"
+" Note that jobs and IDs are the same thing on NeoVim.
+function! ale#util#JoinNeovimOutput(job, last_line, data, mode, callback) abort
+ if a:mode is# 'raw'
+ call a:callback(a:job, join(a:data, "\n"))
+ return ''
+ endif
+
+ let l:lines = a:data[:-2]
+
+ if len(a:data) > 1
+ let l:lines[0] = a:last_line . l:lines[0]
+ let l:new_last_line = a:data[-1]
+ else
+ let l:new_last_line = a:last_line . get(a:data, 0, '')
+ endif
+
+ for l:line in l:lines
+ call a:callback(a:job, l:line)
+ endfor
+
+ return l:new_last_line
+endfunction
+
" Return the number of lines for a given buffer.
function! ale#util#GetLineCount(buffer) abort
return len(getbufline(a:buffer, 1, '$'))