1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #define EXTERN |
5 | #include <assert.h> |
6 | #include <stdint.h> |
7 | #include <string.h> |
8 | #include <stdbool.h> |
9 | |
10 | #include <msgpack.h> |
11 | |
12 | #include "nvim/ascii.h" |
13 | #include "nvim/vim.h" |
14 | #include "nvim/main.h" |
15 | #include "nvim/aucmd.h" |
16 | #include "nvim/buffer.h" |
17 | #include "nvim/charset.h" |
18 | #include "nvim/diff.h" |
19 | #include "nvim/eval.h" |
20 | #include "nvim/ex_cmds.h" |
21 | #include "nvim/ex_cmds2.h" |
22 | #include "nvim/ex_docmd.h" |
23 | #include "nvim/fileio.h" |
24 | #include "nvim/fold.h" |
25 | #include "nvim/getchar.h" |
26 | #include "nvim/hashtab.h" |
27 | #include "nvim/highlight.h" |
28 | #include "nvim/iconv.h" |
29 | #include "nvim/if_cscope.h" |
30 | #ifdef HAVE_LOCALE_H |
31 | # include <locale.h> |
32 | #endif |
33 | #include "nvim/mark.h" |
34 | #include "nvim/mbyte.h" |
35 | #include "nvim/memline.h" |
36 | #include "nvim/message.h" |
37 | #include "nvim/misc1.h" |
38 | #include "nvim/garray.h" |
39 | #include "nvim/log.h" |
40 | #include "nvim/memory.h" |
41 | #include "nvim/move.h" |
42 | #include "nvim/mouse.h" |
43 | #include "nvim/normal.h" |
44 | #include "nvim/ops.h" |
45 | #include "nvim/option.h" |
46 | #include "nvim/os_unix.h" |
47 | #include "nvim/os/os_defs.h" |
48 | #include "nvim/path.h" |
49 | #include "nvim/profile.h" |
50 | #include "nvim/popupmnu.h" |
51 | #include "nvim/quickfix.h" |
52 | #include "nvim/screen.h" |
53 | #include "nvim/sign.h" |
54 | #include "nvim/state.h" |
55 | #include "nvim/strings.h" |
56 | #include "nvim/syntax.h" |
57 | #include "nvim/ui.h" |
58 | #include "nvim/ui_compositor.h" |
59 | #include "nvim/version.h" |
60 | #include "nvim/window.h" |
61 | #include "nvim/shada.h" |
62 | #include "nvim/os/input.h" |
63 | #include "nvim/os/os.h" |
64 | #include "nvim/os/time.h" |
65 | #include "nvim/os/fileio.h" |
66 | #include "nvim/event/loop.h" |
67 | #include "nvim/os/signal.h" |
68 | #include "nvim/event/process.h" |
69 | #include "nvim/msgpack_rpc/helpers.h" |
70 | #include "nvim/msgpack_rpc/server.h" |
71 | #include "nvim/msgpack_rpc/channel.h" |
72 | #include "nvim/api/ui.h" |
73 | #include "nvim/api/private/defs.h" |
74 | #include "nvim/api/private/helpers.h" |
75 | #include "nvim/api/private/handle.h" |
76 | #include "nvim/api/private/dispatch.h" |
77 | #ifndef WIN32 |
78 | # include "nvim/os/pty_process_unix.h" |
79 | #endif |
80 | #include "nvim/api/vim.h" |
81 | |
82 | // Maximum number of commands from + or -c arguments. |
83 | #define MAX_ARG_CMDS 10 |
84 | |
85 | // values for "window_layout" |
86 | #define WIN_HOR 1 // "-o" horizontally split windows |
87 | #define WIN_VER 2 // "-O" vertically split windows |
88 | #define WIN_TABS 3 // "-p" windows on tab pages |
89 | |
90 | // Struct for various parameters passed between main() and other functions. |
91 | typedef struct { |
92 | int argc; |
93 | char **argv; |
94 | |
95 | char *use_vimrc; // vimrc from -u argument |
96 | |
97 | int n_commands; // no. of commands from + or -c |
98 | char *commands[MAX_ARG_CMDS]; // commands from + or -c arg |
99 | char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() |
100 | int n_pre_commands; // no. of commands from --cmd |
101 | char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument |
102 | |
103 | int edit_type; // type of editing to do |
104 | char_u *tagname; // tag from -t argument |
105 | char_u *use_ef; // 'errorfile' from -q argument |
106 | |
107 | bool input_isatty; // stdin is a terminal |
108 | bool output_isatty; // stdout is a terminal |
109 | bool err_isatty; // stderr is a terminal |
110 | int no_swap_file; // "-n" argument used |
111 | int use_debug_break_level; |
112 | int window_count; // number of windows to use |
113 | int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS |
114 | |
115 | int diff_mode; // start with 'diff' set |
116 | |
117 | char *listen_addr; // --listen {address} |
118 | } mparm_T; |
119 | |
120 | // Values for edit_type. |
121 | #define EDIT_NONE 0 // no edit type yet |
122 | #define EDIT_FILE 1 // file name argument[s] given, use argument list |
123 | #define EDIT_STDIN 2 // read file from stdin |
124 | #define EDIT_TAG 3 // tag name argument given, use tagname |
125 | #define EDIT_QF 4 // start in quickfix mode |
126 | |
127 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
128 | # include "main.c.generated.h" |
129 | #endif |
130 | |
131 | Loop main_loop; |
132 | |
133 | static char *argv0 = NULL; |
134 | |
135 | // Error messages |
136 | static const char *err_arg_missing = N_("Argument missing after" ); |
137 | static const char *err_opt_garbage = N_("Garbage after option argument" ); |
138 | static const char *err_opt_unknown = N_("Unknown option argument" ); |
139 | static const char *err_too_many_args = N_("Too many edit arguments" ); |
140 | static const char * = |
141 | N_("Too many \"+command\", \"-c command\" or \"--cmd command\" arguments" ); |
142 | |
143 | |
144 | void event_init(void) |
145 | { |
146 | log_init(); |
147 | loop_init(&main_loop, NULL); |
148 | resize_events = multiqueue_new_child(main_loop.events); |
149 | |
150 | // early msgpack-rpc initialization |
151 | msgpack_rpc_init_method_table(); |
152 | msgpack_rpc_helpers_init(); |
153 | input_init(); |
154 | signal_init(); |
155 | // finish mspgack-rpc initialization |
156 | channel_init(); |
157 | remote_ui_init(); |
158 | api_vim_init(); |
159 | terminal_init(); |
160 | ui_init(); |
161 | } |
162 | |
163 | /// @returns false if main_loop could not be closed gracefully |
164 | bool event_teardown(void) |
165 | { |
166 | if (!main_loop.events) { |
167 | input_stop(); |
168 | return true; |
169 | } |
170 | |
171 | multiqueue_process_events(main_loop.events); |
172 | loop_poll_events(&main_loop, 0); // Drain thread_events, fast_events. |
173 | input_stop(); |
174 | channel_teardown(); |
175 | process_teardown(&main_loop); |
176 | timer_teardown(); |
177 | server_teardown(); |
178 | signal_teardown(); |
179 | terminal_teardown(); |
180 | |
181 | return loop_close(&main_loop, true); |
182 | } |
183 | |
184 | /// Performs early initialization. |
185 | /// |
186 | /// Needed for unit tests. Must be called after `time_init()`. |
187 | void early_init(void) |
188 | { |
189 | env_init(); |
190 | fs_init(); |
191 | handle_init(); |
192 | eval_init(); // init global variables |
193 | init_path(argv0 ? argv0 : "nvim" ); |
194 | init_normal_cmds(); // Init the table of Normal mode commands. |
195 | highlight_init(); |
196 | |
197 | #if defined(HAVE_LOCALE_H) |
198 | // Setup to use the current locale (for ctype() and many other things). |
199 | // NOTE: Translated messages with encodings other than latin1 will not |
200 | // work until set_init_1() has been called! |
201 | init_locale(); |
202 | #endif |
203 | |
204 | // Allocate the first window and buffer. |
205 | // Can't do anything without it, exit when it fails. |
206 | if (!win_alloc_first()) { |
207 | mch_exit(0); |
208 | } |
209 | |
210 | init_yank(); // init yank buffers |
211 | |
212 | alist_init(&global_alist); // Init the argument list to empty. |
213 | global_alist.id = 0; |
214 | |
215 | // Set the default values for the options. |
216 | // NOTE: Non-latin1 translated messages are working only after this, |
217 | // because this is where "has_mbyte" will be set, which is used by |
218 | // msg_outtrans_len_attr(). |
219 | // First find out the home directory, needed to expand "~" in options. |
220 | init_homedir(); // find real value of $HOME |
221 | set_init_1(); |
222 | TIME_MSG("inits 1" ); |
223 | |
224 | set_lang_var(); // set v:lang and v:ctype |
225 | |
226 | init_signs(); |
227 | ui_comp_syn_init(); |
228 | } |
229 | |
230 | #ifdef MAKE_LIB |
231 | int nvim_main(int argc, char **argv); // silence -Wmissing-prototypes |
232 | int nvim_main(int argc, char **argv) |
233 | #elif defined(WIN32) |
234 | int wmain(int argc, wchar_t **argv_w) // multibyte args on Windows. #7060 |
235 | #else |
236 | int main(int argc, char **argv) |
237 | #endif |
238 | { |
239 | #if defined(WIN32) && !defined(MAKE_LIB) |
240 | char **argv = xmalloc((size_t)argc * sizeof(char *)); |
241 | for (int i = 0; i < argc; i++) { |
242 | char *buf = NULL; |
243 | utf16_to_utf8(argv_w[i], -1, &buf); |
244 | assert(buf); |
245 | argv[i] = buf; |
246 | } |
247 | #endif |
248 | |
249 | argv0 = argv[0]; |
250 | |
251 | char_u *fname = NULL; // file name from command line |
252 | mparm_T params; // various parameters passed between |
253 | // main() and other functions. |
254 | char_u *cwd = NULL; // current working dir on startup |
255 | time_init(); |
256 | |
257 | // Many variables are in `params` so that we can pass them around easily. |
258 | // `argc` and `argv` are also copied, so that they can be changed. |
259 | init_params(¶ms, argc, argv); |
260 | |
261 | init_startuptime(¶ms); |
262 | |
263 | event_init(); |
264 | |
265 | early_init(); |
266 | |
267 | // Check if we have an interactive window. |
268 | check_and_set_isatty(¶ms); |
269 | |
270 | // Process the command line arguments. File names are put in the global |
271 | // argument list "global_alist". |
272 | command_line_scan(¶ms); |
273 | |
274 | if (embedded_mode) { |
275 | const char *err; |
276 | if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { |
277 | abort(); |
278 | } |
279 | } |
280 | |
281 | server_init(params.listen_addr); |
282 | |
283 | if (GARGCOUNT > 0) { |
284 | fname = get_fname(¶ms, cwd); |
285 | } |
286 | |
287 | TIME_MSG("expanding arguments" ); |
288 | |
289 | if (params.diff_mode && params.window_count == -1) |
290 | params.window_count = 0; /* open up to 3 windows */ |
291 | |
292 | /* Don't redraw until much later. */ |
293 | ++RedrawingDisabled; |
294 | |
295 | setbuf(stdout, NULL); |
296 | |
297 | full_screen = !silent_mode; |
298 | |
299 | // Set the default values for the options that use Rows and Columns. |
300 | win_init_size(); |
301 | // Set the 'diff' option now, so that it can be checked for in a vimrc |
302 | // file. There is no buffer yet though. |
303 | if (params.diff_mode) { |
304 | diff_win_options(firstwin, false); |
305 | } |
306 | |
307 | assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX); |
308 | cmdline_row = (int)(Rows - p_ch); |
309 | msg_row = cmdline_row; |
310 | screenalloc(); // allocate screen buffers |
311 | set_init_2(headless_mode); |
312 | TIME_MSG("inits 2" ); |
313 | |
314 | msg_scroll = true; |
315 | no_wait_return = true; |
316 | |
317 | init_highlight(true, false); // Default highlight groups. |
318 | TIME_MSG("init highlight" ); |
319 | |
320 | // Set the break level after the terminal is initialized. |
321 | debug_break_level = params.use_debug_break_level; |
322 | |
323 | // Read ex-commands if invoked with "-es". |
324 | if (!params.input_isatty && silent_mode && exmode_active == EXMODE_NORMAL) { |
325 | input_start(STDIN_FILENO); |
326 | } |
327 | |
328 | // open terminals when opening files that start with term:// |
329 | #define PROTO "term://" |
330 | do_cmdline_cmd("augroup nvim_terminal" ); |
331 | do_cmdline_cmd("autocmd!" ); |
332 | do_cmdline_cmd("autocmd BufReadCmd " PROTO "* nested " |
333 | ":if !exists('b:term_title')|call termopen( " |
334 | // Capture the command string |
335 | "matchstr(expand(\"<amatch>\"), " |
336 | "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " |
337 | // capture the working directory |
338 | "{'cwd': get(matchlist(expand(\"<amatch>\"), " |
339 | "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, '')})" |
340 | "|endif" ); |
341 | do_cmdline_cmd("augroup END" ); |
342 | #undef PROTO |
343 | |
344 | // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. |
345 | // Allows for setting 'loadplugins' there. |
346 | if (params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE" )) { |
347 | p_lpl = false; |
348 | } |
349 | |
350 | // Wait for UIs to set up Nvim or show early messages |
351 | // and prompts (--cmd, swapfile dialog, …). |
352 | bool use_remote_ui = (embedded_mode && !headless_mode); |
353 | bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); |
354 | if (use_remote_ui || use_builtin_ui) { |
355 | TIME_MSG("waiting for UI" ); |
356 | if (use_remote_ui) { |
357 | remote_ui_wait_for_attach(); |
358 | } else { |
359 | ui_builtin_start(); |
360 | } |
361 | TIME_MSG("done waiting for UI" ); |
362 | |
363 | // prepare screen now, so external UIs can display messages |
364 | starting = NO_BUFFERS; |
365 | screenclear(); |
366 | TIME_MSG("initialized screen early for UI" ); |
367 | } |
368 | |
369 | // Execute --cmd arguments. |
370 | exe_pre_commands(¶ms); |
371 | |
372 | // Source startup scripts. |
373 | source_startup_scripts(¶ms); |
374 | |
375 | // If using the runtime (-u is not NONE), enable syntax & filetype plugins. |
376 | if (params.use_vimrc == NULL || !strequal(params.use_vimrc, "NONE" )) { |
377 | // Does ":filetype plugin indent on". |
378 | filetype_maybe_enable(); |
379 | // Sources syntax/syntax.vim, which calls `:filetype on`. |
380 | syn_maybe_on(); |
381 | } |
382 | |
383 | /* |
384 | * Read all the plugin files. |
385 | * Only when compiled with +eval, since most plugins need it. |
386 | */ |
387 | load_plugins(); |
388 | |
389 | // Decide about window layout for diff mode after reading vimrc. |
390 | set_window_layout(¶ms); |
391 | |
392 | /* |
393 | * Recovery mode without a file name: List swap files. |
394 | * This uses the 'dir' option, therefore it must be after the |
395 | * initializations. |
396 | */ |
397 | if (recoverymode && fname == NULL) { |
398 | recover_names(NULL, TRUE, 0, NULL); |
399 | mch_exit(0); |
400 | } |
401 | |
402 | // Set some option defaults after reading vimrc files. |
403 | set_init_3(); |
404 | TIME_MSG("inits 3" ); |
405 | |
406 | // "-n" argument: Disable swap file by setting 'updatecount' to 0. |
407 | // Note that this overrides anything from a vimrc file. |
408 | if (params.no_swap_file) { |
409 | p_uc = 0; |
410 | } |
411 | |
412 | // XXX: Minimize 'updatetime' for -es/-Es. #7679 |
413 | if (silent_mode) { |
414 | p_ut = 1; |
415 | } |
416 | |
417 | // |
418 | // Read in registers, history etc, from the ShaDa file. |
419 | // This is where v:oldfiles gets filled. |
420 | // |
421 | if (*p_shada != NUL) { |
422 | shada_read_everything(NULL, false, true); |
423 | TIME_MSG("reading ShaDa" ); |
424 | } |
425 | // It's better to make v:oldfiles an empty list than NULL. |
426 | if (get_vim_var_list(VV_OLDFILES) == NULL) { |
427 | set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); |
428 | } |
429 | |
430 | /* |
431 | * "-q errorfile": Load the error file now. |
432 | * If the error file can't be read, exit before doing anything else. |
433 | */ |
434 | handle_quickfix(¶ms); |
435 | |
436 | /* |
437 | * Start putting things on the screen. |
438 | * Scroll screen down before drawing over it |
439 | * Clear screen now, so file message will not be cleared. |
440 | */ |
441 | starting = NO_BUFFERS; |
442 | no_wait_return = false; |
443 | if (!exmode_active) { |
444 | msg_scroll = false; |
445 | } |
446 | |
447 | // Read file (text, not commands) from stdin if: |
448 | // - stdin is not a tty |
449 | // - and -e/-es was not given |
450 | // |
451 | // Do this before starting Raw mode, because it may change things that the |
452 | // writing end of the pipe doesn't like, e.g., in case stdin and stderr |
453 | // are the same terminal: "cat | vim -". |
454 | // Using autocommands here may cause trouble... |
455 | if (params.edit_type == EDIT_STDIN && !recoverymode) { |
456 | read_stdin(); |
457 | } |
458 | |
459 | setmouse(); // may start using the mouse |
460 | |
461 | if (exmode_active || use_remote_ui || use_builtin_ui) { |
462 | // Don't clear the screen when starting in Ex mode, or when a UI might have |
463 | // displayed messages. |
464 | redraw_later(VALID); |
465 | } else { |
466 | screenclear(); // clear screen |
467 | TIME_MSG("clearing screen" ); |
468 | } |
469 | |
470 | no_wait_return = true; |
471 | |
472 | /* |
473 | * Create the requested number of windows and edit buffers in them. |
474 | * Also does recovery if "recoverymode" set. |
475 | */ |
476 | create_windows(¶ms); |
477 | TIME_MSG("opening buffers" ); |
478 | |
479 | /* clear v:swapcommand */ |
480 | set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); |
481 | |
482 | /* Ex starts at last line of the file */ |
483 | if (exmode_active) |
484 | curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; |
485 | |
486 | apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); |
487 | TIME_MSG("BufEnter autocommands" ); |
488 | setpcmark(); |
489 | |
490 | /* |
491 | * When started with "-q errorfile" jump to first error now. |
492 | */ |
493 | if (params.edit_type == EDIT_QF) { |
494 | qf_jump(NULL, 0, 0, FALSE); |
495 | TIME_MSG("jump to first error" ); |
496 | } |
497 | |
498 | // If opened more than one window, start editing files in the other |
499 | // windows. |
500 | edit_buffers(¶ms, cwd); |
501 | xfree(cwd); |
502 | |
503 | if (params.diff_mode) { |
504 | /* set options in each window for "nvim -d". */ |
505 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
506 | diff_win_options(wp, TRUE); |
507 | } |
508 | } |
509 | |
510 | /* |
511 | * Shorten any of the filenames, but only when absolute. |
512 | */ |
513 | shorten_fnames(FALSE); |
514 | |
515 | /* |
516 | * Need to jump to the tag before executing the '-c command'. |
517 | * Makes "vim -c '/return' -t main" work. |
518 | */ |
519 | handle_tag(params.tagname); |
520 | |
521 | /* Execute any "+", "-c" and "-S" arguments. */ |
522 | if (params.n_commands > 0) |
523 | exe_commands(¶ms); |
524 | |
525 | starting = 0; |
526 | |
527 | RedrawingDisabled = 0; |
528 | redraw_all_later(NOT_VALID); |
529 | no_wait_return = false; |
530 | |
531 | // 'autochdir' has been postponed. |
532 | do_autochdir(); |
533 | |
534 | /* start in insert mode */ |
535 | if (p_im) |
536 | need_start_insertmode = TRUE; |
537 | |
538 | set_vim_var_nr(VV_VIM_DID_ENTER, 1L); |
539 | apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf); |
540 | TIME_MSG("VimEnter autocommands" ); |
541 | if (use_remote_ui || use_builtin_ui) { |
542 | do_autocmd_uienter(use_remote_ui ? CHAN_STDIO : 0, true); |
543 | TIME_MSG("UIEnter autocommands" ); |
544 | } |
545 | |
546 | // Adjust default register name for "unnamed" in 'clipboard'. Can only be |
547 | // done after the clipboard is available and all initial commands that may |
548 | // modify the 'clipboard' setting have run; i.e. just before entering the |
549 | // main loop. |
550 | set_reg_var(get_default_register_name()); |
551 | |
552 | /* When a startup script or session file setup for diff'ing and |
553 | * scrollbind, sync the scrollbind now. */ |
554 | if (curwin->w_p_diff && curwin->w_p_scb) { |
555 | update_topline(); |
556 | check_scrollbind((linenr_T)0, 0L); |
557 | TIME_MSG("diff scrollbinding" ); |
558 | } |
559 | |
560 | /* If ":startinsert" command used, stuff a dummy command to be able to |
561 | * call normal_cmd(), which will then start Insert mode. */ |
562 | if (restart_edit != 0) |
563 | stuffcharReadbuff(K_NOP); |
564 | |
565 | // WORKAROUND(mhi): #3023 |
566 | if (cb_flags & CB_UNNAMEDMASK) { |
567 | (void)eval_has_provider("clipboard" ); |
568 | } |
569 | |
570 | TIME_MSG("before starting main loop" ); |
571 | ILOG("starting main loop" ); |
572 | |
573 | /* |
574 | * Call the main command loop. This never returns. |
575 | */ |
576 | normal_enter(false, false); |
577 | |
578 | #if defined(WIN32) && !defined(MAKE_LIB) |
579 | xfree(argv); |
580 | #endif |
581 | return 0; |
582 | } |
583 | |
584 | /// Exit properly |
585 | void getout(int exitval) |
586 | FUNC_ATTR_NORETURN |
587 | { |
588 | exiting = true; |
589 | |
590 | /* When running in Ex mode an error causes us to exit with a non-zero exit |
591 | * code. POSIX requires this, although it's not 100% clear from the |
592 | * standard. */ |
593 | if (exmode_active) |
594 | exitval += ex_exitval; |
595 | |
596 | set_vim_var_nr(VV_EXITING, exitval); |
597 | |
598 | // Position the cursor on the last screen line, below all the text |
599 | ui_cursor_goto(Rows - 1, 0); |
600 | |
601 | /* Optionally print hashtable efficiency. */ |
602 | hash_debug_results(); |
603 | |
604 | if (get_vim_var_nr(VV_DYING) <= 1) { |
605 | const tabpage_T *next_tp; |
606 | |
607 | // Trigger BufWinLeave for all windows, but only once per buffer. |
608 | for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) { |
609 | next_tp = tp->tp_next; |
610 | FOR_ALL_WINDOWS_IN_TAB(wp, tp) { |
611 | if (wp->w_buffer == NULL) { |
612 | /* Autocmd must have close the buffer already, skip. */ |
613 | continue; |
614 | } |
615 | |
616 | buf_T *buf = wp->w_buffer; |
617 | if (buf_get_changedtick(buf) != -1) { |
618 | bufref_T bufref; |
619 | |
620 | set_bufref(&bufref, buf); |
621 | apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, |
622 | buf->b_fname, false, buf); |
623 | if (bufref_valid(&bufref)) { |
624 | buf_set_changedtick(buf, -1); // note that we did it already |
625 | } |
626 | // start all over, autocommands may mess up the lists |
627 | next_tp = first_tabpage; |
628 | break; |
629 | } |
630 | } |
631 | } |
632 | |
633 | /* Trigger BufUnload for buffers that are loaded */ |
634 | FOR_ALL_BUFFERS(buf) { |
635 | if (buf->b_ml.ml_mfp != NULL) { |
636 | bufref_T bufref; |
637 | set_bufref(&bufref, buf); |
638 | apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, false, buf); |
639 | if (!bufref_valid(&bufref)) { |
640 | // Autocmd deleted the buffer. |
641 | break; |
642 | } |
643 | } |
644 | } |
645 | apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, FALSE, curbuf); |
646 | } |
647 | |
648 | if (p_shada && *p_shada != NUL) { |
649 | // Write out the registers, history, marks etc, to the ShaDa file |
650 | shada_write_file(NULL, false); |
651 | } |
652 | |
653 | if (get_vim_var_nr(VV_DYING) <= 1) |
654 | apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf); |
655 | |
656 | profile_dump(); |
657 | |
658 | if (did_emsg |
659 | ) { |
660 | /* give the user a chance to read the (error) message */ |
661 | no_wait_return = FALSE; |
662 | wait_return(FALSE); |
663 | } |
664 | |
665 | // Position the cursor again, the autocommands may have moved it |
666 | ui_cursor_goto(Rows - 1, 0); |
667 | |
668 | // Apply 'titleold'. |
669 | if (p_title && *p_titleold != NUL) { |
670 | ui_call_set_title(cstr_as_string((char *)p_titleold)); |
671 | } |
672 | |
673 | cs_end(); |
674 | if (garbage_collect_at_exit) { |
675 | garbage_collect(false); |
676 | } |
677 | |
678 | mch_exit(exitval); |
679 | } |
680 | |
681 | /// Gets the integer value of a numeric command line argument if given, |
682 | /// such as '-o10'. |
683 | /// |
684 | /// @param[in] p pointer to argument |
685 | /// @param[in, out] idx pointer to index in argument, is incremented |
686 | /// @param[in] def default value |
687 | /// |
688 | /// @return def unmodified if: |
689 | /// - argument isn't given |
690 | /// - argument is non-numeric |
691 | /// |
692 | /// @return argument's numeric value otherwise |
693 | static int get_number_arg(const char *p, int *idx, int def) |
694 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
695 | { |
696 | if (ascii_isdigit(p[*idx])) { // -V522 |
697 | def = atoi(&(p[*idx])); |
698 | while (ascii_isdigit(p[*idx])) { |
699 | *idx = *idx + 1; |
700 | } |
701 | } |
702 | return def; |
703 | } |
704 | |
705 | #if defined(HAVE_LOCALE_H) |
706 | /// Setup to use the current locale (for ctype() and many other things). |
707 | static void init_locale(void) |
708 | { |
709 | setlocale(LC_ALL, "" ); |
710 | |
711 | # ifdef LC_NUMERIC |
712 | /* Make sure strtod() uses a decimal point, not a comma. */ |
713 | setlocale(LC_NUMERIC, "C" ); |
714 | # endif |
715 | |
716 | char localepath[MAXPATHL] = { 0 }; |
717 | snprintf(localepath, sizeof(localepath), "%s" , get_vim_var_str(VV_PROGPATH)); |
718 | char *tail = (char *)path_tail_with_sep((char_u *)localepath); |
719 | *tail = NUL; |
720 | tail = (char *)path_tail((char_u *)localepath); |
721 | xstrlcpy(tail, "share/locale" , |
722 | sizeof(localepath) - (size_t)(tail - localepath)); |
723 | bindtextdomain(PROJECT_NAME, localepath); |
724 | textdomain(PROJECT_NAME); |
725 | TIME_MSG("locale set" ); |
726 | } |
727 | #endif |
728 | |
729 | /// Decides whether text (as opposed to commands) will be read from stdin. |
730 | /// @see EDIT_STDIN |
731 | static bool edit_stdin(bool explicit, mparm_T *parmp) |
732 | { |
733 | bool implicit = !headless_mode |
734 | && !embedded_mode |
735 | && exmode_active != EXMODE_NORMAL // -E/-Es but not -e/-es. |
736 | && !parmp->input_isatty |
737 | && scriptin[0] == NULL; // `-s -` was not given. |
738 | return explicit || implicit; |
739 | } |
740 | |
741 | /// Scan the command line arguments. |
742 | static void command_line_scan(mparm_T *parmp) |
743 | { |
744 | int argc = parmp->argc; |
745 | char **argv = parmp->argv; |
746 | int argv_idx; // index in argv[n][] |
747 | bool had_stdin_file = false; // found explicit "-" argument |
748 | bool had_minmin = false; // found "--" argument |
749 | int want_argument; // option argument with argument |
750 | int c; |
751 | long n; |
752 | |
753 | argc--; |
754 | argv++; |
755 | argv_idx = 1; // active option letter is argv[0][argv_idx] |
756 | while (argc > 0) { |
757 | // "+" or "+{number}" or "+/{pat}" or "+{command}" argument. |
758 | if (argv[0][0] == '+' && !had_minmin) { |
759 | if (parmp->n_commands >= MAX_ARG_CMDS) { |
760 | mainerr(err_extra_cmd, NULL); |
761 | } |
762 | argv_idx = -1; // skip to next argument |
763 | if (argv[0][1] == NUL) { |
764 | parmp->commands[parmp->n_commands++] = "$" ; |
765 | } else { |
766 | parmp->commands[parmp->n_commands++] = &(argv[0][1]); |
767 | } |
768 | |
769 | // Optional argument. |
770 | } else if (argv[0][0] == '-' && !had_minmin) { |
771 | want_argument = false; |
772 | c = argv[0][argv_idx++]; |
773 | switch (c) { |
774 | case NUL: { // "nvim -" read from stdin |
775 | if (exmode_active) { |
776 | // "nvim -e -" silent mode |
777 | silent_mode = true; |
778 | parmp->no_swap_file = true; |
779 | } else { |
780 | if (parmp->edit_type != EDIT_NONE |
781 | && parmp->edit_type != EDIT_FILE |
782 | && parmp->edit_type != EDIT_STDIN) { |
783 | mainerr(err_too_many_args, argv[0]); |
784 | } |
785 | had_stdin_file = true; |
786 | parmp->edit_type = EDIT_STDIN; |
787 | } |
788 | argv_idx = -1; // skip to next argument |
789 | break; |
790 | } |
791 | case '-': { // "--" don't take any more option arguments |
792 | // "--help" give help message |
793 | // "--version" give version message |
794 | // "--noplugin[s]" skip plugins |
795 | // "--cmd <cmd>" execute cmd before vimrc |
796 | if (STRICMP(argv[0] + argv_idx, "help" ) == 0) { |
797 | usage(); |
798 | mch_exit(0); |
799 | } else if (STRICMP(argv[0] + argv_idx, "version" ) == 0) { |
800 | version(); |
801 | mch_exit(0); |
802 | } else if (STRICMP(argv[0] + argv_idx, "api-info" ) == 0) { |
803 | FileDescriptor fp; |
804 | const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, |
805 | kFileWriteOnly); |
806 | msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write); |
807 | |
808 | if (fof_ret != 0) { |
809 | emsgf(_("E5421: Failed to open stdin: %s" ), os_strerror(fof_ret)); |
810 | } |
811 | |
812 | if (p == NULL) { |
813 | EMSG(_(e_outofmem)); |
814 | } |
815 | |
816 | Object md = DICTIONARY_OBJ(api_metadata()); |
817 | msgpack_rpc_from_object(md, p); |
818 | |
819 | msgpack_packer_free(p); |
820 | const int ff_ret = file_flush(&fp); |
821 | if (ff_ret < 0) { |
822 | msgpack_file_write_error(ff_ret); |
823 | } |
824 | mch_exit(0); |
825 | } else if (STRICMP(argv[0] + argv_idx, "headless" ) == 0) { |
826 | headless_mode = true; |
827 | } else if (STRICMP(argv[0] + argv_idx, "embed" ) == 0) { |
828 | embedded_mode = true; |
829 | } else if (STRNICMP(argv[0] + argv_idx, "listen" , 6) == 0) { |
830 | want_argument = true; |
831 | argv_idx += 6; |
832 | } else if (STRNICMP(argv[0] + argv_idx, "literal" , 7) == 0) { |
833 | // Do nothing: file args are always literal. #7679 |
834 | } else if (STRNICMP(argv[0] + argv_idx, "noplugin" , 8) == 0) { |
835 | p_lpl = false; |
836 | } else if (STRNICMP(argv[0] + argv_idx, "cmd" , 3) == 0) { |
837 | want_argument = true; |
838 | argv_idx += 3; |
839 | } else if (STRNICMP(argv[0] + argv_idx, "startuptime" , 11) == 0) { |
840 | want_argument = true; |
841 | argv_idx += 11; |
842 | } else if (STRNICMP(argv[0] + argv_idx, "clean" , 5) == 0) { |
843 | parmp->use_vimrc = "NONE" ; |
844 | set_option_value("shadafile" , 0L, "NONE" , 0); |
845 | } else { |
846 | if (argv[0][argv_idx]) |
847 | mainerr(err_opt_unknown, argv[0]); |
848 | had_minmin = true; |
849 | } |
850 | if (!want_argument) { |
851 | argv_idx = -1; // skip to next argument |
852 | } |
853 | break; |
854 | } |
855 | case 'A': { // "-A" start in Arabic mode. |
856 | set_option_value("arabic" , 1L, NULL, 0); |
857 | break; |
858 | } |
859 | case 'b': { // "-b" binary mode. |
860 | // Needs to be effective before expanding file names, because |
861 | // for Win32 this makes us edit a shortcut file itself, |
862 | // instead of the file it links to. |
863 | set_options_bin(curbuf->b_p_bin, 1, 0); |
864 | curbuf->b_p_bin = 1; // Binary file I/O. |
865 | break; |
866 | } |
867 | |
868 | case 'D': { // "-D" Debugging |
869 | parmp->use_debug_break_level = 9999; |
870 | break; |
871 | } |
872 | case 'd': { // "-d" 'diff' |
873 | parmp->diff_mode = true; |
874 | break; |
875 | } |
876 | case 'e': { // "-e" Ex mode |
877 | exmode_active = EXMODE_NORMAL; |
878 | break; |
879 | } |
880 | case 'E': { // "-E" Ex mode |
881 | exmode_active = EXMODE_VIM; |
882 | break; |
883 | } |
884 | case 'f': { // "-f" GUI: run in foreground. |
885 | break; |
886 | } |
887 | case '?': // "-?" give help message (for MS-Windows) |
888 | case 'h': { // "-h" give help message |
889 | usage(); |
890 | mch_exit(0); |
891 | } |
892 | case 'H': { // "-H" start in Hebrew mode: rl + hkmap set. |
893 | p_hkmap = true; |
894 | set_option_value("rl" , 1L, NULL, 0); |
895 | break; |
896 | } |
897 | case 'l': { // "-l" lisp mode, 'lisp' and 'showmatch' on. |
898 | set_option_value("lisp" , 1L, NULL, 0); |
899 | p_sm = true; |
900 | break; |
901 | } |
902 | case 'M': { // "-M" no changes or writing of files |
903 | reset_modifiable(); |
904 | FALLTHROUGH; |
905 | } |
906 | case 'm': { // "-m" no writing of files |
907 | p_write = false; |
908 | break; |
909 | } |
910 | |
911 | case 'N': // "-N" Nocompatible |
912 | case 'X': // "-X" Do not connect to X server |
913 | // No-op |
914 | break; |
915 | |
916 | case 'n': { // "-n" no swap file |
917 | parmp->no_swap_file = true; |
918 | break; |
919 | } |
920 | case 'p': { // "-p[N]" open N tab pages |
921 | // default is 0: open window for each file |
922 | parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); |
923 | parmp->window_layout = WIN_TABS; |
924 | break; |
925 | } |
926 | case 'o': { // "-o[N]" open N horizontal split windows |
927 | // default is 0: open window for each file |
928 | parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); |
929 | parmp->window_layout = WIN_HOR; |
930 | break; |
931 | } |
932 | case 'O': { // "-O[N]" open N vertical split windows |
933 | // default is 0: open window for each file |
934 | parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); |
935 | parmp->window_layout = WIN_VER; |
936 | break; |
937 | } |
938 | case 'q': { // "-q" QuickFix mode |
939 | if (parmp->edit_type != EDIT_NONE) { |
940 | mainerr(err_too_many_args, argv[0]); |
941 | } |
942 | parmp->edit_type = EDIT_QF; |
943 | if (argv[0][argv_idx]) { // "-q{errorfile}" |
944 | parmp->use_ef = (char_u *)argv[0] + argv_idx; |
945 | argv_idx = -1; |
946 | } else if (argc > 1) { // "-q {errorfile}" |
947 | want_argument = true; |
948 | } |
949 | break; |
950 | } |
951 | case 'R': { // "-R" readonly mode |
952 | readonlymode = true; |
953 | curbuf->b_p_ro = true; |
954 | p_uc = 10000; // don't update very often |
955 | break; |
956 | } |
957 | case 'r': // "-r" recovery mode |
958 | case 'L': { // "-L" recovery mode |
959 | recoverymode = 1; |
960 | break; |
961 | } |
962 | case 's': { |
963 | if (exmode_active) { // "-es" silent (batch) Ex-mode |
964 | silent_mode = true; |
965 | parmp->no_swap_file = true; |
966 | } else { // "-s {scriptin}" read from script file |
967 | want_argument = true; |
968 | } |
969 | break; |
970 | } |
971 | case 't': { // "-t {tag}" or "-t{tag}" jump to tag |
972 | if (parmp->edit_type != EDIT_NONE) { |
973 | mainerr(err_too_many_args, argv[0]); |
974 | } |
975 | parmp->edit_type = EDIT_TAG; |
976 | if (argv[0][argv_idx]) { // "-t{tag}" |
977 | parmp->tagname = (char_u *)argv[0] + argv_idx; |
978 | argv_idx = -1; |
979 | } else { // "-t {tag}" |
980 | want_argument = true; |
981 | } |
982 | break; |
983 | } |
984 | case 'v': { |
985 | version(); |
986 | mch_exit(0); |
987 | } |
988 | case 'V': { // "-V{N}" Verbose level |
989 | // default is 10: a little bit verbose |
990 | p_verbose = get_number_arg(argv[0], &argv_idx, 10); |
991 | if (argv[0][argv_idx] != NUL) { |
992 | set_option_value("verbosefile" , 0L, argv[0] + argv_idx, 0); |
993 | argv_idx = (int)STRLEN(argv[0]); |
994 | } |
995 | break; |
996 | } |
997 | case 'w': { // "-w{number}" set window height |
998 | // "-w {scriptout}" write to script |
999 | if (ascii_isdigit(((char_u *)argv[0])[argv_idx])) { |
1000 | n = get_number_arg(argv[0], &argv_idx, 10); |
1001 | set_option_value("window" , n, NULL, 0); |
1002 | break; |
1003 | } |
1004 | want_argument = true; |
1005 | break; |
1006 | } |
1007 | case 'Z': { // "-Z" restricted mode |
1008 | restricted = true; |
1009 | break; |
1010 | } |
1011 | |
1012 | case 'c': { // "-c{command}" or "-c {command}" exec command |
1013 | if (argv[0][argv_idx] != NUL) { |
1014 | if (parmp->n_commands >= MAX_ARG_CMDS) { |
1015 | mainerr(err_extra_cmd, NULL); |
1016 | } |
1017 | parmp->commands[parmp->n_commands++] = argv[0] + argv_idx; |
1018 | argv_idx = -1; |
1019 | break; |
1020 | } |
1021 | FALLTHROUGH; |
1022 | } |
1023 | case 'S': // "-S {file}" execute Vim script |
1024 | case 'i': // "-i {shada}" use for ShaDa file |
1025 | case 'u': // "-u {vimrc}" vim inits file |
1026 | case 'U': // "-U {gvimrc}" gvim inits file |
1027 | case 'W': { // "-W {scriptout}" overwrite |
1028 | want_argument = true; |
1029 | break; |
1030 | } |
1031 | |
1032 | default: { |
1033 | mainerr(err_opt_unknown, argv[0]); |
1034 | } |
1035 | } |
1036 | |
1037 | // Handle option arguments with argument. |
1038 | if (want_argument) { |
1039 | // Check for garbage immediately after the option letter. |
1040 | if (argv[0][argv_idx] != NUL) { |
1041 | mainerr(err_opt_garbage, argv[0]); |
1042 | } |
1043 | |
1044 | argc--; |
1045 | if (argc < 1 && c != 'S') { // -S has an optional argument |
1046 | mainerr(err_arg_missing, argv[0]); |
1047 | } |
1048 | argv++; |
1049 | argv_idx = -1; |
1050 | |
1051 | switch (c) { |
1052 | case 'c': // "-c {command}" execute command |
1053 | case 'S': { // "-S {file}" execute Vim script |
1054 | if (parmp->n_commands >= MAX_ARG_CMDS) { |
1055 | mainerr(err_extra_cmd, NULL); |
1056 | } |
1057 | if (c == 'S') { |
1058 | char *a; |
1059 | |
1060 | if (argc < 1) { |
1061 | // "-S" without argument: use default session file name. |
1062 | a = SESSION_FILE; |
1063 | } else if (argv[0][0] == '-') { |
1064 | // "-S" followed by another option: use default session file. |
1065 | a = SESSION_FILE; |
1066 | ++argc; |
1067 | --argv; |
1068 | } else { |
1069 | a = argv[0]; |
1070 | } |
1071 | size_t s_size = STRLEN(a) + 4; |
1072 | char *s = xmalloc(s_size); |
1073 | snprintf(s, s_size, "so %s" , a); |
1074 | parmp->cmds_tofree[parmp->n_commands] = true; |
1075 | parmp->commands[parmp->n_commands++] = s; |
1076 | } else { |
1077 | parmp->commands[parmp->n_commands++] = argv[0]; |
1078 | } |
1079 | break; |
1080 | } |
1081 | |
1082 | case '-': { |
1083 | if (strequal(argv[-1], "--cmd" )) { |
1084 | // "--cmd {command}" execute command |
1085 | if (parmp->n_pre_commands >= MAX_ARG_CMDS) { |
1086 | mainerr(err_extra_cmd, NULL); |
1087 | } |
1088 | parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; |
1089 | } else if (strequal(argv[-1], "--listen" )) { |
1090 | // "--listen {address}" |
1091 | parmp->listen_addr = argv[0]; |
1092 | } |
1093 | // "--startuptime <file>" already handled |
1094 | break; |
1095 | } |
1096 | |
1097 | case 'q': { // "-q {errorfile}" QuickFix mode |
1098 | parmp->use_ef = (char_u *)argv[0]; |
1099 | break; |
1100 | } |
1101 | |
1102 | case 'i': { // "-i {shada}" use for shada |
1103 | set_option_value("shadafile" , 0L, argv[0], 0); |
1104 | break; |
1105 | } |
1106 | |
1107 | case 's': { // "-s {scriptin}" read from script file |
1108 | if (scriptin[0] != NULL) { |
1109 | scripterror: |
1110 | vim_snprintf((char *)IObuff, IOSIZE, |
1111 | _("Attempt to open script file again: \"%s %s\"\n" ), |
1112 | argv[-1], argv[0]); |
1113 | mch_errmsg((const char *)IObuff); |
1114 | mch_exit(2); |
1115 | } |
1116 | int error; |
1117 | if (strequal(argv[0], "-" )) { |
1118 | const int stdin_dup_fd = os_dup(STDIN_FILENO); |
1119 | #ifdef WIN32 |
1120 | // Replace the original stdin with the console input handle. |
1121 | close(STDIN_FILENO); |
1122 | const HANDLE conin_handle = |
1123 | CreateFile("CONIN$" , GENERIC_READ | GENERIC_WRITE, |
1124 | FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, |
1125 | OPEN_EXISTING, 0, (HANDLE)NULL); |
1126 | const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY); |
1127 | assert(conin_fd == STDIN_FILENO); |
1128 | #endif |
1129 | FileDescriptor *const stdin_dup = file_open_fd_new( |
1130 | &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); |
1131 | assert(stdin_dup != NULL); |
1132 | scriptin[0] = stdin_dup; |
1133 | } else if ((scriptin[0] = file_open_new( |
1134 | &error, argv[0], kFileReadOnly|kFileNonBlocking, 0)) == NULL) { |
1135 | vim_snprintf((char *)IObuff, IOSIZE, |
1136 | _("Cannot open for reading: \"%s\": %s\n" ), |
1137 | argv[0], os_strerror(error)); |
1138 | mch_errmsg((const char *)IObuff); |
1139 | mch_exit(2); |
1140 | } |
1141 | save_typebuf(); |
1142 | break; |
1143 | } |
1144 | |
1145 | case 't': { // "-t {tag}" |
1146 | parmp->tagname = (char_u *)argv[0]; |
1147 | break; |
1148 | } |
1149 | case 'u': { // "-u {vimrc}" vim inits file |
1150 | parmp->use_vimrc = argv[0]; |
1151 | break; |
1152 | } |
1153 | case 'U': { // "-U {gvimrc}" gvim inits file |
1154 | break; |
1155 | } |
1156 | |
1157 | case 'w': { // "-w {nr}" 'window' value |
1158 | // "-w {scriptout}" append to script file |
1159 | if (ascii_isdigit(*((char_u *)argv[0]))) { |
1160 | argv_idx = 0; |
1161 | n = get_number_arg(argv[0], &argv_idx, 10); |
1162 | set_option_value("window" , n, NULL, 0); |
1163 | argv_idx = -1; |
1164 | break; |
1165 | } |
1166 | FALLTHROUGH; |
1167 | } |
1168 | case 'W': { // "-W {scriptout}" overwrite script file |
1169 | if (scriptout != NULL) { |
1170 | goto scripterror; |
1171 | } |
1172 | if ((scriptout = os_fopen(argv[0], c == 'w' ? APPENDBIN : WRITEBIN)) |
1173 | == NULL) { |
1174 | mch_errmsg(_("Cannot open for script output: \"" )); |
1175 | mch_errmsg(argv[0]); |
1176 | mch_errmsg("\"\n" ); |
1177 | mch_exit(2); |
1178 | } |
1179 | break; |
1180 | } |
1181 | } |
1182 | } |
1183 | } else { // File name argument. |
1184 | argv_idx = -1; // skip to next argument |
1185 | |
1186 | // Check for only one type of editing. |
1187 | if (parmp->edit_type != EDIT_NONE |
1188 | && parmp->edit_type != EDIT_FILE |
1189 | && parmp->edit_type != EDIT_STDIN) { |
1190 | mainerr(err_too_many_args, argv[0]); |
1191 | } |
1192 | parmp->edit_type = EDIT_FILE; |
1193 | |
1194 | // Add the file to the global argument list. |
1195 | ga_grow(&global_alist.al_ga, 1); |
1196 | char_u *p = vim_strsave((char_u *)argv[0]); |
1197 | |
1198 | if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0 |
1199 | && !os_isdir(alist_name(&GARGLIST[0]))) { |
1200 | char_u *r = (char_u *)concat_fnames((char *)p, |
1201 | (char *)path_tail(alist_name(&GARGLIST[0])), true); |
1202 | xfree(p); |
1203 | p = r; |
1204 | } |
1205 | |
1206 | #ifdef USE_FNAME_CASE |
1207 | // Make the case of the file name match the actual file. |
1208 | path_fix_case(p); |
1209 | #endif |
1210 | |
1211 | int alist_fnum_flag = edit_stdin(had_stdin_file, parmp) |
1212 | ? 1 // add buffer nr after exp. |
1213 | : 2; // add buffer number now and use curbuf |
1214 | alist_add(&global_alist, p, alist_fnum_flag); |
1215 | } |
1216 | |
1217 | // If there are no more letters after the current "-", go to next argument. |
1218 | // argv_idx is set to -1 when the current argument is to be skipped. |
1219 | if (argv_idx <= 0 || argv[0][argv_idx] == NUL) { |
1220 | argc--; |
1221 | argv++; |
1222 | argv_idx = 1; |
1223 | } |
1224 | } |
1225 | |
1226 | if (embedded_mode && silent_mode) { |
1227 | mainerr(_("--embed conflicts with -es/-Es" ), NULL); |
1228 | } |
1229 | |
1230 | // If there is a "+123" or "-c" command, set v:swapcommand to the first one. |
1231 | if (parmp->n_commands > 0) { |
1232 | const size_t swcmd_len = STRLEN(parmp->commands[0]) + 3; |
1233 | char *const swcmd = xmalloc(swcmd_len); |
1234 | snprintf(swcmd, swcmd_len, ":%s\r" , parmp->commands[0]); |
1235 | set_vim_var_string(VV_SWAPCOMMAND, swcmd, -1); |
1236 | xfree(swcmd); |
1237 | } |
1238 | |
1239 | // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 |
1240 | if (edit_stdin(had_stdin_file, parmp)) { |
1241 | parmp->edit_type = EDIT_STDIN; |
1242 | } |
1243 | |
1244 | TIME_MSG("parsing arguments" ); |
1245 | } |
1246 | |
1247 | /* |
1248 | * Many variables are in "params" so that we can pass them to invoked |
1249 | * functions without a lot of arguments. "argc" and "argv" are also |
1250 | * copied, so that they can be changed. */ |
1251 | static void init_params(mparm_T *paramp, int argc, char **argv) |
1252 | { |
1253 | memset(paramp, 0, sizeof(*paramp)); |
1254 | paramp->argc = argc; |
1255 | paramp->argv = argv; |
1256 | paramp->use_debug_break_level = -1; |
1257 | paramp->window_count = -1; |
1258 | paramp->listen_addr = NULL; |
1259 | } |
1260 | |
1261 | /// Initialize global startuptime file if "--startuptime" passed as an argument. |
1262 | static void init_startuptime(mparm_T *paramp) |
1263 | { |
1264 | for (int i = 1; i < paramp->argc; i++) { |
1265 | if (STRICMP(paramp->argv[i], "--startuptime" ) == 0 |
1266 | && i + 1 < paramp->argc) { |
1267 | time_fd = os_fopen(paramp->argv[i + 1], "a" ); |
1268 | time_start("--- NVIM STARTING ---" ); |
1269 | break; |
1270 | } |
1271 | } |
1272 | |
1273 | starttime = time(NULL); |
1274 | } |
1275 | |
1276 | static void check_and_set_isatty(mparm_T *paramp) |
1277 | { |
1278 | stdin_isatty |
1279 | = paramp->input_isatty = os_isatty(STDIN_FILENO); |
1280 | stdout_isatty |
1281 | = paramp->output_isatty = os_isatty(STDOUT_FILENO); |
1282 | paramp->err_isatty = os_isatty(STDERR_FILENO); |
1283 | #ifndef WIN32 |
1284 | int tty_fd = paramp->input_isatty |
1285 | ? STDIN_FILENO |
1286 | : (paramp->output_isatty |
1287 | ? STDOUT_FILENO |
1288 | : (paramp->err_isatty ? STDERR_FILENO : -1)); |
1289 | pty_process_save_termios(tty_fd); |
1290 | #endif |
1291 | TIME_MSG("window checked" ); |
1292 | } |
1293 | |
1294 | // Sets v:progname and v:progpath. Also modifies $PATH on Windows. |
1295 | static void init_path(const char *exename) |
1296 | FUNC_ATTR_NONNULL_ALL |
1297 | { |
1298 | char exepath[MAXPATHL] = { 0 }; |
1299 | size_t exepathlen = MAXPATHL; |
1300 | // Make v:progpath absolute. |
1301 | if (os_exepath(exepath, &exepathlen) != 0) { |
1302 | // Fall back to argv[0]. Missing procfs? #6734 |
1303 | path_guess_exepath(exename, exepath, sizeof(exepath)); |
1304 | } |
1305 | set_vim_var_string(VV_PROGPATH, exepath, -1); |
1306 | set_vim_var_string(VV_PROGNAME, (char *)path_tail((char_u *)exename), -1); |
1307 | |
1308 | #ifdef WIN32 |
1309 | // Append the process start directory to $PATH, so that ":!foo" finds tools |
1310 | // shipped with Windows package. This also mimics SearchPath(). |
1311 | os_setenv_append_path(exepath); |
1312 | #endif |
1313 | } |
1314 | |
1315 | /// Get filename from command line, if any. |
1316 | static char_u *get_fname(mparm_T *parmp, char_u *cwd) |
1317 | { |
1318 | return alist_name(&GARGLIST[0]); |
1319 | } |
1320 | |
1321 | /* |
1322 | * Decide about window layout for diff mode after reading vimrc. |
1323 | */ |
1324 | static void set_window_layout(mparm_T *paramp) |
1325 | { |
1326 | if (paramp->diff_mode && paramp->window_layout == 0) { |
1327 | if (diffopt_horizontal()) |
1328 | paramp->window_layout = WIN_HOR; /* use horizontal split */ |
1329 | else |
1330 | paramp->window_layout = WIN_VER; /* use vertical split */ |
1331 | } |
1332 | } |
1333 | |
1334 | /* |
1335 | * Read all the plugin files. |
1336 | * Only when compiled with +eval, since most plugins need it. |
1337 | */ |
1338 | static void load_plugins(void) |
1339 | { |
1340 | if (p_lpl) { |
1341 | char_u *rtp_copy = NULL; |
1342 | |
1343 | // First add all package directories to 'runtimepath', so that their |
1344 | // autoload directories can be found. Only if not done already with a |
1345 | // :packloadall command. |
1346 | // Make a copy of 'runtimepath', so that source_runtime does not use the |
1347 | // pack directories. |
1348 | if (!did_source_packages) { |
1349 | rtp_copy = vim_strsave(p_rtp); |
1350 | add_pack_start_dirs(); |
1351 | } |
1352 | |
1353 | source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy, |
1354 | (char_u *)"plugin/**/*.vim" , // NOLINT |
1355 | DIP_ALL | DIP_NOAFTER); |
1356 | TIME_MSG("loading plugins" ); |
1357 | xfree(rtp_copy); |
1358 | |
1359 | // Only source "start" packages if not done already with a :packloadall |
1360 | // command. |
1361 | if (!did_source_packages) { |
1362 | load_start_packages(); |
1363 | } |
1364 | TIME_MSG("loading packages" ); |
1365 | |
1366 | source_runtime((char_u *)"plugin/**/*.vim" , DIP_ALL | DIP_AFTER); |
1367 | TIME_MSG("loading after plugins" ); |
1368 | } |
1369 | } |
1370 | |
1371 | /* |
1372 | * "-q errorfile": Load the error file now. |
1373 | * If the error file can't be read, exit before doing anything else. |
1374 | */ |
1375 | static void handle_quickfix(mparm_T *paramp) |
1376 | { |
1377 | if (paramp->edit_type == EDIT_QF) { |
1378 | if (paramp->use_ef != NULL) |
1379 | set_string_option_direct((char_u *)"ef" , -1, |
1380 | paramp->use_ef, OPT_FREE, SID_CARG); |
1381 | vim_snprintf((char *)IObuff, IOSIZE, "cfile %s" , p_ef); |
1382 | if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { |
1383 | msg_putchar('\n'); |
1384 | mch_exit(3); |
1385 | } |
1386 | TIME_MSG("reading errorfile" ); |
1387 | } |
1388 | } |
1389 | |
1390 | /* |
1391 | * Need to jump to the tag before executing the '-c command'. |
1392 | * Makes "vim -c '/return' -t main" work. |
1393 | */ |
1394 | static void handle_tag(char_u *tagname) |
1395 | { |
1396 | if (tagname != NULL) { |
1397 | swap_exists_did_quit = FALSE; |
1398 | |
1399 | vim_snprintf((char *)IObuff, IOSIZE, "ta %s" , tagname); |
1400 | do_cmdline_cmd((char *)IObuff); |
1401 | TIME_MSG("jumping to tag" ); |
1402 | |
1403 | /* If the user doesn't want to edit the file then we quit here. */ |
1404 | if (swap_exists_did_quit) |
1405 | getout(1); |
1406 | } |
1407 | } |
1408 | |
1409 | /// Read text from stdin. |
1410 | static void read_stdin(void) |
1411 | { |
1412 | // When getting the ATTENTION prompt here, use a dialog. |
1413 | swap_exists_action = SEA_DIALOG; |
1414 | no_wait_return = true; |
1415 | int save_msg_didany = msg_didany; |
1416 | set_buflisted(true); |
1417 | (void)open_buffer(true, NULL, 0); // create memfile and read file |
1418 | if (BUFEMPTY() && curbuf->b_next != NULL) { |
1419 | // stdin was empty, go to buffer 2 (e.g. "echo file1 | xargs nvim"). #8561 |
1420 | do_cmdline_cmd("silent! bnext" ); |
1421 | // Delete the empty stdin buffer. |
1422 | do_cmdline_cmd("bwipeout 1" ); |
1423 | } |
1424 | no_wait_return = false; |
1425 | msg_didany = save_msg_didany; |
1426 | TIME_MSG("reading stdin" ); |
1427 | check_swap_exists_action(); |
1428 | } |
1429 | |
1430 | /* |
1431 | * Create the requested number of windows and edit buffers in them. |
1432 | * Also does recovery if "recoverymode" set. |
1433 | */ |
1434 | static void create_windows(mparm_T *parmp) |
1435 | { |
1436 | int dorewind; |
1437 | int done = 0; |
1438 | |
1439 | /* |
1440 | * Create the number of windows that was requested. |
1441 | */ |
1442 | if (parmp->window_count == -1) /* was not set */ |
1443 | parmp->window_count = 1; |
1444 | if (parmp->window_count == 0) |
1445 | parmp->window_count = GARGCOUNT; |
1446 | if (parmp->window_count > 1) { |
1447 | // Don't change the windows if there was a command in vimrc that |
1448 | // already split some windows |
1449 | if (parmp->window_layout == 0) |
1450 | parmp->window_layout = WIN_HOR; |
1451 | if (parmp->window_layout == WIN_TABS) { |
1452 | parmp->window_count = make_tabpages(parmp->window_count); |
1453 | TIME_MSG("making tab pages" ); |
1454 | } else if (firstwin->w_next == NULL) { |
1455 | parmp->window_count = make_windows(parmp->window_count, |
1456 | parmp->window_layout == WIN_VER); |
1457 | TIME_MSG("making windows" ); |
1458 | } else |
1459 | parmp->window_count = win_count(); |
1460 | } else |
1461 | parmp->window_count = 1; |
1462 | |
1463 | if (recoverymode) { /* do recover */ |
1464 | msg_scroll = TRUE; /* scroll message up */ |
1465 | ml_recover(); |
1466 | if (curbuf->b_ml.ml_mfp == NULL) /* failed */ |
1467 | getout(1); |
1468 | do_modelines(0); /* do modelines */ |
1469 | } else { |
1470 | // Open a buffer for windows that don't have one yet. |
1471 | // Commands in the vimrc might have loaded a file or split the window. |
1472 | // Watch out for autocommands that delete a window. |
1473 | // |
1474 | // Don't execute Win/Buf Enter/Leave autocommands here |
1475 | ++autocmd_no_enter; |
1476 | ++autocmd_no_leave; |
1477 | dorewind = TRUE; |
1478 | while (done++ < 1000) { |
1479 | if (dorewind) { |
1480 | if (parmp->window_layout == WIN_TABS) |
1481 | goto_tabpage(1); |
1482 | else |
1483 | curwin = firstwin; |
1484 | } else if (parmp->window_layout == WIN_TABS) { |
1485 | if (curtab->tp_next == NULL) |
1486 | break; |
1487 | goto_tabpage(0); |
1488 | } else { |
1489 | if (curwin->w_next == NULL) |
1490 | break; |
1491 | curwin = curwin->w_next; |
1492 | } |
1493 | dorewind = FALSE; |
1494 | curbuf = curwin->w_buffer; |
1495 | if (curbuf->b_ml.ml_mfp == NULL) { |
1496 | // Set 'foldlevel' to 'foldlevelstart' if it's not negative.. |
1497 | if (p_fdls >= 0) { |
1498 | curwin->w_p_fdl = p_fdls; |
1499 | } |
1500 | // When getting the ATTENTION prompt here, use a dialog. |
1501 | swap_exists_action = SEA_DIALOG; |
1502 | set_buflisted(TRUE); |
1503 | |
1504 | /* create memfile, read file */ |
1505 | (void)open_buffer(FALSE, NULL, 0); |
1506 | |
1507 | if (swap_exists_action == SEA_QUIT) { |
1508 | if (got_int || only_one_window()) { |
1509 | /* abort selected or quit and only one window */ |
1510 | did_emsg = FALSE; /* avoid hit-enter prompt */ |
1511 | getout(1); |
1512 | } |
1513 | /* We can't close the window, it would disturb what |
1514 | * happens next. Clear the file name and set the arg |
1515 | * index to -1 to delete it later. */ |
1516 | setfname(curbuf, NULL, NULL, FALSE); |
1517 | curwin->w_arg_idx = -1; |
1518 | swap_exists_action = SEA_NONE; |
1519 | } else |
1520 | handle_swap_exists(NULL); |
1521 | dorewind = TRUE; /* start again */ |
1522 | } |
1523 | os_breakcheck(); |
1524 | if (got_int) { |
1525 | (void)vgetc(); /* only break the file loading, not the rest */ |
1526 | break; |
1527 | } |
1528 | } |
1529 | if (parmp->window_layout == WIN_TABS) |
1530 | goto_tabpage(1); |
1531 | else |
1532 | curwin = firstwin; |
1533 | curbuf = curwin->w_buffer; |
1534 | --autocmd_no_enter; |
1535 | --autocmd_no_leave; |
1536 | } |
1537 | } |
1538 | |
1539 | /// If opened more than one window, start editing files in the other |
1540 | /// windows. make_windows() has already opened the windows. |
1541 | static void edit_buffers(mparm_T *parmp, char_u *cwd) |
1542 | { |
1543 | int arg_idx; /* index in argument list */ |
1544 | int i; |
1545 | bool advance = true; |
1546 | win_T *win; |
1547 | char *p_shm_save = NULL; |
1548 | |
1549 | /* |
1550 | * Don't execute Win/Buf Enter/Leave autocommands here |
1551 | */ |
1552 | ++autocmd_no_enter; |
1553 | ++autocmd_no_leave; |
1554 | |
1555 | /* When w_arg_idx is -1 remove the window (see create_windows()). */ |
1556 | if (curwin->w_arg_idx == -1) { |
1557 | win_close(curwin, true); |
1558 | advance = false; |
1559 | } |
1560 | |
1561 | arg_idx = 1; |
1562 | for (i = 1; i < parmp->window_count; ++i) { |
1563 | if (cwd != NULL) { |
1564 | os_chdir((char *)cwd); |
1565 | } |
1566 | // When w_arg_idx is -1 remove the window (see create_windows()). |
1567 | if (curwin->w_arg_idx == -1) { |
1568 | arg_idx++; |
1569 | win_close(curwin, true); |
1570 | advance = false; |
1571 | continue; |
1572 | } |
1573 | |
1574 | if (advance) { |
1575 | if (parmp->window_layout == WIN_TABS) { |
1576 | if (curtab->tp_next == NULL) /* just checking */ |
1577 | break; |
1578 | goto_tabpage(0); |
1579 | // Temporarily reset 'shm' option to not print fileinfo when |
1580 | // loading the other buffers. This would overwrite the already |
1581 | // existing fileinfo for the first tab. |
1582 | if (i == 1) { |
1583 | char buf[100]; |
1584 | |
1585 | p_shm_save = xstrdup((char *)p_shm); |
1586 | snprintf(buf, sizeof(buf), "F%s" , p_shm); |
1587 | set_option_value("shm" , 0L, buf, 0); |
1588 | } |
1589 | } else { |
1590 | if (curwin->w_next == NULL) /* just checking */ |
1591 | break; |
1592 | win_enter(curwin->w_next, false); |
1593 | } |
1594 | } |
1595 | advance = true; |
1596 | |
1597 | // Only open the file if there is no file in this window yet (that can |
1598 | // happen when vimrc contains ":sall"). |
1599 | if (curbuf == firstwin->w_buffer || curbuf->b_ffname == NULL) { |
1600 | curwin->w_arg_idx = arg_idx; |
1601 | /* Edit file from arg list, if there is one. When "Quit" selected |
1602 | * at the ATTENTION prompt close the window. */ |
1603 | swap_exists_did_quit = FALSE; |
1604 | (void)do_ecmd(0, arg_idx < GARGCOUNT |
1605 | ? alist_name(&GARGLIST[arg_idx]) : NULL, |
1606 | NULL, NULL, ECMD_LASTL, ECMD_HIDE, curwin); |
1607 | if (swap_exists_did_quit) { |
1608 | /* abort or quit selected */ |
1609 | if (got_int || only_one_window()) { |
1610 | /* abort selected and only one window */ |
1611 | did_emsg = FALSE; /* avoid hit-enter prompt */ |
1612 | getout(1); |
1613 | } |
1614 | win_close(curwin, true); |
1615 | advance = false; |
1616 | } |
1617 | if (arg_idx == GARGCOUNT - 1) { |
1618 | arg_had_last = true; |
1619 | } |
1620 | arg_idx++; |
1621 | } |
1622 | os_breakcheck(); |
1623 | if (got_int) { |
1624 | (void)vgetc(); /* only break the file loading, not the rest */ |
1625 | break; |
1626 | } |
1627 | } |
1628 | |
1629 | if (p_shm_save != NULL) { |
1630 | set_option_value("shm" , 0L, p_shm_save, 0); |
1631 | xfree(p_shm_save); |
1632 | } |
1633 | |
1634 | if (parmp->window_layout == WIN_TABS) |
1635 | goto_tabpage(1); |
1636 | --autocmd_no_enter; |
1637 | |
1638 | /* make the first window the current window */ |
1639 | win = firstwin; |
1640 | /* Avoid making a preview window the current window. */ |
1641 | while (win->w_p_pvw) { |
1642 | win = win->w_next; |
1643 | if (win == NULL) { |
1644 | win = firstwin; |
1645 | break; |
1646 | } |
1647 | } |
1648 | win_enter(win, false); |
1649 | |
1650 | --autocmd_no_leave; |
1651 | TIME_MSG("editing files in windows" ); |
1652 | if (parmp->window_count > 1 && parmp->window_layout != WIN_TABS) |
1653 | win_equal(curwin, false, 'b'); /* adjust heights */ |
1654 | } |
1655 | |
1656 | /* |
1657 | * Execute the commands from --cmd arguments "cmds[cnt]". |
1658 | */ |
1659 | static void exe_pre_commands(mparm_T *parmp) |
1660 | { |
1661 | char **cmds = parmp->pre_commands; |
1662 | int cnt = parmp->n_pre_commands; |
1663 | int i; |
1664 | |
1665 | if (cnt > 0) { |
1666 | curwin->w_cursor.lnum = 0; /* just in case.. */ |
1667 | sourcing_name = (char_u *)_("pre-vimrc command line" ); |
1668 | current_sctx.sc_sid = SID_CMDARG; |
1669 | for (i = 0; i < cnt; i++) { |
1670 | do_cmdline_cmd(cmds[i]); |
1671 | } |
1672 | sourcing_name = NULL; |
1673 | current_sctx.sc_sid = 0; |
1674 | TIME_MSG("--cmd commands" ); |
1675 | } |
1676 | } |
1677 | |
1678 | /* |
1679 | * Execute "+", "-c" and "-S" arguments. |
1680 | */ |
1681 | static void exe_commands(mparm_T *parmp) |
1682 | { |
1683 | int i; |
1684 | |
1685 | /* |
1686 | * We start commands on line 0, make "vim +/pat file" match a |
1687 | * pattern on line 1. But don't move the cursor when an autocommand |
1688 | * with g`" was used. |
1689 | */ |
1690 | msg_scroll = TRUE; |
1691 | if (parmp->tagname == NULL && curwin->w_cursor.lnum <= 1) |
1692 | curwin->w_cursor.lnum = 0; |
1693 | sourcing_name = (char_u *)"command line" ; |
1694 | current_sctx.sc_sid = SID_CARG; |
1695 | current_sctx.sc_seq = 0; |
1696 | for (i = 0; i < parmp->n_commands; i++) { |
1697 | do_cmdline_cmd(parmp->commands[i]); |
1698 | if (parmp->cmds_tofree[i]) |
1699 | xfree(parmp->commands[i]); |
1700 | } |
1701 | sourcing_name = NULL; |
1702 | current_sctx.sc_sid = 0; |
1703 | if (curwin->w_cursor.lnum == 0) { |
1704 | curwin->w_cursor.lnum = 1; |
1705 | } |
1706 | |
1707 | if (!exmode_active) |
1708 | msg_scroll = FALSE; |
1709 | |
1710 | /* When started with "-q errorfile" jump to first error again. */ |
1711 | if (parmp->edit_type == EDIT_QF) |
1712 | qf_jump(NULL, 0, 0, FALSE); |
1713 | TIME_MSG("executing command arguments" ); |
1714 | } |
1715 | |
1716 | /// Source system-wide vimrc if built with one defined |
1717 | /// |
1718 | /// Does one of the following things, stops after whichever succeeds: |
1719 | /// |
1720 | /// 1. Source system vimrc file from $XDG_CONFIG_DIRS/nvim/sysinit.vim |
1721 | /// 2. Source system vimrc file from $VIM |
1722 | static void do_system_initialization(void) |
1723 | { |
1724 | char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); |
1725 | if (config_dirs != NULL) { |
1726 | const void *iter = NULL; |
1727 | const char path_tail[] = { |
1728 | 'n', 'v', 'i', 'm', PATHSEP, |
1729 | 's', 'y', 's', 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL |
1730 | }; |
1731 | do { |
1732 | const char *dir; |
1733 | size_t dir_len; |
1734 | iter = vim_env_iter(':', config_dirs, iter, &dir, &dir_len); |
1735 | if (dir == NULL || dir_len == 0) { |
1736 | break; |
1737 | } |
1738 | char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1); |
1739 | memcpy(vimrc, dir, dir_len); |
1740 | vimrc[dir_len] = PATHSEP; |
1741 | memcpy(vimrc + dir_len + 1, path_tail, sizeof(path_tail)); |
1742 | if (do_source((char_u *)vimrc, false, DOSO_NONE) != FAIL) { |
1743 | xfree(vimrc); |
1744 | xfree(config_dirs); |
1745 | return; |
1746 | } |
1747 | xfree(vimrc); |
1748 | } while (iter != NULL); |
1749 | xfree(config_dirs); |
1750 | } |
1751 | |
1752 | #ifdef SYS_VIMRC_FILE |
1753 | // Get system wide defaults, if the file name is defined. |
1754 | (void)do_source((char_u *)SYS_VIMRC_FILE, false, DOSO_NONE); |
1755 | #endif |
1756 | } |
1757 | |
1758 | /// Source vimrc or do other user initialization |
1759 | /// |
1760 | /// Does one of the following things, stops after whichever succeeds: |
1761 | /// |
1762 | /// 1. Execution of VIMINIT environment variable. |
1763 | /// 2. Sourcing user vimrc file ($XDG_CONFIG_HOME/nvim/init.vim). |
1764 | /// 3. Sourcing other vimrc files ($XDG_CONFIG_DIRS[1]/nvim/init.vim, …). |
1765 | /// 4. Execution of EXINIT environment variable. |
1766 | /// |
1767 | /// @return True if it is needed to attempt to source exrc file according to |
1768 | /// 'exrc' option definition. |
1769 | static bool do_user_initialization(void) |
1770 | FUNC_ATTR_WARN_UNUSED_RESULT |
1771 | { |
1772 | bool do_exrc = p_exrc; |
1773 | if (execute_env("VIMINIT" ) == OK) { |
1774 | do_exrc = p_exrc; |
1775 | return do_exrc; |
1776 | } |
1777 | char_u *user_vimrc = (char_u *)stdpaths_user_conf_subpath("init.vim" ); |
1778 | if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) { |
1779 | do_exrc = p_exrc; |
1780 | if (do_exrc) { |
1781 | do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, false) |
1782 | != kEqualFiles); |
1783 | } |
1784 | xfree(user_vimrc); |
1785 | return do_exrc; |
1786 | } |
1787 | xfree(user_vimrc); |
1788 | char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); |
1789 | if (config_dirs != NULL) { |
1790 | const void *iter = NULL; |
1791 | do { |
1792 | const char *dir; |
1793 | size_t dir_len; |
1794 | iter = vim_env_iter(':', config_dirs, iter, &dir, &dir_len); |
1795 | if (dir == NULL || dir_len == 0) { |
1796 | break; |
1797 | } |
1798 | const char path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP, |
1799 | 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; |
1800 | char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1); |
1801 | memmove(vimrc, dir, dir_len); |
1802 | vimrc[dir_len] = PATHSEP; |
1803 | memmove(vimrc + dir_len + 1, path_tail, sizeof(path_tail)); |
1804 | if (do_source((char_u *) vimrc, true, DOSO_VIMRC) != FAIL) { |
1805 | do_exrc = p_exrc; |
1806 | if (do_exrc) { |
1807 | do_exrc = (path_full_compare((char_u *)VIMRC_FILE, (char_u *)vimrc, |
1808 | false) != kEqualFiles); |
1809 | } |
1810 | xfree(vimrc); |
1811 | xfree(config_dirs); |
1812 | return do_exrc; |
1813 | } |
1814 | xfree(vimrc); |
1815 | } while (iter != NULL); |
1816 | xfree(config_dirs); |
1817 | } |
1818 | if (execute_env("EXINIT" ) == OK) { |
1819 | do_exrc = p_exrc; |
1820 | return do_exrc; |
1821 | } |
1822 | return do_exrc; |
1823 | } |
1824 | |
1825 | /// Source startup scripts |
1826 | static void source_startup_scripts(const mparm_T *const parmp) |
1827 | FUNC_ATTR_NONNULL_ALL |
1828 | { |
1829 | // If -u given, use only the initializations from that file and nothing else. |
1830 | if (parmp->use_vimrc != NULL) { |
1831 | if (strequal(parmp->use_vimrc, "NONE" ) |
1832 | || strequal(parmp->use_vimrc, "NORC" )) { |
1833 | // Do nothing. |
1834 | } else { |
1835 | if (do_source((char_u *)parmp->use_vimrc, false, DOSO_NONE) != OK) { |
1836 | EMSG2(_("E282: Cannot read from \"%s\"" ), parmp->use_vimrc); |
1837 | } |
1838 | } |
1839 | } else if (!silent_mode) { |
1840 | do_system_initialization(); |
1841 | |
1842 | if (do_user_initialization()) { |
1843 | // Read initialization commands from ".vimrc" or ".exrc" in current |
1844 | // directory. This is only done if the 'exrc' option is set. |
1845 | // Because of security reasons we disallow shell and write commands |
1846 | // now, except for unix if the file is owned by the user or 'secure' |
1847 | // option has been reset in environment of global "exrc" or "vimrc". |
1848 | // Only do this if VIMRC_FILE is not the same as vimrc file sourced in |
1849 | // do_user_initialization. |
1850 | #if defined(UNIX) |
1851 | // If vimrc file is not owned by user, set 'secure' mode. |
1852 | if (!file_owned(VIMRC_FILE)) |
1853 | #endif |
1854 | secure = p_secure; |
1855 | |
1856 | if (do_source((char_u *)VIMRC_FILE, true, DOSO_VIMRC) == FAIL) { |
1857 | #if defined(UNIX) |
1858 | // if ".exrc" is not owned by user set 'secure' mode |
1859 | if (!file_owned(EXRC_FILE)) { |
1860 | secure = p_secure; |
1861 | } else { |
1862 | secure = 0; |
1863 | } |
1864 | #endif |
1865 | (void)do_source((char_u *)EXRC_FILE, false, DOSO_NONE); |
1866 | } |
1867 | } |
1868 | if (secure == 2) { |
1869 | need_wait_return = true; |
1870 | } |
1871 | secure = 0; |
1872 | } |
1873 | TIME_MSG("sourcing vimrc file(s)" ); |
1874 | } |
1875 | |
1876 | /// Get an environment variable, and execute it as Ex commands. |
1877 | /// |
1878 | /// @param env environment variable to execute |
1879 | /// |
1880 | /// @return FAIL if the environment variable was not executed, |
1881 | /// OK otherwise. |
1882 | static int execute_env(char *env) |
1883 | FUNC_ATTR_NONNULL_ALL |
1884 | { |
1885 | const char *initstr = os_getenv(env); |
1886 | if (initstr != NULL) { |
1887 | char_u *save_sourcing_name = sourcing_name; |
1888 | linenr_T save_sourcing_lnum = sourcing_lnum; |
1889 | sourcing_name = (char_u *)env; |
1890 | sourcing_lnum = 0; |
1891 | const sctx_T save_current_sctx = current_sctx; |
1892 | current_sctx.sc_sid = SID_ENV; |
1893 | current_sctx.sc_seq = 0; |
1894 | current_sctx.sc_lnum = 0; |
1895 | do_cmdline_cmd((char *)initstr); |
1896 | sourcing_name = save_sourcing_name; |
1897 | sourcing_lnum = save_sourcing_lnum; |
1898 | current_sctx = save_current_sctx; |
1899 | return OK; |
1900 | } |
1901 | return FAIL; |
1902 | } |
1903 | |
1904 | #ifdef UNIX |
1905 | /// Checks if user owns file. |
1906 | /// Use both uv_fs_stat() and uv_fs_lstat() through os_fileinfo() and |
1907 | /// os_fileinfo_link() respectively for extra security. |
1908 | static bool file_owned(const char *fname) |
1909 | { |
1910 | assert(fname != NULL); |
1911 | uid_t uid = getuid(); |
1912 | FileInfo file_info; |
1913 | bool file_owned = os_fileinfo(fname, &file_info) |
1914 | && file_info.stat.st_uid == uid; |
1915 | bool link_owned = os_fileinfo_link(fname, &file_info) |
1916 | && file_info.stat.st_uid == uid; |
1917 | return file_owned && link_owned; |
1918 | } |
1919 | #endif |
1920 | |
1921 | /// Prints the following then exits: |
1922 | /// - An error message `errstr` |
1923 | /// - A string `str` if not null |
1924 | /// |
1925 | /// @param errstr string containing an error message |
1926 | /// @param str string to append to the primary error message, or NULL |
1927 | static void mainerr(const char *errstr, const char *str) |
1928 | { |
1929 | char *prgname = (char *)path_tail((char_u *)argv0); |
1930 | |
1931 | signal_stop(); // kill us with CTRL-C here, if you like |
1932 | |
1933 | mch_errmsg(prgname); |
1934 | mch_errmsg(": " ); |
1935 | mch_errmsg(_(errstr)); |
1936 | if (str != NULL) { |
1937 | mch_errmsg(": \"" ); |
1938 | mch_errmsg(str); |
1939 | mch_errmsg("\"" ); |
1940 | } |
1941 | mch_errmsg(_("\nMore info with \"" )); |
1942 | mch_errmsg(prgname); |
1943 | mch_errmsg(" -h\"\n" ); |
1944 | |
1945 | mch_exit(1); |
1946 | } |
1947 | |
1948 | /// Prints version information for "nvim -v" or "nvim --version". |
1949 | static void version(void) |
1950 | { |
1951 | info_message = TRUE; // use mch_msg(), not mch_errmsg() |
1952 | list_version(); |
1953 | msg_putchar('\n'); |
1954 | msg_didout = FALSE; |
1955 | } |
1956 | |
1957 | /// Prints help message for "nvim -h" or "nvim --help". |
1958 | static void usage(void) |
1959 | { |
1960 | signal_stop(); // kill us with CTRL-C here, if you like |
1961 | |
1962 | mch_msg(_("Usage:\n" )); |
1963 | mch_msg(_(" nvim [options] [file ...] Edit file(s)\n" )); |
1964 | mch_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n" )); |
1965 | mch_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n" )); |
1966 | mch_msg(_("\nOptions:\n" )); |
1967 | mch_msg(_(" -- Only file names after this\n" )); |
1968 | mch_msg(_(" + Start at end of file\n" )); |
1969 | mch_msg(_(" --cmd <cmd> Execute <cmd> before any config\n" )); |
1970 | mch_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n" )); |
1971 | mch_msg("\n" ); |
1972 | mch_msg(_(" -b Binary mode\n" )); |
1973 | mch_msg(_(" -d Diff mode\n" )); |
1974 | mch_msg(_(" -e, -E Ex mode\n" )); |
1975 | mch_msg(_(" -es, -Es Silent (batch) mode\n" )); |
1976 | mch_msg(_(" -h, --help Print this help message\n" )); |
1977 | mch_msg(_(" -i <shada> Use this shada file\n" )); |
1978 | mch_msg(_(" -m Modifications (writing files) not allowed\n" )); |
1979 | mch_msg(_(" -M Modifications in text not allowed\n" )); |
1980 | mch_msg(_(" -n No swap file, use memory only\n" )); |
1981 | mch_msg(_(" -o[N] Open N windows (default: one per file)\n" )); |
1982 | mch_msg(_(" -O[N] Open N vertical windows (default: one per file)\n" )); |
1983 | mch_msg(_(" -p[N] Open N tab pages (default: one per file)\n" )); |
1984 | mch_msg(_(" -r, -L List swap files\n" )); |
1985 | mch_msg(_(" -r <file> Recover edit state for this file\n" )); |
1986 | mch_msg(_(" -R Read-only mode\n" )); |
1987 | mch_msg(_(" -S <session> Source <session> after loading the first file\n" )); |
1988 | mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n" )); |
1989 | mch_msg(_(" -u <config> Use this config file\n" )); |
1990 | mch_msg(_(" -v, --version Print version information\n" )); |
1991 | mch_msg(_(" -V[N][file] Verbose [level][file]\n" )); |
1992 | mch_msg(_(" -Z Restricted mode\n" )); |
1993 | mch_msg("\n" ); |
1994 | mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n" )); |
1995 | mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n" )); |
1996 | mch_msg(_(" --headless Don't start a user interface\n" )); |
1997 | mch_msg(_(" --listen <address> Serve RPC API from this address\n" )); |
1998 | mch_msg(_(" --noplugin Don't load plugins\n" )); |
1999 | mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n" )); |
2000 | mch_msg(_("\nSee \":help startup-options\" for all options.\n" )); |
2001 | } |
2002 | |
2003 | |
2004 | /* |
2005 | * Check the result of the ATTENTION dialog: |
2006 | * When "Quit" selected, exit Vim. |
2007 | * When "Recover" selected, recover the file. |
2008 | */ |
2009 | static void check_swap_exists_action(void) |
2010 | { |
2011 | if (swap_exists_action == SEA_QUIT) |
2012 | getout(1); |
2013 | handle_swap_exists(NULL); |
2014 | } |
2015 | |