To: vim_dev@googlegroups.com Subject: Patch 8.1.0834 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.0834 Problem: GUI may wait too long before dealing with messages. Returning early may cause a mapping to time out. Solution: Use the waiting loop from Unix also for the GUI. (closes #3817, closes #3824) Files: src/ui.c, src/proto/ui.pro, src/os_unix.c, src/gui.c, src/testdir/screendump.vim *** ../vim-8.1.0833/src/ui.c 2019-01-26 17:28:22.236599060 +0100 --- src/ui.c 2019-01-27 16:50:31.054860125 +0100 *************** *** 178,183 **** --- 178,225 ---- ctrl_c_interrupts = FALSE; } + /* + * Here we call gui_inchar() or mch_inchar(), the GUI or machine-dependent + * input function. The functionality they implement is like this: + * + * while (not timed out) + * { + * handle-resize; + * parse-queued-messages; + * if (waited for 'updatetime') + * trigger-cursorhold; + * ui_wait_for_chars_or_timer() + * if (character available) + * break; + * } + * + * ui_wait_for_chars_or_timer() does: + * + * while (not timed out) + * { + * if (any-timer-triggered) + * invoke-timer-callback; + * wait-for-character(); + * if (character available) + * break; + * } + * + * wait-for-character() does: + * while (not timed out) + * { + * Wait for event; + * if (something on channel) + * read/write channel; + * else if (resized) + * handle_resize(); + * else if (system event) + * deal-with-system-event; + * else if (character available) + * break; + * } + * + */ + #ifdef FEAT_GUI if (gui.in_use) retval = gui_inchar(buf, maxlen, wtime, tb_change_cnt); *************** *** 205,210 **** --- 247,422 ---- return retval; } + #if defined(UNIX) || defined(FEAT_GUI) || defined(PROTO) + /* + * Common code for mch_inchar() and gui_inchar(): Wait for a while or + * indefinitely until characters are available, dealing with timers and + * messages on channels. + * + * "buf" may be NULL if the available characters are not to be returned, only + * check if they are available. + * + * Return the number of characters that are available. + * If "wtime" == 0 do not wait for characters. + * If "wtime" == n wait a short time for characters. + * If "wtime" == -1 wait forever for characters. + */ + int + inchar_loop( + char_u *buf, + int maxlen, + long wtime, // don't use "time", MIPS cannot handle it + int tb_change_cnt, + int (*wait_func)(long wtime, int *interrupted, int ignore_input), + int (*resize_func)(int check_only)) + { + int len; + int interrupted = FALSE; + int did_start_blocking = FALSE; + long wait_time; + long elapsed_time = 0; + #ifdef ELAPSED_FUNC + elapsed_T start_tv; + + ELAPSED_INIT(start_tv); + #endif + + /* repeat until we got a character or waited long enough */ + for (;;) + { + /* Check if window changed size while we were busy, perhaps the ":set + * columns=99" command was used. */ + if (resize_func != NULL) + resize_func(FALSE); + + #ifdef MESSAGE_QUEUE + // Only process messages when waiting. + if (wtime != 0) + { + parse_queued_messages(); + // If input was put directly in typeahead buffer bail out here. + if (typebuf_changed(tb_change_cnt)) + return 0; + } + #endif + if (wtime < 0 && did_start_blocking) + // blocking and already waited for p_ut + wait_time = -1; + else + { + if (wtime >= 0) + wait_time = wtime; + else + // going to block after p_ut + wait_time = p_ut; + #ifdef ELAPSED_FUNC + elapsed_time = ELAPSED_FUNC(start_tv); + #endif + wait_time -= elapsed_time; + if (wait_time <= 0) + { + if (wtime >= 0) + // no character available within "wtime" + return 0; + + // No character available within 'updatetime'. + did_start_blocking = TRUE; + if (trigger_cursorhold() && maxlen >= 3 + && !typebuf_changed(tb_change_cnt)) + { + // Put K_CURSORHOLD in the input buffer or return it. + if (buf == NULL) + { + char_u ibuf[3]; + + ibuf[0] = CSI; + ibuf[1] = KS_EXTRA; + ibuf[2] = (int)KE_CURSORHOLD; + add_to_input_buf(ibuf, 3); + } + else + { + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = (int)KE_CURSORHOLD; + } + return 3; + } + + // There is no character available within 'updatetime' seconds: + // flush all the swap files to disk. Also done when + // interrupted by SIGWINCH. + before_blocking(); + continue; + } + } + + #ifdef FEAT_JOB_CHANNEL + if (wait_time < 0 || wait_time > 100L) + { + // Checking if a job ended requires polling. Do this at least + // every 100 msec. + if (has_pending_job()) + wait_time = 100L; + + // If there is readahead then parse_queued_messages() timed out and + // we should call it again soon. + if (channel_any_readahead()) + wait_time = 10L; + } + #endif + #ifdef FEAT_BEVAL_GUI + if (p_beval && wait_time > 100L) + // The 'balloonexpr' may indirectly invoke a callback while waiting + // for a character, need to check often. + wait_time = 100L; + #endif + + // Wait for a character to be typed or another event, such as the winch + // signal or an event on the monitored file descriptors. + if (wait_func(wait_time, &interrupted, FALSE)) + { + // If input was put directly in typeahead buffer bail out here. + if (typebuf_changed(tb_change_cnt)) + return 0; + + // We might have something to return now. + if (buf == NULL) + // "buf" is NULL, we were just waiting, not actually getting + // input. + return input_available(); + + len = read_from_input_buf(buf, (long)maxlen); + if (len > 0) + return len; + continue; + } + // Timed out or interrupted with no character available. + + #ifndef ELAPSED_FUNC + // estimate the elapsed time + elapsed_time += wait_time; + #endif + + if ((resize_func != NULL && resize_func(TRUE)) + #ifdef FEAT_CLIENTSERVER + || server_waiting() + #endif + #ifdef MESSAGE_QUEUE + || interrupted + #endif + || wait_time > 0 + || (wtime < 0 && !did_start_blocking)) + // no character available, but something to be done, keep going + continue; + + // no character available or interrupted, return zero + break; + } + return 0; + } + #endif + #if defined(FEAT_TIMERS) || defined(PROTO) /* * Wait for a timer to fire or "wait_func" to return non-zero. *** ../vim-8.1.0833/src/proto/ui.pro 2018-05-17 13:52:54.000000000 +0200 --- src/proto/ui.pro 2019-01-26 22:35:56.703647735 +0100 *************** *** 2,7 **** --- 2,8 ---- void ui_write(char_u *s, int len); void ui_inchar_undo(char_u *s, int len); int ui_inchar(char_u *buf, int maxlen, long wtime, int tb_change_cnt); + int inchar_loop(char_u *buf, int maxlen, long wtime, int tb_change_cnt, int (*wait_func)(long wtime, int *interrupted, int ignore_input), int (*resize_func)(int check_only)); int ui_wait_for_chars_or_timer(long wtime, int (*wait_func)(long wtime, int *interrupted, int ignore_input), int *interrupted, int ignore_input); int ui_char_avail(void); void ui_delay(long msec, int ignoreinput); *** ../vim-8.1.0833/src/os_unix.c 2019-01-26 15:12:52.558260916 +0100 --- src/os_unix.c 2019-01-26 22:36:20.799486197 +0100 *************** *** 356,361 **** --- 356,376 ---- } /* + * Function passed to inchar_loop() to handle window resizing. + * If "check_only" is TRUE: Return whether there was a resize. + * If "check_only" is FALSE: Deal with the window resized. + */ + static int + resize_func(int check_only) + { + if (check_only) + return do_resize; + while (do_resize) + handle_resize(); + return FALSE; + } + + /* * mch_inchar(): low level input function. * Get a characters from the keyboard. * Return the number of characters that are available. *************** *** 370,507 **** long wtime, /* don't use "time", MIPS cannot handle it */ int tb_change_cnt) { ! int len; ! int interrupted = FALSE; ! int did_start_blocking = FALSE; ! long wait_time; ! long elapsed_time = 0; ! #ifdef ELAPSED_FUNC ! elapsed_T start_tv; ! ! ELAPSED_INIT(start_tv); ! #endif ! ! /* repeat until we got a character or waited long enough */ ! for (;;) ! { ! /* Check if window changed size while we were busy, perhaps the ":set ! * columns=99" command was used. */ ! while (do_resize) ! handle_resize(); ! ! #ifdef MESSAGE_QUEUE ! // Only process messages when waiting. ! if (wtime != 0) ! { ! parse_queued_messages(); ! // If input was put directly in typeahead buffer bail out here. ! if (typebuf_changed(tb_change_cnt)) ! return 0; ! } ! #endif ! if (wtime < 0 && did_start_blocking) ! /* blocking and already waited for p_ut */ ! wait_time = -1; ! else ! { ! if (wtime >= 0) ! wait_time = wtime; ! else ! /* going to block after p_ut */ ! wait_time = p_ut; ! #ifdef ELAPSED_FUNC ! elapsed_time = ELAPSED_FUNC(start_tv); ! #endif ! wait_time -= elapsed_time; ! if (wait_time < 0) ! { ! if (wtime >= 0) ! /* no character available within "wtime" */ ! return 0; ! ! else ! { ! /* no character available within 'updatetime' */ ! did_start_blocking = TRUE; ! if (trigger_cursorhold() && maxlen >= 3 ! && !typebuf_changed(tb_change_cnt)) ! { ! buf[0] = K_SPECIAL; ! buf[1] = KS_EXTRA; ! buf[2] = (int)KE_CURSORHOLD; ! return 3; ! } ! /* ! * If there is no character available within 'updatetime' ! * seconds flush all the swap files to disk. ! * Also done when interrupted by SIGWINCH. ! */ ! before_blocking(); ! continue; ! } ! } ! } ! ! #ifdef FEAT_JOB_CHANNEL ! /* Checking if a job ended requires polling. Do this every 100 msec. */ ! if (has_pending_job() && (wait_time < 0 || wait_time > 100L)) ! wait_time = 100L; ! /* If there is readahead then parse_queued_messages() timed out and we ! * should call it again soon. */ ! if ((wait_time < 0 || wait_time > 100L) && channel_any_readahead()) ! wait_time = 10L; ! #endif ! #ifdef FEAT_BEVAL_GUI ! if (p_beval && wait_time > 100L) ! /* The 'balloonexpr' may indirectly invoke a callback while waiting ! * for a character, need to check often. */ ! wait_time = 100L; ! #endif ! ! /* ! * We want to be interrupted by the winch signal ! * or by an event on the monitored file descriptors. ! */ ! if (WaitForChar(wait_time, &interrupted, FALSE)) ! { ! /* If input was put directly in typeahead buffer bail out here. */ ! if (typebuf_changed(tb_change_cnt)) ! return 0; ! ! /* ! * For some terminals we only get one character at a time. ! * We want the get all available characters, so we could keep on ! * trying until none is available ! * For some other terminals this is quite slow, that's why we don't ! * do it. ! */ ! len = read_from_input_buf(buf, (long)maxlen); ! if (len > 0) ! return len; ! continue; ! } ! ! /* no character available */ ! #ifndef ELAPSED_FUNC ! /* estimate the elapsed time */ ! elapsed_time += wait_time; ! #endif ! ! if (do_resize /* interrupted by SIGWINCH signal */ ! #ifdef FEAT_CLIENTSERVER ! || server_waiting() ! #endif ! #ifdef MESSAGE_QUEUE ! || interrupted ! #endif ! || wait_time > 0 ! || (wtime < 0 && !did_start_blocking)) ! continue; ! ! /* no character available or interrupted */ ! break; ! } ! return 0; } static void --- 385,392 ---- long wtime, /* don't use "time", MIPS cannot handle it */ int tb_change_cnt) { ! return inchar_loop(buf, maxlen, wtime, tb_change_cnt, ! WaitForChar, resize_func); } static void *** ../vim-8.1.0833/src/gui.c 2019-01-26 17:28:22.224599141 +0100 --- src/gui.c 2019-01-26 23:17:38.097634554 +0100 *************** *** 2896,2905 **** * or FAIL otherwise. */ static int ! gui_wait_for_chars_or_timer(long wtime) { #ifdef FEAT_TIMERS ! return ui_wait_for_chars_or_timer(wtime, gui_wait_for_chars_3, NULL, 0); #else return gui_mch_wait_for_chars(wtime); #endif --- 2896,2909 ---- * or FAIL otherwise. */ static int ! gui_wait_for_chars_or_timer( ! long wtime, ! int *interrupted UNUSED, ! int ignore_input UNUSED) { #ifdef FEAT_TIMERS ! return ui_wait_for_chars_or_timer(wtime, gui_wait_for_chars_3, ! interrupted, ignore_input); #else return gui_mch_wait_for_chars(wtime); #endif *************** *** 2907,3000 **** /* * The main GUI input routine. Waits for a character from the keyboard. ! * wtime == -1 Wait forever. ! * wtime == 0 Don't wait. ! * wtime > 0 Wait wtime milliseconds for a character. ! * Returns OK if a character was found to be available within the given time, ! * or FAIL otherwise. */ ! int ! gui_wait_for_chars(long wtime, int tb_change_cnt) { ! int retval; ! #if defined(ELAPSED_FUNC) ! elapsed_T start_tv; ! #endif #ifdef FEAT_MENU ! /* ! * If we're going to wait a bit, update the menus and mouse shape for the ! * current State. ! */ if (wtime != 0) gui_update_menus(0); #endif gui_mch_update(); ! if (input_available()) /* Got char, return immediately */ ! return OK; ! if (wtime == 0) /* Don't wait for char */ ! return FAIL; ! ! /* Before waiting, flush any output to the screen. */ ! gui_mch_flush(); ! ! if (wtime > 0) { ! /* Blink when waiting for a character. Probably only does something ! * for showmatch() */ ! gui_mch_start_blink(); ! retval = gui_wait_for_chars_or_timer(wtime); ! gui_mch_stop_blink(TRUE); ! return retval; } ! #if defined(ELAPSED_FUNC) ! ELAPSED_INIT(start_tv); ! #endif ! /* ! * While we are waiting indefinitely for a character, blink the cursor. ! */ gui_mch_start_blink(); ! retval = FAIL; ! /* ! * We may want to trigger the CursorHold event. First wait for ! * 'updatetime' and if nothing is typed within that time, and feedkeys() ! * wasn't used, put the K_CURSORHOLD key in the input buffer. ! */ ! if (gui_wait_for_chars_or_timer(p_ut) == OK) ! retval = OK; ! else if (trigger_cursorhold() ! #if defined(ELAPSED_FUNC) ! && ELAPSED_FUNC(start_tv) >= p_ut ! #endif ! && typebuf.tb_change_cnt == tb_change_cnt) ! { ! char_u buf[3]; ! ! /* Put K_CURSORHOLD in the input buffer. */ ! buf[0] = CSI; ! buf[1] = KS_EXTRA; ! buf[2] = (int)KE_CURSORHOLD; ! add_to_input_buf(buf, 3); ! ! retval = OK; ! } ! ! if (retval == FAIL && typebuf.tb_change_cnt == tb_change_cnt) ! { ! /* Blocking wait. */ ! before_blocking(); ! retval = gui_wait_for_chars_or_timer(-1L); ! } gui_mch_stop_blink(TRUE); return retval; } /* * Equivalent of mch_inchar() for the GUI. */ int --- 2911,2982 ---- /* * The main GUI input routine. Waits for a character from the keyboard. ! * "wtime" == -1 Wait forever. ! * "wtime" == 0 Don't wait. ! * "wtime" > 0 Wait wtime milliseconds for a character. ! * ! * Returns the number of characters read or zero when timed out or interrupted. ! * "buf" may be NULL, in which case a non-zero number is returned if characters ! * are available. */ ! static int ! gui_wait_for_chars_buf( ! char_u *buf, ! int maxlen, ! long wtime, // don't use "time", MIPS cannot handle it ! int tb_change_cnt) { ! int retval; #ifdef FEAT_MENU ! // If we're going to wait a bit, update the menus and mouse shape for the ! // current State. if (wtime != 0) gui_update_menus(0); #endif gui_mch_update(); ! if (input_available()) // Got char, return immediately { ! if (buf != NULL && !typebuf_changed(tb_change_cnt)) ! return read_from_input_buf(buf, (long)maxlen); ! return 0; } + if (wtime == 0) // Don't wait for char + return FAIL; ! // Before waiting, flush any output to the screen. ! gui_mch_flush(); ! // Blink while waiting for a character. gui_mch_start_blink(); ! // Common function to loop until "wtime" is met, while handling timers and ! // other callbacks. ! retval = inchar_loop(buf, maxlen, wtime, tb_change_cnt, ! gui_wait_for_chars_or_timer, NULL); gui_mch_stop_blink(TRUE); + return retval; } /* + * Wait for a character from the keyboard without actually reading it. + * Also deals with timers. + * wtime == -1 Wait forever. + * wtime == 0 Don't wait. + * wtime > 0 Wait wtime milliseconds for a character. + * Returns OK if a character was found to be available within the given time, + * or FAIL otherwise. + */ + int + gui_wait_for_chars(long wtime, int tb_change_cnt) + { + return gui_wait_for_chars_buf(NULL, 0, wtime, tb_change_cnt); + } + + /* * Equivalent of mch_inchar() for the GUI. */ int *************** *** 3004,3013 **** long wtime, /* milli seconds */ int tb_change_cnt) { ! if (gui_wait_for_chars(wtime, tb_change_cnt) ! && !typebuf_changed(tb_change_cnt)) ! return read_from_input_buf(buf, (long)maxlen); ! return 0; } /* --- 2986,2992 ---- long wtime, /* milli seconds */ int tb_change_cnt) { ! return gui_wait_for_chars_buf(buf, maxlen, wtime, tb_change_cnt); } /* *** ../vim-8.1.0833/src/testdir/screendump.vim 2018-12-04 22:24:12.193693584 +0100 --- src/testdir/screendump.vim 2019-01-27 16:20:34.371242071 +0100 *************** *** 58,63 **** --- 58,67 ---- let cmd .= ' -v ' . a:arguments let buf = term_start(cmd, {'curwin': 1, 'term_rows': rows, 'term_cols': cols}) if &termwinsize == '' + " in the GUI we may end up with a different size, try to set it. + if term_getsize(buf) != [rows, cols] + call term_setsize(buf, rows, cols) + endif call assert_equal([rows, cols], term_getsize(buf)) else let rows = term_getsize(buf)[0] *** ../vim-8.1.0833/src/version.c 2019-01-27 15:07:35.161741346 +0100 --- src/version.c 2019-01-27 16:36:02.924759049 +0100 *************** *** 785,786 **** --- 785,788 ---- { /* Add new patch number below this line */ + /**/ + 834, /**/ -- We do not stumble over mountains, but over molehills. Confucius /// 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 ///