comparison src/terminal.c @ 12009:0d9bfdb3f6f7 v8.0.0885

patch 8.0.0885: terminal window scrollback is stored inefficiently commit https://github.com/vim/vim/commit/33a43bee9cdc62f9cd0999eb23c6eca01b4d2d67 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Aug 6 21:36:22 2017 +0200 patch 8.0.0885: terminal window scrollback is stored inefficiently Problem: Terminal window scrollback is stored inefficiently. Solution: Store the text in the Vim buffer.
author Christian Brabandt <cb@256bit.org>
date Sun, 06 Aug 2017 21:45:03 +0200
parents 64b822c4f7ae
children 1f4e7361ce89
comparison
equal deleted inserted replaced
12008:41ab44ba9753 12009:0d9bfdb3f6f7
34 * 34 *
35 * When the job ends the text is put in a buffer. Redrawing then happens from 35 * When the job ends the text is put in a buffer. Redrawing then happens from
36 * that buffer, attributes come from the scrollback buffer tl_scrollback. 36 * that buffer, attributes come from the scrollback buffer tl_scrollback.
37 * 37 *
38 * TODO: 38 * TODO:
39 * - For the scrollback buffer store lines in the buffer, only attributes in
40 * tl_scrollback.
41 * - When the job ends: 39 * - When the job ends:
42 * - Need an option or argument to drop the window+buffer right away, to be 40 * - Need an option or argument to drop the window+buffer right away, to be
43 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when 41 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when
44 * job finishes). 42 * job finishes).
45 * - add option values to the command: 43 * - add option values to the command:
95 # define MAX(x,y) ((x) > (y) ? (x) : (y)) 93 # define MAX(x,y) ((x) > (y) ? (x) : (y))
96 #endif 94 #endif
97 95
98 #include "libvterm/include/vterm.h" 96 #include "libvterm/include/vterm.h"
99 97
98 /* This is VTermScreenCell without the characters, thus much smaller. */
99 typedef struct {
100 VTermScreenCellAttrs attrs;
101 char width;
102 VTermColor fg, bg;
103 } cellattr_T;
104
100 typedef struct sb_line_S { 105 typedef struct sb_line_S {
101 int sb_cols; /* can differ per line */ 106 int sb_cols; /* can differ per line */
102 VTermScreenCell *sb_cells; /* allocated */ 107 cellattr_T *sb_cells; /* allocated */
103 } sb_line_T; 108 } sb_line_T;
104 109
105 /* typedef term_T in structs.h */ 110 /* typedef term_T in structs.h */
106 struct terminal_S { 111 struct terminal_S {
107 term_T *tl_next; 112 term_T *tl_next;
686 691
687 /* 692 /*
688 * Add the last line of the scrollback buffer to the buffer in the window. 693 * Add the last line of the scrollback buffer to the buffer in the window.
689 */ 694 */
690 static void 695 static void
691 add_scrollback_line_to_buffer(term_T *term) 696 add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
692 { 697 {
693 linenr_T lnum = term->tl_scrollback.ga_len - 1; 698 linenr_T lnum = term->tl_scrollback.ga_len - 1;
694 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum; 699
695 garray_T ga; 700 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
696 int c;
697 int col;
698 int i;
699
700 ga_init2(&ga, 1, 100);
701 for (col = 0; col < line->sb_cols; col += line->sb_cells[col].width)
702 {
703 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
704 goto failed;
705 for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
706 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
707 (char_u *)ga.ga_data + ga.ga_len);
708 }
709 if (ga_grow(&ga, 1) == FAIL)
710 goto failed;
711 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
712 ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
713
714 if (lnum == 0) 701 if (lnum == 0)
715 { 702 {
716 /* Delete the empty line that was in the empty buffer. */ 703 /* Delete the empty line that was in the empty buffer. */
717 curbuf = term->tl_buffer; 704 curbuf = term->tl_buffer;
718 ml_delete(2, FALSE); 705 ml_delete(2, FALSE);
719 curbuf = curwin->w_buffer; 706 curbuf = curwin->w_buffer;
720 } 707 }
721
722 failed:
723 ga_clear(&ga);
724 } 708 }
725 709
726 /* 710 /*
727 * Add the current lines of the terminal to scrollback and to the buffer. 711 * Add the current lines of the terminal to scrollback and to the buffer.
728 * Called after the job has ended and when switching to Terminal mode. 712 * Called after the job has ended and when switching to Terminal-Normal mode.
729 */ 713 */
730 static void 714 static void
731 move_terminal_to_buffer(term_T *term) 715 move_terminal_to_buffer(term_T *term)
732 { 716 {
733 win_T *wp; 717 win_T *wp;
734 int len; 718 int len;
735 int lines_skipped = 0; 719 int lines_skipped = 0;
736 VTermPos pos; 720 VTermPos pos;
737 VTermScreenCell cell; 721 VTermScreenCell cell;
738 VTermScreenCell *p; 722 cellattr_T *p;
739 VTermScreen *screen; 723 VTermScreen *screen;
740 724
741 if (term->tl_vterm == NULL) 725 if (term->tl_vterm == NULL)
742 return; 726 return;
743 screen = vterm_obtain_screen(term->tl_vterm); 727 screen = vterm_obtain_screen(term->tl_vterm);
764 748
765 line->sb_cols = 0; 749 line->sb_cols = 0;
766 line->sb_cells = NULL; 750 line->sb_cells = NULL;
767 ++term->tl_scrollback.ga_len; 751 ++term->tl_scrollback.ga_len;
768 752
769 add_scrollback_line_to_buffer(term); 753 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
770 } 754 }
771 } 755 }
772 756
773 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len); 757 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
774 if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK) 758 if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK)
775 { 759 {
776 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data 760 garray_T ga;
761 int width;
762 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
777 + term->tl_scrollback.ga_len; 763 + term->tl_scrollback.ga_len;
778 764
779 for (pos.col = 0; pos.col < len; ++pos.col) 765 ga_init2(&ga, 1, 100);
766 for (pos.col = 0; pos.col < len; pos.col += width)
780 { 767 {
781 if (vterm_screen_get_cell(screen, pos, &cell) == 0) 768 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
782 vim_memset(p + pos.col, 0, sizeof(cell)); 769 {
770 width = 1;
771 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
772 if (ga_grow(&ga, 1) == OK)
773 ga.ga_len += mb_char2bytes(' ',
774 (char_u *)ga.ga_data + ga.ga_len);
775 }
783 else 776 else
784 p[pos.col] = cell; 777 {
778 width = cell.width;
779
780 p[pos.col].width = cell.width;
781 p[pos.col].attrs = cell.attrs;
782 p[pos.col].fg = cell.fg;
783 p[pos.col].bg = cell.bg;
784
785 if (ga_grow(&ga, MB_MAXBYTES) == OK)
786 {
787 int i;
788 int c;
789
790 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
791 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
792 (char_u *)ga.ga_data + ga.ga_len);
793 }
794 }
785 } 795 }
786 line->sb_cols = len; 796 line->sb_cols = len;
787 line->sb_cells = p; 797 line->sb_cells = p;
788 ++term->tl_scrollback.ga_len; 798 ++term->tl_scrollback.ga_len;
789 799
790 add_scrollback_line_to_buffer(term); 800 if (ga_grow(&ga, 1) == FAIL)
801 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
802 else
803 {
804 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
805 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
806 }
807 ga_clear(&ga);
791 } 808 }
792 else 809 else
793 vim_free(p); 810 vim_free(p);
794 } 811 }
795 } 812 }
1310 handle_pushline(int cols, const VTermScreenCell *cells, void *user) 1327 handle_pushline(int cols, const VTermScreenCell *cells, void *user)
1311 { 1328 {
1312 term_T *term = (term_T *)user; 1329 term_T *term = (term_T *)user;
1313 1330
1314 /* TODO: Limit the number of lines that are stored. */ 1331 /* TODO: Limit the number of lines that are stored. */
1315 /* TODO: put the text in the buffer. */
1316 if (ga_grow(&term->tl_scrollback, 1) == OK) 1332 if (ga_grow(&term->tl_scrollback, 1) == OK)
1317 { 1333 {
1318 VTermScreenCell *p = NULL; 1334 cellattr_T *p = NULL;
1319 int len = 0; 1335 int len = 0;
1320 int i; 1336 int i;
1337 int c;
1338 int col;
1321 sb_line_T *line; 1339 sb_line_T *line;
1340 garray_T ga;
1322 1341
1323 /* do not store empty cells at the end */ 1342 /* do not store empty cells at the end */
1324 for (i = 0; i < cols; ++i) 1343 for (i = 0; i < cols; ++i)
1325 if (cells[i].chars[0] != 0) 1344 if (cells[i].chars[0] != 0)
1326 len = i + 1; 1345 len = i + 1;
1327 1346
1328 if (len > 0) 1347 if (len > 0)
1329 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len); 1348 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1330 if (p != NULL) 1349 if (p != NULL)
1331 mch_memmove(p, cells, sizeof(VTermScreenCell) * len); 1350 {
1351 ga_init2(&ga, 1, 100);
1352 for (col = 0; col < len; col += cells[col].width)
1353 {
1354 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
1355 {
1356 ga.ga_len = 0;
1357 break;
1358 }
1359 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
1360 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
1361 (char_u *)ga.ga_data + ga.ga_len);
1362 p[col].width = cells[col].width;
1363 p[col].attrs = cells[col].attrs;
1364 p[col].fg = cells[col].fg;
1365 p[col].bg = cells[col].bg;
1366 }
1367 }
1368 if (ga_grow(&ga, 1) == FAIL)
1369 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1370 else
1371 {
1372 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1373 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1374 }
1375 ga_clear(&ga);
1332 1376
1333 line = (sb_line_T *)term->tl_scrollback.ga_data 1377 line = (sb_line_T *)term->tl_scrollback.ga_data
1334 + term->tl_scrollback.ga_len; 1378 + term->tl_scrollback.ga_len;
1335 line->sb_cols = len; 1379 line->sb_cols = len;
1336 line->sb_cells = p; 1380 line->sb_cells = p;
1337 ++term->tl_scrollback.ga_len; 1381 ++term->tl_scrollback.ga_len;
1338 ++term->tl_scrollback_scrolled; 1382 ++term->tl_scrollback_scrolled;
1339
1340 add_scrollback_line_to_buffer(term);
1341 } 1383 }
1342 return 0; /* ignored */ 1384 return 0; /* ignored */
1343 } 1385 }
1344 1386
1345 static VTermScreenCallbacks screen_callbacks = { 1387 static VTermScreenCallbacks screen_callbacks = {
1508 1550
1509 /* 1551 /*
1510 * Convert the attributes of a vterm cell into an attribute index. 1552 * Convert the attributes of a vterm cell into an attribute index.
1511 */ 1553 */
1512 static int 1554 static int
1513 cell2attr(VTermScreenCell *cell) 1555 cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
1514 { 1556 {
1515 int attr = 0; 1557 int attr = 0;
1516 1558
1517 if (cell->attrs.bold) 1559 if (cellattrs.bold)
1518 attr |= HL_BOLD; 1560 attr |= HL_BOLD;
1519 if (cell->attrs.underline) 1561 if (cellattrs.underline)
1520 attr |= HL_UNDERLINE; 1562 attr |= HL_UNDERLINE;
1521 if (cell->attrs.italic) 1563 if (cellattrs.italic)
1522 attr |= HL_ITALIC; 1564 attr |= HL_ITALIC;
1523 if (cell->attrs.strike) 1565 if (cellattrs.strike)
1524 attr |= HL_STANDOUT; 1566 attr |= HL_STANDOUT;
1525 if (cell->attrs.reverse) 1567 if (cellattrs.reverse)
1526 attr |= HL_INVERSE; 1568 attr |= HL_INVERSE;
1527 1569
1528 #ifdef FEAT_GUI 1570 #ifdef FEAT_GUI
1529 if (gui.in_use) 1571 if (gui.in_use)
1530 { 1572 {
1531 guicolor_T fg, bg; 1573 guicolor_T fg, bg;
1532 1574
1533 fg = gui_mch_get_rgb_color(cell->fg.red, cell->fg.green, cell->fg.blue); 1575 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
1534 bg = gui_mch_get_rgb_color(cell->bg.red, cell->bg.green, cell->bg.blue); 1576 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
1535 return get_gui_attr_idx(attr, fg, bg); 1577 return get_gui_attr_idx(attr, fg, bg);
1536 } 1578 }
1537 else 1579 else
1538 #endif 1580 #endif
1539 #ifdef FEAT_TERMGUICOLORS 1581 #ifdef FEAT_TERMGUICOLORS
1540 if (p_tgc) 1582 if (p_tgc)
1541 { 1583 {
1542 guicolor_T fg, bg; 1584 guicolor_T fg, bg;
1543 1585
1544 fg = gui_get_rgb_color_cmn(cell->fg.red, cell->fg.green, cell->fg.blue); 1586 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
1545 bg = gui_get_rgb_color_cmn(cell->bg.red, cell->bg.green, cell->bg.blue); 1587 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
1546 1588
1547 return get_tgc_attr_idx(attr, fg, bg); 1589 return get_tgc_attr_idx(attr, fg, bg);
1548 } 1590 }
1549 else 1591 else
1550 #endif 1592 #endif
1551 { 1593 {
1552 int bold = MAYBE; 1594 int bold = MAYBE;
1553 int fg = color2index(&cell->fg, TRUE, &bold); 1595 int fg = color2index(&cellfg, TRUE, &bold);
1554 int bg = color2index(&cell->bg, FALSE, &bold); 1596 int bg = color2index(&cellbg, FALSE, &bold);
1555 1597
1556 /* with 8 colors set the bold attribute to get a bright foreground */ 1598 /* with 8 colors set the bold attribute to get a bright foreground */
1557 if (bold == TRUE) 1599 if (bold == TRUE)
1558 attr |= HL_BOLD; 1600 attr |= HL_BOLD;
1559 return get_cterm_attr_idx(attr, fg, bg); 1601 return get_cterm_attr_idx(attr, fg, bg);
1658 } 1700 }
1659 #else 1701 #else
1660 ScreenLines[off] = c; 1702 ScreenLines[off] = c;
1661 #endif 1703 #endif
1662 } 1704 }
1663 ScreenAttrs[off] = cell2attr(&cell); 1705 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
1664 1706
1665 ++pos.col; 1707 ++pos.col;
1666 ++off; 1708 ++off;
1667 if (cell.width == 2) 1709 if (cell.width == 2)
1668 { 1710 {
1732 * Get the screen attribute for a position in the buffer. 1774 * Get the screen attribute for a position in the buffer.
1733 */ 1775 */
1734 int 1776 int
1735 term_get_attr(buf_T *buf, linenr_T lnum, int col) 1777 term_get_attr(buf_T *buf, linenr_T lnum, int col)
1736 { 1778 {
1737 term_T *term = buf->b_term; 1779 term_T *term = buf->b_term;
1738 sb_line_T *line; 1780 sb_line_T *line;
1781 cellattr_T *cellattr;
1739 1782
1740 if (lnum > term->tl_scrollback.ga_len) 1783 if (lnum > term->tl_scrollback.ga_len)
1741 return 0; 1784 return 0;
1742 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1; 1785 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
1743 if (col >= line->sb_cols) 1786 if (col >= line->sb_cols)
1744 return 0; 1787 return 0;
1745 return cell2attr(line->sb_cells + col); 1788 cellattr = line->sb_cells + col;
1789 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
1746 } 1790 }
1747 1791
1748 /* 1792 /*
1749 * Create a new vterm and initialize it. 1793 * Create a new vterm and initialize it.
1750 */ 1794 */
2084 buf_T *buf = term_get_buf(argvars); 2128 buf_T *buf = term_get_buf(argvars);
2085 VTermScreen *screen = NULL; 2129 VTermScreen *screen = NULL;
2086 VTermPos pos; 2130 VTermPos pos;
2087 list_T *l; 2131 list_T *l;
2088 term_T *term; 2132 term_T *term;
2133 char_u *p;
2134 sb_line_T *line;
2089 2135
2090 if (rettv_list_alloc(rettv) == FAIL) 2136 if (rettv_list_alloc(rettv) == FAIL)
2091 return; 2137 return;
2092 if (buf == NULL) 2138 if (buf == NULL)
2093 return; 2139 return;
2094 term = buf->b_term; 2140 term = buf->b_term;
2141
2142 l = rettv->vval.v_list;
2143 pos.row = get_row_number(&argvars[1], term);
2144
2095 if (term->tl_vterm != NULL) 2145 if (term->tl_vterm != NULL)
2096 screen = vterm_obtain_screen(term->tl_vterm); 2146 screen = vterm_obtain_screen(term->tl_vterm);
2097 2147 else
2098 l = rettv->vval.v_list; 2148 {
2099 pos.row = get_row_number(&argvars[1], term); 2149 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
2150
2151 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
2152 return;
2153 p = ml_get_buf(buf, lnum + 1, FALSE);
2154 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
2155 }
2156
2100 for (pos.col = 0; pos.col < term->tl_cols; ) 2157 for (pos.col = 0; pos.col < term->tl_cols; )
2101 { 2158 {
2102 dict_T *dcell; 2159 dict_T *dcell;
2103 VTermScreenCell cell; 2160 int width;
2161 VTermScreenCellAttrs attrs;
2162 VTermColor fg, bg;
2104 char_u rgb[8]; 2163 char_u rgb[8];
2105 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1]; 2164 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
2106 int off = 0; 2165 int off = 0;
2107 int i; 2166 int i;
2108 2167
2109 if (screen == NULL) 2168 if (screen == NULL)
2110 { 2169 {
2111 linenr_T lnum = pos.row + term->tl_scrollback_scrolled; 2170 cellattr_T *cellattr;
2112 sb_line_T *line; 2171 int len;
2113 2172
2114 /* vterm has finished, get the cell from scrollback */ 2173 /* vterm has finished, get the cell from scrollback */
2115 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
2116 break;
2117 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
2118 if (pos.col >= line->sb_cols) 2174 if (pos.col >= line->sb_cols)
2119 break; 2175 break;
2120 cell = line->sb_cells[pos.col]; 2176 cellattr = line->sb_cells + pos.col;
2121 } 2177 width = cellattr->width;
2122 else if (vterm_screen_get_cell(screen, pos, &cell) == 0) 2178 attrs = cellattr->attrs;
2123 break; 2179 fg = cellattr->fg;
2180 bg = cellattr->bg;
2181 len = MB_PTR2LEN(p);
2182 mch_memmove(mbs, p, len);
2183 mbs[len] = NUL;
2184 p += len;
2185 }
2186 else
2187 {
2188 VTermScreenCell cell;
2189 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
2190 break;
2191 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
2192 {
2193 if (cell.chars[i] == 0)
2194 break;
2195 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
2196 }
2197 mbs[off] = NUL;
2198 width = cell.width;
2199 attrs = cell.attrs;
2200 fg = cell.fg;
2201 bg = cell.bg;
2202 }
2124 dcell = dict_alloc(); 2203 dcell = dict_alloc();
2125 list_append_dict(l, dcell); 2204 list_append_dict(l, dcell);
2126 2205
2127 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
2128 {
2129 if (cell.chars[i] == 0)
2130 break;
2131 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
2132 }
2133 mbs[off] = NUL;
2134 dict_add_nr_str(dcell, "chars", 0, mbs); 2206 dict_add_nr_str(dcell, "chars", 0, mbs);
2135 2207
2136 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x", 2208 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
2137 cell.fg.red, cell.fg.green, cell.fg.blue); 2209 fg.red, fg.green, fg.blue);
2138 dict_add_nr_str(dcell, "fg", 0, rgb); 2210 dict_add_nr_str(dcell, "fg", 0, rgb);
2139 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x", 2211 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
2140 cell.bg.red, cell.bg.green, cell.bg.blue); 2212 bg.red, bg.green, bg.blue);
2141 dict_add_nr_str(dcell, "bg", 0, rgb); 2213 dict_add_nr_str(dcell, "bg", 0, rgb);
2142 2214
2143 dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL); 2215 dict_add_nr_str(dcell, "attr",
2144 dict_add_nr_str(dcell, "width", cell.width, NULL); 2216 cell2attr(attrs, fg, bg), NULL);
2217 dict_add_nr_str(dcell, "width", width, NULL);
2145 2218
2146 ++pos.col; 2219 ++pos.col;
2147 if (cell.width == 2) 2220 if (width == 2)
2148 ++pos.col; 2221 ++pos.col;
2149 } 2222 }
2150 } 2223 }
2151 2224
2152 /* 2225 /*