/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * ex_cmds2.c: some more functions for command line commands */ #include "vim.h" #include "version.h" #if defined(FEAT_EVAL) || defined(PROTO) # if defined(FEAT_TIMERS) || defined(PROTO) static timer_T *first_timer = NULL; static long last_timer_id = 0; /* * Return time left until "due". Negative if past "due". */ long proftime_time_left(proftime_T *due, proftime_T *now) { # ifdef MSWIN LARGE_INTEGER fr; if (now->QuadPart > due->QuadPart) return 0; QueryPerformanceFrequency(&fr); return (long)(((double)(due->QuadPart - now->QuadPart) / (double)fr.QuadPart) * 1000); # else if (now->tv_sec > due->tv_sec) return 0; return (due->tv_sec - now->tv_sec) * 1000 + (due->tv_usec - now->tv_usec) / 1000; # endif } /* * Insert a timer in the list of timers. */ static void insert_timer(timer_T *timer) { timer->tr_next = first_timer; timer->tr_prev = NULL; if (first_timer != NULL) first_timer->tr_prev = timer; first_timer = timer; did_add_timer = TRUE; } /* * Take a timer out of the list of timers. */ static void remove_timer(timer_T *timer) { if (timer->tr_prev == NULL) first_timer = timer->tr_next; else timer->tr_prev->tr_next = timer->tr_next; if (timer->tr_next != NULL) timer->tr_next->tr_prev = timer->tr_prev; } static void free_timer(timer_T *timer) { free_callback(&timer->tr_callback); vim_free(timer); } /* * Create a timer and return it. NULL if out of memory. * Caller should set the callback. */ timer_T * create_timer(long msec, int repeat) { timer_T *timer = ALLOC_CLEAR_ONE(timer_T); long prev_id = last_timer_id; if (timer == NULL) return NULL; if (++last_timer_id <= prev_id) /* Overflow! Might cause duplicates... */ last_timer_id = 0; timer->tr_id = last_timer_id; insert_timer(timer); if (repeat != 0) timer->tr_repeat = repeat - 1; timer->tr_interval = msec; profile_setlimit(msec, &timer->tr_due); return timer; } /* * Invoke the callback of "timer". */ static void timer_callback(timer_T *timer) { typval_T rettv; typval_T argv[2]; argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = (varnumber_T)timer->tr_id; argv[1].v_type = VAR_UNKNOWN; call_callback(&timer->tr_callback, -1, &rettv, 1, argv); clear_tv(&rettv); } /* * Call timers that are due. * Return the time in msec until the next timer is due. * Returns -1 if there are no pending timers. */ long check_due_timer(void) { timer_T *timer; timer_T *timer_next; long this_due; long next_due = -1; proftime_T now; int did_one = FALSE; int need_update_screen = FALSE; long current_id = last_timer_id; /* Don't run any timers while exiting or dealing with an error. */ if (exiting || aborting()) return next_due; profile_start(&now); for (timer = first_timer; timer != NULL && !got_int; timer = timer_next) { timer_next = timer->tr_next; if (timer->tr_id == -1 || timer->tr_firing || timer->tr_paused) continue; this_due = proftime_time_left(&timer->tr_due, &now); if (this_due <= 1) { /* Save and restore a lot of flags, because the timer fires while * waiting for a character, which might be halfway a command. */ int save_timer_busy = timer_busy; int save_vgetc_busy = vgetc_busy; int save_did_emsg = did_emsg; int save_called_emsg = called_emsg; int save_must_redraw = must_redraw; int save_trylevel = trylevel; int save_did_throw = did_throw; int save_ex_pressedreturn = get_pressedreturn(); int save_may_garbage_collect = may_garbage_collect; except_T *save_current_exception = current_exception; vimvars_save_T vvsave; /* Create a scope for running the timer callback, ignoring most of * the current scope, such as being inside a try/catch. */ timer_busy = timer_busy > 0 || vgetc_busy > 0; vgetc_busy = 0; called_emsg = FALSE; did_emsg = FALSE; did_uncaught_emsg = FALSE; must_redraw = 0; trylevel = 0; did_throw = FALSE; current_exception = NULL; may_garbage_collect = FALSE; save_vimvars(&vvsave); timer->tr_firing = TRUE; timer_callback(timer); timer->tr_firing = FALSE; timer_next = timer->tr_next; did_one = TRUE; timer_busy = save_timer_busy; vgetc_busy = save_vgetc_busy; if (did_uncaught_emsg) ++timer->tr_emsg_count; did_emsg = save_did_emsg; called_emsg = save_called_emsg; trylevel = save_trylevel; did_throw = save_did_throw; current_exception = save_current_exception; restore_vimvars(&vvsave); if (must_redraw != 0) need_update_screen = TRUE; must_redraw = must_redraw > save_must_redraw ? must_redraw : save_must_redraw; set_pressedreturn(save_ex_pressedreturn); may_garbage_collect = save_may_garbage_collect; /* Only fire the timer again if it repeats and stop_timer() wasn't * called while inside the callback (tr_id == -1). */ if (timer->tr_repeat != 0 && timer->tr_id != -1 && timer->tr_emsg_count < 3) { profile_setlimit(timer->tr_interval, &timer->tr_due); this_due = proftime_time_left(&timer->tr_due, &now); if (this_due < 1) this_due = 1; if (timer->tr_repeat > 0) --timer->tr_repeat; } else { this_due = -1; remove_timer(timer); free_timer(timer); } } if (this_due > 0 && (next_due == -1 || next_due > this_due)) next_due = this_due; } if (did_one) redraw_after_callback(need_update_screen); #ifdef FEAT_BEVAL_TERM if (bevalexpr_due_set) { this_due = proftime_time_left(&bevalexpr_due, &now); if (this_due <= 1) { bevalexpr_due_set = FALSE; if (balloonEval == NULL) { balloonEval = ALLOC_CLEAR_ONE(BalloonEval); balloonEvalForTerm = TRUE; } if (balloonEval != NULL) { general_beval_cb(balloonEval, 0); setcursor(); out_flush(); } } else if (next_due == -1 || next_due > this_due) next_due = this_due; } #endif #ifdef FEAT_TERMINAL /* Some terminal windows may need their buffer updated. */ next_due = term_check_timers(next_due, &now); #endif return current_id != last_timer_id ? 1 : next_due; } /* * Find a timer by ID. Returns NULL if not found; */ static timer_T * find_timer(long id) { timer_T *timer; if (id >= 0) { for (timer = first_timer; timer != NULL; timer = timer->tr_next) if (timer->tr_id == id) return timer; } return NULL; } /* * Stop a timer and delete it. */ void stop_timer(timer_T *timer) { if (timer->tr_firing) /* Free the timer after the callback returns. */ timer->tr_id = -1; else { remove_timer(timer); free_timer(timer); } } static void stop_all_timers(void) { timer_T *timer; timer_T *timer_next; for (timer = first_timer; timer != NULL; timer = timer_next) { timer_next = timer->tr_next; stop_timer(timer); } } static void add_timer_info(typval_T *rettv, timer_T *timer) { list_T *list = rettv->vval.v_list; dict_T *dict = dict_alloc(); dictitem_T *di; long remaining; proftime_T now; if (dict == NULL) return; list_append_dict(list, dict); dict_add_number(dict, "id", timer->tr_id); dict_add_number(dict, "time", (long)timer->tr_interval); profile_start(&now); remaining = proftime_time_left(&timer->tr_due, &now); dict_add_number(dict, "remaining", (long)remaining); dict_add_number(dict, "repeat", (long)(timer->tr_repeat < 0 ? -1 : timer->tr_repeat + 1)); dict_add_number(dict, "paused", (long)(timer->tr_paused)); di = dictitem_alloc((char_u *)"callback"); if (di != NULL) { if (dict_add(dict, di) == FAIL) vim_free(di); else put_callback(&timer->tr_callback, &di->di_tv); } } static void add_timer_info_all(typval_T *rettv) { timer_T *timer; for (timer = first_timer; timer != NULL; timer = timer->tr_next) if (timer->tr_id != -1) add_timer_info(rettv, timer); } /* * Mark references in partials of timers. */ int set_ref_in_timer(int copyID) { int abort = FALSE; timer_T *timer; typval_T tv; for (timer = first_timer; !abort && timer != NULL; timer = timer->tr_next) { if (timer->tr_callback.cb_partial != NULL) { tv.v_type = VAR_PARTIAL; tv.vval.v_partial = timer->tr_callback.cb_partial; } else { tv.v_type = VAR_FUNC; tv.vval.v_string = timer->tr_callback.cb_name; } abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); } return abort; } # if defined(EXITFREE) || defined(PROTO) void timer_free_all() { timer_T *timer; while (first_timer != NULL) { timer = first_timer; remove_timer(timer); free_timer(timer); } } # endif /* * "timer_info([timer])" function */ void f_timer_info(typval_T *argvars, typval_T *rettv) { timer_T *timer = NULL; if (rettv_list_alloc(rettv) != OK) return; if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) emsg(_(e_number_exp)); else { timer = find_timer((int)tv_get_number(&argvars[0])); if (timer != NULL) add_timer_info(rettv, timer); } } else add_timer_info_all(rettv); } /* * "timer_pause(timer, paused)" function */ void f_timer_pause(typval_T *argvars, typval_T *rettv UNUSED) { timer_T *timer = NULL; int paused = (int)tv_get_number(&argvars[1]); if (argvars[0].v_type != VAR_NUMBER) emsg(_(e_number_exp)); else { timer = find_timer((int)tv_get_number(&argvars[0])); if (timer != NULL) timer->tr_paused = paused; } } /* * "timer_start(time, callback [, options])" function */ void f_timer_start(typval_T *argvars, typval_T *rettv) { long msec = (long)tv_get_number(&argvars[0]); timer_T *timer; int repeat = 0; callback_T callback; dict_T *dict; rettv->vval.v_number = -1; if (check_secure()) return; if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT || (dict = argvars[2].vval.v_dict) == NULL) { semsg(_(e_invarg2), tv_get_string(&argvars[2])); return; } if (dict_find(dict, (char_u *)"repeat", -1) != NULL) repeat = dict_get_number(dict, (char_u *)"repeat"); } callback = get_callback(&argvars[1]); if (callback.cb_name == NULL) return; timer = create_timer(msec, repeat); if (timer == NULL) free_callback(&callback); else { set_callback(&timer->tr_callback, &callback); rettv->vval.v_number = (varnumber_T)timer->tr_id; } } /* * "timer_stop(timer)" function */ void f_timer_stop(typval_T *argvars, typval_T *rettv UNUSED) { timer_T *timer; if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); return; } timer = find_timer((int)tv_get_number(&argvars[0])); if (timer != NULL) stop_timer(timer); } /* * "timer_stopall()" function */ void f_timer_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { stop_all_timers(); } # endif // FEAT_TIMERS #endif // FEAT_EVAL /* * If 'autowrite' option set, try to write the file. * Careful: autocommands may make "buf" invalid! * * return FAIL for failure, OK otherwise */ int autowrite(buf_T *buf, int forceit) { int r; bufref_T bufref; if (!(p_aw || p_awa) || !p_write #ifdef FEAT_QUICKFIX /* never autowrite a "nofile" or "nowrite" buffer */ || bt_dontwrite(buf) #endif || (!forceit && buf->b_p_ro) || buf->b_ffname == NULL) return FAIL; set_bufref(&bufref, buf); r = buf_write_all(buf, forceit); /* Writing may succeed but the buffer still changed, e.g., when there is a * conversion error. We do want to return FAIL then. */ if (bufref_valid(&bufref) && bufIsChanged(buf)) r = FAIL; return r; } /* * Flush all buffers, except the ones that are readonly or are never written. */ void autowrite_all(void) { buf_T *buf; if (!(p_aw || p_awa) || !p_write) return; FOR_ALL_BUFFERS(buf) if (bufIsChanged(buf) && !buf->b_p_ro && !bt_dontwrite(buf)) { bufref_T bufref; set_bufref(&bufref, buf); (void)buf_write_all(buf, FALSE); /* an autocommand may have deleted the buffer */ if (!bufref_valid(&bufref)) buf = firstbuf; } } /* * Return TRUE if buffer was changed and cannot be abandoned. * For flags use the CCGD_ values. */ int check_changed(buf_T *buf, int flags) { int forceit = (flags & CCGD_FORCEIT); bufref_T bufref; set_bufref(&bufref, buf); if ( !forceit && bufIsChanged(buf) && ((flags & CCGD_MULTWIN) || buf->b_nwindows <= 1) && (!(flags & CCGD_AW) || autowrite(buf, forceit) == FAIL)) { #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) if ((p_confirm || cmdmod.confirm) && p_write) { buf_T *buf2; int count = 0; if (flags & CCGD_ALLBUF) FOR_ALL_BUFFERS(buf2) if (bufIsChanged(buf2) && (buf2->b_ffname != NULL # ifdef FEAT_BROWSE || cmdmod.browse # endif )) ++count; if (!bufref_valid(&bufref)) /* Autocommand deleted buffer, oops! It's not changed now. */ return FALSE; dialog_changed(buf, count > 1); if (!bufref_valid(&bufref)) /* Autocommand deleted buffer, oops! It's not changed now. */ return FALSE; return bufIsChanged(buf); } #endif if (flags & CCGD_EXCMD) no_write_message(); else no_write_message_nobang(curbuf); return TRUE; } return FALSE; } #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) || defined(PROTO) #if defined(FEAT_BROWSE) || defined(PROTO) /* * When wanting to write a file without a file name, ask the user for a name. */ void browse_save_fname(buf_T *buf) { if (buf->b_fname == NULL) { char_u *fname; fname = do_browse(BROWSE_SAVE, (char_u *)_("Save As"), NULL, NULL, NULL, NULL, buf); if (fname != NULL) { if (setfname(buf, fname, NULL, TRUE) == OK) buf->b_flags |= BF_NOTEDITED; vim_free(fname); } } } #endif /* * Ask the user what to do when abandoning a changed buffer. * Must check 'write' option first! */ void dialog_changed( buf_T *buf, int checkall) /* may abandon all changed buffers */ { char_u buff[DIALOG_MSG_SIZE]; int ret; buf_T *buf2; exarg_T ea; dialog_msg(buff, _("Save changes to \"%s\"?"), buf->b_fname); if (checkall) ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1); else ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1); // Init ea pseudo-structure, this is needed for the check_overwrite() // function. vim_memset(&ea, 0, sizeof(ea)); if (ret == VIM_YES) { #ifdef FEAT_BROWSE /* May get file name, when there is none */ browse_save_fname(buf); #endif if (buf->b_fname != NULL && check_overwrite(&ea, buf, buf->b_fname, buf->b_ffname, FALSE) == OK) /* didn't hit Cancel */ (void)buf_write_all(buf, FALSE); } else if (ret == VIM_NO) { unchanged(buf, TRUE, FALSE); } else if (ret == VIM_ALL) { /* * Write all modified files that can be written. * Skip readonly buffers, these need to be confirmed * individually. */ FOR_ALL_BUFFERS(buf2) { if (bufIsChanged(buf2) && (buf2->b_ffname != NULL #ifdef FEAT_BROWSE || cmdmod.browse #endif ) && !buf2->b_p_ro) { bufref_T bufref; set_bufref(&bufref, buf2); #ifdef FEAT_BROWSE /* May get file name, when there is none */ browse_save_fname(buf2); #endif if (buf2->b_fname != NULL && check_overwrite(&ea, buf2, buf2->b_fname, buf2->b_ffname, FALSE) == OK) /* didn't hit Cancel */ (void)buf_write_all(buf2, FALSE); /* an autocommand may have deleted the buffer */ if (!bufref_valid(&bufref)) buf2 = firstbuf; } } } else if (ret == VIM_DISCARDALL) { /* * mark all buffers as unchanged */ FOR_ALL_BUFFERS(buf2) unchanged(buf2, TRUE, FALSE); } } #endif /* * Return TRUE if the buffer "buf" can be abandoned, either by making it * hidden, autowriting it or unloading it. */ int can_abandon(buf_T *buf, int forceit) { return ( buf_hide(buf) || !bufIsChanged(buf) || buf->b_nwindows > 1 || autowrite(buf, forceit) == OK || forceit); } /* * Add a buffer number to "bufnrs", unless it's already there. */ static void add_bufnum(int *bufnrs, int *bufnump, int nr) { int i; for (i = 0; i < *bufnump; ++i) if (bufnrs[i] == nr) return; bufnrs[*bufnump] = nr; *bufnump = *bufnump + 1; } /* * Return TRUE if any buffer was changed and cannot be abandoned. * That changed buffer becomes the current buffer. * When "unload" is TRUE the current buffer is unloaded instead of making it * hidden. This is used for ":q!". */ int check_changed_any( int hidden, /* Only check hidden buffers */ int unload) { int ret = FALSE; buf_T *buf; int save; int i; int bufnum = 0; int bufcount = 0; int *bufnrs; tabpage_T *tp; win_T *wp; /* Make a list of all buffers, with the most important ones first. */ FOR_ALL_BUFFERS(buf) ++bufcount; if (bufcount == 0) return FALSE; bufnrs = ALLOC_MULT(int, bufcount); if (bufnrs == NULL) return FALSE; /* curbuf */ bufnrs[bufnum++] = curbuf->b_fnum; /* buffers in current tab */ FOR_ALL_WINDOWS(wp) if (wp->w_buffer != curbuf) add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); /* buffers in other tabs */ FOR_ALL_TABPAGES(tp) if (tp != curtab) for (wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next) add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); /* any other buffer */ FOR_ALL_BUFFERS(buf) add_bufnum(bufnrs, &bufnum, buf->b_fnum); for (i = 0; i < bufnum; ++i) { buf = buflist_findnr(bufnrs[i]); if (buf == NULL) continue; if ((!hidden || buf->b_nwindows == 0) && bufIsChanged(buf)) { bufref_T bufref; set_bufref(&bufref, buf); #ifdef FEAT_TERMINAL if (term_job_running(buf->b_term)) { if (term_try_stop_job(buf) == FAIL) break; } else #endif /* Try auto-writing the buffer. If this fails but the buffer no * longer exists it's not changed, that's OK. */ if (check_changed(buf, (p_awa ? CCGD_AW : 0) | CCGD_MULTWIN | CCGD_ALLBUF) && bufref_valid(&bufref)) break; /* didn't save - still changes */ } } if (i >= bufnum) goto theend; /* Get here if "buf" cannot be abandoned. */ ret = TRUE; exiting = FALSE; #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) /* * When ":confirm" used, don't give an error message. */ if (!(p_confirm || cmdmod.confirm)) #endif { /* There must be a wait_return for this message, do_buffer() * may cause a redraw. But wait_return() is a no-op when vgetc() * is busy (Quit used from window menu), then make sure we don't * cause a scroll up. */ if (vgetc_busy > 0) { msg_row = cmdline_row; msg_col = 0; msg_didout = FALSE; } if ( #ifdef FEAT_TERMINAL term_job_running(buf->b_term) ? semsg(_("E947: Job still running in buffer \"%s\""), buf->b_fname) : #endif semsg(_("E162: No write since last change for buffer \"%s\""), buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname)) { save = no_wait_return; no_wait_return = FALSE; wait_return(FALSE); no_wait_return = save; } } /* Try to find a window that contains the buffer. */ if (buf != curbuf) FOR_ALL_TAB_WINDOWS(tp, wp) if (wp->w_buffer == buf) { bufref_T bufref; set_bufref(&bufref, buf); goto_tabpage_win(tp, wp); // Paranoia: did autocmd wipe out the buffer with changes? if (!bufref_valid(&bufref)) goto theend; goto buf_found; } buf_found: /* Open the changed buffer in the current window. */ if (buf != curbuf) set_curbuf(buf, unload ? DOBUF_UNLOAD : DOBUF_GOTO); theend: vim_free(bufnrs); return ret; } /* * return FAIL if there is no file name, OK if there is one * give error message for FAIL */ int check_fname(void) { if (curbuf->b_ffname == NULL) { emsg(_(e_noname)); return FAIL; } return OK; } /* * flush the contents of a buffer, unless it has no file name * * return FAIL for failure, OK otherwise */ int buf_write_all(buf_T *buf, int forceit) { int retval; buf_T *old_curbuf = curbuf; retval = (buf_write(buf, buf->b_ffname, buf->b_fname, (linenr_T)1, buf->b_ml.ml_line_count, NULL, FALSE, forceit, TRUE, FALSE)); if (curbuf != old_curbuf) { msg_source(HL_ATTR(HLF_W)); msg(_("Warning: Entered other buffer unexpectedly (check autocommands)")); } return retval; } /* * ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo" */ void ex_listdo(exarg_T *eap) { int i; win_T *wp; tabpage_T *tp; buf_T *buf = curbuf; int next_fnum = 0; #if defined(FEAT_SYN_HL) char_u *save_ei = NULL; #endif char_u *p_shm_save; #ifdef FEAT_QUICKFIX int qf_size = 0; int qf_idx; #endif #ifndef FEAT_QUICKFIX if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) { ex_ni(eap); return; } #endif #if defined(FEAT_SYN_HL) if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo) { /* Don't do syntax HL autocommands. Skipping the syntax file is a * great speed improvement. */ save_ei = au_event_disable(",Syntax"); for (buf = firstbuf; buf != NULL; buf = buf->b_next) buf->b_flags &= ~BF_SYN_SET; buf = curbuf; } #endif #ifdef FEAT_CLIPBOARD start_global_changes(); #endif if (eap->cmdidx == CMD_windo || eap->cmdidx == CMD_tabdo || buf_hide(curbuf) || !check_changed(curbuf, CCGD_AW | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) { i = 0; /* start at the eap->line1 argument/window/buffer */ wp = firstwin; tp = first_tabpage; switch (eap->cmdidx) { case CMD_windo: for ( ; wp != NULL && i + 1 < eap->line1; wp = wp->w_next) i++; break; case CMD_tabdo: for( ; tp != NULL && i + 1 < eap->line1; tp = tp->tp_next) i++; break; case CMD_argdo: i = eap->line1 - 1; break; default: break; } /* set pcmark now */ if (eap->cmdidx == CMD_bufdo) { /* Advance to the first listed buffer after "eap->line1". */ for (buf = firstbuf; buf != NULL && (buf->b_fnum < eap->line1 || !buf->b_p_bl); buf = buf->b_next) if (buf->b_fnum > eap->line2) { buf = NULL; break; } if (buf != NULL) goto_buffer(eap, DOBUF_FIRST, FORWARD, buf->b_fnum); } #ifdef FEAT_QUICKFIX else if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) { qf_size = qf_get_valid_size(eap); if (qf_size <= 0 || eap->line1 > qf_size) buf = NULL; else { ex_cc(eap); buf = curbuf; i = eap->line1 - 1; if (eap->addr_count <= 0) /* default is all the quickfix/location list entries */ eap->line2 = qf_size; } } #endif else setpcmark(); listcmd_busy = TRUE; /* avoids setting pcmark below */ while (!got_int && buf != NULL) { if (eap->cmdidx == CMD_argdo) { /* go to argument "i" */ if (i == ARGCOUNT) break; /* Don't call do_argfile() when already there, it will try * reloading the file. */ if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { /* Clear 'shm' to avoid that the file message overwrites * any output from the command. */ p_shm_save = vim_strsave(p_shm); set_option_value((char_u *)"shm", 0L, (char_u *)"", 0); do_argfile(eap, i); set_option_value((char_u *)"shm", 0L, p_shm_save, 0); vim_free(p_shm_save); } if (curwin->w_arg_idx != i) break; } else if (eap->cmdidx == CMD_windo) { /* go to window "wp" */ if (!win_valid(wp)) break; win_goto(wp); if (curwin != wp) break; /* something must be wrong */ wp = curwin->w_next; } else if (eap->cmdidx == CMD_tabdo) { /* go to window "tp" */ if (!valid_tabpage(tp)) break; goto_tabpage_tp(tp, TRUE, TRUE); tp = tp->tp_next; } else if (eap->cmdidx == CMD_bufdo) { /* Remember the number of the next listed buffer, in case * ":bwipe" is used or autocommands do something strange. */ next_fnum = -1; for (buf = curbuf->b_next; buf != NULL; buf = buf->b_next) if (buf->b_p_bl) { next_fnum = buf->b_fnum; break; } } ++i; /* execute the command */ do_cmdline(eap->arg, eap->getline, eap->cookie, DOCMD_VERBOSE + DOCMD_NOWAIT); if (eap->cmdidx == CMD_bufdo) { /* Done? */ if (next_fnum < 0 || next_fnum > eap->line2) break; /* Check if the buffer still exists. */ FOR_ALL_BUFFERS(buf) if (buf->b_fnum == next_fnum) break; if (buf == NULL) break; /* Go to the next buffer. Clear 'shm' to avoid that the file * message overwrites any output from the command. */ p_shm_save = vim_strsave(p_shm); set_option_value((char_u *)"shm", 0L, (char_u *)"", 0); goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); set_option_value((char_u *)"shm", 0L, p_shm_save, 0); vim_free(p_shm_save); /* If autocommands took us elsewhere, quit here. */ if (curbuf->b_fnum != next_fnum) break; } #ifdef FEAT_QUICKFIX if (eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) { if (i >= qf_size || i >= eap->line2) break; qf_idx = qf_get_cur_idx(eap); ex_cnext(eap); /* If jumping to the next quickfix entry fails, quit here */ if (qf_get_cur_idx(eap) == qf_idx) break; } #endif if (eap->cmdidx == CMD_windo) { validate_cursor(); /* cursor may have moved */ /* required when 'scrollbind' has been set */ if (curwin->w_p_scb) do_check_scrollbind(TRUE); } if (eap->cmdidx == CMD_windo || eap->cmdidx == CMD_tabdo) if (i+1 > eap->line2) break; if (eap->cmdidx == CMD_argdo && i >= eap->line2) break; } listcmd_busy = FALSE; } #if defined(FEAT_SYN_HL) if (save_ei != NULL) { buf_T *bnext; aco_save_T aco; au_event_restore(save_ei); for (buf = firstbuf; buf != NULL; buf = bnext) { bnext = buf->b_next; if (buf->b_nwindows > 0 && (buf->b_flags & BF_SYN_SET)) { buf->b_flags &= ~BF_SYN_SET; // buffer was opened while Syntax autocommands were disabled, // need to trigger them now. if (buf == curbuf) apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, TRUE, curbuf); else { aucmd_prepbuf(&aco, buf); apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, TRUE, buf); aucmd_restbuf(&aco); } // start over, in case autocommands messed things up. bnext = firstbuf; } } } #endif #ifdef FEAT_CLIPBOARD end_global_changes(); #endif } #ifdef FEAT_EVAL /* * ":compiler[!] {name}" */ void ex_compiler(exarg_T *eap) { char_u *buf; char_u *old_cur_comp = NULL; char_u *p; if (*eap->arg == NUL) { /* List all compiler scripts. */ do_cmdline_cmd((char_u *)"echo globpath(&rtp, 'compiler/*.vim')"); /* ) keep the indenter happy... */ } else { buf = alloc(STRLEN(eap->arg) + 14); if (buf != NULL) { if (eap->forceit) { /* ":compiler! {name}" sets global options */ do_cmdline_cmd((char_u *) "command -nargs=* CompilerSet set "); } else { /* ":compiler! {name}" sets local options. * To remain backwards compatible "current_compiler" is always * used. A user's compiler plugin may set it, the distributed * plugin will then skip the settings. Afterwards set * "b:current_compiler" and restore "current_compiler". * Explicitly prepend "g:" to make it work in a function. */ old_cur_comp = get_var_value((char_u *)"g:current_compiler"); if (old_cur_comp != NULL) old_cur_comp = vim_strsave(old_cur_comp); do_cmdline_cmd((char_u *) "command -nargs=* CompilerSet setlocal "); } do_unlet((char_u *)"g:current_compiler", TRUE); do_unlet((char_u *)"b:current_compiler", TRUE); sprintf((char *)buf, "compiler/%s.vim", eap->arg); if (source_runtime(buf, DIP_ALL) == FAIL) semsg(_("E666: compiler not supported: %s"), eap->arg); vim_free(buf); do_cmdline_cmd((char_u *)":delcommand CompilerSet"); /* Set "b:current_compiler" from "current_compiler". */ p = get_var_value((char_u *)"g:current_compiler"); if (p != NULL) set_internal_string_var((char_u *)"b:current_compiler", p); /* Restore "current_compiler" for ":compiler {name}". */ if (!eap->forceit) { if (old_cur_comp != NULL) { set_internal_string_var((char_u *)"g:current_compiler", old_cur_comp); vim_free(old_cur_comp); } else do_unlet((char_u *)"g:current_compiler", TRUE); } } } } #endif #if defined(FEAT_PYTHON3) || defined(FEAT_PYTHON) || defined(PROTO) # if (defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)) || defined(PROTO) /* * Detect Python 3 or 2, and initialize 'pyxversion'. */ void init_pyxversion(void) { if (p_pyx == 0) { if (python3_enabled(FALSE)) p_pyx = 3; else if (python_enabled(FALSE)) p_pyx = 2; } } # endif /* * Does a file contain one of the following strings at the beginning of any * line? * "#!(any string)python2" => returns 2 * "#!(any string)python3" => returns 3 * "# requires python 2.x" => returns 2 * "# requires python 3.x" => returns 3 * otherwise return 0. */ static int requires_py_version(char_u *filename) { FILE *file; int requires_py_version = 0; int i, lines; lines = (int)p_mls; if (lines < 0) lines = 5; file = mch_fopen((char *)filename, "r"); if (file != NULL) { for (i = 0; i < lines; i++) { if (vim_fgets(IObuff, IOSIZE, file)) break; if (i == 0 && IObuff[0] == '#' && IObuff[1] == '!') { /* Check shebang. */ if (strstr((char *)IObuff + 2, "python2") != NULL) { requires_py_version = 2; break; } if (strstr((char *)IObuff + 2, "python3") != NULL) { requires_py_version = 3; break; } } IObuff[21] = '\0'; if (STRCMP("# requires python 2.x", IObuff) == 0) { requires_py_version = 2; break; } if (STRCMP("# requires python 3.x", IObuff) == 0) { requires_py_version = 3; break; } } fclose(file); } return requires_py_version; } /* * Source a python file using the requested python version. */ static void source_pyx_file(exarg_T *eap, char_u *fname) { exarg_T ex; int v = requires_py_version(fname); # if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3) init_pyxversion(); # endif if (v == 0) { # if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3) /* user didn't choose a preference, 'pyx' is used */ v = p_pyx; # elif defined(FEAT_PYTHON) v = 2; # elif defined(FEAT_PYTHON3) v = 3; # endif } /* * now source, if required python version is not supported show * unobtrusive message. */ if (eap == NULL) vim_memset(&ex, 0, sizeof(ex)); else ex = *eap; ex.arg = fname; ex.cmd = (char_u *)(v == 2 ? "pyfile" : "pyfile3"); if (v == 2) { # ifdef FEAT_PYTHON ex_pyfile(&ex); # else vim_snprintf((char *)IObuff, IOSIZE, _("W20: Required python version 2.x not supported, ignoring file: %s"), fname); msg((char *)IObuff); # endif return; } else { # ifdef FEAT_PYTHON3 ex_py3file(&ex); # else vim_snprintf((char *)IObuff, IOSIZE, _("W21: Required python version 3.x not supported, ignoring file: %s"), fname); msg((char *)IObuff); # endif return; } } /* * ":pyxfile {fname}" */ void ex_pyxfile(exarg_T *eap) { source_pyx_file(eap, eap->arg); } /* * ":pyx" */ void ex_pyx(exarg_T *eap) { # if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3) init_pyxversion(); if (p_pyx == 2) ex_python(eap); else ex_py3(eap); # elif defined(FEAT_PYTHON) ex_python(eap); # elif defined(FEAT_PYTHON3) ex_py3(eap); # endif } /* * ":pyxdo" */ void ex_pyxdo(exarg_T *eap) { # if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3) init_pyxversion(); if (p_pyx == 2) ex_pydo(eap); else ex_py3do(eap); # elif defined(FEAT_PYTHON) ex_pydo(eap); # elif defined(FEAT_PYTHON3) ex_py3do(eap); # endif } #endif /* * ":checktime [buffer]" */ void ex_checktime(exarg_T *eap) { buf_T *buf; int save_no_check_timestamps = no_check_timestamps; no_check_timestamps = 0; if (eap->addr_count == 0) /* default is all buffers */ check_timestamps(FALSE); else { buf = buflist_findnr((int)eap->line2); if (buf != NULL) /* cannot happen? */ (void)buf_check_timestamp(buf, FALSE); } no_check_timestamps = save_no_check_timestamps; } #if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ && (defined(FEAT_EVAL) || defined(FEAT_MULTI_LANG)) # define HAVE_GET_LOCALE_VAL static char_u * get_locale_val(int what) { char_u *loc; /* Obtain the locale value from the libraries. */ loc = (char_u *)setlocale(what, NULL); # ifdef MSWIN if (loc != NULL) { char_u *p; /* setocale() returns something like "LC_COLLATE=;LC_..." when * one of the values (e.g., LC_CTYPE) differs. */ p = vim_strchr(loc, '='); if (p != NULL) { loc = ++p; while (*p != NUL) /* remove trailing newline */ { if (*p < ' ' || *p == ';') { *p = NUL; break; } ++p; } } } # endif return loc; } #endif #ifdef MSWIN /* * On MS-Windows locale names are strings like "German_Germany.1252", but * gettext expects "de". Try to translate one into another here for a few * supported languages. */ static char_u * gettext_lang(char_u *name) { int i; static char *(mtable[]) = { "afrikaans", "af", "czech", "cs", "dutch", "nl", "german", "de", "english_united kingdom", "en_GB", "spanish", "es", "french", "fr", "italian", "it", "japanese", "ja", "korean", "ko", "norwegian", "no", "polish", "pl", "russian", "ru", "slovak", "sk", "swedish", "sv", "ukrainian", "uk", "chinese_china", "zh_CN", "chinese_taiwan", "zh_TW", NULL}; for (i = 0; mtable[i] != NULL; i += 2) if (STRNICMP(mtable[i], name, STRLEN(mtable[i])) == 0) return (char_u *)mtable[i + 1]; return name; } #endif #if defined(FEAT_MULTI_LANG) || defined(PROTO) /* * Return TRUE when "lang" starts with a valid language name. * Rejects NULL, empty string, "C", "C.UTF-8" and others. */ static int is_valid_mess_lang(char_u *lang) { return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); } /* * Obtain the current messages language. Used to set the default for * 'helplang'. May return NULL or an empty string. */ char_u * get_mess_lang(void) { char_u *p; # ifdef HAVE_GET_LOCALE_VAL # if defined(LC_MESSAGES) p = get_locale_val(LC_MESSAGES); # else /* This is necessary for Win32, where LC_MESSAGES is not defined and $LANG * may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME * and LC_MONETARY may be set differently for a Japanese working in the * US. */ p = get_locale_val(LC_COLLATE); # endif # else p = mch_getenv((char_u *)"LC_ALL"); if (!is_valid_mess_lang(p)) { p = mch_getenv((char_u *)"LC_MESSAGES"); if (!is_valid_mess_lang(p)) p = mch_getenv((char_u *)"LANG"); } # endif # ifdef MSWIN p = gettext_lang(p); # endif return is_valid_mess_lang(p) ? p : NULL; } #endif /* Complicated #if; matches with where get_mess_env() is used below. */ #if (defined(FEAT_EVAL) && !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ && defined(LC_MESSAGES))) \ || ((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ && !defined(LC_MESSAGES)) /* * Get the language used for messages from the environment. */ static char_u * get_mess_env(void) { char_u *p; p = mch_getenv((char_u *)"LC_ALL"); if (p == NULL || *p == NUL) { p = mch_getenv((char_u *)"LC_MESSAGES"); if (p == NULL || *p == NUL) { p = mch_getenv((char_u *)"LANG"); if (p != NULL && VIM_ISDIGIT(*p)) p = NULL; /* ignore something like "1043" */ # ifdef HAVE_GET_LOCALE_VAL if (p == NULL || *p == NUL) p = get_locale_val(LC_CTYPE); # endif } } return p; } #endif #if defined(FEAT_EVAL) || defined(PROTO) /* * Set the "v:lang" variable according to the current locale setting. * Also do "v:lc_time"and "v:ctype". */ void set_lang_var(void) { char_u *loc; # ifdef HAVE_GET_LOCALE_VAL loc = get_locale_val(LC_CTYPE); # else /* setlocale() not supported: use the default value */ loc = (char_u *)"C"; # endif set_vim_var_string(VV_CTYPE, loc, -1); /* When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall * back to LC_CTYPE if it's empty. */ # if defined(HAVE_GET_LOCALE_VAL) && defined(LC_MESSAGES) loc = get_locale_val(LC_MESSAGES); # else loc = get_mess_env(); # endif set_vim_var_string(VV_LANG, loc, -1); # ifdef HAVE_GET_LOCALE_VAL loc = get_locale_val(LC_TIME); # endif set_vim_var_string(VV_LC_TIME, loc, -1); } #endif #if defined(HAVE_LOCALE_H) || defined(X_LOCALE) \ /* * ":language": Set the language (locale). */ void ex_language(exarg_T *eap) { char *loc; char_u *p; char_u *name; int what = LC_ALL; char *whatstr = ""; #ifdef LC_MESSAGES # define VIM_LC_MESSAGES LC_MESSAGES #else # define VIM_LC_MESSAGES 6789 #endif name = eap->arg; /* Check for "messages {name}", "ctype {name}" or "time {name}" argument. * Allow abbreviation, but require at least 3 characters to avoid * confusion with a two letter language name "me" or "ct". */ p = skiptowhite(eap->arg); if ((*p == NUL || VIM_ISWHITE(*p)) && p - eap->arg >= 3) { if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { what = VIM_LC_MESSAGES; name = skipwhite(p); whatstr = "messages "; } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { what = LC_CTYPE; name = skipwhite(p); whatstr = "ctype "; } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { what = LC_TIME; name = skipwhite(p); whatstr = "time "; } } if (*name == NUL) { #ifndef LC_MESSAGES if (what == VIM_LC_MESSAGES) p = get_mess_env(); else #endif p = (char_u *)setlocale(what, NULL); if (p == NULL || *p == NUL) p = (char_u *)"Unknown"; smsg(_("Current %slanguage: \"%s\""), whatstr, p); } else { #ifndef LC_MESSAGES if (what == VIM_LC_MESSAGES) loc = ""; else #endif { loc = setlocale(what, (char *)name); #if defined(FEAT_FLOAT) && defined(LC_NUMERIC) /* Make sure strtod() uses a decimal point, not a comma. */ setlocale(LC_NUMERIC, "C"); #endif } if (loc == NULL) semsg(_("E197: Cannot set language to \"%s\""), name); else { #ifdef HAVE_NL_MSG_CAT_CNTR /* Need to do this for GNU gettext, otherwise cached translations * will be used again. */ extern int _nl_msg_cat_cntr; ++_nl_msg_cat_cntr; #endif /* Reset $LC_ALL, otherwise it would overrule everything. */ vim_setenv((char_u *)"LC_ALL", (char_u *)""); if (what != LC_TIME) { /* Tell gettext() what to translate to. It apparently doesn't * use the currently effective locale. Also do this when * FEAT_GETTEXT isn't defined, so that shell commands use this * value. */ if (what == LC_ALL) { vim_setenv((char_u *)"LANG", name); /* Clear $LANGUAGE because GNU gettext uses it. */ vim_setenv((char_u *)"LANGUAGE", (char_u *)""); # ifdef MSWIN /* Apparently MS-Windows printf() may cause a crash when * we give it 8-bit text while it's expecting text in the * current locale. This call avoids that. */ setlocale(LC_CTYPE, "C"); # endif } if (what != LC_CTYPE) { char_u *mname; #ifdef MSWIN mname = gettext_lang(name); #else mname = name; #endif vim_setenv((char_u *)"LC_MESSAGES", mname); #ifdef FEAT_MULTI_LANG set_helplang_default(mname); #endif } } # ifdef FEAT_EVAL /* Set v:lang, v:lc_time and v:ctype to the final result. */ set_lang_var(); # endif # ifdef FEAT_TITLE maketitle(); # endif } } } static char_u **locales = NULL; /* Array of all available locales */ # ifndef MSWIN static int did_init_locales = FALSE; /* * Return an array of strings for all available locales + NULL for the * last element. Return NULL in case of error. */ static char_u ** find_locales(void) { garray_T locales_ga; char_u *loc; /* Find all available locales by running command "locale -a". If this * doesn't work we won't have completion. */ char_u *locale_a = get_cmd_output((char_u *)"locale -a", NULL, SHELL_SILENT, NULL); if (locale_a == NULL) return NULL; ga_init2(&locales_ga, sizeof(char_u *), 20); /* Transform locale_a string where each locale is separated by "\n" * into an array of locale strings. */ loc = (char_u *)strtok((char *)locale_a, "\n"); while (loc != NULL) { if (ga_grow(&locales_ga, 1) == FAIL) break; loc = vim_strsave(loc); if (loc == NULL) break; ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = loc; loc = (char_u *)strtok(NULL, "\n"); } vim_free(locale_a); if (ga_grow(&locales_ga, 1) == FAIL) { ga_clear(&locales_ga); return NULL; } ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; return (char_u **)locales_ga.ga_data; } # endif /* * Lazy initialization of all available locales. */ static void init_locales(void) { # ifndef MSWIN if (!did_init_locales) { did_init_locales = TRUE; locales = find_locales(); } # endif } # if defined(EXITFREE) || defined(PROTO) void free_locales(void) { int i; if (locales != NULL) { for (i = 0; locales[i] != NULL; i++) vim_free(locales[i]); VIM_CLEAR(locales); } } # endif /* * Function given to ExpandGeneric() to obtain the possible arguments of the * ":language" command. */ char_u * get_lang_arg(expand_T *xp UNUSED, int idx) { if (idx == 0) return (char_u *)"messages"; if (idx == 1) return (char_u *)"ctype"; if (idx == 2) return (char_u *)"time"; init_locales(); if (locales == NULL) return NULL; return locales[idx - 3]; } /* * Function given to ExpandGeneric() to obtain the available locales. */ char_u * get_locales(expand_T *xp UNUSED, int idx) { init_locales(); if (locales == NULL) return NULL; return locales[idx]; } #endif