diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2020-01-27 01:10:23 -0800 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2020-01-28 00:22:14 -0800 |
commit | 75e85622493cfbd357d2d87d988dc0ab79fd8326 (patch) | |
tree | edacf3960ef27e2bfd00659082b8ada1bd08aa6a | |
parent | d3a9d75c0462fea7b00e639d1b324bcf08a8b02d (diff) | |
download | rneovim-75e85622493cfbd357d2d87d988dc0ab79fd8326.tar.gz rneovim-75e85622493cfbd357d2d87d988dc0ab79fd8326.tar.bz2 rneovim-75e85622493cfbd357d2d87d988dc0ab79fd8326.zip |
refactor: move session functions to ex_session.c
-rw-r--r-- | src/nvim/eval.c | 1 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 1002 | ||||
-rw-r--r-- | src/nvim/ex_session.c | 1034 | ||||
-rw-r--r-- | src/nvim/ex_session.h | 13 | ||||
-rw-r--r-- | src/nvim/fold.c | 1 | ||||
-rw-r--r-- | src/nvim/getchar.c | 1 | ||||
-rw-r--r-- | src/nvim/option.c | 1 |
7 files changed, 1053 insertions, 1000 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 55b18d5f4f..5c6e475ac7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -70,6 +70,7 @@ #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" +#include "nvim/ex_session.h" #include "nvim/sha256.h" #include "nvim/sign.h" #include "nvim/spell.h" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d87dd29f88..12bee3ab86 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * ex_docmd.c: functions for executing an Ex command line. - */ +// ex_docmd.c: functions for executing an Ex command line. #include <assert.h> #include <string.h> @@ -40,6 +38,7 @@ #include "nvim/menu.h" #include "nvim/message.h" #include "nvim/misc1.h" +#include "nvim/ex_session.h" #include "nvim/keymap.h" #include "nvim/file_search.h" #include "nvim/garray.h" @@ -79,9 +78,6 @@ static int quitmore = 0; static bool ex_pressedreturn = false; -/// Whether ":lcd" or ":tcd" was produced for a session. -static int did_lcd; - typedef struct ucmd { char_u *uc_name; // The command name uint32_t uc_argt; // The argument type @@ -8085,162 +8081,6 @@ static void close_redir(void) } } -#define PUTLINE_FAIL(s) \ - do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) - -/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". -/// -/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. -/// - SSOP_UNIX: line-endings are always LF -/// - SSOP_SLASH: filenames are always written with "/" slash -static void ex_mkrc(exarg_T *eap) -{ - FILE *fd; - int failed = false; - int view_session = false; // :mkview, :mksession - int using_vdir = false; // using 'viewdir'? - char *viewFile = NULL; - unsigned *flagp; - - if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { - view_session = TRUE; - } - - /* Use the short file name until ":lcd" is used. We also don't use the - * short file name when 'acd' is set, that is checked later. */ - did_lcd = FALSE; - - char *fname; - // ":mkview" or ":mkview 9": generate file name with 'viewdir' - if (eap->cmdidx == CMD_mkview - && (*eap->arg == NUL - || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { - eap->forceit = true; - fname = get_view_file(*eap->arg); - if (fname == NULL) { - return; - } - viewFile = fname; - using_vdir = true; - } else if (*eap->arg != NUL) { - fname = (char *) eap->arg; - } else if (eap->cmdidx == CMD_mkvimrc) { - fname = VIMRC_FILE; - } else if (eap->cmdidx == CMD_mksession) { - fname = SESSION_FILE; - } else { - fname = EXRC_FILE; - } - - /* When using 'viewdir' may have to create the directory. */ - if (using_vdir && !os_isdir(p_vdir)) { - vim_mkdir_emsg((const char *)p_vdir, 0755); - } - - fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); - if (fd != NULL) { - if (eap->cmdidx == CMD_mkview) - flagp = &vop_flags; - else - flagp = &ssop_flags; - - // Write the version command for :mkvimrc - if (eap->cmdidx == CMD_mkvimrc) { - (void)put_line(fd, "version 6.0"); - } - - if (eap->cmdidx == CMD_mksession) { - if (put_line(fd, "let SessionLoad = 1") == FAIL) - failed = TRUE; - } - - if (!view_session || (eap->cmdidx == CMD_mksession - && (*flagp & SSOP_OPTIONS))) { - failed |= (makemap(fd, NULL) == FAIL - || makeset(fd, OPT_GLOBAL, false) == FAIL); - } - - if (!failed && view_session) { - if (put_line(fd, - "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") - == FAIL) - failed = TRUE; - if (eap->cmdidx == CMD_mksession) { - char_u *dirnow; /* current directory */ - - dirnow = xmalloc(MAXPATHL); - /* - * Change to session file's dir. - */ - if (os_dirname(dirnow, MAXPATHL) == FAIL - || os_chdir((char *)dirnow) != 0) - *dirnow = NUL; - if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { - if (vim_chdirfile((char_u *) fname) == OK) { - shorten_fnames(true); - } - } else if (*dirnow != NUL - && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { - if (os_chdir((char *)globaldir) == 0) - shorten_fnames(TRUE); - } - - failed |= (makeopens(fd, dirnow) == FAIL); - - /* restore original dir */ - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != - NULL))) { - if (os_chdir((char *)dirnow) != 0) - EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - /* restore original dir */ - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != - NULL))) { - if (os_chdir((char *)dirnow) != 0) - EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - } - } - xfree(dirnow); - } else { - failed |= (put_view(fd, curwin, !using_vdir, flagp, - -1) == FAIL); - } - if (fprintf(fd, - "%s", - "let &so = s:so_save | let &siso = s:siso_save\n" - "doautoall SessionLoadPost\n") - < 0) { - failed = true; - } - if (eap->cmdidx == CMD_mksession) { - if (fprintf(fd, "unlet SessionLoad\n") < 0) { - failed = true; - } - } - } - if (put_line(fd, "\" vim: set ft=vim :") == FAIL) - failed = TRUE; - - failed |= fclose(fd); - - if (failed) { - EMSG(_(e_write)); - } else if (eap->cmdidx == CMD_mksession) { - // successful session write - set v:this_session - char *const tbuf = xmalloc(MAXPATHL); - if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { - set_vim_var_string(VV_THIS_SESSION, tbuf, -1); - } - xfree(tbuf); - } - } - - xfree(viewFile); -} - /// Try creating a directory, give error message on failure /// /// @param[in] name Directory to create. @@ -9113,844 +8953,6 @@ char_u *expand_sfile(char_u *arg) return result; } - -/// Writes commands for restoring the current buffers, for :mksession. -/// -/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. -/// -/// @param dirnow Current directory name -/// @param fd File descriptor to write to -/// -/// @return FAIL on error, OK otherwise. -static int makeopens(FILE *fd, char_u *dirnow) -{ - int only_save_windows = TRUE; - int nr; - int restore_size = true; - win_T *wp; - char_u *sname; - win_T *edited_win = NULL; - int tabnr; - win_T *tab_firstwin; - frame_T *tab_topframe; - int cur_arg_idx = 0; - int next_arg_idx = 0; - - if (ssop_flags & SSOP_BUFFERS) - only_save_windows = FALSE; /* Save ALL buffers */ - - // Begin by setting v:this_session, and then other sessionable variables. - PUTLINE_FAIL("let v:this_session=expand(\"<sfile>:p\")"); - if (ssop_flags & SSOP_GLOBALS) { - if (store_session_globals(fd) == FAIL) { - return FAIL; - } - } - - // Close all windows but one. - PUTLINE_FAIL("silent only"); - - // - // Now a :cd command to the session directory or the current directory - // - if (ssop_flags & SSOP_SESDIR) { - PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); - } else if (ssop_flags & SSOP_CURDIR) { - sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); - char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); - if (fprintf(fd, "cd %s\n", fname_esc) < 0) { - xfree(fname_esc); - xfree(sname); - return FAIL; - } - xfree(fname_esc); - xfree(sname); - } - - if (fprintf(fd, - "%s", - // If there is an empty, unnamed buffer we will wipe it out later. - // Remember the buffer number. - "if expand('%') == '' && !&modified && line('$') <= 1" - " && getline(1) == ''\n" - " let s:wipebuf = bufnr('%')\n" - "endif\n" - // Now save the current files, current buffer first. - "set shortmess=aoO\n") < 0) { - return FAIL; - } - - // Now put the other buffers into the buffer list. - FOR_ALL_BUFFERS(buf) { - if (!(only_save_windows && buf->b_nwindows == 0) - && !(buf->b_help && !(ssop_flags & SSOP_HELP)) - && buf->b_fname != NULL - && buf->b_p_bl) { - if (fprintf(fd, "badd +%" PRId64 " ", - buf->b_wininfo == NULL - ? (int64_t)1L - : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 - || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { - return FAIL; - } - } - } - - /* the global argument list */ - if (ses_arglist(fd, "argglobal", &global_alist.al_ga, - !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { - return FAIL; - } - - if (ssop_flags & SSOP_RESIZE) { - // Note: after the restore we still check it worked! - if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", - (int64_t)Rows, (int64_t)Columns) < 0) { - return FAIL; - } - } - - int restore_stal = FALSE; - // When there are two or more tabpages and 'showtabline' is 1 the tabline - // will be displayed when creating the next tab. That resizes the windows - // in the first tab, which may cause problems. Set 'showtabline' to 2 - // temporarily to avoid that. - if (p_stal == 1 && first_tabpage->tp_next != NULL) { - PUTLINE_FAIL("set stal=2"); - restore_stal = true; - } - - // - // For each tab: - // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. - // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. - // - tab_firstwin = firstwin; // First window in tab page "tabnr". - tab_topframe = topframe; - for (tabnr = 1;; tabnr++) { - tabpage_T *tp = find_tabpage(tabnr); - if (tp == NULL) { - break; // done all tab pages - } - - int need_tabnew = false; - int cnr = 1; - - if ((ssop_flags & SSOP_TABPAGES)) { - if (tp == curtab) { - tab_firstwin = firstwin; - tab_topframe = topframe; - } else { - tab_firstwin = tp->tp_firstwin; - tab_topframe = tp->tp_topframe; - } - if (tabnr > 1) - need_tabnew = TRUE; - } - - // - // Before creating the window layout, try loading one file. If this - // is aborted we don't end up with a number of useless windows. - // This may have side effects! (e.g., compressed or network file). - // - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp) - && wp->w_buffer->b_ffname != NULL - && !bt_help(wp->w_buffer) - && !bt_nofile(wp->w_buffer) - ) { - if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { - return FAIL; - } - need_tabnew = false; - if (!wp->w_arg_idx_invalid) { - edited_win = wp; - } - break; - } - } - - // If no file got edited create an empty tab page. - if (need_tabnew && put_line(fd, "tabnew") == FAIL) { - return FAIL; - } - - // - // Save current window layout. - // - PUTLINE_FAIL("set splitbelow splitright"); - if (ses_win_rec(fd, tab_topframe) == FAIL) { - return FAIL; - } - if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { - return FAIL; - } - if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { - return FAIL; - } - - // - // Check if window sizes can be restored (no windows omitted). - // Remember the window number of the current window after restoring. - // - nr = 0; - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp)) - ++nr; - else - restore_size = FALSE; - if (curwin == wp) - cnr = nr; - } - - // Go to the first window. - PUTLINE_FAIL("wincmd t"); - - // If more than one window, see if sizes can be restored. - // First set 'winheight' and 'winwidth' to 1 to avoid the windows being - // resized when moving between windows. - // Do this before restoring the view, so that the topline and the - // cursor can be set. This is done again below. - // winminheight and winminwidth need to be set to avoid an error if the - // user has set winheight or winwidth. - if (fprintf(fd, - "set winminheight=0\n" - "set winheight=1\n" - "set winminwidth=0\n" - "set winwidth=1\n") < 0) { - return FAIL; - } - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { - return FAIL; - } - - // - // Restore the view of the window (options, file, cursor, etc.). - // - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) { - continue; - } - if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx) - == FAIL) { - return FAIL; - } - if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { - return FAIL; - } - next_arg_idx = wp->w_arg_idx; - } - - // The argument index in the first tab page is zero, need to set it in - // each window. For further tab pages it's the window where we do - // "tabedit". - cur_arg_idx = next_arg_idx; - - // - // Restore cursor to the current window if it's not the first one. - // - if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { - return FAIL; - } - - // - // Restore window sizes again after jumping around in windows, because - // the current window has a minimum size while others may not. - // - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { - return FAIL; - } - - // Take care of tab-local working directories if applicable - if (tp->tp_localdir) { - if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 - || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL - || fputs(" | endif\n", fd) < 0) { - return FAIL; - } - did_lcd = true; - } - - // Don't continue in another tab page when doing only the current one - // or when at the last tab page. - if (!(ssop_flags & SSOP_TABPAGES)) { - break; - } - } - - if (ssop_flags & SSOP_TABPAGES) { - if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { - return FAIL; - } - } - if (restore_stal && put_line(fd, "set stal=1") == FAIL) { - return FAIL; - } - - // - // Wipe out an empty unnamed buffer we started in. - // - if (fprintf(fd, "%s", - "if exists('s:wipebuf') " - "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" - " silent exe 'bwipe ' . s:wipebuf\n" - "endif\n" - "unlet! s:wipebuf\n") < 0) { - return FAIL; - } - - // Re-apply options. - if (fprintf(fd, - "set winheight=%" PRId64 " winwidth=%" PRId64 - " winminheight=%" PRId64 " winminwidth=%" PRId64 - " shortmess=%s\n", - (int64_t)p_wh, - (int64_t)p_wiw, - (int64_t)p_wmh, - (int64_t)p_wmw, - p_shm) < 0) { - return FAIL; - } - - // - // Lastly, execute the x.vim file if it exists. - // - if (fprintf(fd, "%s", - "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n" - "if file_readable(s:sx)\n" - " exe \"source \" . fnameescape(s:sx)\n" - "endif\n") < 0) { - return FAIL; - } - - return OK; -} - -static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) -{ - int n = 0; - win_T *wp; - - if (restore_size && (ssop_flags & SSOP_WINSIZE)) { - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) { - continue; - } - n++; - - // restore height when not full height - if (wp->w_height + wp->w_status_height < topframe->fr_height - && (fprintf(fd, - "exe '%dresize ' . ((&lines * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")\n", - n, (int64_t)wp->w_height, - (int64_t)Rows / 2, (int64_t)Rows) < 0)) { - return FAIL; - } - - // restore width when not full width - if (wp->w_width < Columns - && (fprintf(fd, - "exe 'vert %dresize ' . ((&columns * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")\n", - n, (int64_t)wp->w_width, (int64_t)Columns / 2, - (int64_t)Columns) < 0)) { - return FAIL; - } - } - } else { - // Just equalize window sizes. - PUTLINE_FAIL("wincmd ="); - } - return OK; -} - -// Write commands to "fd" to recursively create windows for frame "fr", -// horizontally and vertically split. -// After the commands the last window in the frame is the current window. -// Returns FAIL when writing the commands to "fd" fails. -static int ses_win_rec(FILE *fd, frame_T *fr) -{ - frame_T *frc; - int count = 0; - - if (fr->fr_layout != FR_LEAF) { - // Find first frame that's not skipped and then create a window for - // each following one (first frame is already there). - frc = ses_skipframe(fr->fr_child); - if (frc != NULL) - while ((frc = ses_skipframe(frc->fr_next)) != NULL) { - // Make window as big as possible so that we have lots of room - // to split. - if (fprintf(fd, "%s%s", - "wincmd _ | wincmd |\n", - (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { - return FAIL; - } - count++; - } - - // Go back to the first window. - if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { - return FAIL; - } - - // Recursively create frames/windows in each window of this column or row. - frc = ses_skipframe(fr->fr_child); - while (frc != NULL) { - ses_win_rec(fd, frc); - frc = ses_skipframe(frc->fr_next); - // Go to next window. - if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { - return FAIL; - } - } - } - return OK; -} - -// Skip frames that don't contain windows we want to save in the Session. -// Returns NULL when there none. -static frame_T *ses_skipframe(frame_T *fr) -{ - frame_T *frc; - - FOR_ALL_FRAMES(frc, fr) { - if (ses_do_frame(frc)) { - break; - } - } - return frc; -} - -// Return true if frame "fr" has a window somewhere that we want to save in -// the Session. -static bool ses_do_frame(const frame_T *fr) - FUNC_ATTR_NONNULL_ARG(1) -{ - const frame_T *frc; - - if (fr->fr_layout == FR_LEAF) { - return ses_do_win(fr->fr_win); - } - FOR_ALL_FRAMES(frc, fr->fr_child) { - if (ses_do_frame(frc)) { - return true; - } - } - return false; -} - -/// Return non-zero if window "wp" is to be stored in the Session. -static int ses_do_win(win_T *wp) -{ - if (wp->w_buffer->b_fname == NULL - // When 'buftype' is "nofile" can't restore the window contents. - || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { - return ssop_flags & SSOP_BLANK; - } - if (bt_help(wp->w_buffer)) { - return ssop_flags & SSOP_HELP; - } - return true; -} - -static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) -{ - int r; - - if (wp->w_curswant == MAXCOL) { - r = fprintf(fd, "%snormal! $\n", spaces); - } else { - r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); - } - return r >= 0; -} - -/* - * Write commands to "fd" to restore the view of a window. - * Caller must make sure 'scrolloff' is zero. - */ -static int -put_view( - FILE *fd, - win_T *wp, - int add_edit, /* add ":edit" command to view */ - unsigned *flagp, /* vop_flags or ssop_flags */ - int current_arg_idx /* current argument index of the window, use - * -1 if unknown */ -) -{ - win_T *save_curwin; - int f; - int do_cursor; - int did_next = FALSE; - - /* Always restore cursor position for ":mksession". For ":mkview" only - * when 'viewoptions' contains "cursor". */ - do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); - - /* - * Local argument list. - */ - if (wp->w_alist == &global_alist) { - PUTLINE_FAIL("argglobal"); - } else { - if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, - flagp == &vop_flags - || !(*flagp & SSOP_CURDIR) - || wp->w_localdir != NULL, flagp) == FAIL) { - return FAIL; - } - } - - /* Only when part of a session: restore the argument index. Some - * arguments may have been deleted, check if the index is valid. */ - if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) - && flagp == &ssop_flags) { - if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { - return FAIL; - } - did_next = true; - } - - // Edit the file. Skip this when ":next" already did it. - if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { - char *fname_esc = - ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); - // - // Load the file. - // - if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) - ) { - // Editing a file in this buffer: use ":edit file". - // This may have side effects! (e.g., compressed or network file). - // - // Note, if a buffer for that file already exists, use :badd to - // edit that buffer, to not lose folding information (:edit resets - // folds in other buffers) - if (fprintf(fd, - "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" - // Fixup :terminal buffer name. #7836 - "if &buftype ==# 'terminal'\n" - " silent file %s\n" - "endif\n", - fname_esc, - fname_esc, - fname_esc, - fname_esc) < 0) { - xfree(fname_esc); - return FAIL; - } - } else { - // No file in this buffer, just make it empty. - PUTLINE_FAIL("enew"); - if (wp->w_buffer->b_ffname != NULL) { - // The buffer does have a name, but it's not a file name. - if (fprintf(fd, "file %s\n", fname_esc) < 0) { - xfree(fname_esc); - return FAIL; - } - } - do_cursor = false; - } - xfree(fname_esc); - } - - /* - * Local mappings and abbreviations. - */ - if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - && makemap(fd, wp->w_buffer) == FAIL) { - return FAIL; - } - - /* - * Local options. Need to go to the window temporarily. - * Store only local values when using ":mkview" and when ":mksession" is - * used and 'sessionoptions' doesn't include "nvim/options". - * Some folding options are always stored when "folds" is included, - * otherwise the folds would not be restored correctly. - */ - save_curwin = curwin; - curwin = wp; - curbuf = curwin->w_buffer; - if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - f = makeset(fd, OPT_LOCAL, - flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); - else if (*flagp & SSOP_FOLDS) - f = makefoldset(fd); - else - f = OK; - curwin = save_curwin; - curbuf = curwin->w_buffer; - if (f == FAIL) { - return FAIL; - } - - // - // Save Folds when 'buftype' is empty and for help files. - // - if ((*flagp & SSOP_FOLDS) - && wp->w_buffer->b_ffname != NULL - && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) - ) { - if (put_folds(fd, wp) == FAIL) - return FAIL; - } - - // - // Set the cursor after creating folds, since that moves the cursor. - // - if (do_cursor) { - // Restore the cursor line in the file and relatively in the - // window. Don't use "G", it changes the jumplist. - if (fprintf(fd, - "let s:l = %" PRId64 " - ((%" PRId64 - " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" - "if s:l < 1 | let s:l = 1 | endif\n" - "exe s:l\n" - "normal! zt\n" - "%" PRId64 "\n", - (int64_t)wp->w_cursor.lnum, - (int64_t)(wp->w_cursor.lnum - wp->w_topline), - (int64_t)(wp->w_height_inner / 2), - (int64_t)wp->w_height_inner, - (int64_t)wp->w_cursor.lnum) < 0) { - return FAIL; - } - // Restore the cursor column and left offset when not wrapping. - if (wp->w_cursor.col == 0) { - PUTLINE_FAIL("normal! 0"); - } else { - if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { - if (fprintf(fd, - "let s:c = %" PRId64 " - ((%" PRId64 - " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" - "if s:c > 0\n" - " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" - "else\n", - (int64_t)wp->w_virtcol + 1, - (int64_t)(wp->w_virtcol - wp->w_leftcol), - (int64_t)(wp->w_width / 2), - (int64_t)wp->w_width, - (int64_t)wp->w_virtcol + 1) < 0 - || put_view_curpos(fd, wp, " ") == FAIL - || put_line(fd, "endif") == FAIL) { - return FAIL; - } - } else if (put_view_curpos(fd, wp, "") == FAIL) { - return FAIL; - } - } - } - - // - // Local directory, if the current flag is not view options or the "curdir" - // option is included. - // - if (wp->w_localdir != NULL - && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { - if (fputs("lcd ", fd) < 0 - || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL - || fprintf(fd, "\n") < 0) { - return FAIL; - } - did_lcd = true; - } - - return OK; -} - -/// Writes an :argument list to the session file. -/// -/// @param fd -/// @param cmd -/// @param gap -/// @param fullname true: use full path name -/// @param flagp -/// -/// @returns FAIL if writing fails. -static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, - unsigned *flagp) -{ - char_u *buf = NULL; - char_u *s; - - if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { - return FAIL; - } - for (int i = 0; i < gap->ga_len; i++) { - // NULL file names are skipped (only happens when out of memory). - s = alist_name(&((aentry_T *)gap->ga_data)[i]); - if (s != NULL) { - if (fullname) { - buf = xmalloc(MAXPATHL); - (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); - s = buf; - } - char *fname_esc = ses_escape_fname((char *)s, flagp); - if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { - xfree(fname_esc); - xfree(buf); - return FAIL; - } - xfree(fname_esc); - xfree(buf); - } - } - return OK; -} - -/// Gets the buffer name for `buf`. -static char *ses_get_fname(buf_T *buf, unsigned *flagp) -{ - // Use the short file name if the current directory is known at the time - // the session file will be sourced. - // Don't do this for ":mkview", we don't know the current directory. - // Don't do this after ":lcd", we don't keep track of what the current - // directory is. - if (buf->b_sfname != NULL - && flagp == &ssop_flags - && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) - && !p_acd - && !did_lcd) { - return (char *)buf->b_sfname; - } - return (char *)buf->b_ffname; -} - -/// Write a buffer name to the session file. -/// Also ends the line, if "add_eol" is true. -/// Returns FAIL if writing fails. -static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) -{ - char *name = ses_get_fname(buf, flagp); - if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL - || (add_eol && fprintf(fd, "\n") < 0)) { - return FAIL; - } - return OK; -} - -// Escapes a filename for session writing. -// Takes care of "slash" flag in 'sessionoptions' and escapes special -// characters. -// -// Returns allocated string or NULL. -static char *ses_escape_fname(char *name, unsigned *flagp) -{ - char *p; - char *sname = (char *)home_replace_save(NULL, (char_u *)name); - - // Always SSOP_SLASH: change all backslashes to forward slashes. - for (p = sname; *p != NUL; MB_PTR_ADV(p)) { - if (*p == '\\') { - *p = '/'; - } - } - - // Escape special characters. - p = vim_strsave_fnameescape(sname, false); - xfree(sname); - return p; -} - -// Write a file name to the session file. -// Takes care of the "slash" option in 'sessionoptions' and escapes special -// characters. -// Returns FAIL if writing fails. -static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) -{ - char *p = ses_escape_fname((char *)name, flagp); - bool retval = fputs(p, fd) < 0 ? FAIL : OK; - xfree(p); - return retval; -} - -/* - * ":loadview [nr]" - */ -static void ex_loadview(exarg_T *eap) -{ - char *fname = get_view_file(*eap->arg); - if (fname != NULL) { - if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { - EMSG2(_(e_notopen), fname); - } - xfree(fname); - } -} - -/// Get the name of the view file for the current buffer. -static char *get_view_file(int c) -{ - if (curbuf->b_ffname == NULL) { - EMSG(_(e_noname)); - return NULL; - } - char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); - - // We want a file name without separators, because we're not going to make - // a directory. - // "normal" path separator -> "=+" - // "=" -> "==" - // ":" path separator -> "=-" - size_t len = 0; - for (char *p = sname; *p; p++) { - if (*p == '=' || vim_ispathsep(*p)) { - ++len; - } - } - char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); - STRCPY(retval, p_vdir); - add_pathsep(retval); - char *s = retval + strlen(retval); - for (char *p = sname; *p; p++) { - if (*p == '=') { - *s++ = '='; - *s++ = '='; - } else if (vim_ispathsep(*p)) { - *s++ = '='; -#if defined(BACKSLASH_IN_FILENAME) - if (*p == ':') - *s++ = '-'; - else -#endif - *s++ = '+'; - } else - *s++ = *p; - } - *s++ = '='; - *s++ = c; - strcpy(s, ".vim"); - - xfree(sname); - return retval; -} - - -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. -int put_eol(FILE *fd) -{ - if (putc('\n', fd) < 0) { - return FAIL; - } - return OK; -} - -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. -int put_line(FILE *fd, char *s) -{ - if (fprintf(fd, "%s\n", s) < 0) { - return FAIL; - } - return OK; -} - /* * ":rshada" and ":wshada". */ diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c new file mode 100644 index 0000000000..5b0a432215 --- /dev/null +++ b/src/nvim/ex_session.c @@ -0,0 +1,1034 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// Functions for creating a session file, i.e. implementing: +// :mkexrc +// :mkvimrc +// :mkview +// :mksession + +#include <assert.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <inttypes.h> + +#include "nvim/vim.h" +#include "nvim/globals.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/cursor.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/ex_session.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/keymap.h" +#include "nvim/misc1.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/path.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ex_session.c.generated.h" +#endif + +/// Whether ":lcd" or ":tcd" was produced for a session. +static int did_lcd; + +#define PUTLINE_FAIL(s) \ + do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) + +static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) +{ + int r; + + if (wp->w_curswant == MAXCOL) { + r = fprintf(fd, "%snormal! $\n", spaces); + } else { + r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); + } + return r >= 0; +} + +static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) +{ + int n = 0; + win_T *wp; + + if (restore_size && (ssop_flags & SSOP_WINSIZE)) { + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) { + continue; + } + n++; + + // restore height when not full height + if (wp->w_height + wp->w_status_height < topframe->fr_height + && (fprintf(fd, + "exe '%dresize ' . ((&lines * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")\n", + n, (int64_t)wp->w_height, + (int64_t)Rows / 2, (int64_t)Rows) < 0)) { + return FAIL; + } + + // restore width when not full width + if (wp->w_width < Columns + && (fprintf(fd, + "exe 'vert %dresize ' . ((&columns * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")\n", + n, (int64_t)wp->w_width, (int64_t)Columns / 2, + (int64_t)Columns) < 0)) { + return FAIL; + } + } + } else { + // Just equalize window sizes. + PUTLINE_FAIL("wincmd ="); + } + return OK; +} + +// Write commands to "fd" to recursively create windows for frame "fr", +// horizontally and vertically split. +// After the commands the last window in the frame is the current window. +// Returns FAIL when writing the commands to "fd" fails. +static int ses_win_rec(FILE *fd, frame_T *fr) +{ + frame_T *frc; + int count = 0; + + if (fr->fr_layout != FR_LEAF) { + // Find first frame that's not skipped and then create a window for + // each following one (first frame is already there). + frc = ses_skipframe(fr->fr_child); + if (frc != NULL) + while ((frc = ses_skipframe(frc->fr_next)) != NULL) { + // Make window as big as possible so that we have lots of room + // to split. + if (fprintf(fd, "%s%s", + "wincmd _ | wincmd |\n", + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { + return FAIL; + } + count++; + } + + // Go back to the first window. + if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL + ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { + return FAIL; + } + + // Recursively create frames/windows in each window of this column or row. + frc = ses_skipframe(fr->fr_child); + while (frc != NULL) { + ses_win_rec(fd, frc); + frc = ses_skipframe(frc->fr_next); + // Go to next window. + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { + return FAIL; + } + } + } + return OK; +} + +// Skip frames that don't contain windows we want to save in the Session. +// Returns NULL when there none. +static frame_T *ses_skipframe(frame_T *fr) +{ + frame_T *frc; + + FOR_ALL_FRAMES(frc, fr) { + if (ses_do_frame(frc)) { + break; + } + } + return frc; +} + +// Return true if frame "fr" has a window somewhere that we want to save in +// the Session. +static bool ses_do_frame(const frame_T *fr) + FUNC_ATTR_NONNULL_ARG(1) +{ + const frame_T *frc; + + if (fr->fr_layout == FR_LEAF) { + return ses_do_win(fr->fr_win); + } + FOR_ALL_FRAMES(frc, fr->fr_child) { + if (ses_do_frame(frc)) { + return true; + } + } + return false; +} + +/// Return non-zero if window "wp" is to be stored in the Session. +static int ses_do_win(win_T *wp) +{ + if (wp->w_buffer->b_fname == NULL + // When 'buftype' is "nofile" can't restore the window contents. + || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { + return ssop_flags & SSOP_BLANK; + } + if (bt_help(wp->w_buffer)) { + return ssop_flags & SSOP_HELP; + } + return true; +} + +/// Writes an :argument list to the session file. +/// +/// @param fd +/// @param cmd +/// @param gap +/// @param fullname true: use full path name +/// @param flagp +/// +/// @returns FAIL if writing fails. +static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, + unsigned *flagp) +{ + char_u *buf = NULL; + char_u *s; + + if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { + return FAIL; + } + for (int i = 0; i < gap->ga_len; i++) { + // NULL file names are skipped (only happens when out of memory). + s = alist_name(&((aentry_T *)gap->ga_data)[i]); + if (s != NULL) { + if (fullname) { + buf = xmalloc(MAXPATHL); + (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); + s = buf; + } + char *fname_esc = ses_escape_fname((char *)s, flagp); + if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { + xfree(fname_esc); + xfree(buf); + return FAIL; + } + xfree(fname_esc); + xfree(buf); + } + } + return OK; +} + +/// Gets the buffer name for `buf`. +static char *ses_get_fname(buf_T *buf, unsigned *flagp) +{ + // Use the short file name if the current directory is known at the time + // the session file will be sourced. + // Don't do this for ":mkview", we don't know the current directory. + // Don't do this after ":lcd", we don't keep track of what the current + // directory is. + if (buf->b_sfname != NULL + && flagp == &ssop_flags + && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) + && !p_acd + && !did_lcd) { + return (char *)buf->b_sfname; + } + return (char *)buf->b_ffname; +} + +/// Write a buffer name to the session file. +/// Also ends the line, if "add_eol" is true. +/// Returns FAIL if writing fails. +static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) +{ + char *name = ses_get_fname(buf, flagp); + if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL + || (add_eol && fprintf(fd, "\n") < 0)) { + return FAIL; + } + return OK; +} + +// Escapes a filename for session writing. +// Takes care of "slash" flag in 'sessionoptions' and escapes special +// characters. +// +// Returns allocated string or NULL. +static char *ses_escape_fname(char *name, unsigned *flagp) +{ + char *p; + char *sname = (char *)home_replace_save(NULL, (char_u *)name); + + // Always SSOP_SLASH: change all backslashes to forward slashes. + for (p = sname; *p != NUL; MB_PTR_ADV(p)) { + if (*p == '\\') { + *p = '/'; + } + } + + // Escape special characters. + p = vim_strsave_fnameescape(sname, false); + xfree(sname); + return p; +} + +// Write a file name to the session file. +// Takes care of the "slash" option in 'sessionoptions' and escapes special +// characters. +// Returns FAIL if writing fails. +static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +{ + char *p = ses_escape_fname((char *)name, flagp); + bool retval = fputs(p, fd) < 0 ? FAIL : OK; + xfree(p); + return retval; +} + +// Write commands to "fd" to restore the view of a window. +// Caller must make sure 'scrolloff' is zero. +static int put_view( + FILE *fd, + win_T *wp, + int add_edit, /* add ":edit" command to view */ + unsigned *flagp, /* vop_flags or ssop_flags */ + int current_arg_idx /* current argument index of the window, use + * -1 if unknown */ +) +{ + win_T *save_curwin; + int f; + int do_cursor; + int did_next = FALSE; + + /* Always restore cursor position for ":mksession". For ":mkview" only + * when 'viewoptions' contains "cursor". */ + do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); + + /* + * Local argument list. + */ + if (wp->w_alist == &global_alist) { + PUTLINE_FAIL("argglobal"); + } else { + if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, + flagp == &vop_flags + || !(*flagp & SSOP_CURDIR) + || wp->w_localdir != NULL, flagp) == FAIL) { + return FAIL; + } + } + + /* Only when part of a session: restore the argument index. Some + * arguments may have been deleted, check if the index is valid. */ + if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) + && flagp == &ssop_flags) { + if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { + return FAIL; + } + did_next = true; + } + + // Edit the file. Skip this when ":next" already did it. + if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { + char *fname_esc = + ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); + // + // Load the file. + // + if (wp->w_buffer->b_ffname != NULL + && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) + ) { + // Editing a file in this buffer: use ":edit file". + // This may have side effects! (e.g., compressed or network file). + // + // Note, if a buffer for that file already exists, use :badd to + // edit that buffer, to not lose folding information (:edit resets + // folds in other buffers) + if (fprintf(fd, + "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" + // Fixup :terminal buffer name. #7836 + "if &buftype ==# 'terminal'\n" + " silent file %s\n" + "endif\n", + fname_esc, + fname_esc, + fname_esc, + fname_esc) < 0) { + xfree(fname_esc); + return FAIL; + } + } else { + // No file in this buffer, just make it empty. + PUTLINE_FAIL("enew"); + if (wp->w_buffer->b_ffname != NULL) { + // The buffer does have a name, but it's not a file name. + if (fprintf(fd, "file %s\n", fname_esc) < 0) { + xfree(fname_esc); + return FAIL; + } + } + do_cursor = false; + } + xfree(fname_esc); + } + + /* + * Local mappings and abbreviations. + */ + if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + && makemap(fd, wp->w_buffer) == FAIL) { + return FAIL; + } + + /* + * Local options. Need to go to the window temporarily. + * Store only local values when using ":mkview" and when ":mksession" is + * used and 'sessionoptions' doesn't include "nvim/options". + * Some folding options are always stored when "folds" is included, + * otherwise the folds would not be restored correctly. + */ + save_curwin = curwin; + curwin = wp; + curbuf = curwin->w_buffer; + if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + f = makeset(fd, OPT_LOCAL, + flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); + else if (*flagp & SSOP_FOLDS) + f = makefoldset(fd); + else + f = OK; + curwin = save_curwin; + curbuf = curwin->w_buffer; + if (f == FAIL) { + return FAIL; + } + + // + // Save Folds when 'buftype' is empty and for help files. + // + if ((*flagp & SSOP_FOLDS) + && wp->w_buffer->b_ffname != NULL + && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) + ) { + if (put_folds(fd, wp) == FAIL) + return FAIL; + } + + // + // Set the cursor after creating folds, since that moves the cursor. + // + if (do_cursor) { + // Restore the cursor line in the file and relatively in the + // window. Don't use "G", it changes the jumplist. + if (fprintf(fd, + "let s:l = %" PRId64 " - ((%" PRId64 + " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:l < 1 | let s:l = 1 | endif\n" + "exe s:l\n" + "normal! zt\n" + "%" PRId64 "\n", + (int64_t)wp->w_cursor.lnum, + (int64_t)(wp->w_cursor.lnum - wp->w_topline), + (int64_t)(wp->w_height_inner / 2), + (int64_t)wp->w_height_inner, + (int64_t)wp->w_cursor.lnum) < 0) { + return FAIL; + } + // Restore the cursor column and left offset when not wrapping. + if (wp->w_cursor.col == 0) { + PUTLINE_FAIL("normal! 0"); + } else { + if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { + if (fprintf(fd, + "let s:c = %" PRId64 " - ((%" PRId64 + " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:c > 0\n" + " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" + "else\n", + (int64_t)wp->w_virtcol + 1, + (int64_t)(wp->w_virtcol - wp->w_leftcol), + (int64_t)(wp->w_width / 2), + (int64_t)wp->w_width, + (int64_t)wp->w_virtcol + 1) < 0 + || put_view_curpos(fd, wp, " ") == FAIL + || put_line(fd, "endif") == FAIL) { + return FAIL; + } + } else if (put_view_curpos(fd, wp, "") == FAIL) { + return FAIL; + } + } + } + + // + // Local directory, if the current flag is not view options or the "curdir" + // option is included. + // + if (wp->w_localdir != NULL + && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { + if (fputs("lcd ", fd) < 0 + || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL + || fprintf(fd, "\n") < 0) { + return FAIL; + } + did_lcd = true; + } + + return OK; +} + +/// Writes commands for restoring the current buffers, for :mksession. +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// +/// @param dirnow Current directory name +/// @param fd File descriptor to write to +/// +/// @return FAIL on error, OK otherwise. +static int makeopens(FILE *fd, char_u *dirnow) +{ + int only_save_windows = TRUE; + int nr; + int restore_size = true; + win_T *wp; + char_u *sname; + win_T *edited_win = NULL; + int tabnr; + win_T *tab_firstwin; + frame_T *tab_topframe; + int cur_arg_idx = 0; + int next_arg_idx = 0; + + if (ssop_flags & SSOP_BUFFERS) + only_save_windows = FALSE; /* Save ALL buffers */ + + // Begin by setting v:this_session, and then other sessionable variables. + PUTLINE_FAIL("let v:this_session=expand(\"<sfile>:p\")"); + if (ssop_flags & SSOP_GLOBALS) { + if (store_session_globals(fd) == FAIL) { + return FAIL; + } + } + + // Close all windows but one. + PUTLINE_FAIL("silent only"); + + // + // Now a :cd command to the session directory or the current directory + // + if (ssop_flags & SSOP_SESDIR) { + PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); + } else if (ssop_flags & SSOP_CURDIR) { + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); + char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); + if (fprintf(fd, "cd %s\n", fname_esc) < 0) { + xfree(fname_esc); + xfree(sname); + return FAIL; + } + xfree(fname_esc); + xfree(sname); + } + + if (fprintf(fd, + "%s", + // If there is an empty, unnamed buffer we will wipe it out later. + // Remember the buffer number. + "if expand('%') == '' && !&modified && line('$') <= 1" + " && getline(1) == ''\n" + " let s:wipebuf = bufnr('%')\n" + "endif\n" + // Now save the current files, current buffer first. + "set shortmess=aoO\n") < 0) { + return FAIL; + } + + // Now put the other buffers into the buffer list. + FOR_ALL_BUFFERS(buf) { + if (!(only_save_windows && buf->b_nwindows == 0) + && !(buf->b_help && !(ssop_flags & SSOP_HELP)) + && buf->b_fname != NULL + && buf->b_p_bl) { + if (fprintf(fd, "badd +%" PRId64 " ", + buf->b_wininfo == NULL + ? (int64_t)1L + : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { + return FAIL; + } + } + } + + /* the global argument list */ + if (ses_arglist(fd, "argglobal", &global_alist.al_ga, + !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { + return FAIL; + } + + if (ssop_flags & SSOP_RESIZE) { + // Note: after the restore we still check it worked! + if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", + (int64_t)Rows, (int64_t)Columns) < 0) { + return FAIL; + } + } + + int restore_stal = FALSE; + // When there are two or more tabpages and 'showtabline' is 1 the tabline + // will be displayed when creating the next tab. That resizes the windows + // in the first tab, which may cause problems. Set 'showtabline' to 2 + // temporarily to avoid that. + if (p_stal == 1 && first_tabpage->tp_next != NULL) { + PUTLINE_FAIL("set stal=2"); + restore_stal = true; + } + + // + // For each tab: + // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. + // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. + // + tab_firstwin = firstwin; // First window in tab page "tabnr". + tab_topframe = topframe; + for (tabnr = 1;; tabnr++) { + tabpage_T *tp = find_tabpage(tabnr); + if (tp == NULL) { + break; // done all tab pages + } + + int need_tabnew = false; + int cnr = 1; + + if ((ssop_flags & SSOP_TABPAGES)) { + if (tp == curtab) { + tab_firstwin = firstwin; + tab_topframe = topframe; + } else { + tab_firstwin = tp->tp_firstwin; + tab_topframe = tp->tp_topframe; + } + if (tabnr > 1) + need_tabnew = TRUE; + } + + // + // Before creating the window layout, try loading one file. If this + // is aborted we don't end up with a number of useless windows. + // This may have side effects! (e.g., compressed or network file). + // + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp) + && wp->w_buffer->b_ffname != NULL + && !bt_help(wp->w_buffer) + && !bt_nofile(wp->w_buffer) + ) { + if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 + || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { + return FAIL; + } + need_tabnew = false; + if (!wp->w_arg_idx_invalid) { + edited_win = wp; + } + break; + } + } + + // If no file got edited create an empty tab page. + if (need_tabnew && put_line(fd, "tabnew") == FAIL) { + return FAIL; + } + + // + // Save current window layout. + // + PUTLINE_FAIL("set splitbelow splitright"); + if (ses_win_rec(fd, tab_topframe) == FAIL) { + return FAIL; + } + if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { + return FAIL; + } + if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { + return FAIL; + } + + // + // Check if window sizes can be restored (no windows omitted). + // Remember the window number of the current window after restoring. + // + nr = 0; + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp)) + ++nr; + else + restore_size = FALSE; + if (curwin == wp) + cnr = nr; + } + + // Go to the first window. + PUTLINE_FAIL("wincmd t"); + + // If more than one window, see if sizes can be restored. + // First set 'winheight' and 'winwidth' to 1 to avoid the windows being + // resized when moving between windows. + // Do this before restoring the view, so that the topline and the + // cursor can be set. This is done again below. + // winminheight and winminwidth need to be set to avoid an error if the + // user has set winheight or winwidth. + if (fprintf(fd, + "set winminheight=0\n" + "set winheight=1\n" + "set winminwidth=0\n" + "set winwidth=1\n") < 0) { + return FAIL; + } + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { + return FAIL; + } + + // + // Restore the view of the window (options, file, cursor, etc.). + // + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) { + continue; + } + if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx) + == FAIL) { + return FAIL; + } + if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { + return FAIL; + } + next_arg_idx = wp->w_arg_idx; + } + + // The argument index in the first tab page is zero, need to set it in + // each window. For further tab pages it's the window where we do + // "tabedit". + cur_arg_idx = next_arg_idx; + + // + // Restore cursor to the current window if it's not the first one. + // + if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { + return FAIL; + } + + // + // Restore window sizes again after jumping around in windows, because + // the current window has a minimum size while others may not. + // + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { + return FAIL; + } + + // Take care of tab-local working directories if applicable + if (tp->tp_localdir) { + if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 + || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL + || fputs(" | endif\n", fd) < 0) { + return FAIL; + } + did_lcd = true; + } + + // Don't continue in another tab page when doing only the current one + // or when at the last tab page. + if (!(ssop_flags & SSOP_TABPAGES)) { + break; + } + } + + if (ssop_flags & SSOP_TABPAGES) { + if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { + return FAIL; + } + } + if (restore_stal && put_line(fd, "set stal=1") == FAIL) { + return FAIL; + } + + // + // Wipe out an empty unnamed buffer we started in. + // + if (fprintf(fd, "%s", + "if exists('s:wipebuf') " + "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" + " silent exe 'bwipe ' . s:wipebuf\n" + "endif\n" + "unlet! s:wipebuf\n") < 0) { + return FAIL; + } + + // Re-apply options. + if (fprintf(fd, + "set winheight=%" PRId64 " winwidth=%" PRId64 + " winminheight=%" PRId64 " winminwidth=%" PRId64 + " shortmess=%s\n", + (int64_t)p_wh, + (int64_t)p_wiw, + (int64_t)p_wmh, + (int64_t)p_wmw, + p_shm) < 0) { + return FAIL; + } + + // + // Lastly, execute the x.vim file if it exists. + // + if (fprintf(fd, "%s", + "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n" + "if file_readable(s:sx)\n" + " exe \"source \" . fnameescape(s:sx)\n" + "endif\n") < 0) { + return FAIL; + } + + return OK; +} + +/// ":loadview [nr]" +void ex_loadview(exarg_T *eap) +{ + char *fname = get_view_file(*eap->arg); + if (fname != NULL) { + if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { + EMSG2(_(e_notopen), fname); + } + xfree(fname); + } +} + +/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// - SSOP_UNIX: line-endings are always LF +/// - SSOP_SLASH: filenames are always written with "/" slash +void ex_mkrc(exarg_T *eap) +{ + FILE *fd; + int failed = false; + int view_session = false; // :mkview, :mksession + int using_vdir = false; // using 'viewdir'? + char *viewFile = NULL; + unsigned *flagp; + + if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { + view_session = TRUE; + } + + /* Use the short file name until ":lcd" is used. We also don't use the + * short file name when 'acd' is set, that is checked later. */ + did_lcd = FALSE; + + char *fname; + // ":mkview" or ":mkview 9": generate file name with 'viewdir' + if (eap->cmdidx == CMD_mkview + && (*eap->arg == NUL + || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { + eap->forceit = true; + fname = get_view_file(*eap->arg); + if (fname == NULL) { + return; + } + viewFile = fname; + using_vdir = true; + } else if (*eap->arg != NUL) { + fname = (char *) eap->arg; + } else if (eap->cmdidx == CMD_mkvimrc) { + fname = VIMRC_FILE; + } else if (eap->cmdidx == CMD_mksession) { + fname = SESSION_FILE; + } else { + fname = EXRC_FILE; + } + + /* When using 'viewdir' may have to create the directory. */ + if (using_vdir && !os_isdir(p_vdir)) { + vim_mkdir_emsg((const char *)p_vdir, 0755); + } + + fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); + if (fd != NULL) { + if (eap->cmdidx == CMD_mkview) + flagp = &vop_flags; + else + flagp = &ssop_flags; + + // Write the version command for :mkvimrc + if (eap->cmdidx == CMD_mkvimrc) { + (void)put_line(fd, "version 6.0"); + } + + if (eap->cmdidx == CMD_mksession) { + if (put_line(fd, "let SessionLoad = 1") == FAIL) + failed = TRUE; + } + + if (!view_session || (eap->cmdidx == CMD_mksession + && (*flagp & SSOP_OPTIONS))) { + failed |= (makemap(fd, NULL) == FAIL + || makeset(fd, OPT_GLOBAL, false) == FAIL); + } + + if (!failed && view_session) { + if (put_line(fd, + "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") + == FAIL) + failed = TRUE; + if (eap->cmdidx == CMD_mksession) { + char_u *dirnow; /* current directory */ + + dirnow = xmalloc(MAXPATHL); + /* + * Change to session file's dir. + */ + if (os_dirname(dirnow, MAXPATHL) == FAIL + || os_chdir((char *)dirnow) != 0) + *dirnow = NUL; + if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { + if (vim_chdirfile((char_u *) fname) == OK) { + shorten_fnames(true); + } + } else if (*dirnow != NUL + && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { + if (os_chdir((char *)globaldir) == 0) + shorten_fnames(TRUE); + } + + failed |= (makeopens(fd, dirnow) == FAIL); + + /* restore original dir */ + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (os_chdir((char *)dirnow) != 0) + EMSG(_(e_prev_dir)); + shorten_fnames(TRUE); + /* restore original dir */ + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (os_chdir((char *)dirnow) != 0) + EMSG(_(e_prev_dir)); + shorten_fnames(TRUE); + } + } + xfree(dirnow); + } else { + failed |= (put_view(fd, curwin, !using_vdir, flagp, + -1) == FAIL); + } + if (fprintf(fd, + "%s", + "let &so = s:so_save | let &siso = s:siso_save\n" + "doautoall SessionLoadPost\n") + < 0) { + failed = true; + } + if (eap->cmdidx == CMD_mksession) { + if (fprintf(fd, "unlet SessionLoad\n") < 0) { + failed = true; + } + } + } + if (put_line(fd, "\" vim: set ft=vim :") == FAIL) + failed = TRUE; + + failed |= fclose(fd); + + if (failed) { + EMSG(_(e_write)); + } else if (eap->cmdidx == CMD_mksession) { + // successful session write - set v:this_session + char *const tbuf = xmalloc(MAXPATHL); + if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { + set_vim_var_string(VV_THIS_SESSION, tbuf, -1); + } + xfree(tbuf); + } + } + + xfree(viewFile); +} + +/// Get the name of the view file for the current buffer. +static char *get_view_file(int c) +{ + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return NULL; + } + char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); + + // We want a file name without separators, because we're not going to make + // a directory. + // "normal" path separator -> "=+" + // "=" -> "==" + // ":" path separator -> "=-" + size_t len = 0; + for (char *p = sname; *p; p++) { + if (*p == '=' || vim_ispathsep(*p)) { + ++len; + } + } + char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); + STRCPY(retval, p_vdir); + add_pathsep(retval); + char *s = retval + strlen(retval); + for (char *p = sname; *p; p++) { + if (*p == '=') { + *s++ = '='; + *s++ = '='; + } else if (vim_ispathsep(*p)) { + *s++ = '='; +#if defined(BACKSLASH_IN_FILENAME) + if (*p == ':') + *s++ = '-'; + else +#endif + *s++ = '+'; + } else + *s++ = *p; + } + *s++ = '='; + assert(c >= CHAR_MIN && c <= CHAR_MAX); + *s++ = (char)c; + strcpy(s, ".vim"); + + xfree(sname); + return retval; +} + +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +int put_eol(FILE *fd) +{ + if (putc('\n', fd) < 0) { + return FAIL; + } + return OK; +} + +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +int put_line(FILE *fd, char *s) +{ + if (fprintf(fd, "%s\n", s) < 0) { + return FAIL; + } + return OK; +} diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h new file mode 100644 index 0000000000..8d3ea5b91a --- /dev/null +++ b/src/nvim/ex_session.h @@ -0,0 +1,13 @@ +#ifndef NVIM_EX_SESSION_H +#define NVIM_EX_SESSION_H + +#include <stdio.h> + +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ex_session.h.generated.h" +#endif + +#endif // NVIM_EX_SESSION_H + diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 0b14a6affb..331d7f9e29 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -19,6 +19,7 @@ #include "nvim/diff.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_session.h" #include "nvim/func_attr.h" #include "nvim/indent.h" #include "nvim/buffer_updates.h" diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 81666bf5d6..419c6328ee 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -41,6 +41,7 @@ #include "nvim/option.h" #include "nvim/regexp.h" #include "nvim/screen.h" +#include "nvim/ex_session.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" diff --git a/src/nvim/option.c b/src/nvim/option.c index 52742c8b64..cecfd7146c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -66,6 +66,7 @@ #include "nvim/path.h" #include "nvim/popupmnu.h" #include "nvim/regexp.h" +#include "nvim/ex_session.h" #include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/spellfile.h" |