mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #7646 from bfredl/chan_buffered
Document and defer error message when buffered stream would overwrite channels dict key
This commit is contained in:
commit
ec86f4215f
@ -1,20 +0,0 @@
|
|||||||
" Common functionality for providers
|
|
||||||
|
|
||||||
let s:stderr = {}
|
|
||||||
|
|
||||||
function! provider#stderr_collector(chan_id, data, event)
|
|
||||||
let stderr = get(s:stderr, a:chan_id, [''])
|
|
||||||
let stderr[-1] .= a:data[0]
|
|
||||||
call extend(stderr, a:data[1:])
|
|
||||||
let s:stderr[a:chan_id] = stderr
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! provider#clear_stderr(chan_id)
|
|
||||||
if has_key(s:stderr, a:chan_id)
|
|
||||||
call remove(s:stderr, a:chan_id)
|
|
||||||
endif
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! provider#get_stderr(chan_id)
|
|
||||||
return get(s:stderr, a:chan_id, [])
|
|
||||||
endfunction
|
|
@ -7,7 +7,7 @@ let s:clipboard = {}
|
|||||||
|
|
||||||
" When caching is enabled, store the jobid of the xclip/xsel process keeping
|
" When caching is enabled, store the jobid of the xclip/xsel process keeping
|
||||||
" ownership of the selection, so we know how long the cache is valid.
|
" ownership of the selection, so we know how long the cache is valid.
|
||||||
let s:selection = { 'owner': 0, 'data': [], 'on_stderr': function('provider#stderr_collector') }
|
let s:selection = { 'owner': 0, 'data': [], 'stderr_buffered': v:true }
|
||||||
|
|
||||||
function! s:selection.on_exit(jobid, data, event) abort
|
function! s:selection.on_exit(jobid, data, event) abort
|
||||||
" At this point this nvim instance might already have launched
|
" At this point this nvim instance might already have launched
|
||||||
@ -16,12 +16,10 @@ function! s:selection.on_exit(jobid, data, event) abort
|
|||||||
let self.owner = 0
|
let self.owner = 0
|
||||||
endif
|
endif
|
||||||
if a:data != 0
|
if a:data != 0
|
||||||
let stderr = provider#get_stderr(a:jobid)
|
|
||||||
echohl WarningMsg
|
echohl WarningMsg
|
||||||
echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(stderr)
|
echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(self.stderr)
|
||||||
echohl None
|
echohl None
|
||||||
endif
|
endif
|
||||||
call provider#clear_stderr(a:jobid)
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
let s:selections = { '*': s:selection, '+': copy(s:selection) }
|
let s:selections = { '*': s:selection, '+': copy(s:selection) }
|
||||||
@ -142,12 +140,13 @@ function! s:clipboard.set(lines, regtype, reg) abort
|
|||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
let selection = s:selections[a:reg]
|
if s:selections[a:reg].owner > 0
|
||||||
if selection.owner > 0
|
|
||||||
" The previous provider instance should exit when the new one takes
|
" The previous provider instance should exit when the new one takes
|
||||||
" ownership, but kill it to be sure we don't fill up the job table.
|
" ownership, but kill it to be sure we don't fill up the job table.
|
||||||
call jobstop(selection.owner)
|
call jobstop(s:selections[a:reg].owner)
|
||||||
end
|
end
|
||||||
|
let s:selections[a:reg] = copy(s:selection)
|
||||||
|
let selection = s:selections[a:reg]
|
||||||
let selection.data = [a:lines, a:regtype]
|
let selection.data = [a:lines, a:regtype]
|
||||||
let argv = split(s:copy[a:reg], " ")
|
let argv = split(s:copy[a:reg], " ")
|
||||||
let selection.argv = argv
|
let selection.argv = argv
|
||||||
|
@ -3,7 +3,7 @@ if exists('g:loaded_node_provider')
|
|||||||
endif
|
endif
|
||||||
let g:loaded_node_provider = 1
|
let g:loaded_node_provider = 1
|
||||||
|
|
||||||
let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')}
|
let s:job_opts = {'rpc': v:true, 'stderr_buffered': v:true}
|
||||||
|
|
||||||
function! s:is_minimum_version(version, min_major, min_minor) abort
|
function! s:is_minimum_version(version, min_major, min_minor) abort
|
||||||
if empty(a:version)
|
if empty(a:version)
|
||||||
@ -73,19 +73,18 @@ function! provider#node#Require(host) abort
|
|||||||
call add(args, provider#node#Prog())
|
call add(args, provider#node#Prog())
|
||||||
|
|
||||||
try
|
try
|
||||||
let channel_id = jobstart(args, s:job_opts)
|
let job = copy(s:job_opts)
|
||||||
|
let channel_id = jobstart(args, job)
|
||||||
if rpcrequest(channel_id, 'poll') ==# 'ok'
|
if rpcrequest(channel_id, 'poll') ==# 'ok'
|
||||||
return channel_id
|
return channel_id
|
||||||
endif
|
endif
|
||||||
catch
|
catch
|
||||||
echomsg v:throwpoint
|
echomsg v:throwpoint
|
||||||
echomsg v:exception
|
echomsg v:exception
|
||||||
for row in provider#get_stderr(channel_id)
|
for row in job.stderr
|
||||||
echomsg row
|
echomsg row
|
||||||
endfor
|
endfor
|
||||||
endtry
|
endtry
|
||||||
finally
|
|
||||||
call provider#clear_stderr(channel_id)
|
|
||||||
endtry
|
endtry
|
||||||
throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_NODE_LOG_FILE')
|
throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_NODE_LOG_FILE')
|
||||||
endfunction
|
endfunction
|
||||||
|
@ -5,7 +5,7 @@ endif
|
|||||||
|
|
||||||
let s:loaded_pythonx_provider = 1
|
let s:loaded_pythonx_provider = 1
|
||||||
|
|
||||||
let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')}
|
let s:job_opts = {'rpc': v:true, 'stderr_buffered': v:true}
|
||||||
|
|
||||||
function! provider#pythonx#Require(host) abort
|
function! provider#pythonx#Require(host) abort
|
||||||
let ver = (a:host.orig_name ==# 'python') ? 2 : 3
|
let ver = (a:host.orig_name ==# 'python') ? 2 : 3
|
||||||
@ -21,18 +21,17 @@ function! provider#pythonx#Require(host) abort
|
|||||||
endfor
|
endfor
|
||||||
|
|
||||||
try
|
try
|
||||||
let channel_id = jobstart(args, s:job_opts)
|
let job = copy(s:job_opts)
|
||||||
|
let channel_id = jobstart(args, job)
|
||||||
if rpcrequest(channel_id, 'poll') ==# 'ok'
|
if rpcrequest(channel_id, 'poll') ==# 'ok'
|
||||||
return channel_id
|
return channel_id
|
||||||
endif
|
endif
|
||||||
catch
|
catch
|
||||||
echomsg v:throwpoint
|
echomsg v:throwpoint
|
||||||
echomsg v:exception
|
echomsg v:exception
|
||||||
for row in provider#get_stderr(channel_id)
|
for row in job.stderr
|
||||||
echomsg row
|
echomsg row
|
||||||
endfor
|
endfor
|
||||||
finally
|
|
||||||
call provider#clear_stderr(channel_id)
|
|
||||||
endtry
|
endtry
|
||||||
throw remote#host#LoadErrorForHost(a:host.orig_name,
|
throw remote#host#LoadErrorForHost(a:host.orig_name,
|
||||||
\ '$NVIM_PYTHON_LOG_FILE')
|
\ '$NVIM_PYTHON_LOG_FILE')
|
||||||
|
@ -43,7 +43,7 @@ bytes. Additionally, for a job channel using rpc, bytes can still be
|
|||||||
read over its stderr. Similarily, only bytes can be written to nvim's own stderr.
|
read over its stderr. Similarily, only bytes can be written to nvim's own stderr.
|
||||||
|
|
||||||
*channel-callback* *buffered*
|
*channel-callback* *buffered*
|
||||||
*on_stdout* *on_stderr* *on_stdin* *on_data*
|
*E5210* *on_stdout* *on_stderr* *on_stdin* *on_data*
|
||||||
A callback function `on_{stream}` will be invoked with data read from the
|
A callback function `on_{stream}` will be invoked with data read from the
|
||||||
channel. By default, the callback will be invoked immediately when data is
|
channel. By default, the callback will be invoked immediately when data is
|
||||||
available, to facilitate interactive communication. The same callback will
|
available, to facilitate interactive communication. The same callback will
|
||||||
@ -52,7 +52,11 @@ Alternatively the `{stream}_buffered` option can be set to invoke the callback
|
|||||||
only when the underlying stream reaches EOF, and will then be passed in
|
only when the underlying stream reaches EOF, and will then be passed in
|
||||||
complete output. This is helpful when only the complete output is useful, and
|
complete output. This is helpful when only the complete output is useful, and
|
||||||
not partial data. Futhermore if `{stream}_buffered` is set but not a callback,
|
not partial data. Futhermore if `{stream}_buffered` is set but not a callback,
|
||||||
the data is saved in the options dict, with the stream name as key.
|
the data is saved in the options dict, with the stream name as key. For this
|
||||||
|
to work a new options dict must be used for each opened channel. If a script
|
||||||
|
uses a global `s:job_opts` dict, it can be copied with |copy()| before supplying
|
||||||
|
it to |jobstart()|. If a dict is reused, so that the dict key already is
|
||||||
|
occupied, error `E5210` will be raised.
|
||||||
|
|
||||||
- The arguments passed to the callback function are:
|
- The arguments passed to the callback function are:
|
||||||
|
|
||||||
|
@ -599,6 +599,7 @@ static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count,
|
|||||||
on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin");
|
on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @param type must have static lifetime
|
||||||
static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf,
|
static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf,
|
||||||
size_t count, bool eof, CallbackReader *reader,
|
size_t count, bool eof, CallbackReader *reader,
|
||||||
const char *type)
|
const char *type)
|
||||||
@ -613,14 +614,20 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf,
|
|||||||
if (reader->cb.type != kCallbackNone) {
|
if (reader->cb.type != kCallbackNone) {
|
||||||
process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data,
|
process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data,
|
||||||
(size_t)reader->buffer.ga_len, 0);
|
(size_t)reader->buffer.ga_len, 0);
|
||||||
ga_clear(&reader->buffer);
|
|
||||||
} else if (reader->self) {
|
} else if (reader->self) {
|
||||||
list_T *data = buffer_to_tv_list(reader->buffer.ga_data,
|
if (tv_dict_find(reader->self, type, -1) == NULL) {
|
||||||
(size_t)reader->buffer.ga_len);
|
list_T *data = buffer_to_tv_list(reader->buffer.ga_data,
|
||||||
tv_dict_add_list(reader->self, type, strlen(type), data);
|
(size_t)reader->buffer.ga_len);
|
||||||
|
tv_dict_add_list(reader->self, type, strlen(type), data);
|
||||||
|
} else {
|
||||||
|
// can't display error message now, defer it.
|
||||||
|
channel_incref(chan);
|
||||||
|
multiqueue_put(chan->events, on_buffered_error, 2, chan, type);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
ga_clear(&reader->buffer);
|
||||||
} else if (reader->cb.type != kCallbackNone) {
|
} else if (reader->cb.type != kCallbackNone) {
|
||||||
process_channel_event(chan, &reader->cb, type, ptr, 0, 0);
|
process_channel_event(chan, &reader->cb, type, ptr, 0, 0);
|
||||||
}
|
}
|
||||||
@ -641,6 +648,14 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_buffered_error(void **args)
|
||||||
|
{
|
||||||
|
Channel *chan = (Channel *)args[0];
|
||||||
|
const char *stream = (const char *)args[1];
|
||||||
|
EMSG3(_(e_streamkey), stream, chan->id);
|
||||||
|
channel_decref(chan);
|
||||||
|
}
|
||||||
|
|
||||||
static void channel_process_exit_cb(Process *proc, int status, void *data)
|
static void channel_process_exit_cb(Process *proc, int status, void *data)
|
||||||
{
|
{
|
||||||
Channel *chan = data;
|
Channel *chan = data;
|
||||||
|
@ -1063,6 +1063,9 @@ EXTERN char_u e_stdiochan2[] INIT(= N_(
|
|||||||
EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel"));
|
EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel"));
|
||||||
EXTERN char_u e_invstreamrpc[] INIT(= N_(
|
EXTERN char_u e_invstreamrpc[] INIT(= N_(
|
||||||
"E906: invalid stream for rpc channel, use 'rpc'"));
|
"E906: invalid stream for rpc channel, use 'rpc'"));
|
||||||
|
EXTERN char_u e_streamkey[] INIT(= N_(
|
||||||
|
"E5210: dict key '%s' already set for buffered stream in channel %"
|
||||||
|
PRIu64));
|
||||||
EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
|
EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
|
||||||
EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
|
EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
|
||||||
EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
|
EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
|
||||||
|
@ -246,6 +246,22 @@ describe('channels', function()
|
|||||||
eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
|
eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
|
||||||
'20 GOTO 10', ''}}}, next_msg())
|
'20 GOTO 10', ''}}}, next_msg())
|
||||||
|
|
||||||
|
-- if dict is reused the new value is not stored,
|
||||||
|
-- but nvim also does not crash
|
||||||
|
command("let id = jobstart(['cat'], g:job_opts)")
|
||||||
|
id = eval("g:id")
|
||||||
|
|
||||||
|
command([[call chansend(id, "cat text\n")]])
|
||||||
|
sleep(10)
|
||||||
|
command("call chanclose(id, 'stdin')")
|
||||||
|
|
||||||
|
-- old value was not overwritten
|
||||||
|
eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
|
||||||
|
'20 GOTO 10', ''}}}, next_msg())
|
||||||
|
|
||||||
|
-- and an error was thrown.
|
||||||
|
eq("E5210: dict key 'stdout' already set for buffered stream in channel "..id, eval('v:errmsg'))
|
||||||
|
|
||||||
-- reset dictionary
|
-- reset dictionary
|
||||||
source([[
|
source([[
|
||||||
let g:job_opts = {
|
let g:job_opts = {
|
||||||
@ -261,6 +277,5 @@ describe('channels', function()
|
|||||||
|
|
||||||
-- works correctly with no output
|
-- works correctly with no output
|
||||||
eq({"notification", "exit", {id, 1, {''}}}, next_msg())
|
eq({"notification", "exit", {id, 1, {''}}}, next_msg())
|
||||||
|
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user