summaryrefslogtreecommitdiff
path: root/st.c
diff options
context:
space:
mode:
Diffstat (limited to 'st.c')
-rw-r--r--st.c777
1 files changed, 587 insertions, 190 deletions
diff --git a/st.c b/st.c
index bba90d3..d8e347e 100644
--- a/st.c
+++ b/st.c
@@ -19,6 +19,7 @@
#include "st.h"
#include "win.h"
+#include "graphics.h"
#if defined(__linux)
#include <pty.h>
@@ -35,7 +36,10 @@
#define ESC_ARG_SIZ 16
#define STR_BUF_SIZ ESC_BUF_SIZ
#define STR_ARG_SIZ ESC_ARG_SIZ
-#define HISTSIZE 2000
+
+/* PUA character used as an image placeholder */
+#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE
+#define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE
/* macros */
#define IS_SET(flag) ((term.mode & (flag)) != 0)
@@ -43,9 +47,10 @@
#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
#define ISDELIM(u) (u && wcschr(worddelimiters, u))
-#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
- term.scr + HISTSIZE + 1) % HISTSIZE] : \
- term.line[(y) - term.scr])
+
+#define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)]
+#define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size)
+#define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)])
enum term_mode {
MODE_WRAP = 1 << 0,
@@ -113,15 +118,23 @@ typedef struct {
int alt;
} Selection;
+/* Screen lines */
+typedef struct {
+ Line* buffer; /* ring buffer */
+ int size; /* size of buffer */
+ int cur; /* start of active screen */
+ int off; /* scrollback line offset */
+ TCursor sc; /* saved cursor */
+} LineBuffer;
+
/* Internal representation of the screen */
typedef struct {
int row; /* nb row */
int col; /* nb col */
- Line *line; /* screen */
- Line *alt; /* alternate screen */
- Line hist[HISTSIZE]; /* history buffer */
- int histi; /* history index */
- int scr; /* scroll back */
+ int pixw; /* width of the text area in pixels */
+ int pixh; /* height of the text area in pixels */
+ LineBuffer screen[2]; /* screen and alternate screen */
+ int linelen; /* allocated line length */
int *dirty; /* dirtyness of lines */
TCursor c; /* cursor */
int ocx; /* old cursor col */
@@ -192,8 +205,8 @@ static void tnewline(int);
static void tputtab(int);
static void tputc(Rune);
static void treset(void);
-static void tscrollup(int, int, int);
-static void tscrolldown(int, int, int);
+static void tscrollup(int, int);
+static void tscrolldown(int, int);
static void tsetattr(const int *, int);
static void tsetchar(Rune, const Glyph *, int, int);
static void tsetdirt(int, int);
@@ -210,6 +223,8 @@ static void tdeftran(char);
static void tstrsequence(uchar);
static void drawregion(int, int, int, int);
+static void clearline(Line, Glyph, int, int);
+static Line ensureline(Line);
static void selnormalize(void);
static void selscroll(int, int);
@@ -220,7 +235,6 @@ static Rune utf8decodebyte(char, size_t *);
static char utf8encodebyte(Rune, size_t);
static size_t utf8validate(Rune *, size_t);
-static char *base64dec(const char *);
static char base64dec_getc(const char **);
static ssize_t xwrite(int, const char *, size_t);
@@ -239,6 +253,10 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+/* Converts a diacritic to a row/column/etc number. The result is 1-base, 0
+ * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */
+uint16_t diacritic_to_num(uint32_t code);
+
ssize_t
xwrite(int fd, const char *s, size_t len)
{
@@ -415,11 +433,12 @@ int
tlinelen(int y)
{
int i = term.col;
+ Line line = TLINE(y);
- if (TLINE(y)[i - 1].mode & ATTR_WRAP)
+ if (line[i - 1].mode & ATTR_WRAP)
return i;
- while (i > 0 && TLINE(y)[i - 1].u == ' ')
+ while (i > 0 && line[i - 1].u == ' ')
--i;
return i;
@@ -623,6 +642,12 @@ getsel(void)
if (gp->mode & ATTR_WDUMMY)
continue;
+ if (gp->mode & ATTR_IMAGE) {
+ // TODO: Copy diacritics as well
+ ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr);
+ continue;
+ }
+
ptr += utf8encode(gp->u, ptr);
}
@@ -722,11 +747,14 @@ sigchld(int a)
int stat;
pid_t p;
- if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
+ if ((p = waitpid(-1, &stat, WNOHANG)) < 0)
die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
- if (pid != p)
+ if (pid != p) {
+ /* reinstall sigchld handler */
+ signal(SIGCHLD, sigchld);
return;
+ }
if (WIFEXITED(stat) && WEXITSTATUS(stat))
die("child exited with status %d\n", WEXITSTATUS(stat));
@@ -810,7 +838,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args)
break;
default:
#ifdef __OpenBSD__
- if (pledge("stdio rpath tty proc", NULL) == -1)
+ if (pledge("stdio rpath tty proc exec", NULL) == -1)
die("pledge\n");
#endif
close(s);
@@ -826,7 +854,11 @@ ttyread(void)
{
static char buf[BUFSIZ];
static int buflen = 0;
- int ret, written;
+ static int already_processing = 0;
+ int ret, written = 0;
+
+ if (buflen >= LEN(buf))
+ return 0;
/* append read bytes to unprocessed bytes */
ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
@@ -838,7 +870,24 @@ ttyread(void)
die("couldn't read from shell: %s\n", strerror(errno));
default:
buflen += ret;
- written = twrite(buf, buflen, 0);
+ if (already_processing) {
+ /* Avoid recursive call to twrite() */
+ return ret;
+ }
+ already_processing = 1;
+ while (1) {
+ int buflen_before_processing = buflen;
+ written += twrite(buf + written, buflen - written, 0);
+ // If buflen changed during the call to twrite, there is
+ // new data, and we need to keep processing, otherwise
+ // we can exit. This will not loop forever because the
+ // buffer is limited, and we don't clean it in this
+ // loop, so at some point ttywrite will have to drop
+ // some data.
+ if (buflen_before_processing == buflen)
+ break;
+ }
+ already_processing = 0;
buflen -= written;
/* keep any incomplete UTF-8 byte sequence for the next call */
if (buflen > 0)
@@ -851,9 +900,6 @@ void
ttywrite(const char *s, size_t n, int may_echo)
{
const char *next;
- Arg arg = (Arg) { .i = term.scr };
-
- kscrolldown(&arg);
if (may_echo && IS_SET(MODE_ECHO))
twrite(s, n, 1);
@@ -884,6 +930,7 @@ ttywriteraw(const char *s, size_t n)
fd_set wfd, rfd;
ssize_t r;
size_t lim = 256;
+ int retries_left = 100;
/*
* Remember that we are using a pty, which might be a modem line.
@@ -892,6 +939,9 @@ ttywriteraw(const char *s, size_t n)
* FIXME: Migrate the world to Plan 9.
*/
while (n > 0) {
+ if (retries_left-- <= 0)
+ goto too_many_retries;
+
FD_ZERO(&wfd);
FD_ZERO(&rfd);
FD_SET(cmdfd, &wfd);
@@ -933,11 +983,16 @@ ttywriteraw(const char *s, size_t n)
write_error:
die("write error on tty: %s\n", strerror(errno));
+too_many_retries:
+ fprintf(stderr, "Could not write %zu bytes to tty\n", n);
}
void
ttyresize(int tw, int th)
{
+ term.pixw = tw;
+ term.pixh = th;
+
struct winsize w;
w.ws_row = term.row;
@@ -959,12 +1014,15 @@ int
tattrset(int attr)
{
int i, j;
+ int y = TLINEOFFSET(0);
for (i = 0; i < term.row-1; i++) {
+ Line line = TSCREEN.buffer[y];
for (j = 0; j < term.col-1; j++) {
- if (term.line[i][j].mode & attr)
+ if (line[j].mode & attr)
return 1;
}
+ y = (y+1) % TSCREEN.size;
}
return 0;
@@ -986,14 +1044,17 @@ void
tsetdirtattr(int attr)
{
int i, j;
+ int y = TLINEOFFSET(0);
for (i = 0; i < term.row-1; i++) {
+ Line line = TSCREEN.buffer[y];
for (j = 0; j < term.col-1; j++) {
- if (term.line[i][j].mode & attr) {
+ if (line[j].mode & attr) {
tsetdirt(i, i);
break;
}
}
+ y = (y+1) % TSCREEN.size;
}
}
@@ -1006,27 +1067,22 @@ tfulldirt(void)
void
tcursor(int mode)
{
- static TCursor c[2];
- int alt = IS_SET(MODE_ALTSCREEN);
-
if (mode == CURSOR_SAVE) {
- c[alt] = term.c;
+ TSCREEN.sc = term.c;
} else if (mode == CURSOR_LOAD) {
- term.c = c[alt];
- tmoveto(c[alt].x, c[alt].y);
+ term.c = TSCREEN.sc;
+ tmoveto(term.c.x, term.c.y);
}
}
void
treset(void)
{
- uint i;
-
- term.c = (TCursor){{
- .mode = ATTR_NULL,
- .fg = defaultfg,
- .bg = defaultbg
- }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
+ int i, j;
+ Glyph g = (Glyph){.mode = ATTR_NULL,
+ .fg = defaultfg,
+ .bg = defaultbg,
+ .decor = DECOR_DEFAULT_COLOR};
memset(term.tabs, 0, term.col * sizeof(*term.tabs));
for (i = tabspaces; i < term.col; i += tabspaces)
@@ -1038,17 +1094,38 @@ treset(void)
term.charset = 0;
for (i = 0; i < 2; i++) {
- tmoveto(0, 0);
- tcursor(CURSOR_SAVE);
- tclearregion(0, 0, term.col-1, term.row-1);
- tswapscreen();
+ term.screen[i].sc = (TCursor){{
+ .fg = defaultfg,
+ .bg = defaultbg,
+ .decor = DECOR_DEFAULT_COLOR
+ }};
+ term.screen[i].cur = 0;
+ term.screen[i].off = 0;
+ for (j = 0; j < term.row; ++j) {
+ if (term.col != term.linelen)
+ term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph));
+ clearline(term.screen[i].buffer[j], g, 0, term.col);
+ }
+ for (j = term.row; j < term.screen[i].size; ++j) {
+ free(term.screen[i].buffer[j]);
+ term.screen[i].buffer[j] = NULL;
+ }
}
+ tcursor(CURSOR_LOAD);
+ term.linelen = term.col;
+ tfulldirt();
}
void
tnew(int col, int row)
{
- term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+ int i;
+ term = (Term){};
+ term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line));
+ term.screen[0].size = HISTSIZE;
+ term.screen[1].buffer = NULL;
+ for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL;
+
tresize(col, row);
treset();
}
@@ -1056,104 +1133,108 @@ tnew(int col, int row)
void
tswapscreen(void)
{
- Line *tmp = term.line;
-
- term.line = term.alt;
- term.alt = tmp;
term.mode ^= MODE_ALTSCREEN;
tfulldirt();
}
void
-kscrolldown(const Arg* a)
+kscrollup(const Arg *a)
{
int n = a->i;
- if (n < 0)
- n = term.row + n;
-
- if (n > term.scr)
- n = term.scr;
+ if (IS_SET(MODE_ALTSCREEN))
+ return;
- if (term.scr > 0) {
- term.scr -= n;
- selscroll(0, -n);
- tfulldirt();
- }
+ if (n < 0) n = (-n) * term.row;
+ if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off;
+ while (!TLINE(-n)) --n;
+ TSCREEN.off += n;
+ selscroll(0, n);
+ tfulldirt();
}
void
-kscrollup(const Arg* a)
+kscrolldown(const Arg *a)
{
+
int n = a->i;
- if (n < 0)
- n = term.row + n;
+ if (IS_SET(MODE_ALTSCREEN))
+ return;
- if (term.scr <= HISTSIZE-n) {
- term.scr += n;
- selscroll(0, n);
- tfulldirt();
- }
+ if (n < 0) n = (-n) * term.row;
+ if (n > TSCREEN.off) n = TSCREEN.off;
+ TSCREEN.off -= n;
+ selscroll(0, -n);
+ tfulldirt();
}
void
-tscrolldown(int orig, int n, int copyhist)
+tscrolldown(int orig, int n)
{
int i;
Line temp;
LIMIT(n, 0, term.bot-orig+1);
- if (copyhist) {
- term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
- temp = term.hist[term.histi];
- term.hist[term.histi] = term.line[term.bot];
- term.line[term.bot] = temp;
+ /* Ensure that lines are allocated */
+ for (i = -n; i < 0; i++) {
+ TLINE(i) = ensureline(TLINE(i));
}
- tsetdirt(orig, term.bot-n);
- tclearregion(0, term.bot-n+1, term.col-1, term.bot);
-
- for (i = term.bot; i >= orig+n; i--) {
- temp = term.line[i];
- term.line[i] = term.line[i-n];
- term.line[i-n] = temp;
+ /* Shift non-scrolling areas in ring buffer */
+ for (i = term.bot+1; i < term.row; i++) {
+ temp = TLINE(i);
+ TLINE(i) = TLINE(i-n);
+ TLINE(i-n) = temp;
+ }
+ for (i = 0; i < orig; i++) {
+ temp = TLINE(i);
+ TLINE(i) = TLINE(i-n);
+ TLINE(i-n) = temp;
}
- if (term.scr == 0)
- selscroll(orig, n);
+ /* Scroll buffer */
+ TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size;
+ /* Clear lines that have entered the view */
+ tclearregion(0, orig, term.linelen-1, orig+n-1);
+ /* Redraw portion of the screen that has scrolled */
+ tsetdirt(orig+n-1, term.bot);
+ selscroll(orig, n);
}
void
-tscrollup(int orig, int n, int copyhist)
+tscrollup(int orig, int n)
{
int i;
Line temp;
LIMIT(n, 0, term.bot-orig+1);
- if (copyhist) {
- term.histi = (term.histi + 1) % HISTSIZE;
- temp = term.hist[term.histi];
- term.hist[term.histi] = term.line[orig];
- term.line[orig] = temp;
+ /* Ensure that lines are allocated */
+ for (i = term.row; i < term.row + n; i++) {
+ TLINE(i) = ensureline(TLINE(i));
}
- if (term.scr > 0 && term.scr < HISTSIZE)
- term.scr = MIN(term.scr + n, HISTSIZE-1);
-
- tclearregion(0, orig, term.col-1, orig+n-1);
- tsetdirt(orig+n, term.bot);
-
- for (i = orig; i <= term.bot-n; i++) {
- temp = term.line[i];
- term.line[i] = term.line[i+n];
- term.line[i+n] = temp;
+ /* Shift non-scrolling areas in ring buffer */
+ for (i = orig-1; i >= 0; i--) {
+ temp = TLINE(i);
+ TLINE(i) = TLINE(i+n);
+ TLINE(i+n) = temp;
+ }
+ for (i = term.row-1; i >term.bot; i--) {
+ temp = TLINE(i);
+ TLINE(i) = TLINE(i+n);
+ TLINE(i+n) = temp;
}
- if (term.scr == 0)
- selscroll(orig, -n);
+ /* Scroll buffer */
+ TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size;
+ /* Clear lines that have entered the view */
+ tclearregion(0, term.bot-n+1, term.linelen-1, term.bot);
+ /* Redraw portion of the screen that has scrolled */
+ tsetdirt(orig, term.bot-n+1);
+ selscroll(orig, -n);
}
void
@@ -1182,7 +1263,7 @@ tnewline(int first_col)
int y = term.c.y;
if (y == term.bot) {
- tscrollup(term.top, 1, 1);
+ tscrollup(term.top, 1);
} else {
y++;
}
@@ -1194,6 +1275,7 @@ csiparse(void)
{
char *p = csiescseq.buf, *np;
long int v;
+ int sep = ';'; /* colon or semi-colon, but not both */
csiescseq.narg = 0;
if (*p == '?') {
@@ -1211,7 +1293,9 @@ csiparse(void)
v = -1;
csiescseq.arg[csiescseq.narg++] = v;
p = np;
- if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
+ if (sep == ';' && *p == ':')
+ sep = ':'; /* allow override to colon once */
+ if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
break;
p++;
}
@@ -1256,6 +1340,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
};
+ Line line = TLINE(y);
/*
* The table is proudly stolen from rxvt.
@@ -1264,25 +1349,43 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
- if (term.line[y][x].mode & ATTR_WIDE) {
+ if (line[x].mode & ATTR_WIDE) {
if (x+1 < term.col) {
- term.line[y][x+1].u = ' ';
- term.line[y][x+1].mode &= ~ATTR_WDUMMY;
+ line[x+1].u = ' ';
+ line[x+1].mode &= ~ATTR_WDUMMY;
}
- } else if (term.line[y][x].mode & ATTR_WDUMMY) {
- term.line[y][x-1].u = ' ';
- term.line[y][x-1].mode &= ~ATTR_WIDE;
+ } else if (line[x].mode & ATTR_WDUMMY) {
+ line[x-1].u = ' ';
+ line[x-1].mode &= ~ATTR_WIDE;
+ }
+
+ if (u == ' ' && line[x].mode & ATTR_IMAGE &&
+ tgetisclassicplaceholder(&line[x])) {
+ // This is a workaround: don't overwrite classic placement
+ // placeholders with space symbols (unlike Unicode placeholders
+ // which must be overwritten by anything).
+ line[x].bg = attr->bg;
+ term.dirty[y] = 1;
+ return;
}
term.dirty[y] = 1;
- term.line[y][x] = *attr;
- term.line[y][x].u = u;
+ line[x] = *attr;
+ line[x].u = u;
+
+ if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) {
+ line[x].u = 0;
+ line[x].mode |= ATTR_IMAGE;
+ }
+
+ if (isboxdraw(u))
+ line[x].mode |= ATTR_BOXDRAW;
}
void
tclearregion(int x1, int y1, int x2, int y2)
{
- int x, y, temp;
+ int x, y, L, S, temp;
Glyph *gp;
if (x1 > x2)
@@ -1290,22 +1393,122 @@ tclearregion(int x1, int y1, int x2, int y2)
if (y1 > y2)
temp = y1, y1 = y2, y2 = temp;
- LIMIT(x1, 0, term.col-1);
- LIMIT(x2, 0, term.col-1);
+ LIMIT(x1, 0, term.linelen-1);
+ LIMIT(x2, 0, term.linelen-1);
LIMIT(y1, 0, term.row-1);
LIMIT(y2, 0, term.row-1);
+ L = TLINEOFFSET(y1);
for (y = y1; y <= y2; y++) {
term.dirty[y] = 1;
for (x = x1; x <= x2; x++) {
- gp = &term.line[y][x];
+ gp = &TSCREEN.buffer[L][x];
if (selected(x, y))
selclear();
gp->fg = term.c.attr.fg;
gp->bg = term.c.attr.bg;
+ gp->decor = term.c.attr.decor;
gp->mode = 0;
gp->u = ' ';
}
+ L = (L + 1) % TSCREEN.size;
+ }
+}
+
+/// Fills a rectangle area with an image placeholder. The starting point is the
+/// cursor. Adds empty lines if needed. The placeholder will be marked as
+/// classic.
+void tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, int cols,
+ int rows, char do_not_move_cursor,
+ Glyph *text_underneath) {
+ for (int row = 0; row < rows; ++row) {
+ int y = term.c.y;
+ term.dirty[y] = 1;
+ for (int col = 0; col < cols; ++col) {
+ int x = term.c.x + col;
+ if (x >= term.col)
+ break;
+ Glyph *gp = &TLINE(y)[x];
+ if (selected(x, y))
+ selclear();
+ if (text_underneath) {
+ Glyph *to_save = gp;
+ // If there is already a classic placeholder,
+ // use the text underneath it. This will leave
+ // holes in images, but at least we are
+ // guaranteed to restore the original text.
+ if (gp->mode & ATTR_IMAGE &&
+ tgetisclassicplaceholder(gp)) {
+ Glyph *under =
+ gr_get_glyph_underneath_image(
+ tgetimgid(gp),
+ tgetimgplacementid(gp),
+ tgetimgcol(gp),
+ tgetimgrow(gp));
+ if (under)
+ to_save = under;
+ }
+ text_underneath[cols * row + col] = *to_save;
+ }
+ gp->mode = ATTR_IMAGE;
+ gp->u = 0;
+ tsetimgrow(gp, row + 1);
+ tsetimgcol(gp, col + 1);
+ tsetimgid(gp, image_id);
+ tsetimgplacementid(gp, placement_id);
+ tsetimgdiacriticcount(gp, 3);
+ tsetisclassicplaceholder(gp, 1);
+ }
+ // If moving the cursor is not allowed and this is the last line
+ // of the terminal, we are done.
+ if (do_not_move_cursor && y == term.row - 1)
+ break;
+ // Move the cursor down, maybe creating a new line. The x is
+ // preserved (we never change term.c.x in the loop above).
+ if (row != rows - 1)
+ tnewline(/*first_col=*/0);
+ }
+ if (do_not_move_cursor) {
+ // Return the cursor to the original position.
+ tmoveto(term.c.x, term.c.y - rows + 1);
+ } else {
+ // Move the cursor beyond the last column, as required by the
+ // protocol. If the cursor goes beyond the screen edge, insert a
+ // newline to match the behavior of kitty.
+ if (term.c.x + cols >= term.col)
+ tnewline(/*first_col=*/1);
+ else
+ tmoveto(term.c.x + cols, term.c.y);
+ }
+}
+
+void gr_for_each_image_cell(int (*callback)(void *data, Glyph *gp),
+ void *data) {
+ for (int row = 0; row < term.row; ++row) {
+ for (int col = 0; col < term.col; ++col) {
+ Glyph *gp = &TLINE(row)[col];
+ if (gp->mode & ATTR_IMAGE) {
+ if (callback(data, gp))
+ term.dirty[row] = 1;
+ }
+ }
+ }
+}
+
+void gr_schedule_image_redraw_by_id(uint32_t image_id) {
+ for (int row = 0; row < term.row; ++row) {
+ if (term.dirty[row])
+ continue;
+ for (int col = 0; col < term.col; ++col) {
+ Glyph *gp = &TLINE(row)[col];
+ if (gp->mode & ATTR_IMAGE) {
+ uint32_t cell_image_id = tgetimgid(gp);
+ if (cell_image_id == image_id) {
+ term.dirty[row] = 1;
+ break;
+ }
+ }
+ }
}
}
@@ -1320,7 +1523,7 @@ tdeletechar(int n)
dst = term.c.x;
src = term.c.x + n;
size = term.col - src;
- line = term.line[term.c.y];
+ line = TLINE(term.c.y);
memmove(&line[dst], &line[src], size * sizeof(Glyph));
tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
@@ -1337,7 +1540,7 @@ tinsertblank(int n)
dst = term.c.x + n;
src = term.c.x;
size = term.col - dst;
- line = term.line[term.c.y];
+ line = TLINE(term.c.y);
memmove(&line[dst], &line[src], size * sizeof(Glyph));
tclearregion(src, term.c.y, dst - 1, term.c.y);
@@ -1347,14 +1550,14 @@ void
tinsertblankline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
- tscrolldown(term.c.y, n, 0);
+ tscrolldown(term.c.y, n);
}
void
tdeleteline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
- tscrollup(term.c.y, n, 0);
+ tscrollup(term.c.y, n);
}
int32_t
@@ -1427,6 +1630,7 @@ tsetattr(const int *attr, int l)
ATTR_STRUCK );
term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg;
+ term.c.attr.decor = DECOR_DEFAULT_COLOR;
break;
case 1:
term.c.attr.mode |= ATTR_BOLD;
@@ -1439,6 +1643,20 @@ tsetattr(const int *attr, int l)
break;
case 4:
term.c.attr.mode |= ATTR_UNDERLINE;
+ if (i + 1 < l) {
+ idx = attr[++i];
+ if (BETWEEN(idx, 1, 5)) {
+ tsetdecorstyle(&term.c.attr, idx);
+ } else if (idx == 0) {
+ term.c.attr.mode &= ~ATTR_UNDERLINE;
+ tsetdecorstyle(&term.c.attr, 0);
+ } else {
+ fprintf(stderr,
+ "erresc: unknown underline "
+ "style %d\n",
+ idx);
+ }
+ }
break;
case 5: /* slow blink */
/* FALLTHROUGH */
@@ -1462,6 +1680,7 @@ tsetattr(const int *attr, int l)
break;
case 24:
term.c.attr.mode &= ~ATTR_UNDERLINE;
+ tsetdecorstyle(&term.c.attr, 0);
break;
case 25:
term.c.attr.mode &= ~ATTR_BLINK;
@@ -1489,6 +1708,13 @@ tsetattr(const int *attr, int l)
case 49:
term.c.attr.bg = defaultbg;
break;
+ case 58:
+ if ((idx = tdefcolor(attr, &i, l)) >= 0)
+ tsetdecorcolor(&term.c.attr, idx);
+ break;
+ case 59:
+ tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR);
+ break;
default:
if (BETWEEN(attr[i], 30, 37)) {
term.c.attr.fg = attr[i] - 30;
@@ -1764,7 +1990,7 @@ csihandle(void)
}
break;
case 1: /* above */
- if (term.c.y > 1)
+ if (term.c.y > 0)
tclearregion(0, 0, term.col-1, term.c.y-1);
tclearregion(0, term.c.y, term.c.x, term.c.y);
break;
@@ -1792,11 +2018,11 @@ csihandle(void)
case 'S': /* SU -- Scroll <n> line up */
if (csiescseq.priv) break;
DEFAULT(csiescseq.arg[0], 1);
- tscrollup(term.top, csiescseq.arg[0], 0);
+ tscrollup(term.top, csiescseq.arg[0]);
break;
case 'T': /* SD -- Scroll <n> line down */
DEFAULT(csiescseq.arg[0], 1);
- tscrolldown(term.top, csiescseq.arg[0], 0);
+ tscrolldown(term.top, csiescseq.arg[0]);
break;
case 'L': /* IL -- Insert <n> blank lines */
DEFAULT(csiescseq.arg[0], 1);
@@ -1860,7 +2086,11 @@ csihandle(void)
tcursor(CURSOR_SAVE);
break;
case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
- tcursor(CURSOR_LOAD);
+ if (csiescseq.priv) {
+ goto unknown;
+ } else {
+ tcursor(CURSOR_LOAD);
+ }
break;
case ' ':
switch (csiescseq.mode[1]) {
@@ -1872,6 +2102,39 @@ csihandle(void)
goto unknown;
}
break;
+ case '>':
+ switch (csiescseq.mode[1]) {
+ case 'q': /* XTVERSION -- Print terminal name and version */
+ len = snprintf(buf, sizeof(buf),
+ "\033P>|st-graphics(%s)\033\\", VERSION);
+ ttywrite(buf, len, 0);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 't': /* XTWINOPS -- Window manipulation */
+ switch (csiescseq.arg[0]) {
+ case 14: /* Report text area size in pixels. */
+ len = snprintf(buf, sizeof(buf), "\033[4;%i;%it",
+ term.pixh, term.pixw);
+ ttywrite(buf, len, 0);
+ break;
+ case 16: /* Report character cell size in pixels. */
+ len = snprintf(buf, sizeof(buf), "\033[6;%i;%it",
+ term.pixh / term.row,
+ term.pixw / term.col);
+ ttywrite(buf, len, 0);
+ break;
+ case 18: /* Report the size of the text area in characters. */
+ len = snprintf(buf, sizeof(buf), "\033[8;%i;%it",
+ term.row, term.col);
+ ttywrite(buf, len, 0);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
}
}
@@ -2021,8 +2284,27 @@ strhandle(void)
case 'k': /* old title set compatibility */
xsettitle(strescseq.args[0]);
return;
- case 'P': /* DCS -- Device Control String */
case '_': /* APC -- Application Program Command */
+ if (gr_parse_command(strescseq.buf, strescseq.len)) {
+ GraphicsCommandResult *res = &graphics_command_result;
+ if (res->create_placeholder) {
+ tcreateimgplaceholder(
+ res->placeholder.image_id,
+ res->placeholder.placement_id,
+ res->placeholder.columns,
+ res->placeholder.rows,
+ res->placeholder.do_not_move_cursor,
+ res->placeholder.text_underneath);
+ }
+ if (res->response[0])
+ ttywrite(res->response, strlen(res->response),
+ 0);
+ if (res->redraw)
+ tfulldirt();
+ return;
+ }
+ return;
+ case 'P': /* DCS -- Device Control String */
case '^': /* PM -- Privacy Message */
return;
}
@@ -2054,6 +2336,59 @@ strparse(void)
}
void
+externalpipe(const Arg *arg)
+{
+ int to[2];
+ char buf[UTF_SIZ];
+ void (*oldsigpipe)(int);
+ Glyph *bp, *end;
+ int lastpos, n, newline;
+
+ if (pipe(to) == -1)
+ return;
+
+ switch (fork()) {
+ case -1:
+ close(to[0]);
+ close(to[1]);
+ return;
+ case 0:
+ dup2(to[0], STDIN_FILENO);
+ close(to[0]);
+ close(to[1]);
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]);
+ perror("failed");
+ exit(0);
+ }
+
+ close(to[0]);
+ /* ignore sigpipe for now, in case child exists early */
+ oldsigpipe = signal(SIGPIPE, SIG_IGN);
+ newline = 0;
+ for (n = 0; n < term.row; n++) {
+ bp = TLINE(n);
+ lastpos = MIN(tlinelen(n) + 1, term.col) - 1;
+ if (lastpos < 0)
+ break;
+ end = &bp[lastpos + 1];
+ for (; bp < end; ++bp)
+ if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
+ break;
+ if ((newline = TLINE(n)[lastpos].mode & ATTR_WRAP))
+ continue;
+ if (xwrite(to[1], "\n", 1) < 0)
+ break;
+ newline = 0;
+ }
+ if (newline)
+ (void)xwrite(to[1], "\n", 1);
+ close(to[1]);
+ /* restore */
+ signal(SIGPIPE, oldsigpipe);
+}
+
+void
strdump(void)
{
size_t i;
@@ -2141,7 +2476,7 @@ tdumpline(int n)
char buf[UTF_SIZ];
const Glyph *bp, *end;
- bp = &term.line[n][0];
+ bp = &TLINE(n)[0];
end = &bp[MIN(tlinelen(n), term.col) - 1];
if (bp != end || bp->u != ' ') {
for ( ; bp <= end; ++bp)
@@ -2368,7 +2703,7 @@ eschandle(uchar ascii)
return 0;
case 'D': /* IND -- Linefeed */
if (term.c.y == term.bot) {
- tscrollup(term.top, 1, 1);
+ tscrollup(term.top, 1);
} else {
tmoveto(term.c.x, term.c.y+1);
}
@@ -2381,7 +2716,7 @@ eschandle(uchar ascii)
break;
case 'M': /* RI -- Reverse index */
if (term.c.y == term.top) {
- tscrolldown(term.top, 1, 1);
+ tscrolldown(term.top, 1);
} else {
tmoveto(term.c.x, term.c.y-1);
}
@@ -2528,11 +2863,46 @@ check_control_code:
if (selected(term.c.x, term.c.y))
selclear();
- gp = &term.line[term.c.y][term.c.x];
+ // wcwidth is broken on some systems, set the width to 0 if it's a known
+ // diacritic used for images.
+ uint16_t num = diacritic_to_num(u);
+ if (num != 0)
+ width = 0;
+ // Set the width to 1 if it's an image placeholder character.
+ if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD)
+ width = 1;
+
+ if (width == 0) {
+ // It's probably a combining char. Combining characters are not
+ // supported, so we just ignore them, unless it denotes the row and
+ // column of an image character.
+ if (term.c.y <= 0 && term.c.x <= 0)
+ return;
+ else if (term.c.x == 0)
+ gp = &TLINE(term.c.y-1)[term.col-1];
+ else if (term.c.state & CURSOR_WRAPNEXT)
+ gp = &TLINE(term.c.y)[term.c.x];
+ else
+ gp = &TLINE(term.c.y)[term.c.x-1];
+ if (num && (gp->mode & ATTR_IMAGE)) {
+ unsigned diaccount = tgetimgdiacriticcount(gp);
+ if (diaccount == 0)
+ tsetimgrow(gp, num);
+ else if (diaccount == 1)
+ tsetimgcol(gp, num);
+ else if (diaccount == 2)
+ tsetimg4thbyteplus1(gp, num);
+ tsetimgdiacriticcount(gp, diaccount + 1);
+ }
+ term.lastc = u;
+ return;
+ }
+
+ gp = &TLINE(term.c.y)[term.c.x];
if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
gp->mode |= ATTR_WRAP;
tnewline(1);
- gp = &term.line[term.c.y][term.c.x];
+ gp = &TLINE(term.c.y)[term.c.x];
}
if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
@@ -2545,7 +2915,7 @@ check_control_code:
tnewline(1);
else
tmoveto(term.col - width, term.c.y);
- gp = &term.line[term.c.y][term.c.x];
+ gp = &TLINE(term.c.y)[term.c.x];
}
tsetchar(u, &term.c.attr, term.c.x, term.c.y);
@@ -2576,6 +2946,11 @@ twrite(const char *buf, int buflen, int show_ctrl)
Rune u;
int n;
+ if (TSCREEN.off) {
+ TSCREEN.off = 0;
+ tfulldirt();
+ }
+
for (n = 0; n < buflen; n += charsize) {
if (IS_SET(MODE_UTF8)) {
/* process a complete utf8 char */
@@ -2602,64 +2977,85 @@ twrite(const char *buf, int buflen, int show_ctrl)
}
void
+clearline(Line line, Glyph g, int x, int xend)
+{
+ int i;
+ g.mode = 0;
+ g.u = ' ';
+ for (i = x; i < xend; ++i) {
+ line[i] = g;
+ }
+}
+
+Line
+ensureline(Line line)
+{
+ if (!line) {
+ line = xmalloc(term.linelen * sizeof(Glyph));
+ }
+ return line;
+}
+
+void
tresize(int col, int row)
{
int i, j;
int minrow = MIN(row, term.row);
int mincol = MIN(col, term.col);
+ int linelen = MAX(col, term.linelen);
int *bp;
- TCursor c;
- if (col < 1 || row < 1) {
+ if (col < 1 || row < 1 || row > HISTSIZE) {
fprintf(stderr,
"tresize: error resizing to %dx%d\n", col, row);
return;
}
- /*
- * slide screen to keep cursor where we expect it -
- * tscrollup would work here, but we can optimize to
- * memmove because we're freeing the earlier lines
- */
- for (i = 0; i <= term.c.y - row; i++) {
- free(term.line[i]);
- free(term.alt[i]);
+ /* Shift buffer to keep the cursor where we expect it */
+ if (row <= term.c.y) {
+ term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
+ }
+
+ /* Resize and clear line buffers as needed */
+ if (linelen > term.linelen) {
+ for (i = 0; i < term.screen[0].size; ++i) {
+ if (term.screen[0].buffer[i]) {
+ term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph));
+ clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen);
+ }
+ }
+ for (i = 0; i < minrow; ++i) {
+ term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph));
+ clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen);
+ }
+ }
+ /* Allocate all visible lines for regular line buffer */
+ for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size)
+ {
+ if (!term.screen[0].buffer[j]) {
+ term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph));
+ }
+ if (i >= term.row) {
+ clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen);
+ }
}
- /* ensure that both src and dst are not NULL */
- if (i > 0) {
- memmove(term.line, term.line + i, row * sizeof(Line));
- memmove(term.alt, term.alt + i, row * sizeof(Line));
+ /* Resize alt screen */
+ term.screen[1].cur = 0;
+ term.screen[1].size = row;
+ for (i = row; i < term.row; ++i) {
+ free(term.screen[1].buffer[i]);
}
- for (i += row; i < term.row; i++) {
- free(term.line[i]);
- free(term.alt[i]);
+ term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line));
+ for (i = term.row; i < row; ++i) {
+ term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph));
+ clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen);
}
/* resize to new height */
- term.line = xrealloc(term.line, row * sizeof(Line));
- term.alt = xrealloc(term.alt, row * sizeof(Line));
term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
- for (i = 0; i < HISTSIZE; i++) {
- term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
- for (j = mincol; j < col; j++) {
- term.hist[i][j] = term.c.attr;
- term.hist[i][j].u = ' ';
- }
- }
-
- /* resize each row to new width, zero-pad if needed */
- for (i = 0; i < minrow; i++) {
- term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
- term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
- }
-
- /* allocate any new rows */
- for (/* i = minrow */; i < row; i++) {
- term.line[i] = xmalloc(col * sizeof(Glyph));
- term.alt[i] = xmalloc(col * sizeof(Glyph));
- }
+ /* fix tabstops */
if (col > term.col) {
bp = term.tabs + term.col;
@@ -2669,26 +3065,16 @@ tresize(int col, int row)
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
*bp = 1;
}
+
/* update terminal size */
term.col = col;
term.row = row;
+ term.linelen = linelen;
/* reset scrolling region */
tsetscroll(0, row-1);
/* make use of the LIMIT in tmoveto */
tmoveto(term.c.x, term.c.y);
- /* Clearing both screens (it makes dirty all lines) */
- c = term.c;
- for (i = 0; i < 2; i++) {
- if (mincol < col && 0 < minrow) {
- tclearregion(mincol, 0, col - 1, minrow - 1);
- }
- if (0 < col && minrow < row) {
- tclearregion(0, minrow, col - 1, row - 1);
- }
- tswapscreen();
- tcursor(CURSOR_LOAD);
- }
- term.c = c;
+ tfulldirt();
}
void
@@ -2700,15 +3086,20 @@ resettitle(void)
void
drawregion(int x1, int y1, int x2, int y2)
{
- int y;
+ int y, L;
- for (y = y1; y < y2; y++) {
- if (!term.dirty[y])
- continue;
+ xstartimagedraw(term.dirty, term.row);
- term.dirty[y] = 0;
- xdrawline(TLINE(y), x1, y, x2);
+ L = TLINEOFFSET(y1);
+ for (y = y1; y < y2; y++) {
+ if (term.dirty[y]) {
+ term.dirty[y] = 0;
+ xdrawline(TSCREEN.buffer[L], x1, y, x2);
+ }
+ L = (L + 1) % TSCREEN.size;
}
+
+ xfinishimagedraw();
}
void
@@ -2722,16 +3113,16 @@ draw(void)
/* adjust cursor position */
LIMIT(term.ocx, 0, term.col-1);
LIMIT(term.ocy, 0, term.row-1);
- if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
+ if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY)
term.ocx--;
- if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
+ if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY)
cx--;
drawregion(0, 0, term.col, term.row);
- if (term.scr == 0)
- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
- term.ocx, term.ocy, term.line[term.ocy][term.ocx],
- term.line[term.ocy], term.col);
+ if (TSCREEN.off == 0)
+ xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
+ term.ocx, term.ocy, TLINE(term.ocy)[term.ocx],
+ TLINE(term.ocy), term.col);
term.ocx = cx;
term.ocy = term.c.y;
xfinishdraw();
@@ -2745,3 +3136,9 @@ redraw(void)
tfulldirt();
draw();
}
+
+Glyph
+getglyphat(int col, int row)
+{
+ return TLINE(row)[col];
+}