You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

339 lines
11 KiB

  1. #include "grid.h"
  2. #include <string.h>
  3. #include <assert.h>
  4. #define LOG_MODULE "grid"
  5. #define LOG_ENABLE_DBG 0
  6. #include "log.h"
  7. #include "macros.h"
  8. #include "sixel.h"
  9. #include "util.h"
  10. #include "xmalloc.h"
  11. void
  12. grid_swap_row(struct grid *grid, int row_a, int row_b)
  13. {
  14. assert(grid->offset >= 0);
  15. assert(row_a != row_b);
  16. int real_a = (grid->offset + row_a) & (grid->num_rows - 1);
  17. int real_b = (grid->offset + row_b) & (grid->num_rows - 1);
  18. struct row *a = grid->rows[real_a];
  19. struct row *b = grid->rows[real_b];
  20. grid->rows[real_a] = b;
  21. grid->rows[real_b] = a;
  22. }
  23. struct row *
  24. grid_row_alloc(int cols, bool initialize)
  25. {
  26. struct row *row = xmalloc(sizeof(*row));
  27. row->dirty = false;
  28. row->linebreak = false;
  29. if (initialize) {
  30. row->cells = xcalloc(cols, sizeof(row->cells[0]));
  31. for (size_t c = 0; c < cols; c++)
  32. row->cells[c].attrs.clean = 1;
  33. } else
  34. row->cells = xmalloc(cols * sizeof(row->cells[0]));
  35. return row;
  36. }
  37. void
  38. grid_row_free(struct row *row)
  39. {
  40. if (row == NULL)
  41. return;
  42. free(row->cells);
  43. free(row);
  44. }
  45. void
  46. grid_reflow(struct grid *grid, int new_rows, int new_cols,
  47. int old_screen_rows, int new_screen_rows,
  48. size_t tracking_points_count,
  49. struct coord *const _tracking_points[static tracking_points_count],
  50. size_t compose_count, const struct
  51. composed composed[static compose_count])
  52. {
  53. struct row *const *old_grid = grid->rows;
  54. const int old_rows = grid->num_rows;
  55. const int old_cols = grid->num_cols;
  56. /* Is viewpoint tracking current grid offset? */
  57. const bool view_follows = grid->view == grid->offset;
  58. int new_col_idx = 0;
  59. int new_row_idx = 0;
  60. struct row **new_grid = xcalloc(new_rows, sizeof(new_grid[0]));
  61. struct row *new_row = new_grid[new_row_idx];
  62. assert(new_row == NULL);
  63. new_row = grid_row_alloc(new_cols, true);
  64. new_grid[new_row_idx] = new_row;
  65. /* Start at the beginning of the old grid's scrollback. That is,
  66. * at the output that is *oldest* */
  67. int offset = grid->offset + old_screen_rows;
  68. tll(struct sixel) untranslated_sixels = tll_init();
  69. tll_foreach(grid->sixel_images, it)
  70. tll_push_back(untranslated_sixels, it->item);
  71. tll_free(grid->sixel_images);
  72. /* Turn cursor coordinates into grid absolute coordinates */
  73. struct coord cursor = grid->cursor.point;
  74. cursor.row += grid->offset;
  75. cursor.row &= old_rows - 1;
  76. struct coord saved_cursor = grid->saved_cursor.point;
  77. saved_cursor.row += grid->offset;
  78. saved_cursor.row &= old_rows - 1;
  79. tll(struct coord *) tracking_points = tll_init();
  80. tll_push_back(tracking_points, &cursor);
  81. tll_push_back(tracking_points, &saved_cursor);
  82. struct coord viewport = {0, grid->view};
  83. if (!view_follows)
  84. tll_push_back(tracking_points, &viewport);
  85. for (size_t i = 0; i < tracking_points_count; i++)
  86. tll_push_back(tracking_points, _tracking_points[i]);
  87. /*
  88. * Walk the old grid
  89. */
  90. for (int r = 0; r < old_rows; r++) {
  91. const size_t old_row_idx = (offset + r) & (old_rows - 1);
  92. /* Unallocated (empty) rows we can simply skip */
  93. const struct row *old_row = old_grid[old_row_idx];
  94. if (old_row == NULL)
  95. continue;
  96. /* Map sixels on current "old" row to current "new row" */
  97. tll_foreach(untranslated_sixels, it) {
  98. if (it->item.pos.row != old_row_idx)
  99. continue;
  100. struct sixel sixel = it->item;
  101. sixel.pos.row = new_row_idx;
  102. tll_push_back(grid->sixel_images, sixel);
  103. tll_remove(untranslated_sixels, it);
  104. }
  105. #define line_wrap() \
  106. do { \
  107. new_col_idx = 0; \
  108. new_row_idx = (new_row_idx + 1) & (new_rows - 1); \
  109. \
  110. new_row = new_grid[new_row_idx]; \
  111. if (new_row == NULL) { \
  112. new_row = grid_row_alloc(new_cols, true); \
  113. new_grid[new_row_idx] = new_row; \
  114. } else { \
  115. memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); \
  116. new_row->linebreak = false; \
  117. tll_foreach(grid->sixel_images, it) { \
  118. if (it->item.pos.row == new_row_idx) { \
  119. sixel_destroy(&it->item); \
  120. tll_remove(grid->sixel_images, it); \
  121. } \
  122. } \
  123. } \
  124. } while(0)
  125. #define print_spacer() \
  126. do { \
  127. new_row->cells[new_col_idx].wc = CELL_MULT_COL_SPACER; \
  128. new_row->cells[new_col_idx].attrs = old_cell->attrs; \
  129. new_row->cells[new_col_idx].attrs.clean = 1; \
  130. } while (0)
  131. /*
  132. * Keep track of empty cells. If the old line ends with a
  133. * string of empty cells, we don't need to, nor do we want to,
  134. * add those to the new line. However, if there are non-empty
  135. * cells *after* the string of empty cells, we need to emit
  136. * the empty cells too. And that may trigger linebreaks
  137. */
  138. int empty_count = 0;
  139. /* Walk current line of the old grid */
  140. for (int c = 0; c < old_cols; c++) {
  141. /* Check if this cell is one of the tracked cells */
  142. bool is_tracking_point = false;
  143. tll_foreach(tracking_points, it) {
  144. if (it->item->row == old_row_idx && it->item->col == c) {
  145. is_tracking_point = true;
  146. break;
  147. }
  148. }
  149. if (old_row->cells[c].wc == 0 && !is_tracking_point) {
  150. empty_count++;
  151. continue;
  152. }
  153. /* Allow left-adjusted and right-adjusted text, with empty
  154. * cells in between, to be "pushed together" */
  155. int old_cols_left = old_cols - c;
  156. int cols_needed = empty_count + old_cols_left;
  157. int new_cols_left = new_cols - new_col_idx;
  158. if (new_cols_left < cols_needed && new_cols_left >= old_cols_left)
  159. empty_count = max(0, empty_count - (cols_needed - new_cols_left));
  160. wchar_t wc = old_row->cells[c].wc;
  161. if (wc >= CELL_COMB_CHARS_LO &&
  162. wc < (CELL_COMB_CHARS_LO + compose_count))
  163. {
  164. wc = composed[wc - CELL_COMB_CHARS_LO].base;
  165. }
  166. int width = max(1, wcwidth(wc));
  167. /* Multi-column characters are never cut in half */
  168. assert(c + width <= old_cols);
  169. for (int i = 0; i < empty_count + 1; i++) {
  170. const struct cell *old_cell = &old_row->cells[c - empty_count + i];
  171. wc = old_cell->wc;
  172. if (wc == CELL_MULT_COL_SPACER)
  173. continue;
  174. if (wc >= CELL_COMB_CHARS_LO &&
  175. wc < (CELL_COMB_CHARS_LO + compose_count))
  176. {
  177. wc = composed[wc - CELL_COMB_CHARS_LO].base;
  178. }
  179. /* Out of columns on current row in new grid? */
  180. if (new_col_idx + max(1, wcwidth(wc)) > new_cols) {
  181. /* Pad to end-of-line with spacers, then line-wrap */
  182. for (;new_col_idx < new_cols; new_col_idx++)
  183. print_spacer();
  184. line_wrap();
  185. }
  186. assert(new_row != NULL);
  187. assert(new_col_idx >= 0);
  188. assert(new_col_idx < new_cols);
  189. new_row->cells[new_col_idx] = *old_cell;
  190. new_row->cells[new_col_idx].attrs.clean = 1;
  191. /* Translate tracking point(s) */
  192. if (is_tracking_point && i >= empty_count) {
  193. tll_foreach(tracking_points, it) {
  194. if (it->item->row == old_row_idx && it->item->col == c) {
  195. it->item->row = new_row_idx;
  196. it->item->col = new_col_idx;
  197. tll_remove(tracking_points, it);
  198. }
  199. }
  200. }
  201. new_col_idx++;
  202. }
  203. /* For multi-column characters, insert spacers in the
  204. * subsequent cells */
  205. const struct cell *old_cell = &old_row->cells[c];
  206. for (size_t i = 0; i < width - 1; i++) {
  207. assert(new_col_idx < new_cols);
  208. print_spacer();
  209. new_col_idx++;
  210. }
  211. c += width - 1;
  212. empty_count = 0;
  213. }
  214. if (old_row->linebreak) {
  215. new_row->linebreak = true;
  216. line_wrap();
  217. }
  218. #undef print_spacer
  219. #undef line_wrap
  220. }
  221. /* Set offset such that the last reflowed row is at the bottom */
  222. grid->offset = new_row_idx - new_screen_rows + 1;
  223. while (grid->offset < 0)
  224. grid->offset += new_rows;
  225. while (new_grid[grid->offset] == NULL)
  226. grid->offset = (grid->offset + 1) & (new_rows - 1);
  227. /* Ensure all visible rows have been allocated */
  228. for (int r = 0; r < new_screen_rows; r++) {
  229. int idx = (grid->offset + r) & (new_rows - 1);
  230. if (new_grid[idx] == NULL)
  231. new_grid[idx] = grid_row_alloc(new_cols, true);
  232. }
  233. grid->view = view_follows ? grid->offset : viewport.row;
  234. /* If enlarging the window, the old viewport may be too far down,
  235. * with unallocated rows. Make sure this cannot happen */
  236. while (true) {
  237. int idx = (grid->view + new_screen_rows - 1) & (new_rows - 1);
  238. if (new_grid[idx] != NULL)
  239. break;
  240. grid->view--;
  241. if (grid->view < 0)
  242. grid->view += new_rows;
  243. }
  244. for (size_t r = 0; r < new_screen_rows; r++) {
  245. int UNUSED idx = (grid->view + r) & (new_rows - 1);
  246. assert(new_grid[idx] != NULL);
  247. }
  248. /* Free old grid */
  249. for (int r = 0; r < grid->num_rows; r++)
  250. grid_row_free(old_grid[r]);
  251. free(grid->rows);
  252. grid->cur_row = new_grid[cursor.row];
  253. grid->rows = new_grid;
  254. grid->num_rows = new_rows;
  255. grid->num_cols = new_cols;
  256. /* Convert absolute coordinates to screen relative */
  257. cursor.row -= grid->offset;
  258. while (cursor.row < 0)
  259. cursor.row += grid->num_rows;
  260. cursor.row = min(cursor.row, new_screen_rows - 1);
  261. assert(cursor.col >= 0 && cursor.col < new_cols);
  262. saved_cursor.row -= grid->offset;
  263. while (saved_cursor.row < 0)
  264. saved_cursor.row += grid->num_rows;
  265. saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1);
  266. assert(saved_cursor.col >= 0 && saved_cursor.col < new_cols);
  267. grid->cursor.point = cursor;
  268. grid->saved_cursor.point = saved_cursor;
  269. grid->cursor.lcf = false;
  270. grid->saved_cursor.lcf = false;
  271. /* Free sixels we failed to "map" to the new grid */
  272. tll_foreach(untranslated_sixels, it)
  273. sixel_destroy(&it->item);
  274. tll_free(untranslated_sixels);
  275. tll_free(tracking_points);
  276. }