# HG changeset patch # User Bram Moolenaar # Date 1588274104 -7200 # Node ID f2e4c12b24b30336b18040a84bb0a985cc475132 # Parent 6ffcab677df259839d35e7f39f0c30f1855f53c6 patch 8.2.0669: MS-Windows: display in VTP is a bit slow Commit: https://github.com/vim/vim/commit/4e5534fab798ab7c95554da3bc80b08336aedc2b Author: Bram Moolenaar Date: Thu Apr 30 20:59:57 2020 +0200 patch 8.2.0669: MS-Windows: display in VTP is a bit slow Problem: MS-Windows: display in VTP is a bit slow. Solution: Optimize the code. (Nobuhiro Takasaki, closes https://github.com/vim/vim/issues/6014) diff --git a/src/os_win32.c b/src/os_win32.c --- a/src/os_win32.c +++ b/src/os_win32.c @@ -5579,12 +5579,14 @@ clear_chars( COORD coord, DWORD n) { - DWORD dwDummy; - - FillConsoleOutputCharacter(g_hConOut, ' ', n, coord, &dwDummy); - if (!USE_VTP) - FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, n, coord, &dwDummy); + { + DWORD dwDummy; + + FillConsoleOutputCharacter(g_hConOut, ' ', n, coord, &dwDummy); + FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, n, coord, + &dwDummy); + } else { set_console_color_rgb(); @@ -6036,23 +6038,57 @@ write_chars( { COORD coord = g_coord; DWORD written; - DWORD n, cchwritten, cells; + DWORD n, cchwritten; + static DWORD cells; static WCHAR *unicodebuf = NULL; static int unibuflen = 0; - int length; + static int length; int cp = enc_utf8 ? CP_UTF8 : enc_codepage; - - length = MultiByteToWideChar(cp, 0, (LPCSTR)pchBuf, cbToWrite, 0, 0); - if (unicodebuf == NULL || length > unibuflen) - { - vim_free(unicodebuf); - unicodebuf = LALLOC_MULT(WCHAR, length); - unibuflen = length; - } - MultiByteToWideChar(cp, 0, (LPCSTR)pchBuf, cbToWrite, - unicodebuf, unibuflen); - - cells = mb_string2cells(pchBuf, cbToWrite); + static WCHAR *utf8spbuf = NULL; + static int utf8splength; + static DWORD utf8spcells; + static WCHAR **utf8usingbuf = &unicodebuf; + + if (cbToWrite != 1 || *pchBuf != ' ' || !enc_utf8) + { + utf8usingbuf = &unicodebuf; + do + { + length = MultiByteToWideChar(cp, 0, (LPCSTR)pchBuf, cbToWrite, + unicodebuf, unibuflen); + if (length && length <= unibuflen) + break; + vim_free(unicodebuf); + unicodebuf = length ? LALLOC_MULT(WCHAR, length) : NULL; + unibuflen = unibuflen ? 0 : length; + } while(1); + cells = mb_string2cells(pchBuf, cbToWrite); + } + else // cbToWrite == 1 && *pchBuf == ' ' && enc_utf8 + { + if (utf8usingbuf != &utf8spbuf) + { + if (utf8spbuf == NULL) + { + cells = mb_string2cells((char_u *)" ", 1); + length = MultiByteToWideChar(CP_UTF8, 0, " ", 1, NULL, 0); + utf8spbuf = LALLOC_MULT(WCHAR, length); + if (utf8spbuf != NULL) + { + MultiByteToWideChar(CP_UTF8, 0, " ", 1, utf8spbuf, length); + utf8usingbuf = &utf8spbuf; + utf8splength = length; + utf8spcells = cells; + } + } + else + { + utf8usingbuf = &utf8spbuf; + length = utf8splength; + cells = utf8spcells; + } + } + } if (!USE_VTP) { @@ -6060,14 +6096,14 @@ write_chars( coord, &written); // When writing fails or didn't write a single character, pretend one // character was written, otherwise we get stuck. - if (WriteConsoleOutputCharacterW(g_hConOut, unicodebuf, length, + if (WriteConsoleOutputCharacterW(g_hConOut, *utf8usingbuf, length, coord, &cchwritten) == 0 || cchwritten == 0 || cchwritten == (DWORD)-1) cchwritten = 1; } else { - if (WriteConsoleW(g_hConOut, unicodebuf, length, &cchwritten, + if (WriteConsoleW(g_hConOut, *utf8usingbuf, length, &cchwritten, NULL) == 0 || cchwritten == 0) cchwritten = 1; } @@ -6093,11 +6129,119 @@ write_chars( g_coord.Y++; } - gotoxy(g_coord.X + 1, g_coord.Y + 1); + // Cursor under VTP is always in the correct position, no need to reset. + if (!USE_VTP) + gotoxy(g_coord.X + 1, g_coord.Y + 1); return written; } + static char_u * +get_seq( + int *args, + int *count, + char_u *head) +{ + int argc; + char_u *p; + + if (head == NULL || *head != '\033') + return NULL; + + argc = 0; + p = head; + ++p; + do + { + ++p; + args[argc] = getdigits(&p); + argc += (argc < 15) ? 1 : 0; + } while (*p == ';'); + *count = argc; + + return p; +} + + static char_u * +get_sgr( + int *args, + int *count, + char_u *head) +{ + char_u *p = get_seq(args, count, head); + + return (p && *p == 'm') ? ++p : NULL; +} + +/* + * Pointer to next if SGR (^[[n;2;*;*;*m), NULL otherwise. + */ + static char_u * +sgrn2( + char_u *head, + int n) +{ + int argc; + int args[16]; + char_u *p = get_sgr(args, &argc, head); + + return p && argc == 5 && args[0] == n && args[1] == 2 ? p : NULL; +} + +/* + * Pointer to next if SGR(^[[nm)ESC, NULL otherwise. + */ + static char_u * +sgrnc( + char_u *head, + int n) +{ + int argc; + int args[16]; + char_u *p = get_sgr(args, &argc, head); + + return p && argc == 1 && args[0] == n && (p = skipwhite(p)) && *p == '\033' + ? p : NULL; +} + + static char_u * +skipblank(char_u *q) +{ + char_u *p = q; + + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + ++p; + return p; +} + +/* + * Pointer to the next if any whitespace that may follow SGR is ESC, otherwise + * NULL. + */ + static char_u * +sgrn2c( + char_u *head, + int n) +{ + char_u *p = sgrn2(head, n); + + return p && *p != NUL && (p = skipblank(p)) && *p == '\033' ? p : NULL; +} + +/* + * If there is only a newline between the sequence immediately following it, + * a pointer to the character following the newline is returned. + * Otherwise NULL. + */ + static char_u * +sgrn2cn( + char_u *head, + int n) +{ + char_u *p = sgrn2(head, n); + + return p && p[0] == 0x0a && p[1] == '\033' ? ++p : NULL; +} /* * mch_write(): write the output buffer to the screen, translating ESC @@ -6124,9 +6268,21 @@ mch_write( // translate ESC | sequences into faked bios calls while (len--) { - // optimization: use one single write_chars for runs of text, - // rather than once per character It ain't curses, but it helps. - DWORD prefix = (DWORD)strcspn((char *)s, "\n\r\b\a\033"); + int prefix = -1; + char_u ch; + + // While processing a sequence, on rare occasions it seems that another + // sequence may be inserted asynchronously. + if (len < 0) + { + redraw_all_later(CLEAR); + return; + } + + while((ch = s[++prefix])) + if (ch <= 0x1e && !(ch != '\n' && ch != '\r' && ch != '\b' + && ch != '\a' && ch != '\033')) + break; if (p_wd) { @@ -6213,24 +6369,52 @@ mch_write( # endif char_u *p; int arg1 = 0, arg2 = 0, argc = 0, args[16]; + char_u *sp; switch (s[2]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - p = s + 1; - do + if (*(p = get_seq(args, &argc, s)) != 'm') + goto notsgr; + + p = s; + + // Handling frequent optional sequences. Output to the screen + // takes too long, so do not output as much as possible. + + // If resetFG,FG,BG,,BG,FG are connected, the preceding + // resetFG,FG,BG are omitted. + if (sgrn2(sgrn2(sgrn2cn(sgrn2(sgrnc(p, 39), 38), 48), 48), 38)) { - ++p; - args[argc] = getdigits(&p); - argc += (argc < 15) ? 1 : 0; - if (p > s + len) - break; - } while (*p == ';'); - - if (p > s + len) + p = sgrn2(sgrn2(sgrnc(p, 39), 38), 48); + len = len + 1 - (int)(p - s); + s = p; break; - + } + + // If FG,BG,BG,FG of SGR are connected, the first FG can be + // omitted. + if (sgrn2(sgrn2(sgrn2c((sp = sgrn2(p, 38)), 48), 48), 38)) + p = sp; + + // If FG,BG,FG,BG of SGR are connected, the first FG can be + // omitted. + if (sgrn2(sgrn2(sgrn2c((sp = sgrn2(p, 38)), 48), 38), 48)) + p = sp; + + // If BG,BG of SGR are connected, the first BG can be omitted. + if (sgrn2((sp = sgrn2(p, 48)), 48)) + p = sp; + + // If restoreFG and FG are connected, the restoreFG can be + // omitted. + if (sgrn2((sp = sgrnc(p, 39)), 38)) + p = sp; + + p = get_seq(args, &argc, p); + +notsgr: arg1 = args[0]; arg2 = args[1]; if (*p == 'm') @@ -7406,11 +7590,12 @@ vtp_printf( char_u buf[100]; va_list list; DWORD result; + int len; va_start(list, format); - vim_vsnprintf((char *)buf, 100, (char *)format, list); + len = vim_vsnprintf((char *)buf, 100, (char *)format, list); va_end(list); - WriteConsoleA(g_hConOut, buf, (DWORD)STRLEN(buf), &result, NULL); + WriteConsoleA(g_hConOut, buf, (DWORD)len, &result, NULL); return (int)result; } @@ -7424,30 +7609,150 @@ vtp_sgr_bulk( vtp_sgr_bulks(1, args); } +#define FAST256(x) \ + if ((*p-- = "0123456789"[(n = x % 10)]) \ + && x >= 10 && (*p-- = "0123456789"[((m = x % 100) - n) / 10]) \ + && x >= 100 && (*p-- = "012"[((x & 0xff) - m) / 100])); + +#define FAST256CASE(x) \ + case x: \ + FAST256(newargs[x - 1]); + static void vtp_sgr_bulks( int argc, - int *args -) -{ - // 2('\033[') + 4('255.') * 16 + NUL - char_u buf[2 + (4 * 16) + 1]; - char_u *p; - int i; - - p = buf; - *p++ = '\033'; - *p++ = '['; - - for (i = 0; i < argc; ++i) - { - p += vim_snprintf((char *)p, 4, "%d", args[i] & 0xff); - *p++ = ';'; - } - p--; - *p++ = 'm'; - *p = NUL; - vtp_printf((char *)buf); + int *args) +{ +#define MAXSGR 16 +#define SGRBUFSIZE 2 + 4 * MAXSGR + 1 // '\033[' + SGR + 'm' + char_u buf[SGRBUFSIZE]; + char_u *p; + int in, out; + int newargs[16]; + static int sgrfgr = -1, sgrfgg, sgrfgb; + static int sgrbgr = -1, sgrbgg, sgrbgb; + + if (argc == 0) + { + sgrfgr = sgrbgr = -1; + vtp_printf("033[m"); + return; + } + + in = out = 0; + while (in < argc) + { + int s = args[in]; + int copylen = 1; + + if (s == 38) + { + if (argc - in >= 5 && args[in + 1] == 2) + { + if (sgrfgr == args[in + 2] && sgrfgg == args[in + 3] + && sgrfgb == args[in + 4]) + { + in += 5; + copylen = 0; + } + else + { + sgrfgr = args[in + 2]; + sgrfgg = args[in + 3]; + sgrfgb = args[in + 4]; + copylen = 5; + } + } + else if (argc - in >= 3 && args[in + 1] == 5) + { + sgrfgr = -1; + copylen = 3; + } + } + else if (s == 48) + { + if (argc - in >= 5 && args[in + 1] == 2) + { + if (sgrbgr == args[in + 2] && sgrbgg == args[in + 3] + && sgrbgb == args[in + 4]) + { + in += 5; + copylen = 0; + } + else + { + sgrbgr = args[in + 2]; + sgrbgg = args[in + 3]; + sgrbgb = args[in + 4]; + copylen = 5; + } + } + else if (argc - in >= 3 && args[in + 1] == 5) + { + sgrbgr = -1; + copylen = 3; + } + } + else if (30 <= s && s <= 39) + sgrfgr = -1; + else if (90 <= s && s <= 97) + sgrfgr = -1; + else if (40 <= s && s <= 49) + sgrbgr = -1; + else if (100 <= s && s <= 107) + sgrbgr = -1; + else if (s == 0) + sgrfgr = sgrbgr = -1; + + while (copylen--) + newargs[out++] = args[in++]; + } + + p = &buf[sizeof(buf) - 1]; + *p-- = 'm'; + + switch (out) + { + int n, m; + DWORD r; + + FAST256CASE(16); + *p-- = ';'; + FAST256CASE(15); + *p-- = ';'; + FAST256CASE(14); + *p-- = ';'; + FAST256CASE(13); + *p-- = ';'; + FAST256CASE(12); + *p-- = ';'; + FAST256CASE(11); + *p-- = ';'; + FAST256CASE(10); + *p-- = ';'; + FAST256CASE(9); + *p-- = ';'; + FAST256CASE(8); + *p-- = ';'; + FAST256CASE(7); + *p-- = ';'; + FAST256CASE(6); + *p-- = ';'; + FAST256CASE(5); + *p-- = ';'; + FAST256CASE(4); + *p-- = ';'; + FAST256CASE(3); + *p-- = ';'; + FAST256CASE(2); + *p-- = ';'; + FAST256CASE(1); + *p-- = '['; + *p = '\033'; + WriteConsoleA(g_hConOut, p, (DWORD)(&buf[SGRBUFSIZE] - p), &r, NULL); + default: + break; + } } # ifdef FEAT_TERMGUICOLORS diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -1880,6 +1880,9 @@ screen_start_highlight(int attr) screen_stop_highlight(void) { int do_ME = FALSE; // output T_ME code +#if defined(FEAT_VTP) && defined(FEAT_TERMGUICOLORS) + int do_ME_fg, do_ME_bg; +#endif if (screen_attr != 0 #ifdef MSWIN @@ -1913,16 +1916,42 @@ screen_stop_highlight(void) #ifdef FEAT_TERMGUICOLORS p_tgc && aep->ae_u.cterm.fg_rgb != CTERMCOLOR ? aep->ae_u.cterm.fg_rgb != INVALCOLOR +# ifdef FEAT_VTP + ? !(do_ME_fg = TRUE) : (do_ME_fg = FALSE) +# endif : #endif aep->ae_u.cterm.fg_color) || ( #ifdef FEAT_TERMGUICOLORS p_tgc && aep->ae_u.cterm.bg_rgb != CTERMCOLOR ? aep->ae_u.cterm.bg_rgb != INVALCOLOR +# ifdef FEAT_VTP + ? !(do_ME_bg = TRUE) : (do_ME_bg = FALSE) +# endif : #endif aep->ae_u.cterm.bg_color))) do_ME = TRUE; +#if defined(FEAT_VTP) && defined(FEAT_TERMGUICOLORS) + if (use_vtp()) + { + if (do_ME_fg && do_ME_bg) + do_ME = TRUE; + + // FG and BG cannot be separated in T_ME, which is not + // efficient. + if (!do_ME && do_ME_fg) + out_str((char_u *)"\033|39m"); // restore FG + if (!do_ME && do_ME_bg) + out_str((char_u *)"\033|49m"); // restore BG + } + else + { + // Process FG and BG at once. + if (!do_ME) + do_ME = do_ME_fg | do_ME_bg; + } +#endif } else { diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 669, +/**/ 668, /**/ 667,