# HG changeset patch # User Christian Brabandt # Date 1520628306 -3600 # Node ID fa198b71bab29dd33c7ad47f49b24ab4ebf792e7 # Parent 8f7f75828bed2818b979c1bc5035769655829996 patch 8.0.1592: terminal windows in a session are not properly restored commit https://github.com/vim/vim/commit/4d8bac8bf593ff087517ff79090c2d224325aae6 Author: Bram Moolenaar Date: Fri Mar 9 21:33:34 2018 +0100 patch 8.0.1592: terminal windows in a session are not properly restored Problem: Terminal windows in a session are not properly restored. Solution: Add "terminal" in 'sessionoptions'. When possible restore the command running in a terminal. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -4777,6 +4777,13 @@ get_job_options(typval_T *tv, jobopt_T * opt->jo_set |= JO2_HIDDEN; opt->jo_hidden = get_tv_number(item); } + else if (STRCMP(hi->hi_key, "norestore") == 0) + { + if (!(supported2 & JO2_NORESTORE)) + break; + opt->jo_set |= JO2_NORESTORE; + opt->jo_term_norestore = get_tv_number(item); + } #endif else if (STRCMP(hi->hi_key, "env") == 0) { @@ -5470,6 +5477,7 @@ job_start(typval_T *argvars, jobopt_T *o goto theend; } #ifdef USE_ARGV + /* This will modify "cmd". */ if (mch_parse_cmd(cmd, FALSE, &argv, &argc) == FAIL) goto theend; argv[argc] = NULL; diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -867,6 +867,7 @@ static struct fst {"term_list", 0, 0, f_term_list}, {"term_scrape", 2, 2, f_term_scrape}, {"term_sendkeys", 2, 2, f_term_sendkeys}, + {"term_setrestore", 2, 2, f_term_setrestore}, {"term_start", 1, 2, f_term_start}, {"term_wait", 1, 2, f_term_wait}, #endif diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -11095,6 +11095,11 @@ makeopens( { if (!(only_save_windows && buf->b_nwindows == 0) && !(buf->b_help && !(ssop_flags & SSOP_HELP)) +#ifdef FEAT_TERMINAL + /* skip terminal buffers: finished ones are not useful, others + * will be resurrected and result in a new buffer */ + && !bt_terminal(buf) +#endif && buf->b_fname != NULL && buf->b_p_bl) { @@ -11305,7 +11310,8 @@ makeopens( /* * Wipe out an empty unnamed buffer we started in. */ - if (put_line(fd, "if exists('s:wipebuf')") == FAIL) + if (put_line(fd, "if exists('s:wipebuf') && s:wipebuf != bufnr('%')") + == FAIL) return FAIL; if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL) return FAIL; @@ -11465,6 +11471,12 @@ ses_do_frame(frame_T *fr) static int ses_do_win(win_T *wp) { +#ifdef FEAT_TERMINAL + if (bt_terminal(wp->w_buffer)) + return !term_is_finished(wp->w_buffer) + && (ssop_flags & SSOP_TERMINAL) + && term_should_restore(wp->w_buffer); +#endif if (wp->w_buffer->b_fname == NULL #ifdef FEAT_QUICKFIX /* When 'buftype' is "nofile" can't restore the window contents. */ @@ -11530,13 +11542,21 @@ put_view( /* Edit the file. Skip this when ":next" already did it. */ if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { +# ifdef FEAT_TERMINAL + if (bt_terminal(wp->w_buffer)) + { + if (term_write_session(fd, wp) == FAIL) + return FAIL; + } + else +# endif /* * Load the file. */ if (wp->w_buffer->b_ffname != NULL -#ifdef FEAT_QUICKFIX +# ifdef FEAT_QUICKFIX && !bt_nofile(wp->w_buffer) -#endif +# endif ) { /* @@ -11554,8 +11574,7 @@ put_view( || fputs(" | else | edit ", fd) < 0 || ses_fname(fd, wp->w_buffer, flagp, FALSE) == FAIL || fputs(" | endif", fd) < 0 - || - put_eol(fd) == FAIL) + || put_eol(fd) == FAIL) return FAIL; } else diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -2403,7 +2403,7 @@ static struct vimoption options[] = {"sessionoptions", "ssop", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP, #ifdef FEAT_SESSION (char_u *)&p_ssop, PV_NONE, - {(char_u *)"blank,buffers,curdir,folds,help,options,tabpages,winsize", + {(char_u *)"blank,buffers,curdir,folds,help,options,tabpages,winsize,terminal", (char_u *)0L} #else (char_u *)NULL, PV_NONE, diff --git a/src/option.h b/src/option.h --- a/src/option.h +++ b/src/option.h @@ -751,7 +751,7 @@ EXTERN unsigned ssop_flags; /* Also used for 'viewoptions'! */ static char *(p_ssop_values[]) = {"buffers", "winpos", "resize", "winsize", "localoptions", "options", "help", "blank", "globals", "slash", "unix", - "sesdir", "curdir", "folds", "cursor", "tabpages", NULL}; + "sesdir", "curdir", "folds", "cursor", "tabpages", "terminal", NULL}; # endif # define SSOP_BUFFERS 0x001 # define SSOP_WINPOS 0x002 @@ -769,6 +769,7 @@ static char *(p_ssop_values[]) = {"buffe # define SSOP_FOLDS 0x2000 # define SSOP_CURSOR 0x4000 # define SSOP_TABPAGES 0x8000 +# define SSOP_TERMINAL 0x10000 #endif EXTERN char_u *p_sh; /* 'shell' */ EXTERN char_u *p_shcf; /* 'shellcmdflag' */ diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro --- a/src/proto/terminal.pro +++ b/src/proto/terminal.pro @@ -1,5 +1,8 @@ /* terminal.c */ void ex_terminal(exarg_T *eap); +int term_write_session(FILE *fd, win_T *wp); +int term_should_restore(buf_T *buf); +void f_term_setrestore(typval_T *argvars, typval_T *rettv); 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); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1706,7 +1706,8 @@ struct channel_S { #define JO2_HIDDEN 0x0400 /* "hidden" */ #define JO2_TERM_OPENCMD 0x0800 /* "term_opencmd" */ #define JO2_EOF_CHARS 0x1000 /* "eof_chars" */ -#define JO2_ALL 0x1FFF +#define JO2_NORESTORE 0x2000 /* "norestore" */ +#define JO2_ALL 0x2FFF #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) #define JO_CB_ALL \ @@ -1769,6 +1770,7 @@ typedef struct int jo_vertical; int jo_curwin; int jo_hidden; + int jo_term_norestore; char_u *jo_term_name; char_u *jo_term_opencmd; int jo_term_finish; diff --git a/src/terminal.c b/src/terminal.c --- a/src/terminal.c +++ b/src/terminal.c @@ -38,11 +38,10 @@ * in tl_scrollback are no longer used. * * TODO: - * - What to store in a session file? Shell at the prompt would be OK to - * restore, but others may not. Open the window and let the user start the - * command? Also see #2650. + * - Add a flag to kill the job when Vim is exiting. Useful when it's showing + * a logfile. Or send keys there to make it quit: "exit\r" for a shell. + * - When using 'termguicolors' still use the 16 ANSI colors as-is. Helps for * - Adding WinBar to terminal window doesn't display, text isn't shifted down. - * - When using 'termguicolors' still use the 16 ANSI colors as-is. Helps for * a job that uses 16 colors while Vim is using > 256. * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito * Higashi, 2017 Sep 19) @@ -135,6 +134,9 @@ struct terminal_S { void *tl_winpty_config; void *tl_winpty; #endif +#if defined(FEAT_SESSION) + char_u *tl_command; +#endif /* last known vterm size */ int tl_rows; @@ -487,6 +489,52 @@ term_start(typval_T *argvar, jobopt_T *o if (without_job) return curbuf; +#if defined(FEAT_SESSION) + /* Remember the command for the session file. */ + if (opt->jo_term_norestore) + { + term->tl_command = vim_strsave((char_u *)"NONE"); + } + else if (argvar->v_type == VAR_STRING) + { + char_u *cmd = argvar->vval.v_string; + + if (cmd != NULL && STRCMP(cmd, p_sh) != 0) + term->tl_command = vim_strsave(cmd); + } + else if (argvar->v_type == VAR_LIST + && argvar->vval.v_list != NULL + && argvar->vval.v_list->lv_len > 0) + { + garray_T ga; + listitem_T *item; + + ga_init2(&ga, 1, 100); + for (item = argvar->vval.v_list->lv_first; + item != NULL; item = item->li_next) + { + char_u *s = get_tv_string_chk(&item->li_tv); + char_u *p; + + if (s == NULL) + break; + p = vim_strsave_fnameescape(s, FALSE); + if (p == NULL) + break; + ga_concat(&ga, p); + vim_free(p); + ga_append(&ga, ' '); + } + if (item == NULL) + { + ga_append(&ga, NUL); + term->tl_command = ga.ga_data; + } + else + ga_clear(&ga); + } +#endif + /* System dependent: setup the vterm and maybe start the job in it. */ if (argvar->v_type == VAR_STRING && argvar->vval.v_string != NULL @@ -561,6 +609,8 @@ ex_terminal(exarg_T *eap) opt.jo_curwin = 1; else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0) opt.jo_hidden = 1; + else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0) + opt.jo_term_norestore = 1; else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0 && ep != NULL && isdigit(ep[1])) { @@ -620,6 +670,42 @@ ex_terminal(exarg_T *eap) vim_free(opt.jo_eof_chars); } +#if defined(FEAT_SESSION) || defined(PROTO) +/* + * Write a :terminal command to the session file to restore the terminal in + * window "wp". + * Return FAIL if writing fails. + */ + int +term_write_session(FILE *fd, win_T *wp) +{ + term_T *term = wp->w_buffer->b_term; + + /* Create the terminal and run the command. This is not without + * risk, but let's assume the user only creates a session when this + * will be OK. */ + if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ", + term->tl_cols, term->tl_rows) < 0) + return FAIL; + if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0) + return FAIL; + + return put_eol(fd); +} + +/* + * Return TRUE if "buf" has a terminal that should be restored. + */ + int +term_should_restore(buf_T *buf) +{ + term_T *term = buf->b_term; + + return term != NULL && (term->tl_command == NULL + || STRCMP(term->tl_command, "NONE") != 0); +} +#endif + /* * Free the scrollback buffer for "term". */ @@ -669,6 +755,9 @@ free_terminal(buf_T *buf) term_free_vterm(term); vim_free(term->tl_title); +#ifdef FEAT_SESSION + vim_free(term->tl_command); +#endif vim_free(term->tl_status_text); vim_free(term->tl_opencmd); vim_free(term->tl_eof_chars); @@ -4048,6 +4137,29 @@ f_term_sendkeys(typval_T *argvars, typva } /* + * "term_setrestore(buf, command)" function + */ + void +f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +#if defined(FEAT_SESSION) + buf_T *buf = term_get_buf(argvars); + term_T *term; + char_u *cmd; + + if (buf == NULL) + return; + term = buf->b_term; + vim_free(term->tl_command); + cmd = get_tv_string_chk(&argvars[1]); + if (cmd != NULL) + term->tl_command = vim_strsave(cmd); + else + term->tl_command = NULL; +#endif +} + +/* * "term_start(command, options)" function */ void @@ -4064,7 +4176,8 @@ f_term_start(typval_T *argvars, typval_T + 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 - + JO2_CWD + JO2_ENV + JO2_EOF_CHARS) == FAIL) + + JO2_CWD + JO2_ENV + JO2_EOF_CHARS + + JO2_NORESTORE) == FAIL) return; if (opt.jo_vertical) @@ -4566,6 +4679,7 @@ term_and_job_init( { create_vterm(term, term->tl_rows, term->tl_cols); + /* This will change a string in "argvar". */ term->tl_job = job_start(argvar, opt); if (term->tl_job != NULL) ++term->tl_job->jv_refcount; diff --git a/src/testdir/shared.vim b/src/testdir/shared.vim --- a/src/testdir/shared.vim +++ b/src/testdir/shared.vim @@ -270,3 +270,10 @@ func! Screenline(lnum) let line = join(chars, '') return matchstr(line, '^.\{-}\ze\s*$') endfunc + +" Stops the shell running in terminal "buf". +func Stop_shell_in_terminal(buf) + call term_sendkeys(a:buf, "exit\r") + let job = term_getjob(a:buf) + call WaitFor({-> job_status(job) == "dead"}) +endfunc diff --git a/src/testdir/test_mksession.vim b/src/testdir/test_mksession.vim --- a/src/testdir/test_mksession.vim +++ b/src/testdir/test_mksession.vim @@ -7,6 +7,8 @@ if !has('multi_byte') || !has('mksession finish endif +source shared.vim + func Test_mksession() tabnew let wrap_save = &wrap @@ -99,6 +101,7 @@ func Test_mksession() call delete('Xtest_mks.out') call delete(tmpfile) let &wrap = wrap_save + set sessionoptions& endfunc func Test_mksession_winheight() @@ -150,6 +153,107 @@ func Test_mksession_one_buffer_two_windo call delete('Xtest_mks.out') endfunc +if has('terminal') + +func Test_mksession_terminal_shell() + terminal + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let term_cmd = '' + for line in lines + if line =~ '^terminal' + let term_cmd = line + elseif line =~ 'badd.*' . &shell + call assert_report('unexpected shell line: ' . line) + endif + endfor + call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+\s*$', term_cmd) + + call Stop_shell_in_terminal(bufnr('%')) + call delete('Xtest_mks.out') +endfunc + +func Test_mksession_terminal_no_restore_cmdarg() + terminal ++norestore + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let term_cmd = '' + for line in lines + if line =~ '^terminal' + call assert_report('session must not restore teminal') + endif + endfor + + call Stop_shell_in_terminal(bufnr('%')) + call delete('Xtest_mks.out') +endfunc + +func Test_mksession_terminal_no_restore_funcarg() + call term_start(&shell, {'norestore': 1}) + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let term_cmd = '' + for line in lines + if line =~ '^terminal' + call assert_report('session must not restore teminal') + endif + endfor + + call Stop_shell_in_terminal(bufnr('%')) + call delete('Xtest_mks.out') +endfunc + +func Test_mksession_terminal_no_restore_func() + terminal + call term_setrestore(bufnr('%'), 'NONE') + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let term_cmd = '' + for line in lines + if line =~ '^terminal' + call assert_report('session must not restore teminal') + endif + endfor + + call Stop_shell_in_terminal(bufnr('%')) + call delete('Xtest_mks.out') +endfunc + +func Test_mksession_terminal_no_ssop() + terminal + set sessionoptions-=terminal + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let term_cmd = '' + for line in lines + if line =~ '^terminal' + call assert_report('session must not restore teminal') + endif + endfor + + call Stop_shell_in_terminal(bufnr('%')) + call delete('Xtest_mks.out') + set sessionoptions& +endfunc + +func Test_mksession_terminal_restore_other() + terminal + call term_setrestore(bufnr('%'), 'other') + mksession! Xtest_mks.out + let lines = readfile('Xtest_mks.out') + let term_cmd = '' + for line in lines + if line =~ '^terminal' + let term_cmd = line + endif + endfor + call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+ other', term_cmd) + + call Stop_shell_in_terminal(bufnr('%')) + call delete('Xtest_mks.out') +endfunc + +endif " has('terminal') " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -30,13 +30,6 @@ func Run_shell_in_terminal(options) return buf endfunc -" Stops the shell started by Run_shell_in_terminal(). -func Stop_shell_in_terminal(buf) - call term_sendkeys(a:buf, "exit\r") - call WaitFor('job_status(g:job) == "dead"') - call assert_equal('dead', job_status(g:job)) -endfunc - func Test_terminal_basic() au BufWinEnter * if &buftype == 'terminal' | let b:done = 'yes' | endif let buf = Run_shell_in_terminal({}) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -767,6 +767,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1592, +/**/ 1591, /**/ 1590,