// 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 #define EXTERN #include #include #include #include #include #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/ui_client.h" #include "nvim/vim.h" #ifdef HAVE_LOCALE_H # include #endif #include "nvim/garray.h" #include "nvim/grid.h" #include "nvim/log.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/popupmenu.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" #include "nvim/shada.h" #include "nvim/sign.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/version.h" #include "nvim/window.h" #ifdef WIN32 # include "nvim/os/os_win_console.h" #endif #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/signal.h" #ifndef WIN32 # include "nvim/os/pty_process_unix.h" #endif #include "nvim/api/extmark.h" // values for "window_layout" #define WIN_HOR 1 // "-o" horizontally split windows #define WIN_VER 2 // "-O" vertically split windows #define WIN_TABS 3 // "-p" windows on tab pages // Values for edit_type. #define EDIT_NONE 0 // no edit type yet #define EDIT_FILE 1 // file name argument[s] given, use argument list #define EDIT_STDIN 2 // read file from stdin #define EDIT_TAG 3 // tag name argument given, use tagname #define EDIT_QF 4 // start in quickfix mode #ifdef INCLUDE_GENERATED_DECLARATIONS # include "main.c.generated.h" #endif Loop main_loop; static char *argv0 = NULL; // Error messages static const char *err_arg_missing = N_("Argument missing after"); static const char *err_opt_garbage = N_("Garbage after option argument"); static const char *err_opt_unknown = N_("Unknown option argument"); static const char *err_too_many_args = N_("Too many edit arguments"); static const char *err_extra_cmd = N_("Too many \"+command\", \"-c command\" or \"--cmd command\" arguments"); void event_init(void) { loop_init(&main_loop, NULL); resize_events = multiqueue_new_child(main_loop.events); // early msgpack-rpc initialization msgpack_rpc_helpers_init(); input_init(); signal_init(); // finish mspgack-rpc initialization channel_init(); terminal_init(); ui_init(); TIME_MSG("event init"); } /// @returns false if main_loop could not be closed gracefully bool event_teardown(void) { if (!main_loop.events) { input_stop(); return true; } multiqueue_process_events(main_loop.events); loop_poll_events(&main_loop, 0); // Drain thread_events, fast_events. input_stop(); channel_teardown(); process_teardown(&main_loop); timer_teardown(); server_teardown(); signal_teardown(); terminal_teardown(); return loop_close(&main_loop, true); } /// Performs early initialization. /// /// Needed for unit tests. Must be called after `time_init()`. void early_init(mparm_T *paramp) { env_init(); estack_init(); cmdline_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. runtime_init(); highlight_init(); #ifdef WIN32 OSVERSIONINFO ovi; ovi.dwOSVersionInfoSize = sizeof(ovi); GetVersionEx(&ovi); snprintf(windowsVersion, sizeof(windowsVersion), "%d.%d", (int)ovi.dwMajorVersion, (int)ovi.dwMinorVersion); #endif TIME_MSG("early init"); #if defined(HAVE_LOCALE_H) // Setup to use the current locale (for ctype() and many other things). // NOTE: Translated messages with encodings other than latin1 will not // work until set_init_1() has been called! init_locale(); #endif // Allocate the first window and buffer. // Can't do anything without it, exit when it fails. if (!win_alloc_first()) { os_exit(0); } TIME_MSG("init first window"); alist_init(&global_alist); // Init the argument list to empty. global_alist.id = 0; // Set the default values for the options. // First find out the home directory, needed to expand "~" in options. init_homedir(); // find real value of $HOME set_init_1(paramp != NULL ? paramp->clean : false); log_init(); TIME_MSG("inits 1"); set_lang_var(); // set v:lang and v:ctype init_signs(); } #ifdef MAKE_LIB int nvim_main(int argc, char **argv); // silence -Wmissing-prototypes int nvim_main(int argc, char **argv) #elif defined(WIN32) int wmain(int argc, wchar_t **argv_w) // multibyte args on Windows. #7060 #else int main(int argc, char **argv) #endif { #if defined(WIN32) && !defined(MAKE_LIB) char **argv = xmalloc((size_t)argc * sizeof(char *)); for (int i = 0; i < argc; i++) { char *buf = NULL; utf16_to_utf8(argv_w[i], -1, &buf); assert(buf); argv[i] = buf; } #endif argv0 = argv[0]; char_u *fname = NULL; // file name from command line mparm_T params; // various parameters passed between // main() and other functions. char_u *cwd = NULL; // current working dir on startup time_init(); // Many variables are in `params` so that we can pass them around easily. // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); // Since os_open is called during the init_startuptime, we need to call // fs_init before it. fs_init(); init_startuptime(¶ms); // Need to find "--clean" before actually parsing arguments. for (int i = 1; i < params.argc; i++) { if (STRICMP(params.argv[i], "--clean") == 0) { params.clean = true; break; } } event_init(); early_init(¶ms); set_argv_var(argv, argc); // set v:argv // Check if we have an interactive window. check_and_set_isatty(¶ms); // Process the command line arguments. File names are put in the global // argument list "global_alist". command_line_scan(¶ms); nlua_init(); TIME_MSG("init lua interpreter"); if (embedded_mode) { const char *err; if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { abort(); } } server_init(params.listen_addr); if (params.remote) { remote_request(¶ms, params.remote, params.server_addr, argc, argv); } if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); } // Recovery mode without a file name: List swap files. // In this case, no UI is needed. if (recoverymode && fname == NULL) { headless_mode = true; } TIME_MSG("expanding arguments"); if (params.diff_mode && params.window_count == -1) { params.window_count = 0; // open up to 3 windows } // Don't redraw until much later. RedrawingDisabled++; setbuf(stdout, NULL); full_screen = !silent_mode; // Set the default values for the options that use Rows and Columns. win_init_size(); // Set the 'diff' option now, so that it can be checked for in a vimrc // file. There is no buffer yet though. if (params.diff_mode) { diff_win_options(firstwin, false); } assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX); cmdline_row = (int)(Rows - p_ch); msg_row = cmdline_row; screenalloc(); // allocate screen buffers set_init_2(headless_mode); TIME_MSG("inits 2"); msg_scroll = true; no_wait_return = true; init_highlight(true, false); // Default highlight groups. ui_comp_syn_init(); TIME_MSG("init highlight"); // Set the break level after the terminal is initialized. debug_break_level = params.use_debug_break_level; // Read ex-commands if invoked with "-es". if (!params.input_isatty && !params.input_neverscript && silent_mode && exmode_active) { input_start(STDIN_FILENO); } // Wait for UIs to set up Nvim or show early messages // and prompts (--cmd, swapfile dialog, …). bool use_remote_ui = (embedded_mode && !headless_mode); bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); if (use_remote_ui || use_builtin_ui) { TIME_MSG("waiting for UI"); if (use_remote_ui) { remote_ui_wait_for_attach(); } else { ui_builtin_start(); } TIME_MSG("done waiting for UI"); // prepare screen now, so external UIs can display messages starting = NO_BUFFERS; screenclear(); TIME_MSG("init screen for UI"); } if (ui_client_channel_id) { ui_client_init(ui_client_channel_id); ui_client_execute(ui_client_channel_id); abort(); // unreachable } // Default mappings (incl. menus) Error err = ERROR_INIT; Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim._init_default_mappings()"), (Array)ARRAY_DICT_INIT, &err); assert(!ERROR_SET(&err)); api_clear_error(&err); assert(o.type == kObjectTypeNil); api_free_object(o); TIME_MSG("init default mappings"); init_default_autocmds(); TIME_MSG("init default autocommands"); bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE"); // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. // Allows for setting 'loadplugins' there. if (vimrc_none) { // When using --clean we still want to load plugins p_lpl = params.clean; } // Execute --cmd arguments. exe_pre_commands(¶ms); if (!vimrc_none || params.clean) { // Sources ftplugin.vim and indent.vim. We do this *before* the user startup scripts to ensure // ftplugins run before FileType autocommands defined in the init file (which allows those // autocommands to overwrite settings from ftplugins). filetype_plugin_enable(); } // Source startup scripts. source_startup_scripts(¶ms); // If using the runtime (-u is not NONE), enable syntax & filetype plugins. if (!vimrc_none || params.clean) { // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype // off. filetype_maybe_enable(); // Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can // disable syntax highlighting with `:syntax off` if they wish. syn_maybe_enable(); } // Read all the plugin files. load_plugins(); // Decide about window layout for diff mode after reading vimrc. set_window_layout(¶ms); // Recovery mode without a file name: List swap files. // Uses the 'dir' option, therefore it must be after the initializations. if (recoverymode && fname == NULL) { recover_names(NULL, true, 0, NULL); os_exit(0); } // Set some option defaults after reading vimrc files. set_init_3(); TIME_MSG("inits 3"); // "-n" argument: Disable swap file by setting 'updatecount' to 0. // Note that this overrides anything from a vimrc file. if (params.no_swap_file) { p_uc = 0; } // XXX: Minimize 'updatetime' for -es/-Es. #7679 if (silent_mode) { p_ut = 1; } // // Read in registers, history etc, from the ShaDa file. // This is where v:oldfiles gets filled. // if (*p_shada != NUL) { shada_read_everything(NULL, false, true); TIME_MSG("reading ShaDa"); } // It's better to make v:oldfiles an empty list than NULL. if (get_vim_var_list(VV_OLDFILES) == NULL) { set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } // "-q errorfile": Load the error file now. // If the error file can't be read, exit before doing anything else. handle_quickfix(¶ms); // // Start putting things on the screen. // Scroll screen down before drawing over it // Clear screen now, so file message will not be cleared. // starting = NO_BUFFERS; no_wait_return = false; if (!exmode_active) { msg_scroll = false; } // Read file (text, not commands) from stdin if: // - stdin is not a tty // - and -e/-es was not given // // Do this before starting Raw mode, because it may change things that the // writing end of the pipe doesn't like, e.g., in case stdin and stderr // are the same terminal: "cat | vim -". // Using autocommands here may cause trouble... if ((params.edit_type == EDIT_STDIN || stdin_fd >= 0) && !recoverymode) { read_stdin(); } setmouse(); // may start using the mouse if (exmode_active || use_remote_ui || use_builtin_ui) { // Don't clear the screen when starting in Ex mode, or when a UI might have // displayed messages. redraw_later(curwin, VALID); } else { screenclear(); // clear screen TIME_MSG("clearing screen"); } no_wait_return = true; // // Create the requested number of windows and edit buffers in them. // Also does recovery if "recoverymode" set. // create_windows(¶ms); TIME_MSG("opening buffers"); // Clear v:swapcommand set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); // Ex starts at last line of the file. if (exmode_active) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); TIME_MSG("BufEnter autocommands"); setpcmark(); // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { qf_jump(NULL, 0, 0, FALSE); TIME_MSG("jump to first error"); } // If opened more than one window, start editing files in the other // windows. edit_buffers(¶ms, cwd); xfree(cwd); if (params.diff_mode) { // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { diff_win_options(wp, TRUE); } } // Shorten any of the filenames, but only when absolute. shorten_fnames(false); // Need to jump to the tag before executing the '-c command'. // Makes "vim -c '/return' -t main" work. handle_tag((char_u *)params.tagname); // Execute any "+", "-c" and "-S" arguments. if (params.n_commands > 0) { exe_commands(¶ms); } starting = 0; RedrawingDisabled = 0; redraw_all_later(NOT_VALID); no_wait_return = false; // 'autochdir' has been postponed. do_autochdir(); set_vim_var_nr(VV_VIM_DID_ENTER, 1L); apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf); TIME_MSG("VimEnter autocommands"); if (use_remote_ui || use_builtin_ui) { do_autocmd_uienter(use_remote_ui ? CHAN_STDIO : 0, true); TIME_MSG("UIEnter autocommands"); } // Adjust default register name for "unnamed" in 'clipboard'. Can only be // done after the clipboard is available and all initial commands that may // modify the 'clipboard' setting have run; i.e. just before entering the // main loop. set_reg_var(get_default_register_name()); // When a startup script or session file setup for diff'ing and // scrollbind, sync the scrollbind now. if (curwin->w_p_diff && curwin->w_p_scb) { update_topline(curwin); check_scrollbind((linenr_T)0, 0L); TIME_MSG("diff scrollbinding"); } // If ":startinsert" command used, stuff a dummy command to be able to // call normal_cmd(), which will then start Insert mode. if (restart_edit != 0) { stuffcharReadbuff(K_NOP); } // WORKAROUND(mhi): #3023 if (cb_flags & CB_UNNAMEDMASK) { (void)eval_has_provider("clipboard"); } TIME_MSG("before starting main loop"); ILOG("starting main loop"); // Main loop: never returns. normal_enter(false, false); #if defined(WIN32) && !defined(MAKE_LIB) xfree(argv); #endif return 0; } void os_exit(int r) FUNC_ATTR_NORETURN { exiting = true; ui_flush(); ui_call_stop(); ml_close_all(true); // remove all memfiles if (!event_teardown() && r == 0) { r = 1; // Exit with error if main_loop did not teardown gracefully. } if (input_global_fd() >= 0) { stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) } ILOG("Nvim exit: %d", r); #ifdef EXITFREE free_all_mem(); #endif exit(r); } /// Exit properly void getout(int exitval) FUNC_ATTR_NORETURN { exiting = true; // When running in Ex mode an error causes us to exit with a non-zero exit // code. POSIX requires this, although it's not 100% clear from the // standard. if (exmode_active) { exitval += ex_exitval; } set_vim_var_nr(VV_EXITING, exitval); // Position the cursor on the last screen line, below all the text ui_cursor_goto(Rows - 1, 0); // Optionally print hashtable efficiency. hash_debug_results(); if (v_dying <= 1) { const tabpage_T *next_tp; // Trigger BufWinLeave for all windows, but only once per buffer. for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) { next_tp = tp->tp_next; FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp->w_buffer == NULL) { // Autocmd must have close the buffer already, skip. continue; } buf_T *buf = wp->w_buffer; if (buf_get_changedtick(buf) != -1) { bufref_T bufref; set_bufref(&bufref, buf); apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, false, buf); if (bufref_valid(&bufref)) { buf_set_changedtick(buf, -1); // note that we did it already } // start all over, autocommands may mess up the lists next_tp = first_tabpage; break; } } } // Trigger BufUnload for buffers that are loaded FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp != NULL) { bufref_T bufref; set_bufref(&bufref, buf); apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, false, buf); if (!bufref_valid(&bufref)) { // Autocmd deleted the buffer. break; } } } int unblock = 0; // deathtrap() blocks autocommands, but we do want to trigger // VimLeavePre. if (is_autocmd_blocked()) { unblock_autocmds(); unblock++; } apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, false, curbuf); if (unblock) { block_autocmds(); } } if (p_shada && *p_shada != NUL) { // Write out the registers, history, marks etc, to the ShaDa file shada_write_file(NULL, false); } if (v_dying <= 1) { int unblock = 0; // deathtrap() blocks autocommands, but we do want to trigger VimLeave. if (is_autocmd_blocked()) { unblock_autocmds(); unblock++; } apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, false, curbuf); if (unblock) { block_autocmds(); } } profile_dump(); if (did_emsg) { // give the user a chance to read the (error) message no_wait_return = FALSE; wait_return(FALSE); } // Position the cursor again, the autocommands may have moved it ui_cursor_goto(Rows - 1, 0); // Apply 'titleold'. if (p_title && *p_titleold != NUL) { ui_call_set_title(cstr_as_string((char *)p_titleold)); } cs_end(); if (garbage_collect_at_exit) { garbage_collect(false); } os_exit(exitval); } /// Preserve files and exit. /// @note IObuff must contain a message. /// @note This may be called from deadly_signal() in a signal handler, avoid /// unsafe functions, such as allocating memory. void preserve_exit(void) FUNC_ATTR_NORETURN { // 'true' when we are sure to exit, e.g., after a deadly signal static bool really_exiting = false; // Prevent repeated calls into this method. if (really_exiting) { if (input_global_fd() >= 0) { // normalize stream (#2598) stream_set_blocking(input_global_fd(), true); } exit(2); } really_exiting = true; // Ignore SIGHUP while we are already exiting. #9274 signal_reject_deadly(); mch_errmsg(IObuff); mch_errmsg("\n"); ui_flush(); ml_close_notmod(); // close all not-modified buffers FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { mch_errmsg("Vim: preserving files...\r\n"); ui_flush(); ml_sync_all(false, false, true); // preserve all swap files break; } } ml_close_all(false); // close all memfiles, without deleting mch_errmsg("Vim: Finished.\r\n"); getout(1); } /// Gets the integer value of a numeric command line argument if given, /// such as '-o10'. /// /// @param[in] p pointer to argument /// @param[in, out] idx pointer to index in argument, is incremented /// @param[in] def default value /// /// @return def unmodified if: /// - argument isn't given /// - argument is non-numeric /// /// @return argument's numeric value otherwise static int get_number_arg(const char *p, int *idx, int def) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (ascii_isdigit(p[*idx])) { // -V522 def = atoi(&(p[*idx])); while (ascii_isdigit(p[*idx])) { *idx = *idx + 1; } } return def; } #if defined(HAVE_LOCALE_H) /// Setup to use the current locale (for ctype() and many other things). static void init_locale(void) { setlocale(LC_ALL, ""); # ifdef LC_NUMERIC // Make sure strtod() uses a decimal point, not a comma. setlocale(LC_NUMERIC, "C"); # endif char localepath[MAXPATHL] = { 0 }; snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); char *tail = path_tail_with_sep(localepath); *tail = NUL; tail = path_tail(localepath); xstrlcpy(tail, "share/locale", sizeof(localepath) - (size_t)(tail - localepath)); bindtextdomain(PROJECT_NAME, localepath); textdomain(PROJECT_NAME); TIME_MSG("locale set"); } #endif static uint64_t server_connect(char *server_addr, const char **errmsg) { if (server_addr == NULL) { *errmsg = "no address specified"; return 0; } CallbackReader on_data = CALLBACK_READER_INIT; const char *error = NULL; bool is_tcp = strrchr(server_addr, ':') ? true : false; // connected to channel uint64_t chan = channel_connect(is_tcp, server_addr, true, on_data, 50, &error); if (error) { *errmsg = error; return 0; } return chan; } /// Handle remote subcommands static void remote_request(mparm_T *params, int remote_args, char *server_addr, int argc, char **argv) { const char *connect_error = NULL; uint64_t chan = server_connect(server_addr, &connect_error); Object rvobj = OBJECT_INIT; if (strequal(argv[remote_args], "--remote-ui-test")) { if (!chan) { emsg(connect_error); exit(1); } ui_client_channel_id = chan; return; } Array args = ARRAY_DICT_INIT; String arg_s; for (int t_argc = remote_args; t_argc < argc; t_argc++) { arg_s = cstr_to_string(argv[t_argc]); ADD(args, STRING_OBJ(arg_s)); } Error err = ERROR_INIT; Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ((int)chan)); ADD(a, CSTR_TO_OBJ(server_addr)); ADD(a, CSTR_TO_OBJ(connect_error)); ADD(a, ARRAY_OBJ(args)); String s = STATIC_CSTR_AS_STRING("return vim._cs_remote(...)"); Object o = nlua_exec(s, a, &err); api_free_array(a); if (ERROR_SET(&err)) { mch_errmsg(err.msg); mch_errmsg("\n"); os_exit(2); } if (o.type == kObjectTypeDictionary) { rvobj.data.dictionary = o.data.dictionary; } else { mch_errmsg("vim._cs_remote returned unexpected value\n"); os_exit(2); } TriState should_exit = kNone; TriState tabbed = kNone; for (size_t i = 0; i < rvobj.data.dictionary.size; i++) { if (strcmp(rvobj.data.dictionary.items[i].key.data, "errmsg") == 0) { if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) { mch_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n"); os_exit(2); } mch_errmsg(rvobj.data.dictionary.items[i].value.data.string.data); mch_errmsg("\n"); os_exit(2); } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "tabbed") == 0) { if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { mch_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n"); os_exit(2); } tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "should_exit") == 0) { if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { mch_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n"); os_exit(2); } should_exit = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; } } if (should_exit == kNone || tabbed == kNone) { mch_errmsg("vim._cs_remote didn't return a value for should_exit or tabbed, bailing\n"); os_exit(2); } api_free_object(o); if (should_exit == kTrue) { os_exit(0); } if (tabbed == kTrue) { params->window_count = argc - remote_args - 1; params->window_layout = WIN_TABS; } } /// Decides whether text (as opposed to commands) will be read from stdin. /// @see EDIT_STDIN static bool edit_stdin(bool explicit, mparm_T *parmp) { bool implicit = !headless_mode && !embedded_mode && (!exmode_active || parmp->input_neverscript) && !parmp->input_isatty && scriptin[0] == NULL; // `-s -` was not given. return explicit || implicit; } /// Scan the command line arguments. static void command_line_scan(mparm_T *parmp) { int argc = parmp->argc; char **argv = parmp->argv; int argv_idx; // index in argv[n][] bool had_stdin_file = false; // found explicit "-" argument bool had_minmin = false; // found "--" argument int want_argument; // option argument with argument long n; argc--; argv++; argv_idx = 1; // active option letter is argv[0][argv_idx] while (argc > 0) { // "+" or "+{number}" or "+/{pat}" or "+{command}" argument. if (argv[0][0] == '+' && !had_minmin) { if (parmp->n_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); } argv_idx = -1; // skip to next argument if (argv[0][1] == NUL) { parmp->commands[parmp->n_commands++] = "$"; } else { parmp->commands[parmp->n_commands++] = &(argv[0][1]); } // Optional argument. } else if (argv[0][0] == '-' && !had_minmin) { want_argument = false; char c = argv[0][argv_idx++]; switch (c) { case NUL: // "nvim -" read from stdin if (exmode_active) { // "nvim -e -" silent mode silent_mode = true; parmp->no_swap_file = true; } else { if (parmp->edit_type != EDIT_NONE && parmp->edit_type != EDIT_FILE && parmp->edit_type != EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } had_stdin_file = true; parmp->edit_type = EDIT_STDIN; } argv_idx = -1; // skip to next argument break; case '-': // "--" don't take any more option arguments // "--help" give help message // "--version" give version message // "--noplugin[s]" skip plugins // "--cmd " execute cmd before vimrc // "--remote" execute commands remotey on a server // "--server" name of vim server to send remote commands to if (STRICMP(argv[0] + argv_idx, "help") == 0) { usage(); os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "version") == 0) { version(); os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, kFileWriteOnly); msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write); if (fof_ret != 0) { semsg(_("E5421: Failed to open stdin: %s"), os_strerror(fof_ret)); } if (p == NULL) { emsg(_(e_outofmem)); } Object md = DICTIONARY_OBJ(api_metadata()); msgpack_rpc_from_object(md, p); msgpack_packer_free(p); const int ff_ret = file_flush(&fp); if (ff_ret < 0) { msgpack_file_write_error(ff_ret); } os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "headless") == 0) { headless_mode = true; } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) { embedded_mode = true; } else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) { want_argument = true; argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { // Do nothing: file args are always literal. #7679 } else if (STRNICMP(argv[0] + argv_idx, "remote", 6) == 0) { parmp->remote = parmp->argc - argc; } else if (STRNICMP(argv[0] + argv_idx, "server", 6) == 0) { want_argument = true; argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "noplugin", 8) == 0) { p_lpl = false; } else if (STRNICMP(argv[0] + argv_idx, "cmd", 3) == 0) { want_argument = true; argv_idx += 3; } else if (STRNICMP(argv[0] + argv_idx, "startuptime", 11) == 0) { want_argument = true; argv_idx += 11; } else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) { parmp->use_vimrc = "NONE"; parmp->clean = true; set_option_value("shadafile", 0L, "NONE", 0); } else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) { nlua_disable_preload = true; } else { if (argv[0][argv_idx]) { mainerr(err_opt_unknown, argv[0]); } had_minmin = true; } if (!want_argument) { argv_idx = -1; // skip to next argument } break; case 'A': // "-A" start in Arabic mode. set_option_value("arabic", 1L, NULL, 0); break; case 'b': // "-b" binary mode. // Needs to be effective before expanding file names, because // for Win32 this makes us edit a shortcut file itself, // instead of the file it links to. set_options_bin(curbuf->b_p_bin, 1, 0); curbuf->b_p_bin = 1; // Binary file I/O. break; case 'D': // "-D" Debugging parmp->use_debug_break_level = 9999; break; case 'd': // "-d" 'diff' parmp->diff_mode = true; break; case 'e': // "-e" Ex mode exmode_active = true; break; case 'E': // "-E" Ex mode exmode_active = true; parmp->input_neverscript = true; break; case 'f': // "-f" GUI: run in foreground. break; case '?': // "-?" give help message (for MS-Windows) case 'h': // "-h" give help message usage(); os_exit(0); case 'H': // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; set_option_value("rl", 1L, NULL, 0); break; case 'l': // "-l" lisp mode, 'lisp' and 'showmatch' on. set_option_value("lisp", 1L, NULL, 0); p_sm = true; break; case 'M': // "-M" no changes or writing of files reset_modifiable(); FALLTHROUGH; case 'm': // "-m" no writing of files p_write = false; break; case 'N': // "-N" Nocompatible case 'X': // "-X" Do not connect to X server // No-op break; case 'n': // "-n" no swap file parmp->no_swap_file = true; break; case 'p': // "-p[N]" open N tab pages // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_TABS; break; case 'o': // "-o[N]" open N horizontal split windows // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_HOR; break; case 'O': // "-O[N]" open N vertical split windows // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_VER; break; case 'q': // "-q" QuickFix mode if (parmp->edit_type != EDIT_NONE) { mainerr(err_too_many_args, argv[0]); } parmp->edit_type = EDIT_QF; if (argv[0][argv_idx]) { // "-q{errorfile}" parmp->use_ef = argv[0] + argv_idx; argv_idx = -1; } else if (argc > 1) { // "-q {errorfile}" want_argument = true; } break; case 'R': // "-R" readonly mode readonlymode = true; curbuf->b_p_ro = true; p_uc = 10000; // don't update very often break; case 'r': // "-r" recovery mode case 'L': // "-L" recovery mode recoverymode = 1; break; case 's': if (exmode_active) { // "-es" silent (batch) Ex-mode silent_mode = true; parmp->no_swap_file = true; } else { // "-s {scriptin}" read from script file want_argument = true; } break; case 't': // "-t {tag}" or "-t{tag}" jump to tag if (parmp->edit_type != EDIT_NONE) { mainerr(err_too_many_args, argv[0]); } parmp->edit_type = EDIT_TAG; if (argv[0][argv_idx]) { // "-t{tag}" parmp->tagname = argv[0] + argv_idx; argv_idx = -1; } else { // "-t {tag}" want_argument = true; } break; case 'v': version(); os_exit(0); case 'V': // "-V{N}" Verbose level // default is 10: a little bit verbose p_verbose = get_number_arg(argv[0], &argv_idx, 10); if (argv[0][argv_idx] != NUL) { set_option_value("verbosefile", 0L, argv[0] + argv_idx, 0); argv_idx = (int)STRLEN(argv[0]); } break; case 'w': // "-w{number}" set window height // "-w {scriptout}" write to script if (ascii_isdigit(((char_u *)argv[0])[argv_idx])) { n = get_number_arg(argv[0], &argv_idx, 10); set_option_value("window", n, NULL, 0); break; } want_argument = true; break; case 'c': // "-c{command}" or "-c {command}" exec command if (argv[0][argv_idx] != NUL) { if (parmp->n_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); } parmp->commands[parmp->n_commands++] = argv[0] + argv_idx; argv_idx = -1; break; } FALLTHROUGH; case 'S': // "-S {file}" execute Vim script case 'i': // "-i {shada}" use for ShaDa file case 'u': // "-u {vimrc}" vim inits file case 'U': // "-U {gvimrc}" gvim inits file case 'W': // "-W {scriptout}" overwrite want_argument = true; break; default: mainerr(err_opt_unknown, argv[0]); } // Handle option arguments with argument. if (want_argument) { // Check for garbage immediately after the option letter. if (argv[0][argv_idx] != NUL) { mainerr(err_opt_garbage, argv[0]); } argc--; if (argc < 1 && c != 'S') { // -S has an optional argument mainerr(err_arg_missing, argv[0]); } argv++; argv_idx = -1; switch (c) { case 'c': // "-c {command}" execute command case 'S': // "-S {file}" execute Vim script if (parmp->n_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); } if (c == 'S') { char *a; if (argc < 1) { // "-S" without argument: use default session file name. a = SESSION_FILE; } else if (argv[0][0] == '-') { // "-S" followed by another option: use default session file. a = SESSION_FILE; argc++; argv--; } else { a = argv[0]; } size_t s_size = STRLEN(a) + 9; char *s = xmalloc(s_size); snprintf(s, s_size, "so %s", a); parmp->cmds_tofree[parmp->n_commands] = true; parmp->commands[parmp->n_commands++] = s; } else { parmp->commands[parmp->n_commands++] = argv[0]; } break; case '-': if (strequal(argv[-1], "--cmd")) { // "--cmd {command}" execute command if (parmp->n_pre_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); } parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; } else if (strequal(argv[-1], "--listen")) { // "--listen {address}" parmp->listen_addr = argv[0]; } else if (strequal(argv[-1], "--server")) { // "--server {address}" parmp->server_addr = argv[0]; } // "--startuptime " already handled break; case 'q': // "-q {errorfile}" QuickFix mode parmp->use_ef = argv[0]; break; case 'i': // "-i {shada}" use for shada set_option_value("shadafile", 0L, argv[0], 0); break; case 's': { // "-s {scriptin}" read from script file if (scriptin[0] != NULL) { scripterror: vim_snprintf((char *)IObuff, IOSIZE, _("Attempt to open script file again: \"%s %s\"\n"), argv[-1], argv[0]); mch_errmsg((const char *)IObuff); os_exit(2); } int error; if (strequal(argv[0], "-")) { const int stdin_dup_fd = os_dup(STDIN_FILENO); #ifdef WIN32 // Replace the original stdin with the console input handle. os_replace_stdin_to_conin(); #endif FileDescriptor *const stdin_dup = file_open_fd_new(&error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); assert(stdin_dup != NULL); scriptin[0] = stdin_dup; } else if ((scriptin[0] = file_open_new(&error, argv[0], kFileReadOnly|kFileNonBlocking, 0)) == NULL) { vim_snprintf((char *)IObuff, IOSIZE, _("Cannot open for reading: \"%s\": %s\n"), argv[0], os_strerror(error)); mch_errmsg((const char *)IObuff); os_exit(2); } save_typebuf(); break; } case 't': // "-t {tag}" parmp->tagname = argv[0]; break; case 'u': // "-u {vimrc}" vim inits file parmp->use_vimrc = argv[0]; break; case 'U': // "-U {gvimrc}" gvim inits file break; case 'w': // "-w {nr}" 'window' value // "-w {scriptout}" append to script file if (ascii_isdigit(*((char_u *)argv[0]))) { argv_idx = 0; n = get_number_arg(argv[0], &argv_idx, 10); set_option_value("window", n, NULL, 0); argv_idx = -1; break; } FALLTHROUGH; case 'W': // "-W {scriptout}" overwrite script file if (scriptout != NULL) { goto scripterror; } if ((scriptout = os_fopen(argv[0], c == 'w' ? APPENDBIN : WRITEBIN)) == NULL) { mch_errmsg(_("Cannot open for script output: \"")); mch_errmsg(argv[0]); mch_errmsg("\"\n"); os_exit(2); } break; } } } else { // File name argument. argv_idx = -1; // skip to next argument // Check for only one type of editing. if (parmp->edit_type != EDIT_NONE && parmp->edit_type != EDIT_FILE && parmp->edit_type != EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } parmp->edit_type = EDIT_FILE; // Add the file to the global argument list. ga_grow(&global_alist.al_ga, 1); char *p = xstrdup(argv[0]); if (parmp->diff_mode && os_isdir((char_u *)p) && GARGCOUNT > 0 && !os_isdir((char_u *)alist_name(&GARGLIST[0]))) { char *r = concat_fnames(p, path_tail(alist_name(&GARGLIST[0])), true); xfree(p); p = r; } #ifdef USE_FNAME_CASE // Make the case of the file name match the actual file. path_fix_case(p); #endif int alist_fnum_flag = edit_stdin(had_stdin_file, parmp) ? 1 // add buffer nr after exp. : 2; // add buffer number now and use curbuf alist_add(&global_alist, p, alist_fnum_flag); } // If there are no more letters after the current "-", go to next argument. // argv_idx is set to -1 when the current argument is to be skipped. if (argv_idx <= 0 || argv[0][argv_idx] == NUL) { argc--; argv++; argv_idx = 1; } } if (embedded_mode && silent_mode) { mainerr(_("--embed conflicts with -es/-Es"), NULL); } // If there is a "+123" or "-c" command, set v:swapcommand to the first one. if (parmp->n_commands > 0) { const size_t swcmd_len = STRLEN(parmp->commands[0]) + 3; char *const swcmd = xmalloc(swcmd_len); snprintf(swcmd, swcmd_len, ":%s\r", parmp->commands[0]); set_vim_var_string(VV_SWAPCOMMAND, swcmd, -1); xfree(swcmd); } // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 if (edit_stdin(had_stdin_file, parmp)) { parmp->edit_type = EDIT_STDIN; } TIME_MSG("parsing arguments"); } /* * Many variables are in "params" so that we can pass them to invoked * functions without a lot of arguments. "argc" and "argv" are also * copied, so that they can be changed. */ static void init_params(mparm_T *paramp, int argc, char **argv) { CLEAR_POINTER(paramp); paramp->argc = argc; paramp->argv = argv; paramp->use_debug_break_level = -1; paramp->window_count = -1; paramp->listen_addr = NULL; paramp->server_addr = NULL; paramp->remote = 0; } /// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc - 1; i++) { if (STRICMP(paramp->argv[i], "--startuptime") == 0) { time_fd = os_fopen(paramp->argv[i + 1], "a"); time_start("--- NVIM STARTING ---"); break; } } starttime = time(NULL); } static void check_and_set_isatty(mparm_T *paramp) { stdin_isatty = paramp->input_isatty = os_isatty(STDIN_FILENO); stdout_isatty = paramp->output_isatty = os_isatty(STDOUT_FILENO); paramp->err_isatty = os_isatty(STDERR_FILENO); #ifndef WIN32 int tty_fd = paramp->input_isatty ? STDIN_FILENO : (paramp->output_isatty ? STDOUT_FILENO : (paramp->err_isatty ? STDERR_FILENO : -1)); pty_process_save_termios(tty_fd); #endif TIME_MSG("window checked"); } // Sets v:progname and v:progpath. Also modifies $PATH on Windows. static void init_path(const char *exename) FUNC_ATTR_NONNULL_ALL { char exepath[MAXPATHL] = { 0 }; size_t exepathlen = MAXPATHL; // Make v:progpath absolute. if (os_exepath(exepath, &exepathlen) != 0) { // Fall back to argv[0]. Missing procfs? #6734 path_guess_exepath(exename, exepath, sizeof(exepath)); } set_vim_var_string(VV_PROGPATH, exepath, -1); set_vim_var_string(VV_PROGNAME, path_tail(exename), -1); #ifdef WIN32 // Append the process start directory to $PATH, so that ":!foo" finds tools // shipped with Windows package. This also mimics SearchPath(). os_setenv_append_path(exepath); #endif } /// Get filename from command line, if any. static char_u *get_fname(mparm_T *parmp, char_u *cwd) { return (char_u *)alist_name(&GARGLIST[0]); } /* * Decide about window layout for diff mode after reading vimrc. */ static void set_window_layout(mparm_T *paramp) { if (paramp->diff_mode && paramp->window_layout == 0) { if (diffopt_horizontal()) { paramp->window_layout = WIN_HOR; // use horizontal split } else { paramp->window_layout = WIN_VER; // use vertical split } } } /* * "-q errorfile": Load the error file now. * If the error file can't be read, exit before doing anything else. */ static void handle_quickfix(mparm_T *paramp) { if (paramp->edit_type == EDIT_QF) { if (paramp->use_ef != NULL) { set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); } vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, (char *)p_ef, p_efm, true, (char *)IObuff, (char *)p_menc) < 0) { msg_putchar('\n'); os_exit(3); } TIME_MSG("reading errorfile"); } } /* * Need to jump to the tag before executing the '-c command'. * Makes "vim -c '/return' -t main" work. */ static void handle_tag(char_u *tagname) { if (tagname != NULL) { swap_exists_did_quit = false; vim_snprintf((char *)IObuff, IOSIZE, "ta %s", tagname); do_cmdline_cmd((char *)IObuff); TIME_MSG("jumping to tag"); // If the user doesn't want to edit the file then we quit here. if (swap_exists_did_quit) { getout(1); } } } /// Read text from stdin. static void read_stdin(void) { // When getting the ATTENTION prompt here, use a dialog. swap_exists_action = SEA_DIALOG; no_wait_return = true; bool save_msg_didany = msg_didany; set_buflisted(true); // Create memfile and read from stdin. (void)open_buffer(true, NULL, 0); if (buf_is_empty(curbuf) && curbuf->b_next != NULL) { // stdin was empty, go to buffer 2 (e.g. "echo file1 | xargs nvim"). #8561 do_cmdline_cmd("silent! bnext"); // Delete the empty stdin buffer. do_cmdline_cmd("bwipeout 1"); } no_wait_return = false; msg_didany = save_msg_didany; TIME_MSG("reading stdin"); check_swap_exists_action(); } /* * Create the requested number of windows and edit buffers in them. * Also does recovery if "recoverymode" set. */ static void create_windows(mparm_T *parmp) { int dorewind; int done = 0; /* * Create the number of windows that was requested. */ if (parmp->window_count == -1) { // was not set parmp->window_count = 1; } if (parmp->window_count == 0) { parmp->window_count = GARGCOUNT; } if (parmp->window_count > 1) { // Don't change the windows if there was a command in vimrc that // already split some windows if (parmp->window_layout == 0) { parmp->window_layout = WIN_HOR; } if (parmp->window_layout == WIN_TABS) { parmp->window_count = make_tabpages(parmp->window_count); TIME_MSG("making tab pages"); } else if (firstwin->w_next == NULL) { parmp->window_count = make_windows(parmp->window_count, parmp->window_layout == WIN_VER); TIME_MSG("making windows"); } else { parmp->window_count = win_count(); } } else { parmp->window_count = 1; } if (recoverymode) { // do recover msg_scroll = true; // scroll message up ml_recover(true); if (curbuf->b_ml.ml_mfp == NULL) { // failed getout(1); } do_modelines(0); // do modelines } else { // Open a buffer for windows that don't have one yet. // Commands in the vimrc might have loaded a file or split the window. // Watch out for autocommands that delete a window. // // Don't execute Win/Buf Enter/Leave autocommands here autocmd_no_enter++; autocmd_no_leave++; dorewind = true; while (done++ < 1000) { if (dorewind) { if (parmp->window_layout == WIN_TABS) { goto_tabpage(1); } else { curwin = firstwin; } } else if (parmp->window_layout == WIN_TABS) { if (curtab->tp_next == NULL) { break; } goto_tabpage(0); } else { if (curwin->w_next == NULL) { break; } curwin = curwin->w_next; } dorewind = FALSE; curbuf = curwin->w_buffer; if (curbuf->b_ml.ml_mfp == NULL) { // Set 'foldlevel' to 'foldlevelstart' if it's not negative.. if (p_fdls >= 0) { curwin->w_p_fdl = p_fdls; } // When getting the ATTENTION prompt here, use a dialog. swap_exists_action = SEA_DIALOG; set_buflisted(TRUE); // create memfile, read file (void)open_buffer(FALSE, NULL, 0); if (swap_exists_action == SEA_QUIT) { if (got_int || only_one_window()) { // abort selected or quit and only one window did_emsg = FALSE; // avoid hit-enter prompt getout(1); } // We can't close the window, it would disturb what // happens next. Clear the file name and set the arg // index to -1 to delete it later. setfname(curbuf, NULL, NULL, false); curwin->w_arg_idx = -1; swap_exists_action = SEA_NONE; } else { handle_swap_exists(NULL); } dorewind = TRUE; // start again } os_breakcheck(); if (got_int) { (void)vgetc(); // only break the file loading, not the rest break; } } if (parmp->window_layout == WIN_TABS) { goto_tabpage(1); } else { curwin = firstwin; } curbuf = curwin->w_buffer; autocmd_no_enter--; autocmd_no_leave--; } } /// If opened more than one window, start editing files in the other /// windows. make_windows() has already opened the windows. static void edit_buffers(mparm_T *parmp, char_u *cwd) { int arg_idx; // index in argument list int i; bool advance = true; win_T *win; char *p_shm_save = NULL; /* * Don't execute Win/Buf Enter/Leave autocommands here */ autocmd_no_enter++; autocmd_no_leave++; // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { win_close(curwin, true, false); advance = false; } arg_idx = 1; for (i = 1; i < parmp->window_count; ++i) { if (cwd != NULL) { os_chdir((char *)cwd); } // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { arg_idx++; win_close(curwin, true, false); advance = false; continue; } if (advance) { if (parmp->window_layout == WIN_TABS) { if (curtab->tp_next == NULL) { // just checking break; } goto_tabpage(0); // Temporarily reset 'shm' option to not print fileinfo when // loading the other buffers. This would overwrite the already // existing fileinfo for the first tab. if (i == 1) { char buf[100]; p_shm_save = xstrdup((char *)p_shm); snprintf(buf, sizeof(buf), "F%s", p_shm); set_option_value("shm", 0L, buf, 0); } } else { if (curwin->w_next == NULL) { // just checking break; } win_enter(curwin->w_next, false); } } advance = true; // Only open the file if there is no file in this window yet (that can // happen when vimrc contains ":sall"). if (curbuf == firstwin->w_buffer || curbuf->b_ffname == NULL) { curwin->w_arg_idx = arg_idx; // Edit file from arg list, if there is one. When "Quit" selected // at the ATTENTION prompt close the window. swap_exists_did_quit = false; (void)do_ecmd(0, arg_idx < GARGCOUNT ? alist_name(&GARGLIST[arg_idx]) : NULL, NULL, NULL, ECMD_LASTL, ECMD_HIDE, curwin); if (swap_exists_did_quit) { // abort or quit selected if (got_int || only_one_window()) { // abort selected and only one window did_emsg = FALSE; // avoid hit-enter prompt getout(1); } win_close(curwin, true, false); advance = false; } if (arg_idx == GARGCOUNT - 1) { arg_had_last = true; } arg_idx++; } os_breakcheck(); if (got_int) { (void)vgetc(); // only break the file loading, not the rest break; } } if (p_shm_save != NULL) { set_option_value("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } if (parmp->window_layout == WIN_TABS) { goto_tabpage(1); } autocmd_no_enter--; // make the first window the current window win = firstwin; // Avoid making a preview window the current window. while (win->w_p_pvw) { win = win->w_next; if (win == NULL) { win = firstwin; break; } } win_enter(win, false); autocmd_no_leave--; TIME_MSG("editing files in windows"); if (parmp->window_count > 1 && parmp->window_layout != WIN_TABS) { win_equal(curwin, false, 'b'); // adjust heights } } /* * Execute the commands from --cmd arguments "cmds[cnt]". */ static void exe_pre_commands(mparm_T *parmp) { char **cmds = parmp->pre_commands; int cnt = parmp->n_pre_commands; int i; if (cnt > 0) { curwin->w_cursor.lnum = 0; // just in case.. estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0); current_sctx.sc_sid = SID_CMDARG; for (i = 0; i < cnt; i++) { do_cmdline_cmd(cmds[i]); } estack_pop(); current_sctx.sc_sid = 0; TIME_MSG("--cmd commands"); } } /* * Execute "+", "-c" and "-S" arguments. */ static void exe_commands(mparm_T *parmp) { int i; /* * We start commands on line 0, make "vim +/pat file" match a * pattern on line 1. But don't move the cursor when an autocommand * with g`" was used. */ msg_scroll = TRUE; if (parmp->tagname == NULL && curwin->w_cursor.lnum <= 1) { curwin->w_cursor.lnum = 0; } estack_push(ETYPE_ARGS, "command line", 0); current_sctx.sc_sid = SID_CARG; current_sctx.sc_seq = 0; for (i = 0; i < parmp->n_commands; i++) { do_cmdline_cmd(parmp->commands[i]); if (parmp->cmds_tofree[i]) { xfree(parmp->commands[i]); } } estack_pop(); current_sctx.sc_sid = 0; if (curwin->w_cursor.lnum == 0) { curwin->w_cursor.lnum = 1; } if (!exmode_active) { msg_scroll = FALSE; } // When started with "-q errorfile" jump to first error again. if (parmp->edit_type == EDIT_QF) { qf_jump(NULL, 0, 0, FALSE); } TIME_MSG("executing command arguments"); } /// Source system-wide vimrc if built with one defined /// /// Does one of the following things, stops after whichever succeeds: /// /// 1. Source system vimrc file from $XDG_CONFIG_DIRS/nvim/sysinit.vim /// 2. Source system vimrc file from $VIM static void do_system_initialization(void) { char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); if (config_dirs != NULL) { const void *iter = NULL; const char path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP, 's', 'y', 's', 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; do { const char *dir; size_t dir_len; iter = vim_env_iter(':', config_dirs, iter, &dir, &dir_len); if (dir == NULL || dir_len == 0) { break; } char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1); memcpy(vimrc, dir, dir_len); if (vimrc[dir_len - 1] != PATHSEP) { vimrc[dir_len] = PATHSEP; dir_len += 1; } memcpy(vimrc + dir_len, path_tail, sizeof(path_tail)); if (do_source(vimrc, false, DOSO_NONE) != FAIL) { xfree(vimrc); xfree(config_dirs); return; } xfree(vimrc); } while (iter != NULL); xfree(config_dirs); } #ifdef SYS_VIMRC_FILE // Get system wide defaults, if the file name is defined. (void)do_source(SYS_VIMRC_FILE, false, DOSO_NONE); #endif } /// Source vimrc or do other user initialization /// /// Does one of the following things, stops after whichever succeeds: /// /// 1. Execution of VIMINIT environment variable. /// 2. Sourcing user vimrc file ($XDG_CONFIG_HOME/nvim/init.vim). /// 3. Sourcing other vimrc files ($XDG_CONFIG_DIRS[1]/nvim/init.vim, …). /// 4. Execution of EXINIT environment variable. /// /// @return True if it is needed to attempt to source exrc file according to /// 'exrc' option definition. static bool do_user_initialization(void) FUNC_ATTR_WARN_UNUSED_RESULT { bool do_exrc = p_exrc; if (execute_env("VIMINIT") == OK) { do_exrc = p_exrc; return do_exrc; } char_u *init_lua_path = (char_u *)stdpaths_user_conf_subpath("init.lua"); char_u *user_vimrc = (char_u *)stdpaths_user_conf_subpath("init.vim"); // init.lua if (os_path_exists(init_lua_path) && do_source((char *)init_lua_path, true, DOSO_VIMRC)) { if (os_path_exists(user_vimrc)) { semsg(_("E5422: Conflicting configs: \"%s\" \"%s\""), init_lua_path, user_vimrc); } xfree(user_vimrc); xfree(init_lua_path); return false; } xfree(init_lua_path); // init.vim if (do_source((char *)user_vimrc, true, DOSO_VIMRC) != FAIL) { do_exrc = p_exrc; if (do_exrc) { do_exrc = (path_full_compare(VIMRC_FILE, (char *)user_vimrc, false, true) != kEqualFiles); } xfree(user_vimrc); return do_exrc; } xfree(user_vimrc); char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); if (config_dirs != NULL) { const void *iter = NULL; do { const char *dir; size_t dir_len; iter = vim_env_iter(':', config_dirs, iter, &dir, &dir_len); if (dir == NULL || dir_len == 0) { break; } const char path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP, 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1); memmove(vimrc, dir, dir_len); vimrc[dir_len] = PATHSEP; memmove(vimrc + dir_len + 1, path_tail, sizeof(path_tail)); if (do_source(vimrc, true, DOSO_VIMRC) != FAIL) { do_exrc = p_exrc; if (do_exrc) { do_exrc = (path_full_compare(VIMRC_FILE, vimrc, false, true) != kEqualFiles); } xfree(vimrc); xfree(config_dirs); return do_exrc; } xfree(vimrc); } while (iter != NULL); xfree(config_dirs); } if (execute_env("EXINIT") == OK) { do_exrc = p_exrc; return do_exrc; } return do_exrc; } /// Source startup scripts static void source_startup_scripts(const mparm_T *const parmp) FUNC_ATTR_NONNULL_ALL { // If -u given, use only the initializations from that file and nothing else. if (parmp->use_vimrc != NULL) { if (strequal(parmp->use_vimrc, "NONE") || strequal(parmp->use_vimrc, "NORC")) { // Do nothing. } else { if (do_source(parmp->use_vimrc, false, DOSO_NONE) != OK) { semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc); } } } else if (!silent_mode) { do_system_initialization(); if (do_user_initialization()) { // Read initialization commands from ".vimrc" or ".exrc" in current // directory. This is only done if the 'exrc' option is set. // Because of security reasons we disallow shell and write commands // now, except for unix if the file is owned by the user or 'secure' // option has been reset in environment of global "exrc" or "vimrc". // Only do this if VIMRC_FILE is not the same as vimrc file sourced in // do_user_initialization. #if defined(UNIX) // If vimrc file is not owned by user, set 'secure' mode. if (!os_file_owned(VIMRC_FILE)) // NOLINT(readability/braces) #endif secure = p_secure; if (do_source(VIMRC_FILE, true, DOSO_VIMRC) == FAIL) { #if defined(UNIX) // if ".exrc" is not owned by user set 'secure' mode if (!os_file_owned(EXRC_FILE)) { secure = p_secure; } else { secure = 0; } #endif (void)do_source(EXRC_FILE, false, DOSO_NONE); } } if (secure == 2) { need_wait_return = true; } secure = 0; } TIME_MSG("sourcing vimrc file(s)"); } /// Get an environment variable, and execute it as Ex commands. /// /// @param env environment variable to execute /// /// @return FAIL if the environment variable was not executed, /// OK otherwise. static int execute_env(char *env) FUNC_ATTR_NONNULL_ALL { const char *initstr = os_getenv(env); if (initstr != NULL) { estack_push(ETYPE_ENV, env, 0); const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_ENV; current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; do_cmdline_cmd((char *)initstr); estack_pop(); current_sctx = save_current_sctx; return OK; } return FAIL; } /// Prints the following then exits: /// - An error message `errstr` /// - A string `str` if not null /// /// @param errstr string containing an error message /// @param str string to append to the primary error message, or NULL static void mainerr(const char *errstr, const char *str) FUNC_ATTR_NORETURN { char *prgname = path_tail(argv0); signal_stop(); // kill us with CTRL-C here, if you like mch_errmsg(prgname); mch_errmsg(": "); mch_errmsg(_(errstr)); if (str != NULL) { mch_errmsg(": \""); mch_errmsg(str); mch_errmsg("\""); } mch_errmsg(_("\nMore info with \"")); mch_errmsg(prgname); mch_errmsg(" -h\"\n"); os_exit(1); } /// Prints version information for "nvim -v" or "nvim --version". static void version(void) { // TODO(bfred): not like this? nlua_init(); info_message = true; // use mch_msg(), not mch_errmsg() list_version(); msg_putchar('\n'); msg_didout = false; } /// Prints help message for "nvim -h" or "nvim --help". static void usage(void) { signal_stop(); // kill us with CTRL-C here, if you like mch_msg(_("Usage:\n")); mch_msg(_(" nvim [options] [file ...] Edit file(s)\n")); mch_msg(_(" nvim [options] -t Edit file where tag is defined\n")); mch_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n")); mch_msg(_("\nOptions:\n")); mch_msg(_(" -- Only file names after this\n")); mch_msg(_(" + Start at end of file\n")); mch_msg(_(" --cmd Execute before any config\n")); mch_msg(_(" +, -c Execute after config and first file\n")); mch_msg("\n"); mch_msg(_(" -b Binary mode\n")); mch_msg(_(" -d Diff mode\n")); mch_msg(_(" -e, -E Ex mode\n")); mch_msg(_(" -es, -Es Silent (batch) mode\n")); mch_msg(_(" -h, --help Print this help message\n")); mch_msg(_(" -i Use this shada file\n")); mch_msg(_(" -m Modifications (writing files) not allowed\n")); mch_msg(_(" -M Modifications in text not allowed\n")); mch_msg(_(" -n No swap file, use memory only\n")); mch_msg(_(" -o[N] Open N windows (default: one per file)\n")); mch_msg(_(" -O[N] Open N vertical windows (default: one per file)\n")); mch_msg(_(" -p[N] Open N tab pages (default: one per file)\n")); mch_msg(_(" -r, -L List swap files\n")); mch_msg(_(" -r Recover edit state for this file\n")); mch_msg(_(" -R Read-only mode\n")); mch_msg(_(" -S Source after loading the first file\n")); mch_msg(_(" -s Read Normal mode commands from \n")); mch_msg(_(" -u Use this config file\n")); mch_msg(_(" -v, --version Print version information\n")); mch_msg(_(" -V[N][file] Verbose [level][file]\n")); mch_msg("\n"); mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); mch_msg(_(" --listen
Serve RPC API from this address\n")); mch_msg(_(" --noplugin Don't load plugins\n")); mch_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); mch_msg(_(" --server
Specify RPC server to send commands to\n")); mch_msg(_(" --startuptime Write startup timing messages to \n")); mch_msg(_("\nSee \":help startup-options\" for all options.\n")); } /* * Check the result of the ATTENTION dialog: * When "Quit" selected, exit Vim. * When "Recover" selected, recover the file. */ static void check_swap_exists_action(void) { if (swap_exists_action == SEA_QUIT) { getout(1); } handle_swap_exists(NULL); }