st-flexipatch

My st-flexipatch configuration
git clone git://git.ethandl.dev/st-flexipatch
Log | Files | Refs | README | LICENSE

reflow.c (20722B)


      1 void
      2 tloaddefscreen(int clear, int loadcursor)
      3 {
      4 	int col, row, alt = IS_SET(MODE_ALTSCREEN);
      5 
      6 	if (alt) {
      7 		if (clear) {
      8 			tclearregion(0, 0, term.col-1, term.row-1, 1);
      9 			#if SIXEL_PATCH
     10 			tdeleteimages();
     11 			#endif // SIXEL_PATCH
     12 		}
     13 		col = term.col, row = term.row;
     14 		tswapscreen();
     15 	}
     16 	if (loadcursor)
     17 		tcursor(CURSOR_LOAD);
     18 	if (alt)
     19 		tresizedef(col, row);
     20 }
     21 
     22 void
     23 tloadaltscreen(int clear, int savecursor)
     24 {
     25 	int col, row, def = !IS_SET(MODE_ALTSCREEN);
     26 
     27 	if (savecursor)
     28 		tcursor(CURSOR_SAVE);
     29 	if (def) {
     30 		col = term.col, row = term.row;
     31 		kscrolldown(&((Arg){ .i = term.scr }));
     32 		tswapscreen();
     33 		tresizealt(col, row);
     34 	}
     35 	if (clear) {
     36 		tclearregion(0, 0, term.col-1, term.row-1, 1);
     37 		#if SIXEL_PATCH
     38 		tdeleteimages();
     39 		#endif // SIXEL_PATCH
     40 	}
     41 }
     42 
     43 void
     44 selmove(int n)
     45 {
     46 	sel.ob.y += n, sel.nb.y += n;
     47 	sel.oe.y += n, sel.ne.y += n;
     48 }
     49 
     50 void
     51 tclearglyph(Glyph *gp, int usecurattr)
     52 {
     53 	if (usecurattr) {
     54 		gp->fg = term.c.attr.fg;
     55 		gp->bg = term.c.attr.bg;
     56 	} else {
     57 		gp->fg = defaultfg;
     58 		gp->bg = defaultbg;
     59 	}
     60 	gp->mode = ATTR_NULL;
     61 	gp->u = ' ';
     62 }
     63 
     64 #if SIXEL_PATCH
     65 void
     66 treflow_moveimages(int oldy, int newy)
     67 {
     68 	ImageList *im;
     69 
     70 	for (im = term.images; im; im = im->next) {
     71 		if (im->y == oldy)
     72 			im->reflow_y = newy;
     73 	}
     74 }
     75 #endif // SIXEL_PATCH
     76 
     77 void
     78 treflow(int col, int row)
     79 {
     80 	int i, j;
     81 	int oce, nce, bot, scr;
     82 	int ox = 0, oy = -term.histf, nx = 0, ny = -1, len;
     83 	int cy = -1; /* proxy for new y coordinate of cursor */
     84 	int buflen, nlines;
     85 	Line *buf, bufline, line;
     86 	#if SIXEL_PATCH
     87 	ImageList *im, *next;
     88 
     89 	for (im = term.images; im; im = im->next)
     90 		im->reflow_y = INT_MIN; /* unset reflow_y */
     91 	#endif // SIXEL_PATCH
     92 
     93 	/* y coordinate of cursor line end */
     94 	for (oce = term.c.y; oce < term.row - 1 &&
     95 	                     tiswrapped(term.line[oce]); oce++);
     96 
     97 	nlines = HISTSIZE + row;
     98 	buf = xmalloc(nlines * sizeof(Line));
     99 	do {
    100 		if (!nx && ++ny < nlines)
    101 			buf[ny] = xmalloc(col * sizeof(Glyph));
    102 		if (!ox) {
    103 			line = TLINEABS(oy);
    104 			len = tlinelen(line);
    105 		}
    106 		if (oy == term.c.y) {
    107 			if (!ox)
    108 				len = MAX(len, term.c.x + 1);
    109 			/* update cursor */
    110 			if (cy < 0 && term.c.x - ox < col - nx) {
    111 				term.c.x = nx + term.c.x - ox, cy = ny;
    112 				UPDATEWRAPNEXT(0, col);
    113 			}
    114 		}
    115 		/* get reflowed lines in buf */
    116 		bufline = buf[ny % nlines];
    117 		if (col - nx > len - ox) {
    118 			memcpy(&bufline[nx], &line[ox], (len-ox) * sizeof(Glyph));
    119 			nx += len - ox;
    120 			if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) {
    121 				for (j = nx; j < col; j++)
    122 					tclearglyph(&bufline[j], 0);
    123 				#if SIXEL_PATCH
    124 				treflow_moveimages(oy+term.scr, ny);
    125 				#endif // SIXEL_PATCH
    126 				nx = 0;
    127 			} else if (nx > 0) {
    128 				bufline[nx - 1].mode &= ~ATTR_WRAP;
    129 			}
    130 			ox = 0, oy++;
    131 		} else if (col - nx == len - ox) {
    132 			memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph));
    133 			#if SIXEL_PATCH
    134 			treflow_moveimages(oy+term.scr, ny);
    135 			#endif // SIXEL_PATCH
    136 			ox = 0, oy++, nx = 0;
    137 		} else/* if (col - nx < len - ox) */ {
    138 			memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph));
    139 			if (bufline[col - 1].mode & ATTR_WIDE) {
    140 				bufline[col - 2].mode |= ATTR_WRAP;
    141 				tclearglyph(&bufline[col - 1], 0);
    142 				ox--;
    143 			} else {
    144 				bufline[col - 1].mode |= ATTR_WRAP;
    145 			}
    146 			#if SIXEL_PATCH
    147 			treflow_moveimages(oy+term.scr, ny);
    148 			#endif // SIXEL_PATCH
    149 			ox += col - nx;
    150 			nx = 0;
    151 		}
    152 	} while (oy <= oce);
    153 	if (nx)
    154 		for (j = nx; j < col; j++)
    155 			tclearglyph(&bufline[j], 0);
    156 
    157 	/* free extra lines */
    158 	for (i = row; i < term.row; i++)
    159 		free(term.line[i]);
    160 	/* resize to new height */
    161 	term.line = xrealloc(term.line, row * sizeof(Line));
    162 
    163 	buflen = MIN(ny + 1, nlines);
    164 	bot = MIN(ny, row - 1);
    165 	scr = MAX(row - term.row, 0);
    166 	/* update y coordinate of cursor line end */
    167 	nce = MIN(oce + scr, bot);
    168 	/* update cursor y coordinate */
    169 	term.c.y = nce - (ny - cy);
    170 	if (term.c.y < 0) {
    171 		j = nce, nce = MIN(nce + -term.c.y, bot);
    172 		term.c.y += nce - j;
    173 		while (term.c.y < 0) {
    174 			free(buf[ny-- % nlines]);
    175 			buflen--;
    176 			term.c.y++;
    177 		}
    178 	}
    179 	/* allocate new rows */
    180 	for (i = row - 1; i > nce; i--) {
    181 		if (i >= term.row)
    182 			term.line[i] = xmalloc(col * sizeof(Glyph));
    183 		else
    184 			term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
    185 		for (j = 0; j < col; j++)
    186 			tclearglyph(&term.line[i][j], 0);
    187 	}
    188 	/* fill visible area */
    189 	for (/*i = nce */; i >= term.row; i--, ny--, buflen--)
    190 		term.line[i] = buf[ny % nlines];
    191 	for (/*i = term.row - 1 */; i >= 0; i--, ny--, buflen--) {
    192 		free(term.line[i]);
    193 		term.line[i] = buf[ny % nlines];
    194 	}
    195 	/* fill lines in history buffer and update term.histf */
    196 	for (/*i = -1 */; buflen > 0 && i >= -HISTSIZE; i--, ny--, buflen--) {
    197 		j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
    198 		free(term.hist[j]);
    199 		term.hist[j] = buf[ny % nlines];
    200 	}
    201 	term.histf = -i - 1;
    202 	term.scr = MIN(term.scr, term.histf);
    203 	/* resize rest of the history lines */
    204 	for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) {
    205 		j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
    206 		term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph));
    207 	}
    208 
    209 	#if SIXEL_PATCH
    210 	/* move images to the final position */
    211 	for (im = term.images; im; im = next) {
    212 		next = im->next;
    213 		if (im->reflow_y == INT_MIN) {
    214 			delete_image(im);
    215 		} else {
    216 			im->y = im->reflow_y - term.histf + term.scr - (ny + 1);
    217 			if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= row)
    218 				delete_image(im);
    219 		}
    220 	}
    221 
    222 	/* expand images into new text cells */
    223 	for (im = term.images; im; im = im->next) {
    224 		j = MIN(im->x + im->cols, col);
    225 		line = TLINE(im->y);
    226 		for (i = im->x; i < j; i++) {
    227 			if (!(line[i].mode & ATTR_SET))
    228 				line[i].mode |= ATTR_SIXEL;
    229 		}
    230 	}
    231 	#endif // SIXEL_PATCH
    232 
    233 	for (; buflen > 0; ny--, buflen--)
    234 		free(buf[ny % nlines]);
    235 	free(buf);
    236 }
    237 
    238 void
    239 rscrolldown(int n)
    240 {
    241 	int i;
    242 	Line temp;
    243 
    244 	/* can never be true as of now
    245 	if (IS_SET(MODE_ALTSCREEN))
    246 		return; */
    247 
    248 	if ((n = MIN(n, term.histf)) <= 0)
    249 		return;
    250 
    251 	for (i = term.c.y + n; i >= n; i--) {
    252 		temp = term.line[i];
    253 		term.line[i] = term.line[i-n];
    254 		term.line[i-n] = temp;
    255 	}
    256 	for (/*i = n - 1 */; i >= 0; i--) {
    257 		temp = term.line[i];
    258 		term.line[i] = term.hist[term.histi];
    259 		term.hist[term.histi] = temp;
    260 		term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
    261 	}
    262 	term.c.y += n;
    263 	term.histf -= n;
    264 	if ((i = term.scr - n) >= 0) {
    265 		term.scr = i;
    266 	} else {
    267 		#if SIXEL_PATCH
    268 		scroll_images(n - term.scr);
    269 		#endif // SIXEL_PATCH
    270 		term.scr = 0;
    271 		if (sel.ob.x != -1 && !sel.alt)
    272 			selmove(-i);
    273 	}
    274 }
    275 
    276 void
    277 tresizedef(int col, int row)
    278 {
    279 	int i, j;
    280 
    281 	/* return if dimensions haven't changed */
    282 	if (term.col == col && term.row == row) {
    283 		tfulldirt();
    284 		return;
    285 	}
    286 	if (col != term.col) {
    287 		if (!sel.alt)
    288 			selremove();
    289 		treflow(col, row);
    290 	} else {
    291 		/* slide screen up if otherwise cursor would get out of the screen */
    292 		if (term.c.y >= row) {
    293 			tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE);
    294 			term.c.y = row - 1;
    295 		}
    296 		for (i = row; i < term.row; i++)
    297 			free(term.line[i]);
    298 
    299 		/* resize to new height */
    300 		term.line = xrealloc(term.line, row * sizeof(Line));
    301 		/* allocate any new rows */
    302 		for (i = term.row; i < row; i++) {
    303 			term.line[i] = xmalloc(col * sizeof(Glyph));
    304 			for (j = 0; j < col; j++)
    305 				tclearglyph(&term.line[i][j], 0);
    306 		}
    307 		/* scroll down as much as height has increased */
    308 		rscrolldown(row - term.row);
    309 	}
    310 	/* update terminal size */
    311 	term.col = col, term.row = row;
    312 	/* reset scrolling region */
    313 	term.top = 0, term.bot = row - 1;
    314 	/* dirty all lines */
    315 	tfulldirt();
    316 }
    317 
    318 void
    319 tresizealt(int col, int row)
    320 {
    321 	int i, j;
    322 	#if SIXEL_PATCH
    323 	ImageList *im, *next;
    324 	#endif // SIXEL_PATCH
    325 
    326 	/* return if dimensions haven't changed */
    327 	if (term.col == col && term.row == row) {
    328 		tfulldirt();
    329 		return;
    330 	}
    331 	if (sel.alt)
    332 		selremove();
    333 	/* slide screen up if otherwise cursor would get out of the screen */
    334 	for (i = 0; i <= term.c.y - row; i++)
    335 		free(term.line[i]);
    336 	if (i > 0) {
    337 		/* ensure that both src and dst are not NULL */
    338 		memmove(term.line, term.line + i, row * sizeof(Line));
    339 		#if SIXEL_PATCH
    340 		scroll_images(-i);
    341 		#endif // SIXEL_PATCH
    342 		term.c.y = row - 1;
    343 	}
    344 	for (i += row; i < term.row; i++)
    345 		free(term.line[i]);
    346 	/* resize to new height */
    347 	term.line = xrealloc(term.line, row * sizeof(Line));
    348 	/* resize to new width */
    349 	for (i = 0; i < MIN(row, term.row); i++) {
    350 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
    351 		for (j = term.col; j < col; j++)
    352 			tclearglyph(&term.line[i][j], 0);
    353 	}
    354 	/* allocate any new rows */
    355 	for (/*i = MIN(row, term.row) */; i < row; i++) {
    356 		term.line[i] = xmalloc(col * sizeof(Glyph));
    357 		for (j = 0; j < col; j++)
    358 			tclearglyph(&term.line[i][j], 0);
    359 	}
    360 	/* update cursor */
    361 	if (term.c.x >= col) {
    362 		term.c.state &= ~CURSOR_WRAPNEXT;
    363 		term.c.x = col - 1;
    364 	} else {
    365 		UPDATEWRAPNEXT(1, col);
    366 	}
    367 	/* update terminal size */
    368 	term.col = col, term.row = row;
    369 	/* reset scrolling region */
    370 	term.top = 0, term.bot = row - 1;
    371 
    372 	#if SIXEL_PATCH
    373 	/* delete or clip images if they are not inside the screen */
    374 	for (im = term.images; im; im = next) {
    375 		next = im->next;
    376 		if (im->x >= term.col || im->y >= term.row || im->y < 0) {
    377 			delete_image(im);
    378 		} else {
    379 			if ((im->cols = MIN(im->x + im->cols, term.col) - im->x) <= 0)
    380 				delete_image(im);
    381 		}
    382 	}
    383 	#endif // SIXEL_PATCH
    384 
    385 	/* dirty all lines */
    386 	tfulldirt();
    387 }
    388 
    389 void
    390 kscrolldown(const Arg* a)
    391 {
    392 	int n = a->i;
    393 
    394 	if (!term.scr || IS_SET(MODE_ALTSCREEN))
    395 		return;
    396 
    397 	if (n < 0)
    398 		n = MAX(term.row / -n, 1);
    399 
    400 	if (n <= term.scr) {
    401 		term.scr -= n;
    402 	} else {
    403 		n = term.scr;
    404 		term.scr = 0;
    405 	}
    406 
    407 	if (sel.ob.x != -1 && !sel.alt)
    408 		selmove(-n); /* negate change in term.scr */
    409 	tfulldirt();
    410 
    411 	#if SIXEL_PATCH
    412 	scroll_images(-1*n);
    413 	#endif // SIXEL_PATCH
    414 
    415 	#if OPENURLONCLICK_PATCH
    416 	if (n > 0)
    417 		restoremousecursor();
    418 	#endif // OPENURLONCLICK_PATCH
    419 }
    420 
    421 void
    422 kscrollup(const Arg* a)
    423 {
    424 	int n = a->i;
    425 
    426 	if (!term.histf || IS_SET(MODE_ALTSCREEN))
    427 		return;
    428 
    429 	if (n < 0)
    430 		n = MAX(term.row / -n, 1);
    431 
    432 	if (term.scr + n <= term.histf) {
    433 		term.scr += n;
    434 	} else {
    435 		n = term.histf - term.scr;
    436 		term.scr = term.histf;
    437 	}
    438 
    439 	if (sel.ob.x != -1 && !sel.alt)
    440 		selmove(n); /* negate change in term.scr */
    441 	tfulldirt();
    442 
    443 	#if SIXEL_PATCH
    444 	scroll_images(n);
    445 	#endif // SIXEL_PATCH
    446 
    447 	#if OPENURLONCLICK_PATCH
    448 	if (n > 0)
    449 		restoremousecursor();
    450 	#endif // OPENURLONCLICK_PATCH
    451 }
    452 
    453 void
    454 tscrollup(int top, int bot, int n, int mode)
    455 {
    456 	#if OPENURLONCLICK_PATCH
    457 	restoremousecursor();
    458 	#endif //OPENURLONCLICK_PATCH
    459 
    460 	int i, j, s;
    461 	Line temp;
    462 	int alt = IS_SET(MODE_ALTSCREEN);
    463 	int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST;
    464 	int scr = alt ? 0 : term.scr;
    465 	#if SIXEL_PATCH
    466 	int itop = top + scr, ibot = bot + scr;
    467 	ImageList *im, *next;
    468 	#endif // SIXEL_PATCH
    469 
    470 	if (n <= 0)
    471 		return;
    472 	n = MIN(n, bot-top+1);
    473 
    474 	if (savehist) {
    475 		for (i = 0; i < n; i++) {
    476 			term.histi = (term.histi + 1) % HISTSIZE;
    477 			temp = term.hist[term.histi];
    478 			for (j = 0; j < term.col; j++)
    479 				tclearglyph(&temp[j], 1);
    480 			term.hist[term.histi] = term.line[i];
    481 			term.line[i] = temp;
    482 		}
    483 		term.histf = MIN(term.histf + n, HISTSIZE);
    484 		s = n;
    485 		if (term.scr) {
    486 			j = term.scr;
    487 			term.scr = MIN(j + n, HISTSIZE);
    488 			s = j + n - term.scr;
    489 		}
    490 		if (mode != SCROLL_RESIZE)
    491 			tfulldirt();
    492 	} else {
    493 		tclearregion(0, top, term.col-1, top+n-1, 1);
    494 		tsetdirt(top + scr, bot + scr);
    495 	}
    496 
    497 	for (i = top; i <= bot-n; i++) {
    498 		temp = term.line[i];
    499 		term.line[i] = term.line[i+n];
    500 		term.line[i+n] = temp;
    501 	}
    502 
    503 	#if SIXEL_PATCH
    504 	if (alt || !savehist) {
    505 		/* move images, if they are inside the scrolling region */
    506 		for (im = term.images; im; im = next) {
    507 			next = im->next;
    508 			if (im->y >= itop && im->y <= ibot) {
    509 				im->y -= n;
    510 				if (im->y < itop)
    511 					delete_image(im);
    512 			}
    513 		}
    514 	} else {
    515 		/* move images, if they are inside the scrolling region or scrollback */
    516 		for (im = term.images; im; im = next) {
    517 			next = im->next;
    518 			im->y -= scr;
    519 			if (im->y < 0) {
    520 				im->y -= n;
    521 			} else if (im->y >= top && im->y <= bot) {
    522 				im->y -= n;
    523 				if (im->y < top)
    524 					im->y -= top; // move to scrollback
    525 			}
    526 			if (im->y < -HISTSIZE)
    527 				delete_image(im);
    528 			else
    529 				im->y += term.scr;
    530 		}
    531 	}
    532 	#endif // SIXEL_PATCH
    533 
    534 	if (sel.ob.x != -1 && sel.alt == alt) {
    535 		if (!savehist) {
    536 			selscroll(top, bot, -n);
    537 		} else if (s > 0) {
    538 			selmove(-s);
    539 			if (-term.scr + sel.nb.y < -term.histf)
    540 				selremove();
    541 		}
    542 	}
    543 }
    544 
    545 void
    546 tscrolldown(int top, int n)
    547 {
    548 	#if OPENURLONCLICK_PATCH
    549 	restoremousecursor();
    550 	#endif //OPENURLONCLICK_PATCH
    551 
    552 	int i, bot = term.bot;
    553 	int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
    554 	int itop = top + scr, ibot = bot + scr;
    555 	Line temp;
    556 	#if SIXEL_PATCH
    557 	ImageList *im, *next;
    558 	#endif // SIXEL_PATCH
    559 
    560 	if (n <= 0)
    561 		return;
    562 	n = MIN(n, bot-top+1);
    563 
    564 	tsetdirt(top + scr, bot + scr);
    565 	tclearregion(0, bot-n+1, term.col-1, bot, 1);
    566 
    567 	for (i = bot; i >= top+n; i--) {
    568 		temp = term.line[i];
    569 		term.line[i] = term.line[i-n];
    570 		term.line[i-n] = temp;
    571 	}
    572 
    573 	#if SIXEL_PATCH
    574 	/* move images, if they are inside the scrolling region */
    575 	for (im = term.images; im; im = next) {
    576 		next = im->next;
    577 		if (im->y >= itop && im->y <= ibot) {
    578 			im->y += n;
    579 			if (im->y > ibot)
    580 				delete_image(im);
    581 		}
    582 	}
    583 	#endif // SIXEL_PATCH
    584 
    585 	if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN))
    586 		selscroll(top, bot, n);
    587 }
    588 
    589 void
    590 tresize(int col, int row)
    591 {
    592 	int *bp;
    593 
    594 	#if KEYBOARDSELECT_PATCH
    595 	if (row != term.row || col != term.col)
    596 		win.mode ^= kbds_keyboardhandler(XK_Escape, NULL, 0, 1);
    597 	#endif // KEYBOARDSELECT_PATCH
    598 
    599 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
    600 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
    601 	if (col > term.col) {
    602 		bp = term.tabs + term.col;
    603 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
    604 		while (--bp > term.tabs && !*bp)
    605 			/* nothing */ ;
    606 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
    607 			*bp = 1;
    608 	}
    609 
    610 	if (IS_SET(MODE_ALTSCREEN))
    611 		tresizealt(col, row);
    612 	else
    613 		tresizedef(col, row);
    614 }
    615 
    616 void
    617 tclearregion(int x1, int y1, int x2, int y2, int usecurattr)
    618 {
    619 	int x, y;
    620 
    621 	/* regionselected() takes relative coordinates */
    622 	if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr))
    623 		selremove();
    624 
    625 	for (y = y1; y <= y2; y++) {
    626 		term.dirty[y] = 1;
    627 		for (x = x1; x <= x2; x++)
    628 			tclearglyph(&term.line[y][x], usecurattr);
    629 	}
    630 }
    631 
    632 void
    633 tnew(int col, int row)
    634 {
    635 	int i, j;
    636 	for (i = 0; i < 2; i++) {
    637 		term.line = xmalloc(row * sizeof(Line));
    638 		for (j = 0; j < row; j++)
    639 			term.line[j] = xmalloc(col * sizeof(Glyph));
    640 		term.col = col, term.row = row;
    641 		tswapscreen();
    642 	}
    643 	term.dirty = xmalloc(row * sizeof(*term.dirty));
    644 	term.tabs = xmalloc(col * sizeof(*term.tabs));
    645 	for (i = 0; i < HISTSIZE; i++)
    646 		term.hist[i] = xmalloc(col * sizeof(Glyph));
    647 	treset();
    648 }
    649 
    650 void
    651 tdeletechar(int n)
    652 {
    653 	int src, dst, size;
    654 	Line line;
    655 
    656 	if (n <= 0)
    657 		return;
    658 	dst = term.c.x;
    659 	src = MIN(term.c.x + n, term.col);
    660 	size = term.col - src;
    661 	if (size > 0) { /* otherwise src would point beyond the array
    662 	                   https://stackoverflow.com/questions/29844298 */
    663 		line = term.line[term.c.y];
    664 		memmove(&line[dst], &line[src], size * sizeof(Glyph));
    665 	}
    666 	tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1);
    667 }
    668 
    669 void
    670 tinsertblank(int n)
    671 {
    672 	int src, dst, size;
    673 	Line line;
    674 
    675 	if (n <= 0)
    676 		return;
    677 	dst = MIN(term.c.x + n, term.col);
    678 	src = term.c.x;
    679 	size = term.col - dst;
    680 
    681 	if (size > 0) { /* otherwise dst would point beyond the array */
    682 		line = term.line[term.c.y];
    683 		memmove(&line[dst], &line[src], size * sizeof(Glyph));
    684 	}
    685 	tclearregion(src, term.c.y, dst - 1, term.c.y, 1);
    686 }
    687 
    688 int
    689 tlinelen(Line line)
    690 {
    691 	int i = term.col - 1;
    692 
    693 	/* We are using a different algorithm on the alt screen because an
    694 	 * application might use spaces to clear the screen and in that case it is
    695 	 * impossible to find the end of the line when every cell has the ATTR_SET
    696 	 * attribute. The second algorithm is more accurate on the main screen and
    697 	 * and we can use it there. */
    698 	if (IS_SET(MODE_ALTSCREEN))
    699 		for (; i >= 0 && !(line[i].mode & ATTR_WRAP) && line[i].u == ' '; i--);
    700 	else
    701 		for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--);
    702 
    703 	return i + 1;
    704 }
    705 
    706 int
    707 tiswrapped(Line line)
    708 {
    709 	int len = tlinelen(line);
    710 
    711 	return len > 0 && (line[len - 1].mode & ATTR_WRAP);
    712 }
    713 
    714 char *
    715 tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp)
    716 {
    717 	while (gp <= lgp)
    718 		if (gp->mode & ATTR_WDUMMY) {
    719 			gp++;
    720 		} else {
    721 			buf += utf8encode((gp++)->u, buf);
    722 		}
    723 	return buf;
    724 }
    725 
    726 size_t
    727 tgetline(char *buf, const Glyph *fgp)
    728 {
    729 	char *ptr;
    730 	const Glyph *lgp = &fgp[term.col - 1];
    731 
    732 	while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP)))
    733 		lgp--;
    734 	ptr = tgetglyphs(buf, fgp, lgp);
    735 	if (!(lgp->mode & ATTR_WRAP))
    736 		*(ptr++) = '\n';
    737 	return ptr - buf;
    738 }
    739 
    740 int
    741 regionselected(int x1, int y1, int x2, int y2)
    742 {
    743 	if (sel.ob.x == -1 || sel.mode == SEL_EMPTY ||
    744 	    sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1)
    745 		return 0;
    746 
    747 	return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1
    748 		: (sel.nb.y != y2 || sel.nb.x <= x2) &&
    749 		  (sel.ne.y != y1 || sel.ne.x >= x1);
    750 }
    751 
    752 int
    753 selected(int x, int y)
    754 {
    755 	return regionselected(x, y, x, y);
    756 }
    757 
    758 void
    759 selsnap(int *x, int *y, int direction)
    760 {
    761 	int newx, newy;
    762 	int rtop = 0, rbot = term.row - 1;
    763 	int delim, prevdelim, maxlen;
    764 	const Glyph *gp, *prevgp;
    765 
    766 	if (!IS_SET(MODE_ALTSCREEN))
    767 		rtop += -term.histf + term.scr, rbot += term.scr;
    768 
    769 	switch (sel.snap) {
    770 	case SNAP_WORD:
    771 		/*
    772 		 * Snap around if the word wraps around at the end or
    773 		 * beginning of a line.
    774 		 */
    775 		maxlen = (TLINE(*y)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col;
    776 		LIMIT(*x, 0, maxlen - 1);
    777 		prevgp = &TLINE(*y)[*x];
    778 		prevdelim = ISDELIM(prevgp->u);
    779 		for (;;) {
    780 			newx = *x + direction;
    781 			newy = *y;
    782 			if (!BETWEEN(newx, 0, maxlen - 1)) {
    783 				newy += direction;
    784 				if (!BETWEEN(newy, rtop, rbot))
    785 					break;
    786 
    787 				if (!tiswrapped(TLINE(direction > 0 ? *y : newy)))
    788 					break;
    789 
    790 				maxlen = (TLINE(newy)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col;
    791 				newx = direction > 0 ? 0 : maxlen - 1;
    792 			}
    793 
    794 			gp = &TLINE(newy)[newx];
    795 			delim = ISDELIM(gp->u);
    796 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
    797 					|| (delim && gp->u != prevgp->u)))
    798 				break;
    799 
    800 			*x = newx;
    801 			*y = newy;
    802 			if (!(gp->mode & ATTR_WDUMMY)) {
    803 				prevgp = gp;
    804 				prevdelim = delim;
    805 			}
    806 		}
    807 		break;
    808 	case SNAP_LINE:
    809 		/*
    810 		 * Snap around if the the previous line or the current one
    811 		 * has set ATTR_WRAP at its end. Then the whole next or
    812 		 * previous line will be selected.
    813 		 */
    814 		*x = (direction < 0) ? 0 : term.col - 1;
    815 		if (direction < 0) {
    816 			for (; *y > rtop; *y -= 1) {
    817 				if (!tiswrapped(TLINE(*y-1)))
    818 					break;
    819 			}
    820 		} else if (direction > 0) {
    821 			for (; *y < rbot; *y += 1) {
    822 				if (!tiswrapped(TLINE(*y)))
    823 					break;
    824 			}
    825 		}
    826 		break;
    827 	}
    828 }
    829 
    830 void
    831 selscroll(int top, int bot, int n)
    832 {
    833 	/* turn absolute coordinates into relative */
    834 	top += term.scr, bot += term.scr;
    835 
    836 	if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) {
    837 		selclear();
    838 	} else if (BETWEEN(sel.nb.y, top, bot)) {
    839 		selmove(n);
    840 		if (sel.nb.y < top || sel.ne.y > bot)
    841 			selclear();
    842 	}
    843 }
    844 
    845 void
    846 tswapscreen(void)
    847 {
    848 	static Line *altline;
    849 	static int altcol, altrow;
    850 	Line *tmpline = term.line;
    851 	int tmpcol = term.col, tmprow = term.row;
    852 	#if SIXEL_PATCH
    853 	ImageList *im = term.images;
    854 	#endif // SIXEL_PATCH
    855 
    856 	term.line = altline;
    857 	term.col = altcol, term.row = altrow;
    858 	altline = tmpline;
    859 	altcol = tmpcol, altrow = tmprow;
    860 	term.mode ^= MODE_ALTSCREEN;
    861 
    862 	#if SIXEL_PATCH
    863 	term.images = term.images_alt;
    864 	term.images_alt = im;
    865 	#endif // SIXEL_PATCH
    866 }
    867 
    868 char *
    869 getsel(void)
    870 {
    871 	char *str, *ptr;
    872 	int y, lastx, linelen;
    873 	const Glyph *gp, *lgp;
    874 
    875 	if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
    876 		return NULL;
    877 
    878 	str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ);
    879 	ptr = str;
    880 
    881 	/* append every set & selected glyph to the selection */
    882 	for (y = sel.nb.y; y <= sel.ne.y; y++) {
    883 		Line line = TLINE(y);
    884 
    885 		if ((linelen = tlinelen(line)) == 0) {
    886 			*ptr++ = '\n';
    887 			continue;
    888 		}
    889 
    890 		if (sel.type == SEL_RECTANGULAR) {
    891 			gp = &line[sel.nb.x];
    892 			lastx = sel.ne.x;
    893 		} else {
    894 			gp = &line[sel.nb.y == y ? sel.nb.x : 0];
    895 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
    896 		}
    897 		lgp = &line[MIN(lastx, linelen-1)];
    898 
    899 		ptr = tgetglyphs(ptr, gp, lgp);
    900 		/*
    901 		 * Copy and pasting of line endings is inconsistent
    902 		 * in the inconsistent terminal and GUI world.
    903 		 * The best solution seems like to produce '\n' when
    904 		 * something is copied from st and convert '\n' to
    905 		 * '\r', when something to be pasted is received by
    906 		 * st.
    907 		 * FIXME: Fix the computer world.
    908 		 */
    909 		if ((y < sel.ne.y || lastx >= linelen) &&
    910 		    (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
    911 			*ptr++ = '\n';
    912 	}
    913 	*ptr = '\0';
    914 	return str;
    915 }
    916 
    917 void
    918 tdumpline(int n)
    919 {
    920 	char str[(term.col + 1) * UTF_SIZ];
    921 
    922 	tprinter(str, tgetline(str, &term.line[n][0]));
    923 }