Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RDY] Job control fixes and pseudo terminal support #2037

Merged
merged 4 commits into from
Feb 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .valgrind.supp
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
Memcheck:Leak
fun:malloc
fun:uv_spawn
fun:pipe_process_spawn
fun:process_spawn
fun:job_start
}
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ get_compile_flags(NVIM_VERSION_CFLAGS)

add_subdirectory(test/includes)
add_subdirectory(config)
add_subdirectory(test/functional/job) # compile pty test program


# Setup some test-related bits. We do this after going down the tree because we
# need some of the targets.
Expand Down Expand Up @@ -316,5 +318,5 @@ if(BUSTED_PRG)
-DBUILD_DIR=${CMAKE_BINARY_DIR}
-DTEST_TYPE=functional
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
DEPENDS nvim)
DEPENDS nvim tty-test)
endif()
1 change: 1 addition & 0 deletions src/nvim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ list(APPEND NVIM_LINK_LIBRARIES
${LIBTERMKEY_LIBRARIES}
${LIBUNIBILIUM_LIBRARIES}
m
util
${CMAKE_THREAD_LIBS_INIT}
)

Expand Down
169 changes: 108 additions & 61 deletions src/nvim/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,8 +454,8 @@ static dictitem_T vimvars_var; /* variable used for v: */
// Memory pool for reusing JobEvent structures
typedef struct {
int id;
char *name, *type, *received;
size_t received_len;
char *name, *type;
list_T *received;
} JobEvent;
#define JobEventFreer(x)
KMEMPOOL_INIT(JobEventPool, JobEvent, JobEventFreer)
Expand Down Expand Up @@ -6488,8 +6488,9 @@ static struct fst {
{"isdirectory", 1, 1, f_isdirectory},
{"islocked", 1, 1, f_islocked},
{"items", 1, 1, f_items},
{"jobresize", 3, 3, f_jobresize},
{"jobsend", 2, 2, f_jobsend},
{"jobstart", 2, 3, f_jobstart},
{"jobstart", 2, 4, f_jobstart},
{"jobstop", 1, 1, f_jobstop},
{"join", 1, 2, f_join},
{"keys", 1, 1, f_keys},
Expand Down Expand Up @@ -10665,6 +10666,39 @@ static void f_jobsend(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = job_write(job, buf);
}

// "jobresize()" function
static void f_jobresize(typval_T *argvars, typval_T *rettv)
{
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;

if (check_restricted() || check_secure()) {
return;
}

if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER
|| argvars[2].v_type != VAR_NUMBER) {
// job id, width, height
EMSG(_(e_invarg));
return;
}

Job *job = job_find(argvars[0].vval.v_number);

if (!job) {
// Probably an invalid job id
EMSG(_(e_invjob));
return;
}

if (!job_resize(job, argvars[1].vval.v_number, argvars[2].vval.v_number)) {
EMSG(_(e_jobnotpty));
return;
}

rettv->vval.v_number = 1;
}

// "jobstart()" function
static void f_jobstart(typval_T *argvars, typval_T *rettv)
{
Expand All @@ -10682,8 +10716,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)

if (argvars[0].v_type != VAR_STRING
|| argvars[1].v_type != VAR_STRING
|| (argvars[2].v_type != VAR_LIST
&& argvars[2].v_type != VAR_UNKNOWN)) {
|| (argvars[2].v_type != VAR_LIST && argvars[2].v_type != VAR_UNKNOWN)) {
// Wrong argument types
EMSG(_(e_invarg));
return;
Expand Down Expand Up @@ -10725,15 +10758,31 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)

// The last item of argv must be NULL
argv[i] = NULL;
JobOptions opts = JOB_OPTIONS_INIT;
opts.argv = argv;
opts.data = xstrdup((char *)argvars[0].vval.v_string);
opts.stdout_cb = on_job_stdout;
opts.stderr_cb = on_job_stderr;
opts.exit_cb = on_job_exit;

job_start(argv,
xstrdup((char *)argvars[0].vval.v_string),
true,
on_job_stdout,
on_job_stderr,
on_job_exit,
0,
&rettv->vval.v_number);
if (args && argvars[3].v_type == VAR_DICT) {
dict_T *job_opts = argvars[3].vval.v_dict;
opts.pty = true;
uint16_t width = get_dict_number(job_opts, (uint8_t *)"width");
if (width > 0) {
opts.width = width;
}
uint16_t height = get_dict_number(job_opts, (uint8_t *)"height");
if (height > 0) {
opts.height = height;
}
char *term = (char *)get_dict_string(job_opts, (uint8_t *)"TERM", true);
if (term) {
opts.term_name = term;
}
}

job_start(opts, &rettv->vval.v_number);

if (rettv->vval.v_number <= 0) {
if (rettv->vval.v_number == 0) {
Expand Down Expand Up @@ -19705,72 +19754,73 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)

// JobActivity autocommands will execute vimscript code, so it must be executed
// on Nvim main loop
#define push_job_event(j, r, t, eof) \
do { \
JobEvent *event_data = kmp_alloc(JobEventPool, job_event_pool); \
event_data->received = NULL; \
size_t read_count = 0; \
if (r) { \
if (eof) { \
read_count = rstream_pending(r); \
} else { \
char *read = rstream_read_ptr(r); \
char *lastnl = xmemrchr(read, NL, rstream_pending(r)); \
if (lastnl) { \
read_count = (size_t) (lastnl - read) + 1; \
} else if (rstream_available(r) == 0) { \
/* No newline or room to grow; flush everything. */ \
read_count = rstream_pending(r); \
} \
} \
if (read_count == 0) { \
/* Either we're at EOF or we need to wait until next time */ \
/* to receive a '\n. */ \
kmp_free(JobEventPool, job_event_pool, event_data); \
return; \
} \
event_data->received_len = read_count; \
event_data->received = xmallocz(read_count); \
rstream_read(r, event_data->received, read_count); \
} \
event_data->id = job_id(j); \
event_data->name = job_data(j); \
event_data->type = t; \
event_push((Event) { \
.handler = on_job_event, \
.data = event_data \
}, true); \
} while(0)
static inline void push_job_event(Job *job, RStream *rstream, char *type)
{
JobEvent *event_data = kmp_alloc(JobEventPool, job_event_pool);
event_data->received = NULL;
if (rstream) {
event_data->received = list_alloc();
char *ptr = rstream_read_ptr(rstream);
size_t count = rstream_pending(rstream);
size_t remaining = count;
size_t off = 0;

while (off < remaining) {
// append the line
if (ptr[off] == NL) {
list_append_string(event_data->received, (uint8_t *)ptr, off);
size_t skip = off + 1;
ptr += skip;
remaining -= skip;
off = 0;
continue;
}
if (ptr[off] == NUL) {
// Translate NUL to NL
ptr[off] = NL;
}
off++;
}
list_append_string(event_data->received, (uint8_t *)ptr, off);
rbuffer_consumed(rstream_buffer(rstream), count);
}
event_data->id = job_id(job);
event_data->name = job_data(job);
event_data->type = type;
event_push((Event) {
.handler = on_job_event,
.data = event_data
}, true);
}

static void on_job_stdout(RStream *rstream, void *data, bool eof)
{
if (rstream_pending(rstream)) {
push_job_event(data, rstream, "stdout", eof);
if (!eof) {
push_job_event(data, rstream, "stdout");
}
}

static void on_job_stderr(RStream *rstream, void *data, bool eof)
{
if (rstream_pending(rstream)) {
push_job_event(data, rstream, "stderr", eof);
if (!eof) {
push_job_event(data, rstream, "stderr");
}
}

static void on_job_exit(Job *job, void *data)
{
push_job_event(job, NULL, "exit", true);
push_job_event(job, NULL, "exit");
}

static void on_job_event(Event event)
{
JobEvent *data = event.data;
apply_job_autocmds(data->id, data->name, data->type,
data->received, data->received_len);
apply_job_autocmds(data->id, data->name, data->type, data->received);
kmp_free(JobEventPool, job_event_pool, data);
}

static void apply_job_autocmds(int id, char *name, char *type,
char *received, size_t received_len)
list_T *received)
{
// Create the list which will be set to v:job_data
list_T *list = list_alloc();
Expand All @@ -19781,12 +19831,9 @@ static void apply_job_autocmds(int id, char *name, char *type,
listitem_T *str_slot = listitem_alloc();
str_slot->li_tv.v_type = VAR_LIST;
str_slot->li_tv.v_lock = 0;
str_slot->li_tv.vval.v_list =
string_to_list((char_u *) received, received_len, false);
str_slot->li_tv.vval.v_list = received;
str_slot->li_tv.vval.v_list->lv_refcount++;
list_append(list, str_slot);

free(received);
}

// Update v:job_data for the autocommands
Expand Down
1 change: 1 addition & 0 deletions src/nvim/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ EXTERN char_u e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
EXTERN char_u e_invjob[] INIT(= N_("E900: Invalid job id"));
EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full"));
EXTERN char_u e_jobexe[] INIT(= N_("E902: \"%s\" is not an executable"));
EXTERN char_u e_jobnotpty[] INIT(= N_("E904: Job is not connected to a pty"));
EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
EXTERN char_u e_marknotset[] INIT(= N_("E20: Mark not set"));
Expand Down
15 changes: 7 additions & 8 deletions src/nvim/msgpack_rpc/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,13 @@ uint64_t channel_from_job(char **argv)
incref(channel); // job channels are only closed by the exit_cb

int status;
channel->data.job = job_start(argv,
channel,
true,
job_out,
job_err,
job_exit,
0,
&status);
JobOptions opts = JOB_OPTIONS_INIT;
opts.argv = argv;
opts.data = channel;
opts.stdout_cb = job_out;
opts.stderr_cb = job_err;
opts.exit_cb = job_exit;
channel->data.job = job_start(opts, &status);

if (status <= 0) {
if (status == 0) { // Two decrefs needed if status == 0.
Expand Down