To: vim_dev@googlegroups.com Subject: Patch 8.1.2047 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.2047 Problem: Cannot check the current state. Solution: Add the state() function. Files: runtime/doc/eval.txt, src/misc1.c, src/proto/misc1.pro, src/evalfunc.c, src/proto/evalfunc.pro, src/main.c, src/proto/main.pro, src/channel.c, src/proto/channel.pro, src/userfunc.c, src/proto/userfunc.pro *** ../vim-8.1.2046/runtime/doc/eval.txt 2019-09-15 14:32:49.552731470 +0200 --- runtime/doc/eval.txt 2019-09-16 22:53:17.384608555 +0200 *************** *** 2750,2755 **** --- 2755,2761 ---- split({expr} [, {pat} [, {keepempty}]]) List make |List| from {pat} separated {expr} sqrt({expr}) Float square root of {expr} + state([{what}]) String current state of Vim str2float({expr}) Float convert String to Float str2list({expr} [, {utf8}]) List convert each character of {expr} to ASCII/UTF8 value *************** *** 7056,7061 **** --- 7067,7073 ---- If [expr] is supplied and it evaluates to a non-zero Number or a non-empty String (|non-zero-arg|), then the full mode is returned, otherwise only the first letter is returned. + Also see |state()|. n Normal, Terminal-Normal no Operator-pending *************** *** 9031,9036 **** --- 9043,9076 ---- {only available when compiled with the |+float| feature} + state([{what}]) *state()* + Return a string which contains characters indicating the + current state. Mostly useful in callbacks that want to do + work that may not always be safe. Roughly this works like: + - callback uses state() to check if work is safe to do. + If yes, then do it right away. + Otherwise add to work queue and add SafeState and/or + SafeStateAgain autocommand. + - When SafeState or SafeStateAgain is triggered, check with + state() if the work can be done now, and if yes remove it + from the queue and execute. + Also see |mode()|. + + When {what} is given only characters in this string will be + added. E.g, this checks if the screen has scrolled: > + if state('s') != '' + < + These characters indicate the state: + m halfway a mapping, :normal command, feedkeys() or + stuffed command + o operator pending or waiting for a command argument + a Insert mode autocomplete active + x executing an autocommand + w blocked on waiting, e.g. ch_evalexpr() and + ch_read(), ch_readraw() when reading json. + c callback invoked (repeats for recursiveness up to "ccc") + s screen has scrolled for messages + str2float({expr}) *str2float()* Convert String {expr} to a Float. This mostly works the same as when using a floating point number in an expression, see *** ../vim-8.1.2046/src/misc1.c 2019-09-10 21:27:15.175646978 +0200 --- src/misc1.c 2019-09-16 22:44:09.886708358 +0200 *************** *** 1213,1218 **** --- 1213,1356 ---- } #endif + #if defined(FEAT_EVAL) || defined(PROTO) + + /* + * "mode()" function + */ + void + f_mode(typval_T *argvars, typval_T *rettv) + { + char_u buf[4]; + + vim_memset(buf, 0, sizeof(buf)); + + if (time_for_testing == 93784) + { + /* Testing the two-character code. */ + buf[0] = 'x'; + buf[1] = '!'; + } + #ifdef FEAT_TERMINAL + else if (term_use_loop()) + buf[0] = 't'; + #endif + else if (VIsual_active) + { + if (VIsual_select) + buf[0] = VIsual_mode + 's' - 'v'; + else + buf[0] = VIsual_mode; + } + else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE + || State == CONFIRM) + { + buf[0] = 'r'; + if (State == ASKMORE) + buf[1] = 'm'; + else if (State == CONFIRM) + buf[1] = '?'; + } + else if (State == EXTERNCMD) + buf[0] = '!'; + else if (State & INSERT) + { + if (State & VREPLACE_FLAG) + { + buf[0] = 'R'; + buf[1] = 'v'; + } + else + { + if (State & REPLACE_FLAG) + buf[0] = 'R'; + else + buf[0] = 'i'; + if (ins_compl_active()) + buf[1] = 'c'; + else if (ctrl_x_mode_not_defined_yet()) + buf[1] = 'x'; + } + } + else if ((State & CMDLINE) || exmode_active) + { + buf[0] = 'c'; + if (exmode_active == EXMODE_VIM) + buf[1] = 'v'; + else if (exmode_active == EXMODE_NORMAL) + buf[1] = 'e'; + } + else + { + buf[0] = 'n'; + if (finish_op) + { + buf[1] = 'o'; + // to be able to detect force-linewise/blockwise/characterwise operations + buf[2] = motion_force; + } + else if (restart_edit == 'I' || restart_edit == 'R' + || restart_edit == 'V') + { + buf[1] = 'i'; + buf[2] = restart_edit; + } + } + + /* Clear out the minor mode when the argument is not a non-zero number or + * non-empty string. */ + if (!non_zero_arg(&argvars[0])) + buf[1] = NUL; + + rettv->vval.v_string = vim_strsave(buf); + rettv->v_type = VAR_STRING; + } + + static void + may_add_state_char(garray_T *gap, char_u *include, int c) + { + if (include == NULL || vim_strchr(include, c) != NULL) + ga_append(gap, c); + } + + /* + * "state()" function + */ + void + f_state(typval_T *argvars, typval_T *rettv) + { + garray_T ga; + char_u *include = NULL; + int i; + + ga_init2(&ga, 1, 20); + if (argvars[0].v_type != VAR_UNKNOWN) + include = tv_get_string(&argvars[0]); + + if (!(stuff_empty() && typebuf.tb_len == 0 && scriptin[curscript] == NULL)) + may_add_state_char(&ga, include, 'm'); + if (op_pending()) + may_add_state_char(&ga, include, 'o'); + if (autocmd_busy) + may_add_state_char(&ga, include, 'x'); + if (!ctrl_x_mode_none()) + may_add_state_char(&ga, include, 'a'); + + # ifdef FEAT_JOB_CHANNEL + if (channel_in_blocking_wait()) + may_add_state_char(&ga, include, 'w'); + # endif + for (i = 0; i < get_callback_depth() && i < 3; ++i) + may_add_state_char(&ga, include, 'c'); + if (msg_scrolled > 0) + may_add_state_char(&ga, include, 's'); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ga.ga_data; + } + + #endif // FEAT_EVAL + /* * Get a key stroke directly from the user. * Ignores mouse clicks and scrollbar events, except a click for the left *** ../vim-8.1.2046/src/proto/misc1.pro 2019-09-10 21:27:15.175646978 +0200 --- src/proto/misc1.pro 2019-09-16 22:29:00.170493457 +0200 *************** *** 24,29 **** --- 24,31 ---- void check_status(buf_T *buf); int ask_yesno(char_u *str, int direct); int is_mouse_key(int c); + void f_mode(typval_T *argvars, typval_T *rettv); + void f_state(typval_T *argvars, typval_T *rettv); int get_keystroke(void); int get_number(int colon, int *mouse_used); int prompt_for_number(int *mouse_used); *** ../vim-8.1.2046/src/evalfunc.c 2019-09-15 21:00:51.362604284 +0200 --- src/evalfunc.c 2019-09-16 22:29:49.386320301 +0200 *************** *** 146,152 **** static void f_matchstrpos(typval_T *argvars, typval_T *rettv); static void f_max(typval_T *argvars, typval_T *rettv); static void f_min(typval_T *argvars, typval_T *rettv); - static void f_mode(typval_T *argvars, typval_T *rettv); #ifdef FEAT_MZSCHEME static void f_mzeval(typval_T *argvars, typval_T *rettv); #endif --- 146,151 ---- *************** *** 723,728 **** --- 722,730 ---- {"split", 1, 3, FEARG_1, f_split}, #ifdef FEAT_FLOAT {"sqrt", 1, 1, FEARG_1, f_sqrt}, + #endif + {"state", 0, 1, FEARG_1, f_state}, + #ifdef FEAT_FLOAT {"str2float", 1, 1, FEARG_1, f_str2float}, #endif {"str2list", 1, 2, FEARG_1, f_str2list}, *************** *** 1046,1052 **** /* * Return TRUE for a non-zero Number and a non-empty String. */ ! static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER --- 1048,1054 ---- /* * Return TRUE for a non-zero Number and a non-empty String. */ ! int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER *************** *** 4911,5007 **** max_min(argvars, rettv, FALSE); } - /* - * "mode()" function - */ - static void - f_mode(typval_T *argvars, typval_T *rettv) - { - char_u buf[4]; - - vim_memset(buf, 0, sizeof(buf)); - - if (time_for_testing == 93784) - { - /* Testing the two-character code. */ - buf[0] = 'x'; - buf[1] = '!'; - } - #ifdef FEAT_TERMINAL - else if (term_use_loop()) - buf[0] = 't'; - #endif - else if (VIsual_active) - { - if (VIsual_select) - buf[0] = VIsual_mode + 's' - 'v'; - else - buf[0] = VIsual_mode; - } - else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE - || State == CONFIRM) - { - buf[0] = 'r'; - if (State == ASKMORE) - buf[1] = 'm'; - else if (State == CONFIRM) - buf[1] = '?'; - } - else if (State == EXTERNCMD) - buf[0] = '!'; - else if (State & INSERT) - { - if (State & VREPLACE_FLAG) - { - buf[0] = 'R'; - buf[1] = 'v'; - } - else - { - if (State & REPLACE_FLAG) - buf[0] = 'R'; - else - buf[0] = 'i'; - if (ins_compl_active()) - buf[1] = 'c'; - else if (ctrl_x_mode_not_defined_yet()) - buf[1] = 'x'; - } - } - else if ((State & CMDLINE) || exmode_active) - { - buf[0] = 'c'; - if (exmode_active == EXMODE_VIM) - buf[1] = 'v'; - else if (exmode_active == EXMODE_NORMAL) - buf[1] = 'e'; - } - else - { - buf[0] = 'n'; - if (finish_op) - { - buf[1] = 'o'; - // to be able to detect force-linewise/blockwise/characterwise operations - buf[2] = motion_force; - } - else if (restart_edit == 'I' || restart_edit == 'R' - || restart_edit == 'V') - { - buf[1] = 'i'; - buf[2] = restart_edit; - } - } - - /* Clear out the minor mode when the argument is not a non-zero number or - * non-empty string. */ - if (!non_zero_arg(&argvars[0])) - buf[1] = NUL; - - rettv->vval.v_string = vim_strsave(buf); - rettv->v_type = VAR_STRING; - } - #if defined(FEAT_MZSCHEME) || defined(PROTO) /* * "mzeval()" function --- 4913,4918 ---- *** ../vim-8.1.2046/src/proto/evalfunc.pro 2019-09-07 15:45:09.977228927 +0200 --- src/proto/evalfunc.pro 2019-09-16 22:30:12.546238795 +0200 *************** *** 4,12 **** int has_internal_func(char_u *name); int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv); int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv); linenr_T tv_get_lnum(typval_T *argvars); linenr_T tv_get_lnum_buf(typval_T *argvars, buf_T *buf); - buf_T *buflist_find_by_name(char_u *name, int curtab_only); buf_T *tv_get_buf(typval_T *tv, int curtab_only); buf_T *get_buf_arg(typval_T *arg); win_T *get_optional_window(typval_T *argvars, int idx); --- 4,12 ---- int has_internal_func(char_u *name); int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv); int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv); + int non_zero_arg(typval_T *argvars); linenr_T tv_get_lnum(typval_T *argvars); linenr_T tv_get_lnum_buf(typval_T *argvars, buf_T *buf); buf_T *tv_get_buf(typval_T *tv, int curtab_only); buf_T *get_buf_arg(typval_T *arg); win_T *get_optional_window(typval_T *argvars, int idx); *** ../vim-8.1.2046/src/main.c 2019-09-16 21:58:08.792800986 +0200 --- src/main.c 2019-09-16 22:28:25.686614721 +0200 *************** *** 1031,1050 **** // When TRUE in a safe state when starting to wait for a character. static int was_safe = FALSE; /* ! * Trigger SafeState if currently in a safe state for main_loop(). */ ! static void ! may_trigger_safestate_main(oparg_T *oap) { ! may_trigger_safestate( ! !finish_op ! && oap->prev_opcount > 0 ! && oap->prev_count0 == 0 ! && oap->op_type == OP_NOP ! && oap->regname == NUL ! && restart_edit == 0); } /* --- 1031,1051 ---- // When TRUE in a safe state when starting to wait for a character. static int was_safe = FALSE; + static oparg_T *current_oap = NULL; /* ! * Return TRUE if an operator was started but not finished yet. ! * Includes typing a count or a register name. */ ! int ! op_pending(void) { ! return !(current_oap != NULL ! && !finish_op ! && current_oap->prev_opcount == 0 ! && current_oap->prev_count0 == 0 ! && current_oap->op_type == OP_NOP ! && current_oap->regname == NUL); } /* *************** *** 1100,1114 **** int cmdwin, /* TRUE when working in the command-line window */ int noexmode) /* TRUE when return on entering Ex mode */ { ! oparg_T oa; /* operator arguments */ ! volatile int previous_got_int = FALSE; /* "got_int" was TRUE */ #ifdef FEAT_CONCEAL ! /* these are static to avoid a compiler warning */ static linenr_T conceal_old_cursor_line = 0; static linenr_T conceal_new_cursor_line = 0; static int conceal_update_lines = FALSE; #endif #if defined(FEAT_X11) && defined(FEAT_XCLIPBOARD) /* Setup to catch a terminating error from the X server. Just ignore * it, restore the state and continue. This might not always work --- 1101,1119 ---- int cmdwin, /* TRUE when working in the command-line window */ int noexmode) /* TRUE when return on entering Ex mode */ { ! oparg_T oa; // operator arguments ! oparg_T *prev_oap; // operator arguments ! volatile int previous_got_int = FALSE; // "got_int" was TRUE #ifdef FEAT_CONCEAL ! // these are static to avoid a compiler warning static linenr_T conceal_old_cursor_line = 0; static linenr_T conceal_new_cursor_line = 0; static int conceal_update_lines = FALSE; #endif + prev_oap = current_oap; + current_oap = &oa; + #if defined(FEAT_X11) && defined(FEAT_XCLIPBOARD) /* Setup to catch a terminating error from the X server. Just ignore * it, restore the state and continue. This might not always work *************** *** 1276,1282 **** // If nothing is pending and we are going to wait for the user to // type a character, trigger SafeState. ! may_trigger_safestate_main(&oa); #if defined(FEAT_DIFF) // Updating diffs from changed() does not always work properly, --- 1281,1287 ---- // If nothing is pending and we are going to wait for the user to // type a character, trigger SafeState. ! may_trigger_safestate(!op_pending() && restart_edit == 0); #if defined(FEAT_DIFF) // Updating diffs from changed() does not always work properly, *************** *** 1430,1436 **** if (exmode_active) { if (noexmode) /* End of ":global/path/visual" commands */ ! return; do_exmode(exmode_active == EXMODE_VIM); } else --- 1435,1441 ---- if (exmode_active) { if (noexmode) /* End of ":global/path/visual" commands */ ! goto theend; do_exmode(exmode_active == EXMODE_VIM); } else *************** *** 1457,1462 **** --- 1462,1470 ---- } } } + + theend: + current_oap = prev_oap; } *** ../vim-8.1.2046/src/proto/main.pro 2019-09-16 21:58:08.792800986 +0200 --- src/proto/main.pro 2019-09-16 22:28:30.294598515 +0200 *************** *** 2,7 **** --- 2,8 ---- int vim_main2(void); void common_init(mparm_T *paramp); int is_not_a_term(void); + int op_pending(void); void may_trigger_safestate(int safe); void state_no_longer_safe(void); void leave_unsafe_state(void); *** ../vim-8.1.2046/src/channel.c 2019-09-16 21:58:08.792800986 +0200 --- src/channel.c 2019-09-16 22:38:13.432152959 +0200 *************** *** 3483,3488 **** --- 3483,3489 ---- * Read from RAW or NL "channel"/"part". Blocks until there is something to * read or the timeout expires. * When "raw" is TRUE don't block waiting on a NL. + * Does not trigger timers or handle messages. * Returns what was read in allocated memory. * Returns NULL in case of error or timeout. */ *************** *** 3569,3574 **** --- 3570,3586 ---- return msg; } + static int channel_blocking_wait = 0; + + /* + * Return TRUE if in a blocking wait that might trigger callbacks. + */ + int + channel_in_blocking_wait(void) + { + return channel_blocking_wait > 0; + } + /* * Read one JSON message with ID "id" from "channel"/"part" and store the * result in "rettv". *************** *** 3592,3597 **** --- 3604,3610 ---- int retval = FAIL; ch_log(channel, "Blocking read JSON for id %d", id); + ++channel_blocking_wait; if (id >= 0) channel_add_block_id(chanpart, id); *************** *** 3661,3666 **** --- 3674,3680 ---- } if (id >= 0) channel_remove_block_id(chanpart, id); + --channel_blocking_wait; return retval; } *** ../vim-8.1.2046/src/proto/channel.pro 2019-08-20 20:13:40.330821936 +0200 --- src/proto/channel.pro 2019-09-16 22:38:16.424140406 +0200 *************** *** 24,29 **** --- 24,30 ---- void channel_close(channel_T *channel, int invoke_close_cb); void channel_clear(channel_T *channel); void channel_free_all(void); + int channel_in_blocking_wait(void); void channel_handle_events(int only_keep_open); int channel_any_keep_open(void); void channel_set_nonblock(channel_T *channel, ch_part_T part); *** ../vim-8.1.2046/src/userfunc.c 2019-08-29 21:32:52.248093098 +0200 --- src/userfunc.c 2019-09-16 22:42:10.199183306 +0200 *************** *** 1447,1452 **** --- 1447,1460 ---- return r; } + static int callback_depth = 0; + + int + get_callback_depth(void) + { + return callback_depth; + } + /* * Invoke call_func() with a callback. */ *************** *** 1460,1471 **** // PLUS ONE elements! { funcexe_T funcexe; vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.evaluate = TRUE; funcexe.partial = callback->cb_partial; ! return call_func(callback->cb_name, len, rettv, argcount, argvars, ! &funcexe); } /* --- 1468,1482 ---- // PLUS ONE elements! { funcexe_T funcexe; + int ret; vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.evaluate = TRUE; funcexe.partial = callback->cb_partial; ! ++callback_depth; ! ret = call_func(callback->cb_name, len, rettv, argcount, argvars, &funcexe); ! --callback_depth; ! return ret; } /* *** ../vim-8.1.2046/src/proto/userfunc.pro 2019-08-03 18:17:07.680638632 +0200 --- src/proto/userfunc.pro 2019-09-16 22:42:29.207107298 +0200 *************** *** 10,15 **** --- 10,16 ---- funccall_T *get_current_funccal(void); void free_all_functions(void); int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv); + int get_callback_depth(void); int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int skip, int flags, funcdict_T *fdp, partial_T **partial); *** ../vim-8.1.2046/src/version.c 2019-09-16 21:58:08.792800986 +0200 --- src/version.c 2019-09-16 22:11:29.370159862 +0200 *************** *** 759,760 **** --- 759,762 ---- { /* Add new patch number below this line */ + /**/ + 2047, /**/ -- A KNIGHT rides into shot and hacks him to the ground. He rides off. We stay for a moment on the glade. A MIDDLE-AGED LADY in a C. & A. twin-set emerges from the trees and looks in horror at the body of her HUSBAND. MRS HISTORIAN: FRANK! "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///