mirror of
https://github.com/mintycube/st.git
synced 2024-10-22 14:05:49 +02:00
7a581fe4e1
In the current implementation, when text is written over an image, we have to cut the entire text line out of the image, regardless of how long the text is. It doesn't look good, but it was a design choice for the following reasons: 1) To keep the sixel engine as fast as possible 2) Most applications do not write text on the images anyway To bring the st terminal in line with other terminals that support sixels, I have now improved the sixel renderer so that the images can now have gaps, which allows the text to be printed inside the images. The changes should not affect performance in normal cases. Only when the renderer has to deal with the text there might be some performance hits depending on how many gaps there are in the images.
924 lines
20 KiB
C
924 lines
20 KiB
C
void
|
|
tloaddefscreen(int clear, int loadcursor)
|
|
{
|
|
int col, row, alt = IS_SET(MODE_ALTSCREEN);
|
|
|
|
if (alt) {
|
|
if (clear) {
|
|
tclearregion(0, 0, term.col-1, term.row-1, 1);
|
|
#if SIXEL_PATCH
|
|
tdeleteimages();
|
|
#endif // SIXEL_PATCH
|
|
}
|
|
col = term.col, row = term.row;
|
|
tswapscreen();
|
|
}
|
|
if (loadcursor)
|
|
tcursor(CURSOR_LOAD);
|
|
if (alt)
|
|
tresizedef(col, row);
|
|
}
|
|
|
|
void
|
|
tloadaltscreen(int clear, int savecursor)
|
|
{
|
|
int col, row, def = !IS_SET(MODE_ALTSCREEN);
|
|
|
|
if (savecursor)
|
|
tcursor(CURSOR_SAVE);
|
|
if (def) {
|
|
col = term.col, row = term.row;
|
|
kscrolldown(&((Arg){ .i = term.scr }));
|
|
tswapscreen();
|
|
tresizealt(col, row);
|
|
}
|
|
if (clear) {
|
|
tclearregion(0, 0, term.col-1, term.row-1, 1);
|
|
#if SIXEL_PATCH
|
|
tdeleteimages();
|
|
#endif // SIXEL_PATCH
|
|
}
|
|
}
|
|
|
|
void
|
|
selmove(int n)
|
|
{
|
|
sel.ob.y += n, sel.nb.y += n;
|
|
sel.oe.y += n, sel.ne.y += n;
|
|
}
|
|
|
|
void
|
|
tclearglyph(Glyph *gp, int usecurattr)
|
|
{
|
|
if (usecurattr) {
|
|
gp->fg = term.c.attr.fg;
|
|
gp->bg = term.c.attr.bg;
|
|
} else {
|
|
gp->fg = defaultfg;
|
|
gp->bg = defaultbg;
|
|
}
|
|
gp->mode = ATTR_NULL;
|
|
gp->u = ' ';
|
|
}
|
|
|
|
#if SIXEL_PATCH
|
|
void
|
|
treflow_moveimages(int oldy, int newy)
|
|
{
|
|
ImageList *im;
|
|
|
|
for (im = term.images; im; im = im->next) {
|
|
if (im->y == oldy)
|
|
im->reflow_y = newy;
|
|
}
|
|
}
|
|
#endif // SIXEL_PATCH
|
|
|
|
void
|
|
treflow(int col, int row)
|
|
{
|
|
int i, j;
|
|
int oce, nce, bot, scr;
|
|
int ox = 0, oy = -term.histf, nx = 0, ny = -1, len;
|
|
int cy = -1; /* proxy for new y coordinate of cursor */
|
|
int buflen, nlines;
|
|
Line *buf, bufline, line;
|
|
#if SIXEL_PATCH
|
|
ImageList *im, *next;
|
|
|
|
for (im = term.images; im; im = im->next)
|
|
im->reflow_y = INT_MIN; /* unset reflow_y */
|
|
#endif // SIXEL_PATCH
|
|
|
|
/* y coordinate of cursor line end */
|
|
for (oce = term.c.y; oce < term.row - 1 &&
|
|
tiswrapped(term.line[oce]); oce++);
|
|
|
|
nlines = HISTSIZE + row;
|
|
buf = xmalloc(nlines * sizeof(Line));
|
|
do {
|
|
if (!nx && ++ny < nlines)
|
|
buf[ny] = xmalloc(col * sizeof(Glyph));
|
|
if (!ox) {
|
|
line = TLINEABS(oy);
|
|
len = tlinelen(line);
|
|
}
|
|
if (oy == term.c.y) {
|
|
if (!ox)
|
|
len = MAX(len, term.c.x + 1);
|
|
/* update cursor */
|
|
if (cy < 0 && term.c.x - ox < col - nx) {
|
|
term.c.x = nx + term.c.x - ox, cy = ny;
|
|
UPDATEWRAPNEXT(0, col);
|
|
}
|
|
}
|
|
/* get reflowed lines in buf */
|
|
bufline = buf[ny % nlines];
|
|
if (col - nx > len - ox) {
|
|
memcpy(&bufline[nx], &line[ox], (len-ox) * sizeof(Glyph));
|
|
nx += len - ox;
|
|
if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) {
|
|
for (j = nx; j < col; j++)
|
|
tclearglyph(&bufline[j], 0);
|
|
#if SIXEL_PATCH
|
|
treflow_moveimages(oy+term.scr, ny);
|
|
#endif // SIXEL_PATCH
|
|
nx = 0;
|
|
} else if (nx > 0) {
|
|
bufline[nx - 1].mode &= ~ATTR_WRAP;
|
|
}
|
|
ox = 0, oy++;
|
|
} else if (col - nx == len - ox) {
|
|
memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph));
|
|
#if SIXEL_PATCH
|
|
treflow_moveimages(oy+term.scr, ny);
|
|
#endif // SIXEL_PATCH
|
|
ox = 0, oy++, nx = 0;
|
|
} else/* if (col - nx < len - ox) */ {
|
|
memcpy(&bufline[nx], &line[ox], (col-nx) * sizeof(Glyph));
|
|
if (bufline[col - 1].mode & ATTR_WIDE) {
|
|
bufline[col - 2].mode |= ATTR_WRAP;
|
|
tclearglyph(&bufline[col - 1], 0);
|
|
ox--;
|
|
} else {
|
|
bufline[col - 1].mode |= ATTR_WRAP;
|
|
}
|
|
#if SIXEL_PATCH
|
|
treflow_moveimages(oy+term.scr, ny);
|
|
#endif // SIXEL_PATCH
|
|
ox += col - nx;
|
|
nx = 0;
|
|
}
|
|
} while (oy <= oce);
|
|
if (nx)
|
|
for (j = nx; j < col; j++)
|
|
tclearglyph(&bufline[j], 0);
|
|
|
|
/* free extra lines */
|
|
for (i = row; i < term.row; i++)
|
|
free(term.line[i]);
|
|
/* resize to new height */
|
|
term.line = xrealloc(term.line, row * sizeof(Line));
|
|
|
|
buflen = MIN(ny + 1, nlines);
|
|
bot = MIN(ny, row - 1);
|
|
scr = MAX(row - term.row, 0);
|
|
/* update y coordinate of cursor line end */
|
|
nce = MIN(oce + scr, bot);
|
|
/* update cursor y coordinate */
|
|
term.c.y = nce - (ny - cy);
|
|
if (term.c.y < 0) {
|
|
j = nce, nce = MIN(nce + -term.c.y, bot);
|
|
term.c.y += nce - j;
|
|
while (term.c.y < 0) {
|
|
free(buf[ny-- % nlines]);
|
|
buflen--;
|
|
term.c.y++;
|
|
}
|
|
}
|
|
/* allocate new rows */
|
|
for (i = row - 1; i > nce; i--) {
|
|
if (i >= term.row)
|
|
term.line[i] = xmalloc(col * sizeof(Glyph));
|
|
else
|
|
term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
|
|
for (j = 0; j < col; j++)
|
|
tclearglyph(&term.line[i][j], 0);
|
|
}
|
|
/* fill visible area */
|
|
for (/*i = nce */; i >= term.row; i--, ny--, buflen--)
|
|
term.line[i] = buf[ny % nlines];
|
|
for (/*i = term.row - 1 */; i >= 0; i--, ny--, buflen--) {
|
|
free(term.line[i]);
|
|
term.line[i] = buf[ny % nlines];
|
|
}
|
|
/* fill lines in history buffer and update term.histf */
|
|
for (/*i = -1 */; buflen > 0 && i >= -HISTSIZE; i--, ny--, buflen--) {
|
|
j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
|
|
free(term.hist[j]);
|
|
term.hist[j] = buf[ny % nlines];
|
|
}
|
|
term.histf = -i - 1;
|
|
term.scr = MIN(term.scr, term.histf);
|
|
/* resize rest of the history lines */
|
|
for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) {
|
|
j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE;
|
|
term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph));
|
|
}
|
|
|
|
#if SIXEL_PATCH
|
|
/* move images to the final position */
|
|
for (im = term.images; im; im = next) {
|
|
next = im->next;
|
|
if (im->reflow_y == INT_MIN) {
|
|
delete_image(im);
|
|
} else {
|
|
im->y = im->reflow_y - term.histf + term.scr - (ny + 1);
|
|
if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= row)
|
|
delete_image(im);
|
|
}
|
|
}
|
|
|
|
/* expand images into new text cells */
|
|
for (im = term.images; im; im = im->next) {
|
|
j = MIN(im->x + im->cols, col);
|
|
line = TLINE(im->y);
|
|
for (i = im->x; i < j; i++) {
|
|
if (!(line[i].mode & ATTR_SET))
|
|
line[i].mode |= ATTR_SIXEL;
|
|
}
|
|
}
|
|
#endif // SIXEL_PATCH
|
|
|
|
for (; buflen > 0; ny--, buflen--)
|
|
free(buf[ny % nlines]);
|
|
free(buf);
|
|
}
|
|
|
|
void
|
|
rscrolldown(int n)
|
|
{
|
|
int i;
|
|
Line temp;
|
|
|
|
/* can never be true as of now
|
|
if (IS_SET(MODE_ALTSCREEN))
|
|
return; */
|
|
|
|
if ((n = MIN(n, term.histf)) <= 0)
|
|
return;
|
|
|
|
for (i = term.c.y + n; i >= n; i--) {
|
|
temp = term.line[i];
|
|
term.line[i] = term.line[i-n];
|
|
term.line[i-n] = temp;
|
|
}
|
|
for (/*i = n - 1 */; i >= 0; i--) {
|
|
temp = term.line[i];
|
|
term.line[i] = term.hist[term.histi];
|
|
term.hist[term.histi] = temp;
|
|
term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
|
|
}
|
|
term.c.y += n;
|
|
term.histf -= n;
|
|
if ((i = term.scr - n) >= 0) {
|
|
term.scr = i;
|
|
} else {
|
|
#if SIXEL_PATCH
|
|
scroll_images(n - term.scr);
|
|
#endif // SIXEL_PATCH
|
|
term.scr = 0;
|
|
if (sel.ob.x != -1 && !sel.alt)
|
|
selmove(-i);
|
|
}
|
|
}
|
|
|
|
void
|
|
tresizedef(int col, int row)
|
|
{
|
|
int i, j;
|
|
|
|
/* return if dimensions haven't changed */
|
|
if (term.col == col && term.row == row) {
|
|
tfulldirt();
|
|
return;
|
|
}
|
|
if (col != term.col) {
|
|
if (!sel.alt)
|
|
selremove();
|
|
treflow(col, row);
|
|
} else {
|
|
/* slide screen up if otherwise cursor would get out of the screen */
|
|
if (term.c.y >= row) {
|
|
tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE);
|
|
term.c.y = row - 1;
|
|
}
|
|
for (i = row; i < term.row; i++)
|
|
free(term.line[i]);
|
|
|
|
/* resize to new height */
|
|
term.line = xrealloc(term.line, row * sizeof(Line));
|
|
/* allocate any new rows */
|
|
for (i = term.row; i < row; i++) {
|
|
term.line[i] = xmalloc(col * sizeof(Glyph));
|
|
for (j = 0; j < col; j++)
|
|
tclearglyph(&term.line[i][j], 0);
|
|
}
|
|
/* scroll down as much as height has increased */
|
|
rscrolldown(row - term.row);
|
|
}
|
|
/* update terminal size */
|
|
term.col = col, term.row = row;
|
|
/* reset scrolling region */
|
|
term.top = 0, term.bot = row - 1;
|
|
/* dirty all lines */
|
|
tfulldirt();
|
|
}
|
|
|
|
void
|
|
tresizealt(int col, int row)
|
|
{
|
|
int i, j;
|
|
#if SIXEL_PATCH
|
|
ImageList *im, *next;
|
|
#endif // SIXEL_PATCH
|
|
|
|
/* return if dimensions haven't changed */
|
|
if (term.col == col && term.row == row) {
|
|
tfulldirt();
|
|
return;
|
|
}
|
|
if (sel.alt)
|
|
selremove();
|
|
/* slide screen up if otherwise cursor would get out of the screen */
|
|
for (i = 0; i <= term.c.y - row; i++)
|
|
free(term.line[i]);
|
|
if (i > 0) {
|
|
/* ensure that both src and dst are not NULL */
|
|
memmove(term.line, term.line + i, row * sizeof(Line));
|
|
#if SIXEL_PATCH
|
|
scroll_images(-i);
|
|
#endif // SIXEL_PATCH
|
|
term.c.y = row - 1;
|
|
}
|
|
for (i += row; i < term.row; i++)
|
|
free(term.line[i]);
|
|
/* resize to new height */
|
|
term.line = xrealloc(term.line, row * sizeof(Line));
|
|
/* resize to new width */
|
|
for (i = 0; i < MIN(row, term.row); i++) {
|
|
term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
|
|
for (j = term.col; j < col; j++)
|
|
tclearglyph(&term.line[i][j], 0);
|
|
}
|
|
/* allocate any new rows */
|
|
for (/*i = MIN(row, term.row) */; i < row; i++) {
|
|
term.line[i] = xmalloc(col * sizeof(Glyph));
|
|
for (j = 0; j < col; j++)
|
|
tclearglyph(&term.line[i][j], 0);
|
|
}
|
|
/* update cursor */
|
|
if (term.c.x >= col) {
|
|
term.c.state &= ~CURSOR_WRAPNEXT;
|
|
term.c.x = col - 1;
|
|
} else {
|
|
UPDATEWRAPNEXT(1, col);
|
|
}
|
|
/* update terminal size */
|
|
term.col = col, term.row = row;
|
|
/* reset scrolling region */
|
|
term.top = 0, term.bot = row - 1;
|
|
|
|
#if SIXEL_PATCH
|
|
/* delete or clip images if they are not inside the screen */
|
|
for (im = term.images; im; im = next) {
|
|
next = im->next;
|
|
if (im->x >= term.col || im->y >= term.row || im->y < 0) {
|
|
delete_image(im);
|
|
} else {
|
|
if ((im->cols = MIN(im->x + im->cols, term.col) - im->x) <= 0)
|
|
delete_image(im);
|
|
}
|
|
}
|
|
#endif // SIXEL_PATCH
|
|
|
|
/* dirty all lines */
|
|
tfulldirt();
|
|
}
|
|
|
|
void
|
|
kscrolldown(const Arg* a)
|
|
{
|
|
int n = a->i;
|
|
|
|
if (!term.scr || IS_SET(MODE_ALTSCREEN))
|
|
return;
|
|
|
|
if (n < 0)
|
|
n = MAX(term.row / -n, 1);
|
|
|
|
if (n <= term.scr) {
|
|
term.scr -= n;
|
|
} else {
|
|
n = term.scr;
|
|
term.scr = 0;
|
|
}
|
|
|
|
if (sel.ob.x != -1 && !sel.alt)
|
|
selmove(-n); /* negate change in term.scr */
|
|
tfulldirt();
|
|
|
|
#if SIXEL_PATCH
|
|
scroll_images(-1*n);
|
|
#endif // SIXEL_PATCH
|
|
|
|
#if OPENURLONCLICK_PATCH
|
|
if (n > 0)
|
|
restoremousecursor();
|
|
#endif // OPENURLONCLICK_PATCH
|
|
}
|
|
|
|
void
|
|
kscrollup(const Arg* a)
|
|
{
|
|
int n = a->i;
|
|
|
|
if (!term.histf || IS_SET(MODE_ALTSCREEN))
|
|
return;
|
|
|
|
if (n < 0)
|
|
n = MAX(term.row / -n, 1);
|
|
|
|
if (term.scr + n <= term.histf) {
|
|
term.scr += n;
|
|
} else {
|
|
n = term.histf - term.scr;
|
|
term.scr = term.histf;
|
|
}
|
|
|
|
if (sel.ob.x != -1 && !sel.alt)
|
|
selmove(n); /* negate change in term.scr */
|
|
tfulldirt();
|
|
|
|
#if SIXEL_PATCH
|
|
scroll_images(n);
|
|
#endif // SIXEL_PATCH
|
|
|
|
#if OPENURLONCLICK_PATCH
|
|
if (n > 0)
|
|
restoremousecursor();
|
|
#endif // OPENURLONCLICK_PATCH
|
|
}
|
|
|
|
void
|
|
tscrollup(int top, int bot, int n, int mode)
|
|
{
|
|
#if OPENURLONCLICK_PATCH
|
|
restoremousecursor();
|
|
#endif //OPENURLONCLICK_PATCH
|
|
|
|
int i, j, s;
|
|
Line temp;
|
|
int alt = IS_SET(MODE_ALTSCREEN);
|
|
int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST;
|
|
int scr = alt ? 0 : term.scr;
|
|
#if SIXEL_PATCH
|
|
int itop = top + scr, ibot = bot + scr;
|
|
ImageList *im, *next;
|
|
#endif // SIXEL_PATCH
|
|
|
|
if (n <= 0)
|
|
return;
|
|
n = MIN(n, bot-top+1);
|
|
|
|
if (savehist) {
|
|
for (i = 0; i < n; i++) {
|
|
term.histi = (term.histi + 1) % HISTSIZE;
|
|
temp = term.hist[term.histi];
|
|
for (j = 0; j < term.col; j++)
|
|
tclearglyph(&temp[j], 1);
|
|
term.hist[term.histi] = term.line[i];
|
|
term.line[i] = temp;
|
|
}
|
|
term.histf = MIN(term.histf + n, HISTSIZE);
|
|
s = n;
|
|
if (term.scr) {
|
|
j = term.scr;
|
|
term.scr = MIN(j + n, HISTSIZE);
|
|
s = j + n - term.scr;
|
|
}
|
|
if (mode != SCROLL_RESIZE)
|
|
tfulldirt();
|
|
} else {
|
|
tclearregion(0, top, term.col-1, top+n-1, 1);
|
|
tsetdirt(top + scr, bot + scr);
|
|
}
|
|
|
|
for (i = top; i <= bot-n; i++) {
|
|
temp = term.line[i];
|
|
term.line[i] = term.line[i+n];
|
|
term.line[i+n] = temp;
|
|
}
|
|
|
|
#if SIXEL_PATCH
|
|
if (alt || !savehist) {
|
|
/* move images, if they are inside the scrolling region */
|
|
for (im = term.images; im; im = next) {
|
|
next = im->next;
|
|
if (im->y >= itop && im->y <= ibot) {
|
|
im->y -= n;
|
|
if (im->y < itop)
|
|
delete_image(im);
|
|
}
|
|
}
|
|
} else {
|
|
/* move images, if they are inside the scrolling region or scrollback */
|
|
for (im = term.images; im; im = next) {
|
|
next = im->next;
|
|
im->y -= scr;
|
|
if (im->y < 0) {
|
|
im->y -= n;
|
|
} else if (im->y >= top && im->y <= bot) {
|
|
im->y -= n;
|
|
if (im->y < top)
|
|
im->y -= top; // move to scrollback
|
|
}
|
|
if (im->y < -HISTSIZE)
|
|
delete_image(im);
|
|
else
|
|
im->y += term.scr;
|
|
}
|
|
}
|
|
#endif // SIXEL_PATCH
|
|
|
|
if (sel.ob.x != -1 && sel.alt == alt) {
|
|
if (!savehist) {
|
|
selscroll(top, bot, -n);
|
|
} else if (s > 0) {
|
|
selmove(-s);
|
|
if (-term.scr + sel.nb.y < -term.histf)
|
|
selremove();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
tscrolldown(int top, int n)
|
|
{
|
|
#if OPENURLONCLICK_PATCH
|
|
restoremousecursor();
|
|
#endif //OPENURLONCLICK_PATCH
|
|
|
|
int i, bot = term.bot;
|
|
int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr;
|
|
int itop = top + scr, ibot = bot + scr;
|
|
Line temp;
|
|
#if SIXEL_PATCH
|
|
ImageList *im, *next;
|
|
#endif // SIXEL_PATCH
|
|
|
|
if (n <= 0)
|
|
return;
|
|
n = MIN(n, bot-top+1);
|
|
|
|
tsetdirt(top + scr, bot + scr);
|
|
tclearregion(0, bot-n+1, term.col-1, bot, 1);
|
|
|
|
for (i = bot; i >= top+n; i--) {
|
|
temp = term.line[i];
|
|
term.line[i] = term.line[i-n];
|
|
term.line[i-n] = temp;
|
|
}
|
|
|
|
#if SIXEL_PATCH
|
|
/* move images, if they are inside the scrolling region */
|
|
for (im = term.images; im; im = next) {
|
|
next = im->next;
|
|
if (im->y >= itop && im->y <= ibot) {
|
|
im->y += n;
|
|
if (im->y > ibot)
|
|
delete_image(im);
|
|
}
|
|
}
|
|
#endif // SIXEL_PATCH
|
|
|
|
if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN))
|
|
selscroll(top, bot, n);
|
|
}
|
|
|
|
void
|
|
tresize(int col, int row)
|
|
{
|
|
int *bp;
|
|
|
|
#if KEYBOARDSELECT_PATCH
|
|
if (row != term.row || col != term.col)
|
|
win.mode ^= kbds_keyboardhandler(XK_Escape, NULL, 0, 1);
|
|
#endif // KEYBOARDSELECT_PATCH
|
|
|
|
term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
|
|
term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
|
|
if (col > term.col) {
|
|
bp = term.tabs + term.col;
|
|
memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
|
|
while (--bp > term.tabs && !*bp)
|
|
/* nothing */ ;
|
|
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
|
|
*bp = 1;
|
|
}
|
|
|
|
if (IS_SET(MODE_ALTSCREEN))
|
|
tresizealt(col, row);
|
|
else
|
|
tresizedef(col, row);
|
|
}
|
|
|
|
void
|
|
tclearregion(int x1, int y1, int x2, int y2, int usecurattr)
|
|
{
|
|
int x, y;
|
|
|
|
/* regionselected() takes relative coordinates */
|
|
if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr))
|
|
selremove();
|
|
|
|
for (y = y1; y <= y2; y++) {
|
|
term.dirty[y] = 1;
|
|
for (x = x1; x <= x2; x++)
|
|
tclearglyph(&term.line[y][x], usecurattr);
|
|
}
|
|
}
|
|
|
|
void
|
|
tnew(int col, int row)
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < 2; i++) {
|
|
term.line = xmalloc(row * sizeof(Line));
|
|
for (j = 0; j < row; j++)
|
|
term.line[j] = xmalloc(col * sizeof(Glyph));
|
|
term.col = col, term.row = row;
|
|
tswapscreen();
|
|
}
|
|
term.dirty = xmalloc(row * sizeof(*term.dirty));
|
|
term.tabs = xmalloc(col * sizeof(*term.tabs));
|
|
for (i = 0; i < HISTSIZE; i++)
|
|
term.hist[i] = xmalloc(col * sizeof(Glyph));
|
|
treset();
|
|
}
|
|
|
|
void
|
|
tdeletechar(int n)
|
|
{
|
|
int src, dst, size;
|
|
Line line;
|
|
|
|
if (n <= 0)
|
|
return;
|
|
dst = term.c.x;
|
|
src = MIN(term.c.x + n, term.col);
|
|
size = term.col - src;
|
|
if (size > 0) { /* otherwise src would point beyond the array
|
|
https://stackoverflow.com/questions/29844298 */
|
|
line = term.line[term.c.y];
|
|
memmove(&line[dst], &line[src], size * sizeof(Glyph));
|
|
}
|
|
tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1);
|
|
}
|
|
|
|
void
|
|
tinsertblank(int n)
|
|
{
|
|
int src, dst, size;
|
|
Line line;
|
|
|
|
if (n <= 0)
|
|
return;
|
|
dst = MIN(term.c.x + n, term.col);
|
|
src = term.c.x;
|
|
size = term.col - dst;
|
|
|
|
if (size > 0) { /* otherwise dst would point beyond the array */
|
|
line = term.line[term.c.y];
|
|
memmove(&line[dst], &line[src], size * sizeof(Glyph));
|
|
}
|
|
tclearregion(src, term.c.y, dst - 1, term.c.y, 1);
|
|
}
|
|
|
|
int
|
|
tlinelen(Line line)
|
|
{
|
|
int i = term.col - 1;
|
|
|
|
/* We are using a different algorithm on the alt screen because an
|
|
* application might use spaces to clear the screen and in that case it is
|
|
* impossible to find the end of the line when every cell has the ATTR_SET
|
|
* attribute. The second algorithm is more accurate on the main screen and
|
|
* and we can use it there. */
|
|
if (IS_SET(MODE_ALTSCREEN))
|
|
for (; i >= 0 && !(line[i].mode & ATTR_WRAP) && line[i].u == ' '; i--);
|
|
else
|
|
for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--);
|
|
|
|
return i + 1;
|
|
}
|
|
|
|
int
|
|
tiswrapped(Line line)
|
|
{
|
|
int len = tlinelen(line);
|
|
|
|
return len > 0 && (line[len - 1].mode & ATTR_WRAP);
|
|
}
|
|
|
|
char *
|
|
tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp)
|
|
{
|
|
while (gp <= lgp)
|
|
if (gp->mode & ATTR_WDUMMY) {
|
|
gp++;
|
|
} else {
|
|
buf += utf8encode((gp++)->u, buf);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
size_t
|
|
tgetline(char *buf, const Glyph *fgp)
|
|
{
|
|
char *ptr;
|
|
const Glyph *lgp = &fgp[term.col - 1];
|
|
|
|
while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP)))
|
|
lgp--;
|
|
ptr = tgetglyphs(buf, fgp, lgp);
|
|
if (!(lgp->mode & ATTR_WRAP))
|
|
*(ptr++) = '\n';
|
|
return ptr - buf;
|
|
}
|
|
|
|
int
|
|
regionselected(int x1, int y1, int x2, int y2)
|
|
{
|
|
if (sel.ob.x == -1 || sel.mode == SEL_EMPTY ||
|
|
sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1)
|
|
return 0;
|
|
|
|
return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1
|
|
: (sel.nb.y != y2 || sel.nb.x <= x2) &&
|
|
(sel.ne.y != y1 || sel.ne.x >= x1);
|
|
}
|
|
|
|
int
|
|
selected(int x, int y)
|
|
{
|
|
return regionselected(x, y, x, y);
|
|
}
|
|
|
|
void
|
|
selsnap(int *x, int *y, int direction)
|
|
{
|
|
int newx, newy;
|
|
int rtop = 0, rbot = term.row - 1;
|
|
int delim, prevdelim, maxlen;
|
|
const Glyph *gp, *prevgp;
|
|
|
|
if (!IS_SET(MODE_ALTSCREEN))
|
|
rtop += -term.histf + term.scr, rbot += term.scr;
|
|
|
|
switch (sel.snap) {
|
|
case SNAP_WORD:
|
|
/*
|
|
* Snap around if the word wraps around at the end or
|
|
* beginning of a line.
|
|
*/
|
|
maxlen = (TLINE(*y)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col;
|
|
LIMIT(*x, 0, maxlen - 1);
|
|
prevgp = &TLINE(*y)[*x];
|
|
prevdelim = ISDELIM(prevgp->u);
|
|
for (;;) {
|
|
newx = *x + direction;
|
|
newy = *y;
|
|
if (!BETWEEN(newx, 0, maxlen - 1)) {
|
|
newy += direction;
|
|
if (!BETWEEN(newy, rtop, rbot))
|
|
break;
|
|
|
|
if (!tiswrapped(TLINE(direction > 0 ? *y : newy)))
|
|
break;
|
|
|
|
maxlen = (TLINE(newy)[term.col-2].mode & ATTR_WRAP) ? term.col-1 : term.col;
|
|
newx = direction > 0 ? 0 : maxlen - 1;
|
|
}
|
|
|
|
gp = &TLINE(newy)[newx];
|
|
delim = ISDELIM(gp->u);
|
|
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|
|
|| (delim && gp->u != prevgp->u)))
|
|
break;
|
|
|
|
*x = newx;
|
|
*y = newy;
|
|
if (!(gp->mode & ATTR_WDUMMY)) {
|
|
prevgp = gp;
|
|
prevdelim = delim;
|
|
}
|
|
}
|
|
break;
|
|
case SNAP_LINE:
|
|
/*
|
|
* Snap around if the the previous line or the current one
|
|
* has set ATTR_WRAP at its end. Then the whole next or
|
|
* previous line will be selected.
|
|
*/
|
|
*x = (direction < 0) ? 0 : term.col - 1;
|
|
if (direction < 0) {
|
|
for (; *y > rtop; *y -= 1) {
|
|
if (!tiswrapped(TLINE(*y-1)))
|
|
break;
|
|
}
|
|
} else if (direction > 0) {
|
|
for (; *y < rbot; *y += 1) {
|
|
if (!tiswrapped(TLINE(*y)))
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
selscroll(int top, int bot, int n)
|
|
{
|
|
/* turn absolute coordinates into relative */
|
|
top += term.scr, bot += term.scr;
|
|
|
|
if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) {
|
|
selclear();
|
|
} else if (BETWEEN(sel.nb.y, top, bot)) {
|
|
selmove(n);
|
|
if (sel.nb.y < top || sel.ne.y > bot)
|
|
selclear();
|
|
}
|
|
}
|
|
|
|
void
|
|
tswapscreen(void)
|
|
{
|
|
static Line *altline;
|
|
static int altcol, altrow;
|
|
Line *tmpline = term.line;
|
|
int tmpcol = term.col, tmprow = term.row;
|
|
#if SIXEL_PATCH
|
|
ImageList *im = term.images;
|
|
#endif // SIXEL_PATCH
|
|
|
|
term.line = altline;
|
|
term.col = altcol, term.row = altrow;
|
|
altline = tmpline;
|
|
altcol = tmpcol, altrow = tmprow;
|
|
term.mode ^= MODE_ALTSCREEN;
|
|
|
|
#if SIXEL_PATCH
|
|
term.images = term.images_alt;
|
|
term.images_alt = im;
|
|
#endif // SIXEL_PATCH
|
|
}
|
|
|
|
char *
|
|
getsel(void)
|
|
{
|
|
char *str, *ptr;
|
|
int y, lastx, linelen;
|
|
const Glyph *gp, *lgp;
|
|
|
|
if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
|
|
return NULL;
|
|
|
|
str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ);
|
|
ptr = str;
|
|
|
|
/* append every set & selected glyph to the selection */
|
|
for (y = sel.nb.y; y <= sel.ne.y; y++) {
|
|
Line line = TLINE(y);
|
|
|
|
if ((linelen = tlinelen(line)) == 0) {
|
|
*ptr++ = '\n';
|
|
continue;
|
|
}
|
|
|
|
if (sel.type == SEL_RECTANGULAR) {
|
|
gp = &line[sel.nb.x];
|
|
lastx = sel.ne.x;
|
|
} else {
|
|
gp = &line[sel.nb.y == y ? sel.nb.x : 0];
|
|
lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
|
|
}
|
|
lgp = &line[MIN(lastx, linelen-1)];
|
|
|
|
ptr = tgetglyphs(ptr, gp, lgp);
|
|
/*
|
|
* Copy and pasting of line endings is inconsistent
|
|
* in the inconsistent terminal and GUI world.
|
|
* The best solution seems like to produce '\n' when
|
|
* something is copied from st and convert '\n' to
|
|
* '\r', when something to be pasted is received by
|
|
* st.
|
|
* FIXME: Fix the computer world.
|
|
*/
|
|
if ((y < sel.ne.y || lastx >= linelen) &&
|
|
(!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
|
|
*ptr++ = '\n';
|
|
}
|
|
*ptr = '\0';
|
|
return str;
|
|
}
|
|
|
|
void
|
|
tdumpline(int n)
|
|
{
|
|
char str[(term.col + 1) * UTF_SIZ];
|
|
|
|
tprinter(str, tgetline(str, &term.line[n][0]));
|
|
}
|