diff --git a/Filelist b/Filelist index 594b33552a..0587917c36 100644 --- a/Filelist +++ b/Filelist @@ -638,6 +638,7 @@ RT_ALL = \ runtime/pack/dist/opt/matchit/doc/tags \ runtime/pack/dist/opt/shellmenu/plugin/shellmenu.vim \ runtime/pack/dist/opt/swapmouse/plugin/swapmouse.vim \ + runtime/pack/dist/opt/termdebug/plugin/termdebug.vim \ # runtime files for all distributions without CR-NL translation RT_ALL_BIN = \ diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 65098533b4..92074cd006 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2401,7 +2401,7 @@ term_getscrolled({buf}) Number get the scroll count of a terminal term_getsize({buf}) List get the size of a terminal term_getstatus({buf}) String get the status of a terminal term_gettitle({buf}) String get the title of a terminal -term_gettty({buf}) String get the tty name of a terminal +term_getttty({buf}, [{input}]) String get the tty name of a terminal term_list() List get the list of terminal buffers term_scrape({buf}, {row}) List get row of a terminal screen term_sendkeys({buf}, {keys}) none send keystrokes to a terminal @@ -5245,7 +5245,8 @@ job_info({job}) *job_info()* "status" what |job_status()| returns "channel" what |job_getchannel()| returns "process" process ID - "tty" controlling terminal name, empty when none + "tty_in" terminal input name, empty when none + "tty_out" terminal output name, empty when none "exitval" only valid when "status" is "dead" "exit_cb" function to be called on exit "stoponexit" |job-stoponexit| @@ -8092,10 +8093,13 @@ term_gettitle({buf}) *term_gettitle()* string is returned. {only available when compiled with the |+terminal| feature} -term_gettty({buf}) *term_gettty()* +term_gettty({buf} [, {input}]) *term_gettty()* Get the name of the controlling terminal associated with - terminal window {buf}. - {buf} is used as with |term_getsize()|. + terminal window {buf}. {buf} is used as with |term_getsize()|. + + When {input} is omitted or 0, return the name for writing + (stdout). When {input} is 1 return the name for reading + (stdin). On UNIX, both return same name. {only available when compiled with the |+terminal| feature} term_list() *term_list()* @@ -8173,10 +8177,9 @@ term_start({cmd}, {options}) *term_start()* specified "botright sbuf %d" is used "eof_chars" Text to send after all buffer lines were written to the terminal. When not set - CTRL-D is used. For Python use CTRL-Z or - "exit()". For a shell use "exit". A CR - is always added. - {only on MS-Windows} + CTRL-D is used on MS-Windows. For Python + use CTRL-Z or "exit()". For a shell use + "exit". A CR is always added. {only available when compiled with the |+terminal| feature} diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 97fadf695a..f69990b52b 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -1,35 +1,128 @@ -" Debugger commands. +" Debugger plugin using gdb. " " WORK IN PROGRESS - much doesn't work yet " -" Open two terminal windows: +" Open two visible terminal windows: " 1. run a pty, as with ":term NONE" " 2. run gdb, passing the pty -" The current window is used to edit source code and follows gdb. +" The current window is used to view source code and follows gdb. +" +" A third terminal window is hidden, it is used for communication with gdb. +" +" The communication with gdb uses GDB/MI. See: +" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html " " Author: Bram Moolenaar -" Copyright: Vim license applies +" Copyright: Vim license applies, see ":help license" +" The command that starts debugging, e.g. ":Termdebug vim". +" To end type "quit" in the gdb window. command -nargs=* -complete=file Termdebug call s:StartDebug() +" Name of the gdb command, defaults to "gdb". if !exists('debugger') let debugger = 'gdb' endif +" Sign used to highlight the line where the program has stopped. +sign define debugPC linehl=debugPC +if &background == 'light' + hi debugPC term=reverse ctermbg=lightblue guibg=lightblue +else + hi debugPC term=reverse ctermbg=darkblue guibg=darkblue +endif +let s:pc_id = 12 + func s:StartDebug(cmd) + let s:startwin = win_getid(winnr()) + let s:startsigncolumn = &signcolumn + " Open a terminal window without a job, to run the debugged program - let s:ptybuf = term_start('NONE', {}) - let pty = job_info(term_getjob(s:ptybuf))['tty'] + let s:ptybuf = term_start('NONE', { + \ 'term_name': 'gdb program', + \ }) + if s:ptybuf == 0 + echoerr 'Failed to open the program terminal window' + return + endif + let pty = job_info(term_getjob(s:ptybuf))['tty_out'] + + " Create a hidden terminal window to communicate with gdb + let s:commbuf = term_start('NONE', { + \ 'term_name': 'gdb communication', + \ 'out_cb': function('s:CommOutput'), + \ 'hidden': 1, + \ }) + if s:commbuf == 0 + echoerr 'Failed to open the communication terminal window' + exe 'bwipe! ' . s:ptybuf + return + endif + let commpty = job_info(term_getjob(s:commbuf))['tty_out'] " Open a terminal window to run the debugger. let cmd = [g:debugger, '-tty', pty, a:cmd] echomsg 'executing "' . join(cmd) . '"' let gdbbuf = term_start(cmd, { \ 'exit_cb': function('s:EndDebug'), - \ 'term_finish': 'close' + \ 'term_finish': 'close', \ }) + if gdbbuf == 0 + echoerr 'Failed to open the gdb terminal window' + exe 'bwipe! ' . s:ptybuf + exe 'bwipe! ' . s:commbuf + return + endif + + " Connect gdb to the communication pty, using the GDB/MI interface + call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r") endfunc func s:EndDebug(job, status) - exe 'bwipe! ' . s:ptybuf + exe 'bwipe! ' . s:ptybuf + exe 'bwipe! ' . s:commbuf + call setwinvar(s:startwin, '&signcolumn', s:startsigncolumn) +endfunc + +" Handle a message received from gdb on the GDB/MI interface. +func s:CommOutput(chan, msg) + let msgs = split(a:msg, "\r") + + for msg in msgs + " remove prefixed NL + if msg[0] == "\n" + let msg = msg[1:] + endif + if msg != '' + if msg =~ '^\*\(stopped\|running\)' + let wid = win_getid(winnr()) + + if win_gotoid(s:startwin) + if msg =~ '^\*stopped' + " TODO: proper parsing + let fname = substitute(msg, '.*fullname="\([^"]*\)".*', '\1', '') + let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') + if lnum =~ '^[0-9]*$' + if expand('%:h') != fname + if &modified + " TODO: find existing window + exe 'split ' . fnameescape(fname) + let s:startwin = win_getid(winnr()) + else + exe 'edit ' . fnameescape(fname) + endif + endif + exe lnum + exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname) + setlocal signcolumn=yes + endif + else + exe 'sign unplace ' . s:pc_id + endif + + call win_gotoid(wid) + endif + endif + endif + endfor endfunc diff --git a/src/buffer.c b/src/buffer.c index a3e259e997..f65490dd13 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -3925,8 +3925,8 @@ build_stl_str_hl( char_u *t; int byteval; #ifdef FEAT_EVAL - win_T *o_curwin; - buf_T *o_curbuf; + win_T *save_curwin; + buf_T *save_curbuf; #endif int empty_line; colnr_T virtcol; @@ -3968,6 +3968,9 @@ build_stl_str_hl( char_u tmp[TMPLEN]; char_u *usefmt = fmt; struct stl_hlrec *sp; + int save_must_redraw = must_redraw; + int save_redr_type = curwin->w_redr_type; + int save_highlight_shcnaged = need_highlight_changed; #ifdef FEAT_EVAL /* @@ -4277,15 +4280,15 @@ build_stl_str_hl( vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum); set_internal_string_var((char_u *)"actual_curbuf", tmp); - o_curbuf = curbuf; - o_curwin = curwin; + save_curbuf = curbuf; + save_curwin = curwin; curwin = wp; curbuf = wp->w_buffer; str = eval_to_string_safe(p, &t, use_sandbox); - curwin = o_curwin; - curbuf = o_curbuf; + curwin = save_curwin; + curbuf = save_curbuf; do_unlet((char_u *)"g:actual_curbuf", TRUE); if (str != NULL && *str != 0) @@ -4740,6 +4743,13 @@ build_stl_str_hl( sp->userhl = 0; } + /* We do not want redrawing a stausline, ruler, title, etc. to trigger + * another redraw, it may cause an endless loop. This happens when a + * statusline changes a highlight group. */ + must_redraw = save_must_redraw; + curwin->w_redr_type = save_redr_type; + need_highlight_changed = save_highlight_shcnaged; + return width; } #endif /* FEAT_STL_OPT */ diff --git a/src/channel.c b/src/channel.c index 5e441673dd..7a9a223b7a 100644 --- a/src/channel.c +++ b/src/channel.c @@ -990,7 +990,13 @@ ch_close_part(channel_T *channel, ch_part_T part) if ((part == PART_IN || channel->CH_IN_FD != *fd) && (part == PART_OUT || channel->CH_OUT_FD != *fd) && (part == PART_ERR || channel->CH_ERR_FD != *fd)) + { +#ifdef WIN32 + if (channel->ch_named_pipe) + DisconnectNamedPipe((HANDLE)fd); +#endif fd_close(*fd); + } } *fd = INVALID_FD; @@ -3107,7 +3113,20 @@ channel_wait(channel_T *channel, sock_T fd, int timeout) if (r && nread > 0) return CW_READY; if (r == 0) - return CW_ERROR; + { + DWORD err = GetLastError(); + + if (err != ERROR_BAD_PIPE && err != ERROR_BROKEN_PIPE) + return CW_ERROR; + + if (channel->ch_named_pipe) + { + DisconnectNamedPipe((HANDLE)fd); + ConnectNamedPipe((HANDLE)fd, NULL); + } + else + return CW_ERROR; + } /* perhaps write some buffer lines */ channel_write_any_lines(); @@ -3695,7 +3714,17 @@ channel_send( if (part == PART_SOCK) res = sock_write(fd, (char *)buf, len); else + { res = fd_write(fd, (char *)buf, len); +#ifdef WIN32 + if (channel->ch_named_pipe && res < 0) + { + DisconnectNamedPipe((HANDLE)fd); + ConnectNamedPipe((HANDLE)fd, NULL); + } +#endif + + } if (res < 0 && (errno == EWOULDBLOCK #ifdef EAGAIN || errno == EAGAIN @@ -4077,6 +4106,7 @@ channel_select_check(int ret_in, void *rfds_in, void *wfds_in) if (ret > 0 && fd != INVALID_FD && FD_ISSET(fd, rfds)) { channel_read(channel, part, "channel_select_check"); + FD_CLR(fd, rfds); --ret; } } @@ -4086,6 +4116,7 @@ channel_select_check(int ret_in, void *rfds_in, void *wfds_in) && FD_ISSET(in_part->ch_fd, wfds)) { channel_write_input(channel); + FD_CLR(in_part->ch_fd, wfds); --ret; } } @@ -4874,7 +4905,8 @@ job_free_contents(job_T *job) } mch_clear_job(job); - vim_free(job->jv_tty_name); + vim_free(job->jv_tty_in); + vim_free(job->jv_tty_out); vim_free(job->jv_stoponexit); free_callback(job->jv_exit_cb, job->jv_exit_partial); } @@ -5528,8 +5560,10 @@ job_info(job_T *job, dict_T *dict) nr = job->jv_proc_info.dwProcessId; #endif dict_add_nr_str(dict, "process", nr, NULL); - dict_add_nr_str(dict, "tty", 0L, - job->jv_tty_name != NULL ? job->jv_tty_name : (char_u *)""); + dict_add_nr_str(dict, "tty_in", 0L, + job->jv_tty_in != NULL ? job->jv_tty_in : (char_u *)""); + dict_add_nr_str(dict, "tty_out", 0L, + job->jv_tty_out != NULL ? job->jv_tty_out : (char_u *)""); dict_add_nr_str(dict, "exitval", job->jv_exitval, NULL); dict_add_nr_str(dict, "exit_cb", 0L, job->jv_exit_cb); diff --git a/src/evalfunc.c b/src/evalfunc.c index 5503094598..0985f28968 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -843,7 +843,7 @@ static struct fst {"term_getsize", 1, 1, f_term_getsize}, {"term_getstatus", 1, 1, f_term_getstatus}, {"term_gettitle", 1, 1, f_term_gettitle}, - {"term_gettty", 1, 1, f_term_gettty}, + {"term_gettty", 1, 2, f_term_gettty}, {"term_list", 0, 0, f_term_list}, {"term_scrape", 2, 2, f_term_scrape}, {"term_sendkeys", 2, 2, f_term_sendkeys}, diff --git a/src/os_unix.c b/src/os_unix.c index 8d6ca4a2e9..2f45e476ed 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5285,7 +5285,11 @@ mch_job_start(char **argv, job_T *job, jobopt_T *options) && (!(use_file_for_in || use_null_for_in) || !(use_file_for_in || use_null_for_out) || !(use_out_for_err || use_file_for_err || use_null_for_err))) - open_pty(&pty_master_fd, &pty_slave_fd, &job->jv_tty_name); + { + open_pty(&pty_master_fd, &pty_slave_fd, &job->jv_tty_out); + if (job->jv_tty_out != NULL) + job->jv_tty_in = vim_strsave(job->jv_tty_out); + } /* TODO: without the channel feature connect the child to /dev/null? */ /* Open pipes for stdin, stdout, stderr. */ @@ -5709,7 +5713,9 @@ mch_create_pty_channel(job_T *job, jobopt_T *options) int pty_slave_fd = -1; channel_T *channel; - open_pty(&pty_master_fd, &pty_slave_fd, &job->jv_tty_name); + open_pty(&pty_master_fd, &pty_slave_fd, &job->jv_tty_out); + if (job->jv_tty_out != NULL) + job->jv_tty_in = vim_strsave(job->jv_tty_out); close(pty_slave_fd); channel = add_channel(); diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro index 3811ba7a51..639318d518 100644 --- a/src/proto/terminal.pro +++ b/src/proto/terminal.pro @@ -3,6 +3,7 @@ void ex_terminal(exarg_T *eap); void free_terminal(buf_T *buf); void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel); int term_job_running(term_T *term); +int term_none_open(term_T *term); int term_in_normal_mode(void); void term_enter_job_mode(void); int send_keys_to_term(term_T *term, int c, int typed); @@ -16,7 +17,6 @@ int term_update_window(win_T *wp); int term_is_finished(buf_T *buf); int term_show_buffer(buf_T *buf); void term_change_in_curbuf(void); -void term_send_eof(channel_T *ch); int term_get_attr(buf_T *buf, linenr_T lnum, int col); char_u *term_get_status_text(term_T *term); int set_ref_in_term(int copyID); @@ -35,5 +35,6 @@ void f_term_scrape(typval_T *argvars, typval_T *rettv); void f_term_sendkeys(typval_T *argvars, typval_T *rettv); void f_term_start(typval_T *argvars, typval_T *rettv); void f_term_wait(typval_T *argvars, typval_T *rettv); +void term_send_eof(channel_T *ch); int terminal_enabled(void); /* vim: set ft=c : */ diff --git a/src/structs.h b/src/structs.h index ca86063d63..fdf6379d9f 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1487,7 +1487,8 @@ struct jobvar_S PROCESS_INFORMATION jv_proc_info; HANDLE jv_job_object; #endif - char_u *jv_tty_name; /* controlling tty, allocated */ + char_u *jv_tty_in; /* controlling tty input, allocated */ + char_u *jv_tty_out; /* controlling tty output, allocated */ jobstatus_T jv_status; char_u *jv_stoponexit; /* allocated */ int jv_exitval; @@ -1655,6 +1656,9 @@ struct channel_S { /* callback for Netbeans when channel is * closed */ +#ifdef WIN32 + int ch_named_pipe; /* using named pipe instead of pty */ +#endif char_u *ch_callback; /* call when any msg is not handled */ partial_T *ch_partial; char_u *ch_close_cb; /* call when channel is closed */ diff --git a/src/syntax.c b/src/syntax.c index 0c9be41c3a..aba3507a46 100644 --- a/src/syntax.c +++ b/src/syntax.c @@ -7364,6 +7364,8 @@ do_highlight( int attr; int id; int idx; + struct hl_group *item; + struct hl_group item_before; int dodefault = FALSE; int doclear = FALSE; int dolink = FALSE; @@ -7459,12 +7461,13 @@ do_highlight( } from_id = syn_check_group(from_start, (int)(from_end - from_start)); + item = &HL_TABLE()[from_id - 1]; if (STRNCMP(to_start, "NONE", 4) == 0) to_id = 0; else to_id = syn_check_group(to_start, (int)(to_end - to_start)); - if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) + if (from_id > 0 && (!init || item->sg_set == 0)) { /* * Don't allow a link when there already is some highlighting @@ -7476,22 +7479,26 @@ do_highlight( if (sourcing_name == NULL && !dodefault) EMSG(_("E414: group has settings, highlight link ignored")); } - else + else if (item->sg_link != to_id +#ifdef FEAT_EVAL + || item->sg_scriptID != current_SID +#endif + || item->sg_cleared) { if (!init) - HL_TABLE()[from_id - 1].sg_set |= SG_LINK; - HL_TABLE()[from_id - 1].sg_link = to_id; + item->sg_set |= SG_LINK; + item->sg_link = to_id; #ifdef FEAT_EVAL - HL_TABLE()[from_id - 1].sg_scriptID = current_SID; + item->sg_scriptID = current_SID; #endif - HL_TABLE()[from_id - 1].sg_cleared = FALSE; + item->sg_cleared = FALSE; redraw_all_later(SOME_VALID); + + /* Only call highlight_changed() once after multiple changes. */ + need_highlight_changed = TRUE; } } - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; - return; } @@ -7578,19 +7585,23 @@ do_highlight( if (id == 0) /* failed (out of memory) */ return; idx = id - 1; /* index is ID minus one */ + item = &HL_TABLE()[idx]; /* Return if "default" was used and the group already has settings. */ if (dodefault && hl_has_settings(idx, TRUE)) return; - if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0) + /* Make a copy so we can check if any attribute actually changed. */ + item_before = *item; + + if (STRCMP(item->sg_name_u, "NORMAL") == 0) is_normal_group = TRUE; #ifdef FEAT_GUI_X11 - else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0) + else if (STRCMP(item->sg_name_u, "MENU") == 0) is_menu_group = TRUE; - else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0) + else if (STRCMP(item->sg_name_u, "SCROLLBAR") == 0) is_scrollbar_group = TRUE; - else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0) + else if (STRCMP(item->sg_name_u, "TOOLTIP") == 0) is_tooltip_group = TRUE; #endif @@ -7599,7 +7610,7 @@ do_highlight( { highlight_clear(idx); if (!doclear) - HL_TABLE()[idx].sg_set = 0; + item->sg_set = 0; } if (!doclear) @@ -7630,10 +7641,10 @@ do_highlight( if (STRCMP(key, "NONE") == 0) { - if (!init || HL_TABLE()[idx].sg_set == 0) + if (!init || item->sg_set == 0) { if (!init) - HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI; + item->sg_set |= SG_TERM+SG_CTERM+SG_GUI; highlight_clear(idx); } continue; @@ -7720,31 +7731,31 @@ do_highlight( break; if (*key == 'T') { - if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM)) + if (!init || !(item->sg_set & SG_TERM)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_TERM; - HL_TABLE()[idx].sg_term = attr; + item->sg_set |= SG_TERM; + item->sg_term = attr; } } else if (*key == 'C') { - if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) + if (!init || !(item->sg_set & SG_CTERM)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_CTERM; - HL_TABLE()[idx].sg_cterm = attr; - HL_TABLE()[idx].sg_cterm_bold = FALSE; + item->sg_set |= SG_CTERM; + item->sg_cterm = attr; + item->sg_cterm_bold = FALSE; } } #if defined(FEAT_GUI) || defined(FEAT_EVAL) else { - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) + if (!init || !(item->sg_set & SG_GUI)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_GUI; - HL_TABLE()[idx].sg_gui = attr; + item->sg_set |= SG_GUI; + item->sg_gui = attr; } } #endif @@ -7753,69 +7764,74 @@ do_highlight( { /* in non-GUI fonts are simply ignored */ #ifdef FEAT_GUI - if (!gui.shell_created) + if (item->sg_font_name != NULL + && STRCMP(item->sg_font_name, arg) == 0) + { + /* Font name didn't change, ignore. */ + } + else if (!gui.shell_created) { /* GUI not started yet, always accept the name. */ - vim_free(HL_TABLE()[idx].sg_font_name); - HL_TABLE()[idx].sg_font_name = vim_strsave(arg); + vim_free(item->sg_font_name); + item->sg_font_name = vim_strsave(arg); } else { - GuiFont temp_sg_font = HL_TABLE()[idx].sg_font; + GuiFont temp_sg_font = item->sg_font; # ifdef FEAT_XFONTSET - GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset; + GuiFontset temp_sg_fontset = item->sg_fontset; # endif /* First, save the current font/fontset. * Then try to allocate the font/fontset. - * If the allocation fails, HL_TABLE()[idx].sg_font OR + * If the allocation fails, item->sg_font OR * sg_fontset will be set to NOFONT or NOFONTSET respectively. */ - HL_TABLE()[idx].sg_font = NOFONT; + item->sg_font = NOFONT; # ifdef FEAT_XFONTSET - HL_TABLE()[idx].sg_fontset = NOFONTSET; + item->sg_fontset = NOFONTSET; # endif hl_do_font(idx, arg, is_normal_group, is_menu_group, is_tooltip_group, FALSE); # ifdef FEAT_XFONTSET - if (HL_TABLE()[idx].sg_fontset != NOFONTSET) + if (item->sg_fontset != NOFONTSET) { /* New fontset was accepted. Free the old one, if there * was one. */ gui_mch_free_fontset(temp_sg_fontset); - vim_free(HL_TABLE()[idx].sg_font_name); - HL_TABLE()[idx].sg_font_name = vim_strsave(arg); + vim_free(item->sg_font_name); + item->sg_font_name = vim_strsave(arg); } else - HL_TABLE()[idx].sg_fontset = temp_sg_fontset; + item->sg_fontset = temp_sg_fontset; # endif - if (HL_TABLE()[idx].sg_font != NOFONT) + if (item->sg_font != NOFONT) { /* New font was accepted. Free the old one, if there was * one. */ gui_mch_free_font(temp_sg_font); - vim_free(HL_TABLE()[idx].sg_font_name); - HL_TABLE()[idx].sg_font_name = vim_strsave(arg); + vim_free(item->sg_font_name); + item->sg_font_name = vim_strsave(arg); } else - HL_TABLE()[idx].sg_font = temp_sg_font; + item->sg_font = temp_sg_font; } #endif } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { - if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) + if (!init || !(item->sg_set & SG_CTERM)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_CTERM; + item->sg_set |= SG_CTERM; /* When setting the foreground color, and previously the "bold" * flag was set for a light color, reset it now */ - if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold) + if (key[5] == 'F' && item->sg_cterm_bold) { - HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = FALSE; + item->sg_cterm &= ~HL_BOLD; + item->sg_cterm_bold = FALSE; } if (VIM_ISDIGIT(*arg)) @@ -7872,22 +7888,22 @@ do_highlight( * colors (on some terminals, e.g. "linux") */ if (bold == TRUE) { - HL_TABLE()[idx].sg_cterm |= HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = TRUE; + item->sg_cterm |= HL_BOLD; + item->sg_cterm_bold = TRUE; } else if (bold == FALSE) - HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; + item->sg_cterm &= ~HL_BOLD; } /* Add one to the argument, to avoid zero. Zero is used for * "NONE", then "color" is -1. */ if (key[5] == 'F') { - HL_TABLE()[idx].sg_cterm_fg = color + 1; + item->sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; - cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD); + cterm_normal_fg_bold = (item->sg_cterm & HL_BOLD); #ifdef FEAT_GUI /* Don't do this if the GUI is used. */ if (!gui.in_use && !gui.starting) @@ -7901,7 +7917,7 @@ do_highlight( } else { - HL_TABLE()[idx].sg_cterm_bg = color + 1; + item->sg_cterm_bg = color + 1; if (is_normal_group) { cterm_normal_bg_color = color + 1; @@ -7941,23 +7957,23 @@ do_highlight( else if (STRCMP(key, "GUIFG") == 0) { #if defined(FEAT_GUI) || defined(FEAT_EVAL) - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) + if (!init || !(item->sg_set & SG_GUI)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_GUI; + item->sg_set |= SG_GUI; # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) /* In GUI guifg colors are only used when recognized */ i = color_name2handle(arg); if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT) { - HL_TABLE()[idx].sg_gui_fg = i; + item->sg_gui_fg = i; # endif - vim_free(HL_TABLE()[idx].sg_gui_fg_name); + vim_free(item->sg_gui_fg_name); if (STRCMP(arg, "NONE") != 0) - HL_TABLE()[idx].sg_gui_fg_name = vim_strsave(arg); + item->sg_gui_fg_name = vim_strsave(arg); else - HL_TABLE()[idx].sg_gui_fg_name = NULL; + item->sg_gui_fg_name = NULL; # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) # ifdef FEAT_GUI_X11 if (is_menu_group) @@ -7978,23 +7994,23 @@ do_highlight( else if (STRCMP(key, "GUIBG") == 0) { #if defined(FEAT_GUI) || defined(FEAT_EVAL) - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) + if (!init || !(item->sg_set & SG_GUI)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_GUI; + item->sg_set |= SG_GUI; # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) /* In GUI guifg colors are only used when recognized */ i = color_name2handle(arg); if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT) { - HL_TABLE()[idx].sg_gui_bg = i; + item->sg_gui_bg = i; # endif - vim_free(HL_TABLE()[idx].sg_gui_bg_name); + vim_free(item->sg_gui_bg_name); if (STRCMP(arg, "NONE") != 0) - HL_TABLE()[idx].sg_gui_bg_name = vim_strsave(arg); + item->sg_gui_bg_name = vim_strsave(arg); else - HL_TABLE()[idx].sg_gui_bg_name = NULL; + item->sg_gui_bg_name = NULL; # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) # ifdef FEAT_GUI_X11 if (is_menu_group) @@ -8015,22 +8031,22 @@ do_highlight( else if (STRCMP(key, "GUISP") == 0) { #if defined(FEAT_GUI) || defined(FEAT_EVAL) - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) + if (!init || !(item->sg_set & SG_GUI)) { if (!init) - HL_TABLE()[idx].sg_set |= SG_GUI; + item->sg_set |= SG_GUI; # ifdef FEAT_GUI i = color_name2handle(arg); if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use) { - HL_TABLE()[idx].sg_gui_sp = i; + item->sg_gui_sp = i; # endif - vim_free(HL_TABLE()[idx].sg_gui_sp_name); + vim_free(item->sg_gui_sp_name); if (STRCMP(arg, "NONE") != 0) - HL_TABLE()[idx].sg_gui_sp_name = vim_strsave(arg); + item->sg_gui_sp_name = vim_strsave(arg); else - HL_TABLE()[idx].sg_gui_sp_name = NULL; + item->sg_gui_sp_name = NULL; # ifdef FEAT_GUI } # endif @@ -8043,7 +8059,7 @@ do_highlight( char_u *tname; if (!init) - HL_TABLE()[idx].sg_set |= SG_TERM; + item->sg_set |= SG_TERM; /* * The "start" and "stop" arguments can be a literal escape @@ -8110,13 +8126,13 @@ do_highlight( p = vim_strsave(buf); if (key[2] == 'A') { - vim_free(HL_TABLE()[idx].sg_start); - HL_TABLE()[idx].sg_start = p; + vim_free(item->sg_start); + item->sg_start = p; } else { - vim_free(HL_TABLE()[idx].sg_stop); - HL_TABLE()[idx].sg_stop = p; + vim_free(item->sg_stop); + item->sg_stop = p; } } else @@ -8125,13 +8141,13 @@ do_highlight( error = TRUE; break; } - HL_TABLE()[idx].sg_cleared = FALSE; + item->sg_cleared = FALSE; /* * When highlighting has been given for a group, don't link it. */ - if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) - HL_TABLE()[idx].sg_link = 0; + if (!init || !(item->sg_set & SG_LINK)) + item->sg_link = 0; /* * Continue with next argument. @@ -8148,10 +8164,10 @@ do_highlight( { if (is_normal_group) { - HL_TABLE()[idx].sg_term_attr = 0; - HL_TABLE()[idx].sg_cterm_attr = 0; + item->sg_term_attr = 0; + item->sg_cterm_attr = 0; #ifdef FEAT_GUI - HL_TABLE()[idx].sg_gui_attr = 0; + item->sg_gui_attr = 0; /* * Need to update all groups, because they might be using "bg" * and/or "fg", which have been changed now. @@ -8186,15 +8202,20 @@ do_highlight( else set_hl_attr(idx); #ifdef FEAT_EVAL - HL_TABLE()[idx].sg_scriptID = current_SID; + item->sg_scriptID = current_SID; #endif - redraw_all_later(NOT_VALID); } + vim_free(key); vim_free(arg); - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; + /* Only call highlight_changed() once, after a sequence of highlight + * commands, and only if an attribute actually changed. */ + if (memcmp(item, &item_before, sizeof(item_before)) != 0) + { + redraw_all_later(NOT_VALID); + need_highlight_changed = TRUE; + } } #if defined(EXITFREE) || defined(PROTO) diff --git a/src/terminal.c b/src/terminal.c index db8a65cbc8..556c7e2a52 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -38,8 +38,7 @@ * in tl_scrollback are no longer used. * * TODO: - * - ":term NONE" does not work on MS-Windows. - * https://github.com/vim/vim/pull/2056 + * - patch to use GUI or cterm colors for vterm. Yasuhiro, #2067 * - Redirecting output does not work on MS-Windows. * - implement term_setsize() * - add test for giving error for invalid 'termsize' value. @@ -97,7 +96,8 @@ struct terminal_S { /* used when tl_job is NULL and only a pty was created */ int tl_tty_fd; - char_u *tl_tty_name; + char_u *tl_tty_in; + char_u *tl_tty_out; int tl_normal_mode; /* TRUE: Terminal-Normal mode */ int tl_channel_closed; @@ -245,7 +245,11 @@ setup_job_options(jobopt_T *opt, int rows, int cols) opt->jo_term_cols = cols; } - static void +/* + * Start a terminal window and return its buffer. + * Returns NULL when failed. + */ + static buf_T * term_start(typval_T *argvar, jobopt_T *opt, int forceit) { exarg_T split_ea; @@ -253,9 +257,10 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) term_T *term; buf_T *old_curbuf = NULL; int res; + buf_T *newbuf; if (check_restricted() || check_secure()) - return; + return NULL; if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)) == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO) @@ -263,12 +268,12 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF))) { EMSG(_(e_invarg)); - return; + return NULL; } term = (term_T *)alloc_clear(sizeof(term_T)); if (term == NULL) - return; + return NULL; term->tl_dirty_row_end = MAX_ROW; term->tl_cursor_visible = TRUE; term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; @@ -283,13 +288,13 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) { no_write_message(); vim_free(term); - return; + return NULL; } if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE, ECMD_HIDE + (forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { vim_free(term); - return; + return NULL; } } else if (opt->jo_hidden) @@ -303,7 +308,7 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) if (buf == NULL || ml_open(buf) == FAIL) { vim_free(term); - return; + return NULL; } old_curbuf = curbuf; --curbuf->b_nwindows; @@ -333,7 +338,7 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) { /* split failed */ vim_free(term); - return; + return NULL; } } term->tl_buffer = curbuf; @@ -419,6 +424,7 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) else res = term_and_job_init(term, argvar, opt); + newbuf = curbuf; if (res == OK) { /* Get and remember the size we ended up with. Update the pty. */ @@ -453,7 +459,9 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit) /* Wiping out the buffer will also close the window and call * free_terminal(). */ do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE); + return NULL; } + return newbuf; } /* @@ -693,7 +701,7 @@ write_to_term(buf_T *buffer, char_u *msg, channel_T *channel) gui_macvim_force_flush(); #endif } - else + else if (buffer->b_nwindows > 0) redraw_after_callback(TRUE); } } @@ -883,6 +891,20 @@ term_job_running(term_T *term) || term->tl_job->jv_channel->ch_keep_open); } +/* + * Return TRUE if "term" has an active channel and used ":term NONE". + */ + int +term_none_open(term_T *term) +{ + /* Also consider the job finished when the channel is closed, to avoid a + * race condition when updating the title. */ + return term != NULL + && term->tl_job != NULL + && channel_is_open(term->tl_job->jv_channel) + && term->tl_job->jv_channel->ch_keep_open; +} + /* * Add the last line of the scrollback buffer to the buffer in the window. */ @@ -2384,6 +2406,8 @@ term_get_status_text(term_T *term) } else if (term->tl_title != NULL) txt = term->tl_title; + else if (term_none_open(term)) + txt = (char_u *)_("active"); else if (term_job_running(term)) txt = (char_u *)_("running"); else @@ -2671,14 +2695,32 @@ f_term_gettty(typval_T *argvars, typval_T *rettv) { buf_T *buf = term_get_buf(argvars); char_u *p; + int num = 0; rettv->v_type = VAR_STRING; if (buf == NULL) return; - if (buf->b_term->tl_job != NULL) - p = buf->b_term->tl_job->jv_tty_name; - else - p = buf->b_term->tl_tty_name; + if (argvars[1].v_type != VAR_UNKNOWN) + num = get_tv_number(&argvars[1]); + + switch (num) + { + case 0: + if (buf->b_term->tl_job != NULL) + p = buf->b_term->tl_job->jv_tty_out; + else + p = buf->b_term->tl_tty_out; + break; + case 1: + if (buf->b_term->tl_job != NULL) + p = buf->b_term->tl_job->jv_tty_in; + else + p = buf->b_term->tl_tty_in; + break; + default: + EMSG2(_(e_invarg2), get_tv_string(&argvars[1])); + return; + } if (p != NULL) rettv->vval.v_string = vim_strsave(p); } @@ -2845,11 +2887,13 @@ f_term_sendkeys(typval_T *argvars, typval_T *rettv) f_term_start(typval_T *argvars, typval_T *rettv) { jobopt_T opt; + buf_T *buf; init_job_options(&opt); if (argvars[1].v_type != VAR_UNKNOWN && get_job_options(&argvars[1], &opt, JO_TIMEOUT_ALL + JO_STOPONEXIT + + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO, JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN @@ -2858,10 +2902,10 @@ f_term_start(typval_T *argvars, typval_T *rettv) if (opt.jo_vertical) cmdmod.split = WSP_VERT; - term_start(&argvars[0], &opt, FALSE); + buf = term_start(&argvars[0], &opt, FALSE); - if (curbuf->b_term != NULL) - rettv->vval.v_number = curbuf->b_fnum; + if (buf != NULL && buf->b_term != NULL) + rettv->vval.v_number = buf->b_fnum; } /* @@ -2959,11 +3003,13 @@ term_send_eof(channel_T *ch) #define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul #define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull +#define WINPTY_MOUSE_MODE_FORCE 2 void* (*winpty_config_new)(UINT64, void*); void* (*winpty_open)(void*, void*); void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*); BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*); +void (*winpty_config_set_mouse_mode)(void*, int); void (*winpty_config_set_initial_size)(void*, int, int); LPCWSTR (*winpty_conin_name)(void*); LPCWSTR (*winpty_conout_name)(void*); @@ -2994,7 +3040,10 @@ dyn_winpty_init(int verbose) {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name}, {"winpty_config_free", (FARPROC*)&winpty_config_free}, {"winpty_config_new", (FARPROC*)&winpty_config_new}, - {"winpty_config_set_initial_size", (FARPROC*)&winpty_config_set_initial_size}, + {"winpty_config_set_mouse_mode", + (FARPROC*)&winpty_config_set_mouse_mode}, + {"winpty_config_set_initial_size", + (FARPROC*)&winpty_config_set_initial_size}, {"winpty_conin_name", (FARPROC*)&winpty_conin_name}, {"winpty_conout_name", (FARPROC*)&winpty_conout_name}, {"winpty_error_free", (FARPROC*)&winpty_error_free}, @@ -3060,7 +3109,6 @@ term_and_job_init( HANDLE child_thread_handle; void *winpty_err; void *spawn_config = NULL; - char buf[MAX_PATH]; garray_T ga; char_u *cmd; @@ -3093,13 +3141,14 @@ term_and_job_init( if (term->tl_winpty_config == NULL) goto failed; + winpty_config_set_mouse_mode(term->tl_winpty_config, + WINPTY_MOUSE_MODE_FORCE); winpty_config_set_initial_size(term->tl_winpty_config, term->tl_cols, term->tl_rows); term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err); if (term->tl_winpty == NULL) goto failed; - /* TODO: if the command is "NONE" only create a pty. */ spawn_config = winpty_spawn_config_new( WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN | WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN, @@ -3167,9 +3216,10 @@ term_and_job_init( job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle); job->jv_job_object = jo; job->jv_status = JOB_STARTED; - sprintf(buf, "winpty://%lu", - GetProcessId(winpty_agent_process(term->tl_winpty))); - job->jv_tty_name = vim_strsave((char_u*)buf); + job->jv_tty_in = utf16_to_enc( + (short_u*)winpty_conin_name(term->tl_winpty), NULL); + job->jv_tty_out = utf16_to_enc( + (short_u*)winpty_conout_name(term->tl_winpty), NULL); ++job->jv_refcount; term->tl_job = job; @@ -3210,9 +3260,68 @@ failed: } static int -create_pty_only(term_T *term, jobopt_T *opt) +create_pty_only(term_T *term, jobopt_T *options) { - /* TODO: implement this */ + HANDLE hPipeIn = INVALID_HANDLE_VALUE; + HANDLE hPipeOut = INVALID_HANDLE_VALUE; + char in_name[80], out_name[80]; + channel_T *channel = NULL; + + create_vterm(term, term->tl_rows, term->tl_cols); + + vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d", + GetCurrentProcessId(), + curbuf->b_fnum); + hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND, + PIPE_TYPE_MESSAGE | PIPE_NOWAIT, + PIPE_UNLIMITED_INSTANCES, + 0, 0, NMPWAIT_NOWAIT, NULL); + if (hPipeIn == INVALID_HANDLE_VALUE) + goto failed; + + vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d", + GetCurrentProcessId(), + curbuf->b_fnum); + hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND, + PIPE_TYPE_MESSAGE | PIPE_NOWAIT, + PIPE_UNLIMITED_INSTANCES, + 0, 0, 0, NULL); + if (hPipeOut == INVALID_HANDLE_VALUE) + goto failed; + + ConnectNamedPipe(hPipeIn, NULL); + ConnectNamedPipe(hPipeOut, NULL); + + term->tl_job = job_alloc(); + if (term->tl_job == NULL) + goto failed; + ++term->tl_job->jv_refcount; + + /* behave like the job is already finished */ + term->tl_job->jv_status = JOB_FINISHED; + + channel = add_channel(); + if (channel == NULL) + goto failed; + term->tl_job->jv_channel = channel; + channel->ch_keep_open = TRUE; + channel->ch_named_pipe = TRUE; + + channel_set_pipes(channel, + (sock_T)hPipeIn, + (sock_T)hPipeOut, + (sock_T)hPipeOut); + channel_set_job(channel, term->tl_job, options); + term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name); + term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name); + + return OK; + +failed: + if (hPipeIn != NULL) + CloseHandle(hPipeIn); + if (hPipeOut != NULL) + CloseHandle(hPipeOut); return FAIL; } @@ -3239,7 +3348,8 @@ term_free_vterm(term_T *term) static void term_report_winsize(term_T *term, int rows, int cols) { - winpty_set_size(term->tl_winpty, cols, rows, NULL); + if (term->tl_winpty) + winpty_set_size(term->tl_winpty, cols, rows, NULL); } int @@ -3280,8 +3390,6 @@ term_and_job_init( static int create_pty_only(term_T *term, jobopt_T *opt) { - int ret; - create_vterm(term, term->tl_rows, term->tl_cols); term->tl_job = job_alloc(); @@ -3292,9 +3400,7 @@ create_pty_only(term_T *term, jobopt_T *opt) /* behave like the job is already finished */ term->tl_job->jv_status = JOB_FINISHED; - ret = mch_create_pty_channel(term->tl_job, opt); - - return ret; + return mch_create_pty_channel(term->tl_job, opt); } /* diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim index 32b17aa637..b35d88093f 100644 --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -36,11 +36,11 @@ endfunc func Test_terminal_basic() let buf = Run_shell_in_terminal({}) if has("unix") - call assert_match("^/dev/", job_info(g:job).tty) - call assert_match("^/dev/", term_gettty('')) + call assert_match('^/dev/', job_info(g:job).tty_out) + call assert_match('^/dev/', term_gettty('')) else - call assert_match("^winpty://", job_info(g:job).tty) - call assert_match("^winpty://", term_gettty('')) + call assert_match('^\\\\.\\pipe\\', job_info(g:job).tty_out) + call assert_match('^\\\\.\\pipe\\', term_gettty('')) endif call assert_equal('t', mode()) call assert_match('%aR[^\n]*running]', execute('ls')) @@ -544,10 +544,6 @@ func Test_terminal_write_stdin() endfunc func Test_terminal_no_cmd() - " Todo: make this work on all systems. - if !has('unix') - return - endif " Todo: make this work in the GUI if !has('gui_running') return @@ -555,11 +551,20 @@ func Test_terminal_no_cmd() let buf = term_start('NONE', {}) call assert_notequal(0, buf) - let pty = job_info(term_getjob(buf))['tty'] + let pty = job_info(term_getjob(buf))['tty_out'] call assert_notequal('', pty) - call system('echo "look here" > ' . pty) + if has('win32') + silent exe '!cmd /c "echo look here > ' . pty . '"' + else + call system('echo "look here" > ' . pty) + endif call term_wait(buf) - call assert_equal('look here', term_getline(buf, 1)) + + let result = term_getline(buf, 1) + if has('win32') + let result = substitute(result, '\s\+$', '', '') + endif + call assert_equal('look here', result) bwipe! endfunc @@ -605,6 +610,7 @@ func Test_terminal_redir_file() call WaitFor('len(readfile("Xfile")) > 0') call assert_match('123', readfile('Xfile')[0]) call delete('Xfile') + bwipe endif if has('unix') @@ -613,6 +619,7 @@ func Test_terminal_redir_file() call WaitFor('len(readfile("Xfile")) > 0') call assert_match('executing job failed', readfile('Xfile')[0]) call delete('Xfile') + bwipe call writefile(['one line'], 'Xfile') let buf = term_start('cat', {'in_io': 'file', 'in_name': 'Xfile'}) diff --git a/src/version.c b/src/version.c index 950a496574..4497552be7 100644 --- a/src/version.c +++ b/src/version.c @@ -784,6 +784,18 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1077, +/**/ + 1076, +/**/ + 1075, +/**/ + 1074, +/**/ + 1073, +/**/ + 1072, /**/ 1071, /**/