ex_session.c (35520B)
1 // Functions for creating a session file, i.e. implementing: 2 // :mkexrc 3 // :mkvimrc 4 // :mkview 5 // :mksession 6 7 #include <inttypes.h> 8 #include <stdbool.h> 9 #include <stdio.h> 10 #include <string.h> 11 12 #include "klib/kvec.h" 13 #include "nvim/arglist.h" 14 #include "nvim/arglist_defs.h" 15 #include "nvim/ascii_defs.h" 16 #include "nvim/autocmd.h" 17 #include "nvim/autocmd_defs.h" 18 #include "nvim/buffer.h" 19 #include "nvim/buffer_defs.h" 20 #include "nvim/errors.h" 21 #include "nvim/eval.h" 22 #include "nvim/eval/typval.h" 23 #include "nvim/eval/typval_defs.h" 24 #include "nvim/eval/vars.h" 25 #include "nvim/ex_cmds_defs.h" 26 #include "nvim/ex_docmd.h" 27 #include "nvim/ex_getln.h" 28 #include "nvim/ex_session.h" 29 #include "nvim/file_search.h" 30 #include "nvim/fileio.h" 31 #include "nvim/fold.h" 32 #include "nvim/garray_defs.h" 33 #include "nvim/gettext_defs.h" 34 #include "nvim/globals.h" 35 #include "nvim/macros_defs.h" 36 #include "nvim/mapping.h" 37 #include "nvim/mbyte.h" 38 #include "nvim/memory.h" 39 #include "nvim/message.h" 40 #include "nvim/option.h" 41 #include "nvim/option_vars.h" 42 #include "nvim/os/fs.h" 43 #include "nvim/os/os.h" 44 #include "nvim/os/os_defs.h" 45 #include "nvim/path.h" 46 #include "nvim/pos_defs.h" 47 #include "nvim/runtime.h" 48 #include "nvim/strings.h" 49 #include "nvim/types_defs.h" 50 #include "nvim/vim_defs.h" 51 #include "nvim/window.h" 52 53 #include "ex_session.c.generated.h" 54 55 /// Whether ":lcd" or ":tcd" was produced for a session. 56 static int did_lcd; 57 58 #define PUTLINE_FAIL(s) \ 59 do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) 60 61 static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) 62 { 63 int r; 64 65 if (wp->w_curswant == MAXCOL) { 66 r = fprintf(fd, "%snormal! $\n", spaces); 67 } else { 68 r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); 69 } 70 return r >= 0; 71 } 72 73 static int ses_winsizes(FILE *fd, bool restore_size, win_T *tab_firstwin) 74 { 75 if (restore_size && (ssop_flags & kOptSsopFlagWinsize)) { 76 int n = 0; 77 for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) { 78 if (!ses_do_win(wp)) { 79 continue; 80 } 81 n++; 82 83 // restore height when not full height 84 if (wp->w_height + wp->w_hsep_height + wp->w_status_height < topframe->fr_height 85 && (fprintf(fd, 86 "exe '%dresize ' . ((&lines * %" PRId64 87 " + %" PRId64 ") / %" PRId64 ")\n", 88 n, (int64_t)wp->w_height, 89 (int64_t)Rows / 2, (int64_t)Rows) < 0)) { 90 return FAIL; 91 } 92 93 // restore width when not full width 94 if (wp->w_width < Columns 95 && (fprintf(fd, 96 "exe 'vert %dresize ' . ((&columns * %" PRId64 97 " + %" PRId64 ") / %" PRId64 ")\n", 98 n, (int64_t)wp->w_width, (int64_t)Columns / 2, 99 (int64_t)Columns) < 0)) { 100 return FAIL; 101 } 102 } 103 } else { 104 // Just equalize window sizes. 105 PUTLINE_FAIL("wincmd ="); 106 } 107 return OK; 108 } 109 110 /// Write commands to "fd" to recursively create windows for frame "fr", 111 /// horizontally and vertically split. 112 /// After the commands the last window in the frame is the current window. 113 /// 114 /// @return FAIL when writing the commands to "fd" fails. 115 static int ses_win_rec(FILE *fd, frame_T *fr) 116 { 117 int count = 0; 118 119 if (fr->fr_layout == FR_LEAF) { 120 return OK; 121 } 122 123 // Find first frame that's not skipped and then create a window for 124 // each following one (first frame is already there). 125 frame_T *frc = ses_skipframe(fr->fr_child); 126 if (frc != NULL) { 127 while ((frc = ses_skipframe(frc->fr_next)) != NULL) { 128 // Make window as big as possible so that we have lots of room 129 // to split. 130 if (fprintf(fd, "%s%s", 131 "wincmd _ | wincmd |\n", 132 (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { 133 return FAIL; 134 } 135 count++; 136 } 137 } 138 139 // Go back to the first window. 140 if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL 141 ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { 142 return FAIL; 143 } 144 145 // Recursively create frames/windows in each window of this column or row. 146 frc = ses_skipframe(fr->fr_child); 147 while (frc != NULL) { 148 ses_win_rec(fd, frc); 149 frc = ses_skipframe(frc->fr_next); 150 // Go to next window. 151 if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { 152 return FAIL; 153 } 154 } 155 156 return OK; 157 } 158 159 /// Skip frames that don't contain windows we want to save in the Session. 160 /// 161 /// @return NULL when there none. 162 static frame_T *ses_skipframe(frame_T *fr) 163 { 164 frame_T *frc; 165 166 FOR_ALL_FRAMES(frc, fr) { 167 if (ses_do_frame(frc)) { 168 break; 169 } 170 } 171 return frc; 172 } 173 174 /// @return true if frame "fr" has a window somewhere that we want to save in 175 /// the Session. 176 static bool ses_do_frame(const frame_T *fr) 177 FUNC_ATTR_NONNULL_ARG(1) 178 { 179 const frame_T *frc; 180 181 if (fr->fr_layout == FR_LEAF) { 182 return ses_do_win(fr->fr_win); 183 } 184 FOR_ALL_FRAMES(frc, fr->fr_child) { 185 if (ses_do_frame(frc)) { 186 return true; 187 } 188 } 189 return false; 190 } 191 192 /// @return non-zero if window "wp" is to be stored in the Session. 193 static int ses_do_win(win_T *wp) 194 { 195 // Skip floating windows to avoid issues when restoring the Session. #18432 196 if (wp->w_floating) { 197 return false; 198 } 199 if (wp->w_buffer->b_fname == NULL 200 // When 'buftype' is "nofile" can't restore the window contents. 201 || (!wp->w_buffer->terminal && bt_nofilename(wp->w_buffer))) { 202 return ssop_flags & kOptSsopFlagBlank; 203 } 204 if (bt_help(wp->w_buffer)) { 205 return ssop_flags & kOptSsopFlagHelp; 206 } 207 if (bt_terminal(wp->w_buffer)) { 208 return ssop_flags & kOptSsopFlagTerminal; 209 } 210 return true; 211 } 212 213 /// Writes an :argument list to the session file. 214 /// 215 /// @param fd 216 /// @param cmd 217 /// @param gap 218 /// @param fullname true: use full path name 219 /// @param flagp 220 /// 221 /// @returns FAIL if writing fails. 222 static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, bool fullname, unsigned *flagp) 223 { 224 char *buf = NULL; 225 226 if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { 227 return FAIL; 228 } 229 for (int i = 0; i < gap->ga_len; i++) { 230 // NULL file names are skipped (only happens when out of memory). 231 char *s = alist_name(&((aentry_T *)gap->ga_data)[i]); 232 if (s != NULL) { 233 if (fullname) { 234 buf = xmalloc(MAXPATHL); 235 vim_FullName(s, buf, MAXPATHL, false); 236 s = buf; 237 } 238 char *fname_esc = ses_escape_fname(s, flagp); 239 if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { 240 xfree(fname_esc); 241 xfree(buf); 242 return FAIL; 243 } 244 xfree(fname_esc); 245 xfree(buf); 246 } 247 } 248 return OK; 249 } 250 251 /// @return the buffer name for `buf`. 252 static char *ses_get_fname(buf_T *buf, const unsigned *flagp) 253 { 254 // Use the short file name if the current directory is known at the time 255 // the session file will be sourced. 256 // Don't do this for ":mkview", we don't know the current directory. 257 // Don't do this after ":lcd", we don't keep track of what the current 258 // directory is. 259 if (buf->b_sfname != NULL 260 && flagp == &ssop_flags 261 && (ssop_flags & (kOptSsopFlagCurdir | kOptSsopFlagSesdir)) 262 && !p_acd 263 && !did_lcd) { 264 return buf->b_sfname; 265 } 266 return buf->b_ffname; 267 } 268 269 /// Write a buffer name to the session file. 270 /// Also ends the line, if "add_eol" is true. 271 /// 272 /// @return FAIL if writing fails. 273 static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) 274 { 275 char *name = ses_get_fname(buf, flagp); 276 if (ses_put_fname(fd, name, flagp) == FAIL 277 || (add_eol && fprintf(fd, "\n") < 0)) { 278 return FAIL; 279 } 280 return OK; 281 } 282 283 /// Escapes a filename for session writing. 284 /// Takes care of "slash" flag in 'sessionoptions' and escapes special 285 /// characters. 286 /// 287 /// @return allocated string or NULL. 288 static char *ses_escape_fname(char *name, unsigned *flagp) 289 { 290 char *p; 291 char *sname = home_replace_save(NULL, name); 292 293 // Always kOptSsopFlagSlash: change all backslashes to forward slashes. 294 for (p = sname; *p != NUL; MB_PTR_ADV(p)) { 295 if (*p == '\\') { 296 *p = '/'; 297 } 298 } 299 300 // Escape special characters. 301 p = vim_strsave_fnameescape(sname, VSE_NONE); 302 xfree(sname); 303 return p; 304 } 305 306 /// Write a file name to the session file. 307 /// Takes care of the "slash" option in 'sessionoptions' and escapes special 308 /// characters. 309 /// 310 /// @return FAIL if writing fails. 311 static int ses_put_fname(FILE *fd, char *name, unsigned *flagp) 312 { 313 char *p = ses_escape_fname(name, flagp); 314 bool retval = fputs(p, fd) < 0 ? FAIL : OK; 315 xfree(p); 316 return retval; 317 } 318 319 /// Write commands to "fd" to restore the view of a window. 320 /// Caller must make sure 'scrolloff' is zero. 321 /// 322 /// @param add_edit add ":edit" command to view 323 /// @param flagp vop_flags or ssop_flags 324 /// @param current_arg_idx current argument index of the window, use -1 if unknown 325 static int put_view(FILE *fd, win_T *wp, tabpage_T *tp, bool add_edit, unsigned *flagp, 326 int current_arg_idx) 327 { 328 int f; 329 bool did_next = false; 330 331 // Always restore cursor position for ":mksession". For ":mkview" only 332 // when 'viewoptions' contains "cursor". 333 bool do_cursor = (flagp == &ssop_flags || *flagp & kOptSsopFlagCursor); 334 335 // Local argument list. 336 if (wp->w_alist == &global_alist) { 337 PUTLINE_FAIL("argglobal"); 338 } else { 339 if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, 340 flagp == &vop_flags 341 || !(*flagp & kOptSsopFlagCurdir) 342 || tp->tp_localdir != NULL 343 || wp->w_localdir != NULL, flagp) == FAIL) { 344 return FAIL; 345 } 346 } 347 348 // Only when part of a session: restore the argument index. Some 349 // arguments may have been deleted, check if the index is valid. 350 if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) 351 && flagp == &ssop_flags) { 352 if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { 353 return FAIL; 354 } 355 did_next = true; 356 } 357 358 // Edit the file. Skip this when ":next" already did it. 359 if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { 360 char *fname_esc = ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); 361 if (bt_help(wp->w_buffer)) { 362 char *curtag = ""; 363 364 // A help buffer needs some options to be set. 365 // First, create a new empty buffer with "buftype=help". 366 // Then ":help" will re-use both the buffer and the window and set 367 // the options, even when "options" is not in 'sessionoptions'. 368 if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) { 369 curtag = wp->w_tagstack[wp->w_tagstackidx - 1].tagname; 370 } 371 372 if (put_line(fd, "enew | setl bt=help") == FAIL 373 || fprintf(fd, "help %s", curtag) < 0 || put_eol(fd) == FAIL) { 374 xfree(fname_esc); 375 return FAIL; 376 } 377 } else if (wp->w_buffer->b_ffname != NULL 378 && (!bt_nofilename(wp->w_buffer) || wp->w_buffer->terminal)) { 379 // Load the file. 380 381 // Editing a file in this buffer: use ":edit file". 382 // This may have side effects! (e.g., compressed or network file). 383 // 384 // Note, if a buffer for that file already exists, use :badd to 385 // edit that buffer, to not lose folding information (:edit resets 386 // folds in other buffers) 387 if (fprintf(fd, 388 "if bufexists(fnamemodify(\"%s\", \":p\")) | buffer %s | else | edit %s | endif\n" 389 // Fixup :terminal buffer name. #7836 390 "if &buftype ==# 'terminal'\n" 391 " silent file %s\n" 392 "endif\n", 393 fname_esc, 394 fname_esc, 395 fname_esc, 396 fname_esc) < 0) { 397 xfree(fname_esc); 398 return FAIL; 399 } 400 } else { 401 // No file in this buffer, just make it empty. 402 PUTLINE_FAIL("enew"); 403 if (wp->w_buffer->b_ffname != NULL) { 404 // The buffer does have a name, but it's not a file name. 405 if (fprintf(fd, "file %s\n", fname_esc) < 0) { 406 xfree(fname_esc); 407 return FAIL; 408 } 409 } 410 do_cursor = false; 411 } 412 xfree(fname_esc); 413 } 414 415 if (wp->w_alt_fnum) { 416 buf_T *const alt = buflist_findnr(wp->w_alt_fnum); 417 418 // Set the alternate file if the buffer is listed. 419 if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL 420 && *alt->b_fname != NUL 421 && alt->b_p_bl 422 // do not set balt if buffer is terminal and "terminal" is not set in options 423 && !(bt_terminal(alt) && !(ssop_flags & kOptSsopFlagTerminal)) 424 && (fputs("balt ", fd) < 0 425 || ses_fname(fd, alt, flagp, true) == FAIL)) { 426 return FAIL; 427 } 428 } 429 430 // Local mappings and abbreviations. 431 if ((*flagp & (kOptSsopFlagOptions | kOptSsopFlagLocaloptions)) 432 && makemap(fd, wp->w_buffer) == FAIL) { 433 return FAIL; 434 } 435 436 // Local options. Need to go to the window temporarily. 437 // Store only local values when using ":mkview" and when ":mksession" is 438 // used and 'sessionoptions' doesn't include "nvim/options". 439 // Some folding options are always stored when "folds" is included, 440 // otherwise the folds would not be restored correctly. 441 win_T *save_curwin = curwin; 442 curwin = wp; 443 curbuf = curwin->w_buffer; 444 if (*flagp & (kOptSsopFlagOptions | kOptSsopFlagLocaloptions)) { 445 f = makeset(fd, OPT_LOCAL, 446 flagp == &vop_flags || !(*flagp & kOptSsopFlagOptions)); 447 } else if (*flagp & kOptSsopFlagFolds) { 448 f = makefoldset(fd); 449 } else { 450 f = OK; 451 } 452 curwin = save_curwin; 453 curbuf = curwin->w_buffer; 454 if (f == FAIL) { 455 return FAIL; 456 } 457 458 // Save Folds when 'buftype' is empty and for help files. 459 if ((*flagp & kOptSsopFlagFolds) 460 && wp->w_buffer->b_ffname != NULL 461 && (bt_normal(wp->w_buffer) 462 || bt_help(wp->w_buffer))) { 463 if (put_folds(fd, wp) == FAIL) { 464 return FAIL; 465 } 466 } 467 468 // Set the cursor after creating folds, since that moves the cursor. 469 if (do_cursor) { 470 // Restore the cursor line in the file and relatively in the 471 // window. Don't use "G", it changes the jumplist. 472 if (wp->w_view_height <= 0) { 473 if (fprintf(fd, "let s:l = %" PRIdLINENR "\n", wp->w_cursor.lnum) < 0) { 474 return FAIL; 475 } 476 } else if (fprintf(fd, 477 "let s:l = %" PRIdLINENR " - ((%" PRIdLINENR 478 " * winheight(0) + %d) / %d)\n", 479 wp->w_cursor.lnum, 480 wp->w_cursor.lnum - wp->w_topline, 481 (wp->w_view_height / 2), 482 wp->w_view_height) < 0) { 483 return FAIL; 484 } 485 if (fprintf(fd, 486 "if s:l < 1 | let s:l = 1 | endif\n" 487 "keepjumps exe s:l\n" 488 "normal! zt\n" 489 "keepjumps %" PRIdLINENR "\n", 490 wp->w_cursor.lnum) < 0) { 491 return FAIL; 492 } 493 // Restore the cursor column and left offset when not wrapping. 494 if (wp->w_cursor.col == 0) { 495 PUTLINE_FAIL("normal! 0"); 496 } else { 497 if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { 498 if (fprintf(fd, 499 "let s:c = %" PRId64 " - ((%" PRId64 500 " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" 501 "if s:c > 0\n" 502 " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" 503 "else\n", 504 (int64_t)wp->w_virtcol + 1, 505 (int64_t)(wp->w_virtcol - wp->w_leftcol), 506 (int64_t)(wp->w_width / 2), 507 (int64_t)wp->w_width, 508 (int64_t)wp->w_virtcol + 1) < 0 509 || put_view_curpos(fd, wp, " ") == FAIL 510 || put_line(fd, "endif") == FAIL) { 511 return FAIL; 512 } 513 } else if (put_view_curpos(fd, wp, "") == FAIL) { 514 return FAIL; 515 } 516 } 517 } 518 519 // Local directory, if the current flag is not view options or the "curdir" 520 // option is included. 521 if (wp->w_localdir != NULL 522 && (flagp != &vop_flags || (*flagp & kOptSsopFlagCurdir))) { 523 if (fputs("lcd ", fd) < 0 524 || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL 525 || fprintf(fd, "\n") < 0) { 526 return FAIL; 527 } 528 did_lcd = true; 529 } 530 531 return OK; 532 } 533 534 static int store_session_globals(FILE *fd) 535 { 536 TV_DICT_ITER(get_globvar_dict(), this_var, { 537 if ((this_var->di_tv.v_type == VAR_NUMBER 538 || this_var->di_tv.v_type == VAR_STRING) 539 && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) { 540 // Escape special characters with a backslash. Turn a LF and 541 // CR into \n and \r. 542 char *const p = vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r"); 543 for (char *t = p; *t != NUL; t++) { 544 if (*t == '\n') { 545 *t = 'n'; 546 } else if (*t == '\r') { 547 *t = 'r'; 548 } 549 } 550 if ((fprintf(fd, "let %s = %c%s%c", 551 this_var->di_key, 552 ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' '), 553 p, 554 ((this_var->di_tv.v_type == VAR_STRING) ? '"' : ' ')) < 0) 555 || put_eol(fd) == FAIL) { 556 xfree(p); 557 return FAIL; 558 } 559 xfree(p); 560 } else if (this_var->di_tv.v_type == VAR_FLOAT 561 && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) { 562 float_T f = this_var->di_tv.vval.v_float; 563 int sign = ' '; 564 565 if (f < 0) { 566 f = -f; 567 sign = '-'; 568 } 569 if ((fprintf(fd, "let %s = %c%f", this_var->di_key, sign, f) < 0) 570 || put_eol(fd) == FAIL) { 571 return FAIL; 572 } 573 } 574 }); 575 return OK; 576 } 577 578 /// Writes commands for restoring the current buffers, for :mksession. 579 /// 580 /// Legacy 'sessionoptions'/'viewoptions' flags kOptSsopFlagUnix, kOptSsopFlagSlash are 581 /// always enabled. 582 /// 583 /// @param dirnow Current directory name 584 /// @param fd File descriptor to write to 585 /// 586 /// @return FAIL on error, OK otherwise. 587 static int makeopens(FILE *fd, char *dirnow) 588 { 589 bool only_save_windows = true; 590 bool restore_size = true; 591 win_T *edited_win = NULL; 592 win_T *tab_firstwin; 593 frame_T *tab_topframe; 594 int cur_arg_idx = 0; 595 int next_arg_idx = 0; 596 597 if (ssop_flags & kOptSsopFlagBuffers) { 598 only_save_windows = false; // Save ALL buffers 599 } 600 601 // Begin by setting v:this_session, and then other sessionable variables. 602 PUTLINE_FAIL("let v:this_session=expand(\"<sfile>:p\")"); 603 604 PUTLINE_FAIL("doautoall SessionLoadPre"); 605 606 if (ssop_flags & kOptSsopFlagGlobals) { 607 if (store_session_globals(fd) == FAIL) { 608 return FAIL; 609 } 610 } 611 612 // Close all windows and tabs but one. 613 PUTLINE_FAIL("silent only"); 614 if ((ssop_flags & kOptSsopFlagTabpages) 615 && put_line(fd, "silent tabonly") == FAIL) { 616 return FAIL; 617 } 618 619 // Now a :cd command to the session directory or the current directory 620 if (ssop_flags & kOptSsopFlagSesdir) { 621 PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); 622 } else if (ssop_flags & kOptSsopFlagCurdir) { 623 char *sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); 624 char *fname_esc = ses_escape_fname(sname, &ssop_flags); 625 if (fprintf(fd, "cd %s\n", fname_esc) < 0) { 626 xfree(fname_esc); 627 xfree(sname); 628 return FAIL; 629 } 630 xfree(fname_esc); 631 xfree(sname); 632 } 633 634 if (fprintf(fd, 635 "%s", 636 // If there is an empty, unnamed buffer we will wipe it out later. 637 // Remember the buffer number. 638 "if expand('%') == '' && !&modified && line('$') <= 1" 639 " && getline(1) == ''\n" 640 " let s:wipebuf = bufnr('%')\n" 641 "endif\n") < 0) { 642 return FAIL; 643 } 644 645 // Save 'shortmess' if not storing options. 646 if ((ssop_flags & kOptSsopFlagOptions) == 0) { 647 PUTLINE_FAIL("let s:shortmess_save = &shortmess"); 648 } 649 650 // Set 'shortmess' for the following. 651 PUTLINE_FAIL("set shortmess+=aoO"); 652 653 // Now save the current files, current buffer first. 654 // Put all buffers into the buffer list. 655 // Do it very early to preserve buffer order after loading session (which 656 // can be disrupted by prior `edit` or `tabedit` calls). 657 FOR_ALL_BUFFERS(buf) { 658 if (!(only_save_windows && buf->b_nwindows == 0) 659 && !(buf->b_help && !(ssop_flags & kOptSsopFlagHelp)) 660 && !(bt_terminal(buf) && !(ssop_flags & kOptSsopFlagTerminal)) 661 && buf->b_fname != NULL 662 && buf->b_p_bl) { 663 if (fprintf(fd, "badd +%" PRId64 " ", 664 kv_size(buf->b_wininfo) == 0 665 ? 1 : (int64_t)kv_A(buf->b_wininfo, 0)->wi_mark.mark.lnum) < 0 666 || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { 667 return FAIL; 668 } 669 } 670 } 671 672 // the global argument list 673 if (ses_arglist(fd, "argglobal", &global_alist.al_ga, 674 !(ssop_flags & kOptSsopFlagCurdir), &ssop_flags) == FAIL) { 675 return FAIL; 676 } 677 678 if (ssop_flags & kOptSsopFlagResize) { 679 // Note: after the restore we still check it worked! 680 if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", 681 (int64_t)Rows, (int64_t)Columns) < 0) { 682 return FAIL; 683 } 684 } 685 686 bool restore_stal = false; 687 // When there are two or more tabpages and 'showtabline' is 1 the tabline 688 // will be displayed when creating the next tab. That resizes the windows 689 // in the first tab, which may cause problems. Set 'showtabline' to 2 690 // temporarily to avoid that. 691 if (p_stal == 1 && first_tabpage->tp_next != NULL) { 692 PUTLINE_FAIL("set stal=2"); 693 restore_stal = true; 694 } 695 696 if ((ssop_flags & kOptSsopFlagTabpages)) { 697 // "tabpages" is in 'sessionoptions': Similar to ses_win_rec() below, 698 // populate the tab pages first so later local options won't be copied 699 // to the new tabs. 700 FOR_ALL_TABS(tp) { 701 // Use `bufhidden=wipe` to remove empty "placeholder" buffers once 702 // they are not needed. This prevents creating extra buffers (see 703 // cause of Vim patch 8.1.0829) 704 if (tp->tp_next != NULL && put_line(fd, "tabnew +setlocal\\ bufhidden=wipe") == FAIL) { 705 return FAIL; 706 } 707 } 708 709 if (first_tabpage->tp_next != NULL && put_line(fd, "tabrewind") == FAIL) { 710 return FAIL; 711 } 712 } 713 714 // Assume "tabpages" is in 'sessionoptions'. If not then we only do 715 // "curtab" and bail out of the loop. 716 bool restore_height_width = false; 717 FOR_ALL_TABS(tp) { 718 bool need_tabnext = false; 719 int cnr = 1; 720 721 // May repeat putting Windows for each tab, when "tabpages" is in 722 // 'sessionoptions'. 723 // Don't use goto_tabpage(), it may change directory and trigger 724 // autocommands. 725 if ((ssop_flags & kOptSsopFlagTabpages)) { 726 if (tp == curtab) { 727 tab_firstwin = firstwin; 728 tab_topframe = topframe; 729 } else { 730 tab_firstwin = tp->tp_firstwin; 731 tab_topframe = tp->tp_topframe; 732 } 733 if (tp != first_tabpage) { 734 need_tabnext = true; 735 } 736 } else { 737 tp = curtab; 738 tab_firstwin = firstwin; 739 tab_topframe = topframe; 740 } 741 742 // Before creating the window layout, try loading one file. If this 743 // is aborted we don't end up with a number of useless windows. 744 // This may have side effects! (e.g., compressed or network file). 745 for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) { 746 if (ses_do_win(wp) 747 && wp->w_buffer->b_ffname != NULL 748 && !bt_help(wp->w_buffer) 749 && !bt_nofilename(wp->w_buffer)) { 750 if (need_tabnext && put_line(fd, "tabnext") == FAIL) { 751 return FAIL; 752 } 753 need_tabnext = false; 754 755 if (fputs("edit ", fd) < 0 756 || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { 757 return FAIL; 758 } 759 if (!wp->w_arg_idx_invalid) { 760 edited_win = wp; 761 } 762 break; 763 } 764 } 765 766 // If no file got edited create an empty tab page. 767 if (need_tabnext && put_line(fd, "tabnext") == FAIL) { 768 return FAIL; 769 } 770 771 if (tab_topframe->fr_layout != FR_LEAF) { 772 // Save current window layout. 773 PUTLINE_FAIL("let s:save_splitbelow = &splitbelow"); 774 PUTLINE_FAIL("let s:save_splitright = &splitright"); 775 PUTLINE_FAIL("set splitbelow splitright"); 776 if (ses_win_rec(fd, tab_topframe) == FAIL) { 777 return FAIL; 778 } 779 PUTLINE_FAIL("let &splitbelow = s:save_splitbelow"); 780 PUTLINE_FAIL("let &splitright = s:save_splitright"); 781 } 782 783 // Check if window sizes can be restored (no windows omitted). 784 // Remember the window number of the current window after restoring. 785 int nr = 0; 786 for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) { 787 if (ses_do_win(wp)) { 788 nr++; 789 } else if (!wp->w_floating) { 790 restore_size = false; 791 } 792 if (curwin == wp) { 793 cnr = nr; 794 } 795 } 796 797 if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) { 798 // Go to the first window. 799 PUTLINE_FAIL("wincmd t"); 800 801 // If more than one window, see if sizes can be restored. 802 // First set 'winheight' and 'winwidth' to 1 to avoid the windows 803 // being resized when moving between windows. 804 // Do this before restoring the view, so that the topline and the 805 // cursor can be set. This is done again below. 806 // winminheight and winminwidth need to be set to avoid an error if 807 // the user has set winheight or winwidth. 808 PUTLINE_FAIL("let s:save_winminheight = &winminheight"); 809 PUTLINE_FAIL("let s:save_winminwidth = &winminwidth"); 810 if (fprintf(fd, 811 "set winminheight=0\n" 812 "set winheight=1\n" 813 "set winminwidth=0\n" 814 "set winwidth=1\n") < 0) { 815 return FAIL; 816 } 817 restore_height_width = true; 818 } 819 if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { 820 return FAIL; 821 } 822 823 // Restore the tab-local working directory if specified 824 // Do this before the windows, so that the window-local directory can 825 // override the tab-local directory. 826 if ((ssop_flags & kOptSsopFlagCurdir) && tp->tp_localdir != NULL) { 827 if (fputs("tcd ", fd) < 0 828 || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL 829 || put_eol(fd) == FAIL) { 830 return FAIL; 831 } 832 did_lcd = true; 833 } 834 835 // Restore the view of the window (options, file, cursor, etc.). 836 for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) { 837 if (!ses_do_win(wp)) { 838 continue; 839 } 840 if (put_view(fd, wp, tp, wp != edited_win, &ssop_flags, cur_arg_idx) 841 == FAIL) { 842 return FAIL; 843 } 844 if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { 845 return FAIL; 846 } 847 next_arg_idx = wp->w_arg_idx; 848 } 849 850 // The argument index in the first tab page is zero, need to set it in 851 // each window. For further tab pages it's the window where we do 852 // "tabedit". 853 cur_arg_idx = next_arg_idx; 854 855 // Restore cursor to the current window if it's not the first one. 856 if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { 857 return FAIL; 858 } 859 860 // Restore window sizes again after jumping around in windows, because 861 // the current window has a minimum size while others may not. 862 if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { 863 return FAIL; 864 } 865 866 // Don't continue in another tab page when doing only the current one 867 // or when at the last tab page. 868 if (!(ssop_flags & kOptSsopFlagTabpages)) { 869 break; 870 } 871 } 872 873 if (ssop_flags & kOptSsopFlagTabpages) { 874 if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { 875 return FAIL; 876 } 877 } 878 if (restore_stal && put_line(fd, "set stal=1") == FAIL) { 879 return FAIL; 880 } 881 882 // Wipe out an empty unnamed buffer we started in. 883 if (fprintf(fd, "%s", 884 "if exists('s:wipebuf') " 885 "&& len(win_findbuf(s:wipebuf)) == 0 " 886 "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" 887 " silent exe 'bwipe ' . s:wipebuf\n" 888 "endif\n" 889 "unlet! s:wipebuf\n") < 0) { 890 return FAIL; 891 } 892 893 // Re-apply 'winheight' and 'winwidth'. 894 if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 "\n", 895 (int64_t)p_wh, (int64_t)p_wiw) < 0) { 896 return FAIL; 897 } 898 899 // Restore 'shortmess'. 900 if (ssop_flags & kOptSsopFlagOptions) { 901 if (fprintf(fd, "set shortmess=%s\n", p_shm) < 0) { 902 return FAIL; 903 } 904 } else { 905 PUTLINE_FAIL("let &shortmess = s:shortmess_save"); 906 } 907 908 if (restore_height_width) { 909 // Restore 'winminheight' and 'winminwidth'. 910 PUTLINE_FAIL("let &winminheight = s:save_winminheight"); 911 PUTLINE_FAIL("let &winminwidth = s:save_winminwidth"); 912 } 913 914 // Lastly, execute the x.vim file if it exists. 915 if (fprintf(fd, "%s", 916 "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n" 917 "if filereadable(s:sx)\n" 918 " exe \"source \" . fnameescape(s:sx)\n" 919 "endif\n") < 0) { 920 return FAIL; 921 } 922 923 return OK; 924 } 925 926 /// ":loadview [nr]" 927 void ex_loadview(exarg_T *eap) 928 { 929 char *fname = get_view_file(*eap->arg); 930 if (fname == NULL) { 931 return; 932 } 933 934 if (do_source(fname, false, DOSO_NONE, NULL) == FAIL) { 935 semsg(_(e_notopen), fname); 936 } 937 xfree(fname); 938 } 939 940 /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". 941 /// 942 /// Legacy 'sessionoptions'/'viewoptions' flags are always enabled: 943 /// - kOptSsopFlagUnix: line-endings are LF 944 /// - kOptSsopFlagSlash: filenames are written with "/" slash 945 void ex_mkrc(exarg_T *eap) 946 { 947 bool view_session = false; // :mkview, :mksession 948 int using_vdir = false; // using 'viewdir'? 949 char *viewFile = NULL; 950 951 if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { 952 view_session = true; 953 } 954 955 // Use the short file name until ":lcd" is used. We also don't use the 956 // short file name when 'acd' is set, that is checked later. 957 did_lcd = false; 958 959 char *fname; 960 // ":mkview" or ":mkview 9": generate file name with 'viewdir' 961 if (eap->cmdidx == CMD_mkview 962 && (*eap->arg == NUL 963 || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { 964 eap->forceit = true; 965 fname = get_view_file(*eap->arg); 966 if (fname == NULL) { 967 return; 968 } 969 viewFile = fname; 970 using_vdir = true; 971 } else if (*eap->arg != NUL) { 972 fname = eap->arg; 973 } else if (eap->cmdidx == CMD_mkvimrc) { 974 fname = VIMRC_FILE; 975 } else if (eap->cmdidx == CMD_mksession) { 976 fname = SESSION_FILE; 977 } else { 978 fname = EXRC_FILE; 979 } 980 981 // When using 'viewdir' may have to create the directory. 982 if (using_vdir && !os_isdir(p_vdir)) { 983 vim_mkdir_emsg(p_vdir, 0755); 984 } 985 986 FILE *fd = open_exfile(fname, eap->forceit, WRITEBIN); 987 if (fd != NULL) { 988 bool failed = false; 989 unsigned *flagp; 990 if (eap->cmdidx == CMD_mkview) { 991 flagp = &vop_flags; 992 } else { 993 flagp = &ssop_flags; 994 } 995 996 // Write the version command for :mkvimrc 997 if (eap->cmdidx == CMD_mkvimrc) { 998 put_line(fd, "version 6.0"); 999 } 1000 1001 if (eap->cmdidx == CMD_mksession) { 1002 if (put_line(fd, "let SessionLoad = 1") == FAIL) { 1003 failed = true; 1004 } 1005 } 1006 1007 if (!view_session || (eap->cmdidx == CMD_mksession 1008 && (*flagp & kOptSsopFlagOptions))) { 1009 int flags = OPT_GLOBAL; 1010 1011 if (eap->cmdidx == CMD_mksession && (*flagp & kOptSsopFlagSkiprtp)) { 1012 flags |= OPT_SKIPRTP; 1013 } 1014 failed |= (makemap(fd, NULL) == FAIL 1015 || makeset(fd, flags, false) == FAIL); 1016 } 1017 1018 if (!failed && view_session) { 1019 if (put_line(fd, 1020 "let s:so_save = &g:so | let s:siso_save = &g:siso" 1021 " | setg so=0 siso=0 | setl so=-1 siso=-1") == FAIL) { 1022 failed = true; 1023 } 1024 if (eap->cmdidx == CMD_mksession) { 1025 char *dirnow; // current directory 1026 1027 dirnow = xmalloc(MAXPATHL); 1028 1029 // Change to session file's dir. 1030 if (os_dirname(dirnow, MAXPATHL) == FAIL 1031 || os_chdir(dirnow) != 0) { 1032 *dirnow = NUL; 1033 } 1034 if (*dirnow != NUL && (ssop_flags & kOptSsopFlagSesdir)) { 1035 if (vim_chdirfile(fname, kCdCauseOther) == OK) { 1036 shorten_fnames(true); 1037 } 1038 } else if (*dirnow != NUL 1039 && (ssop_flags & kOptSsopFlagCurdir) && globaldir != NULL) { 1040 if (os_chdir(globaldir) == 0) { 1041 shorten_fnames(true); 1042 } 1043 } 1044 1045 failed |= (makeopens(fd, dirnow) == FAIL); 1046 1047 // restore original dir 1048 if (*dirnow != NUL && ((ssop_flags & kOptSsopFlagSesdir) 1049 || ((ssop_flags & kOptSsopFlagCurdir) && globaldir != 1050 NULL))) { 1051 if (os_chdir(dirnow) != 0) { 1052 emsg(_(e_prev_dir)); 1053 } 1054 shorten_fnames(true); 1055 } 1056 xfree(dirnow); 1057 } else { 1058 failed |= (put_view(fd, curwin, curtab, !using_vdir, flagp, -1) == FAIL); 1059 } 1060 if (fprintf(fd, 1061 "%s", 1062 "let &g:so = s:so_save | let &g:siso = s:siso_save\n") 1063 < 0) { 1064 failed = true; 1065 } 1066 if (p_hls && fprintf(fd, "%s", "set hlsearch\n") < 0) { 1067 failed = true; 1068 } 1069 if (no_hlsearch && fprintf(fd, "%s", "nohlsearch\n") < 0) { 1070 failed = true; 1071 } 1072 if (fprintf(fd, "%s", "doautoall SessionLoadPost\n") < 0) { 1073 failed = true; 1074 } 1075 if (eap->cmdidx == CMD_mksession) { 1076 if (fprintf(fd, "unlet SessionLoad\n") < 0) { 1077 failed = true; 1078 } 1079 } 1080 } 1081 if (put_line(fd, "\" vim: set ft=vim :") == FAIL) { 1082 failed = true; 1083 } 1084 1085 failed |= fclose(fd); 1086 1087 if (failed) { 1088 emsg(_(e_write)); 1089 } else if (eap->cmdidx == CMD_mksession) { 1090 // successful session write - set v:this_session 1091 char *const tbuf = xmalloc(MAXPATHL); 1092 if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { 1093 set_vim_var_string(VV_THIS_SESSION, tbuf, -1); 1094 } 1095 xfree(tbuf); 1096 } 1097 } 1098 1099 xfree(viewFile); 1100 1101 apply_autocmds(EVENT_SESSIONWRITEPOST, NULL, NULL, false, curbuf); 1102 } 1103 1104 /// @return the name of the view file for the current buffer. 1105 static char *get_view_file(char c) 1106 { 1107 if (curbuf->b_ffname == NULL) { 1108 emsg(_(e_noname)); 1109 return NULL; 1110 } 1111 char *sname = home_replace_save(NULL, curbuf->b_ffname); 1112 1113 // We want a file name without separators, because we're not going to make 1114 // a directory. 1115 // "normal" path separator -> "=+" 1116 // "=" -> "==" 1117 // ":" path separator -> "=-" 1118 size_t len = 0; 1119 for (char *p = sname; *p; p++) { 1120 if (*p == '=' || vim_ispathsep(*p)) { 1121 len++; 1122 } 1123 } 1124 char *retval = xmalloc(strlen(sname) + len + strlen(p_vdir) + 9); 1125 STRCPY(retval, p_vdir); 1126 add_pathsep(retval); 1127 char *s = retval + strlen(retval); 1128 for (char *p = sname; *p; p++) { 1129 if (*p == '=') { 1130 *s++ = '='; 1131 *s++ = '='; 1132 } else if (vim_ispathsep(*p)) { 1133 *s++ = '='; 1134 #if defined(BACKSLASH_IN_FILENAME) 1135 *s++ = (*p == ':') ? '-' : '+'; 1136 #else 1137 *s++ = '+'; 1138 #endif 1139 } else { 1140 *s++ = *p; 1141 } 1142 } 1143 *s++ = '='; 1144 *s++ = c; 1145 xmemcpyz(s, S_LEN(".vim")); 1146 1147 xfree(sname); 1148 return retval; 1149 } 1150 1151 /// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. 1152 int put_eol(FILE *fd) 1153 { 1154 if (putc('\n', fd) < 0) { 1155 return FAIL; 1156 } 1157 return OK; 1158 } 1159 1160 /// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. 1161 int put_line(FILE *fd, char *s) 1162 { 1163 if (fprintf(fd, "%s\n", s) < 0) { 1164 return FAIL; 1165 } 1166 return OK; 1167 }