aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui/tui.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/tui/tui.c')
-rw-r--r--src/nvim/tui/tui.c350
1 files changed, 261 insertions, 89 deletions
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 197bbcabb5..7fae34d33f 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -3,6 +3,7 @@
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -15,37 +16,37 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/cursor_shape.h"
+#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
#include "nvim/event/stream.h"
-#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
+#include "nvim/grid_defs.h"
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
#include "nvim/macros_defs.h"
#include "nvim/main.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/strings.h"
#include "nvim/tui/input.h"
#include "nvim/tui/terminfo.h"
#include "nvim/tui/tui.h"
#include "nvim/types_defs.h"
#include "nvim/ugrid.h"
-#include "nvim/ui.h"
#include "nvim/ui_client.h"
+#include "nvim/ui_defs.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
-# include "nvim/os/tty.h"
#endif
-// Space reserved in two output buffers to make the cursor normal or invisible
-// when flushing. No existing terminal will require 32 bytes to do that.
-#define CNORM_COMMAND_MAX_SIZE 32
#define OUTBUF_SIZE 0xffff
#define TOO_MANY_EVENTS 1000000
@@ -78,7 +79,7 @@ struct TUIData {
TermInput input;
uv_loop_t write_loop;
unibi_term *ut;
- char *term; // value of $TERM
+ char *term; ///< value of $TERM
union {
uv_tty_t tty;
uv_pipe_t pipe;
@@ -136,11 +137,14 @@ struct TUIData {
int sync;
} unibi_ext;
char *space_buf;
+ size_t space_buf_len;
bool stopped;
int seen_error_exit;
int width;
int height;
bool rgb;
+ int url; ///< Index of URL currently being printed, if any
+ StringBuilder urlbuf; ///< Re-usable buffer for writing OSC 8 control sequences
};
static int got_winch = 0;
@@ -149,7 +153,10 @@ static bool cursor_style_enabled = false;
# include "tui/tui.c.generated.h"
#endif
-void tui_start(TUIData **tui_p, int *width, int *height, char **term)
+static Set(cstr_t) urls = SET_INIT;
+
+void tui_start(TUIData **tui_p, int *width, int *height, char **term, bool *rgb)
+ FUNC_ATTR_NONNULL_ALL
{
TUIData *tui = xcalloc(1, sizeof(TUIData));
tui->is_starting = true;
@@ -157,7 +164,9 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term)
tui->stopped = false;
tui->seen_error_exit = 0;
tui->loop = &main_loop;
+ tui->url = -1;
kv_init(tui->invalid_regions);
+ kv_init(tui->urlbuf);
signal_watcher_init(tui->loop, &tui->winch_handle, tui);
// TODO(bfredl): zero hl is empty, send this explicitly?
@@ -170,14 +179,14 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term)
uv_timer_init(&tui->loop->uv, &tui->startup_delay_timer);
tui->startup_delay_timer.data = tui;
- uv_timer_start(&tui->startup_delay_timer, after_startup_cb,
- 100, 0);
+ uv_timer_start(&tui->startup_delay_timer, after_startup_cb, 100, 0);
*tui_p = tui;
loop_poll_events(&main_loop, 1);
*width = tui->width;
*height = tui->height;
*term = tui->term;
+ *rgb = tui->rgb;
}
void tui_set_key_encoding(TUIData *tui)
@@ -261,6 +270,10 @@ static void tui_query_kitty_keyboard(TUIData *tui)
out(tui, S_LEN("\x1b[?u\x1b[c"));
}
+/// Enable the alternate screen and emit other control sequences to start the TUI.
+///
+/// This is also called when the TUI is resumed after being suspended. We reinitialize all state
+/// from terminfo just in case the controlling terminal has changed (#27177).
static void terminfo_start(TUIData *tui)
{
tui->scroll_region_is_full_screen = true;
@@ -335,6 +348,9 @@ static void terminfo_start(TUIData *tui)
int konsolev = konsolev_env ? (int)strtol(konsolev_env, NULL, 10)
: (konsole ? 1 : 0);
+ // truecolor support must be checked before patching/augmenting terminfo
+ tui->rgb = term_has_truecolor(tui, colorterm);
+
patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm);
augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm);
tui->can_change_scroll_region =
@@ -407,10 +423,11 @@ static void terminfo_start(TUIData *tui)
flush_buf(tui);
}
+/// Disable the alternate screen and prepare for the TUI to close.
static void terminfo_stop(TUIData *tui)
{
// Destroy output stuff
- tui_mode_change(tui, (String)STRING_INIT, SHAPE_IDX_N);
+ tui_mode_change(tui, NULL_STRING, SHAPE_IDX_N);
tui_mouse_off(tui);
unibi_out(tui, unibi_exit_attribute_mode);
// Reset cursor to normal before exiting alternate screen.
@@ -421,7 +438,7 @@ static void terminfo_stop(TUIData *tui)
tui_reset_key_encoding(tui);
// May restore old title before exiting alternate screen.
- tui_set_title(tui, (String)STRING_INIT);
+ tui_set_title(tui, NULL_STRING);
if (ui_client_exit_status == 0) {
ui_client_exit_status = tui->seen_error_exit;
}
@@ -478,7 +495,7 @@ static void tui_terminal_after_startup(TUIData *tui)
flush_buf(tui);
}
-/// stop the terminal but allow it to restart later (like after suspend)
+/// Stop the terminal but allow it to restart later (like after suspend)
static void tui_terminal_stop(TUIData *tui)
{
if (uv_is_closing((uv_handle_t *)&tui->output_handle)) {
@@ -520,7 +537,15 @@ void tui_free_all_mem(TUIData *tui)
{
ugrid_free(&tui->grid);
kv_destroy(tui->invalid_regions);
+
+ const char *url;
+ set_foreach(&urls, url, {
+ xfree((void *)url);
+ });
+ set_destroy(cstr_t, &urls);
+
kv_destroy(tui->attrs);
+ kv_destroy(tui->urlbuf);
xfree(tui->space_buf);
xfree(tui->term);
xfree(tui);
@@ -548,6 +573,10 @@ static bool attrs_differ(TUIData *tui, int id1, int id2, bool rgb)
HlAttrs a1 = kv_A(tui->attrs, (size_t)id1);
HlAttrs a2 = kv_A(tui->attrs, (size_t)id2);
+ if (a1.url != a2.url) {
+ return true;
+ }
+
if (rgb) {
return a1.rgb_fg_color != a2.rgb_fg_color
|| a1.rgb_bg_color != a2.rgb_bg_color
@@ -707,6 +736,19 @@ static void update_attrs(TUIData *tui, int attr_id)
}
}
+ if (tui->url != attrs.url) {
+ if (attrs.url >= 0) {
+ const char *url = urls.keys[attrs.url];
+ kv_size(tui->urlbuf) = 0;
+ kv_printf(tui->urlbuf, "\x1b]8;;%s\x1b\\", url);
+ out(tui, tui->urlbuf.items, kv_size(tui->urlbuf));
+ } else {
+ out(tui, S_LEN("\x1b]8;;\x1b\\"));
+ }
+
+ tui->url = attrs.url;
+ }
+
tui->default_attr = fg == -1 && bg == -1
&& !bold && !italic && !has_any_underline && !reverse && !standout
&& !strikethrough;
@@ -783,6 +825,13 @@ static void cursor_goto(TUIData *tui, int row, int col)
if (row == grid->row && col == grid->col) {
return;
}
+
+ // If an OSC 8 sequence is active terminate it before moving the cursor
+ if (tui->url >= 0) {
+ out(tui, S_LEN("\x1b]8;;\x1b\\"));
+ tui->url = -1;
+ }
+
if (0 == row && 0 == col) {
unibi_out(tui, unibi_cursor_home);
ugrid_goto(grid, row, col);
@@ -1007,10 +1056,7 @@ void tui_grid_resize(TUIData *tui, Integer g, Integer width, Integer height)
{
UGrid *grid = &tui->grid;
ugrid_resize(grid, (int)width, (int)height);
-
- xfree(tui->space_buf);
- tui->space_buf = xmalloc((size_t)width * sizeof(*tui->space_buf));
- memset(tui->space_buf, ' ', (size_t)width);
+ ensure_space_buf_size(tui, (size_t)width);
// resize might not always be followed by a clear before flush
// so clip the invalid region
@@ -1222,8 +1268,10 @@ void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, Integer endrow,
Integer endcol, Integer rows, Integer cols FUNC_ATTR_UNUSED)
{
UGrid *grid = &tui->grid;
- int top = (int)startrow, bot = (int)endrow - 1;
- int left = (int)startcol, right = (int)endcol - 1;
+ int top = (int)startrow;
+ int bot = (int)endrow - 1;
+ int left = (int)startcol;
+ int right = (int)endcol - 1;
bool fullwidth = left == 0 && right == tui->width - 1;
tui->scroll_region_is_full_screen = fullwidth
@@ -1277,11 +1325,32 @@ void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, Integer endrow,
}
}
+/// Add a URL to be used in an OSC 8 hyperlink.
+///
+/// @param tui TUIData
+/// @param url URL to add
+/// @return Index of new URL, or -1 if URL is invalid
+int32_t tui_add_url(TUIData *tui, const char *url)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (url == NULL) {
+ return -1;
+ }
+
+ MHPutStatus status;
+ uint32_t k = set_put_idx(cstr_t, &urls, url, &status);
+ if (status != kMHExisting) {
+ urls.keys[k] = xstrdup(url);
+ }
+ return (int32_t)k;
+}
+
void tui_hl_attr_define(TUIData *tui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info)
{
attrs.cterm_ae_attr = cterm_attrs.cterm_ae_attr;
attrs.cterm_fg_color = cterm_attrs.cterm_fg_color;
attrs.cterm_bg_color = cterm_attrs.cterm_bg_color;
+
kv_a(tui->attrs, (size_t)id) = attrs;
}
@@ -1298,49 +1367,19 @@ void tui_visual_bell(TUIData *tui)
void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
{
- tui->clear_attrs.rgb_fg_color = (int)rgb_fg;
- tui->clear_attrs.rgb_bg_color = (int)rgb_bg;
- tui->clear_attrs.rgb_sp_color = (int)rgb_sp;
- tui->clear_attrs.cterm_fg_color = (int)cterm_fg;
- tui->clear_attrs.cterm_bg_color = (int)cterm_bg;
+ tui->clear_attrs.rgb_fg_color = (RgbValue)rgb_fg;
+ tui->clear_attrs.rgb_bg_color = (RgbValue)rgb_bg;
+ tui->clear_attrs.rgb_sp_color = (RgbValue)rgb_sp;
+ tui->clear_attrs.cterm_fg_color = (int16_t)cterm_fg;
+ tui->clear_attrs.cterm_bg_color = (int16_t)cterm_bg;
tui->print_attr_id = -1;
invalidate(tui, 0, tui->grid.height, 0, tui->grid.width);
}
-/// Begin flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
-/// begin a synchronized update. Otherwise, hide the cursor to avoid cursor jumping.
-static void tui_flush_start(TUIData *tui)
- FUNC_ATTR_NONNULL_ALL
-{
- if (tui->sync_output && tui->unibi_ext.sync != -1) {
- UNIBI_SET_NUM_VAR(tui->params[0], 1);
- unibi_out_ext(tui, tui->unibi_ext.sync);
- } else if (!tui->is_invisible) {
- unibi_out(tui, unibi_cursor_invisible);
- tui->is_invisible = true;
- }
-}
-
-/// Finish flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
-/// end a synchronized update. Otherwise, make the cursor visible again.
-static void tui_flush_end(TUIData *tui)
- FUNC_ATTR_NONNULL_ALL
-{
- if (tui->sync_output && tui->unibi_ext.sync != -1) {
- UNIBI_SET_NUM_VAR(tui->params[0], 0);
- unibi_out_ext(tui, tui->unibi_ext.sync);
- }
- bool should_invisible = tui->busy || tui->want_invisible;
- if (tui->is_invisible && !should_invisible) {
- unibi_out(tui, unibi_cursor_normal);
- tui->is_invisible = false;
- } else if (!tui->is_invisible && should_invisible) {
- unibi_out(tui, unibi_cursor_invisible);
- tui->is_invisible = true;
- }
-}
-
+/// Flushes TUI grid state to a buffer (which is later flushed to the TTY by `flush_buf`).
+///
+/// @see flush_buf
void tui_flush(TUIData *tui)
{
UGrid *grid = &tui->grid;
@@ -1357,8 +1396,6 @@ void tui_flush(TUIData *tui)
tui_busy_stop(tui); // avoid hidden cursor
}
- tui_flush_start(tui);
-
while (kv_size(tui->invalid_regions)) {
Rect r = kv_pop(tui->invalid_regions);
assert(r.bot <= grid->height && r.right <= grid->width);
@@ -1386,8 +1423,6 @@ void tui_flush(TUIData *tui)
cursor_goto(tui, tui->row, tui->col);
- tui_flush_end(tui);
-
flush_buf(tui);
}
@@ -1399,28 +1434,28 @@ static void show_verbose_terminfo(TUIData *tui)
abort();
}
- Array chunks = ARRAY_DICT_INIT;
- Array title = ARRAY_DICT_INIT;
- ADD(title, CSTR_TO_OBJ("\n\n--- Terminal info --- {{{\n"));
- ADD(title, CSTR_TO_OBJ("Title"));
- ADD(chunks, ARRAY_OBJ(title));
- Array info = ARRAY_DICT_INIT;
+ MAXSIZE_TEMP_ARRAY(chunks, 3);
+ MAXSIZE_TEMP_ARRAY(title, 2);
+ ADD_C(title, CSTR_AS_OBJ("\n\n--- Terminal info --- {{{\n"));
+ ADD_C(title, CSTR_AS_OBJ("Title"));
+ ADD_C(chunks, ARRAY_OBJ(title));
+ MAXSIZE_TEMP_ARRAY(info, 2);
String str = terminfo_info_msg(ut, tui->term);
- ADD(info, STRING_OBJ(str));
- ADD(chunks, ARRAY_OBJ(info));
- Array end_fold = ARRAY_DICT_INIT;
- ADD(end_fold, CSTR_TO_OBJ("}}}\n"));
- ADD(end_fold, CSTR_TO_OBJ("Title"));
- ADD(chunks, ARRAY_OBJ(end_fold));
-
- Array args = ARRAY_DICT_INIT;
- ADD(args, ARRAY_OBJ(chunks));
- ADD(args, BOOLEAN_OBJ(true)); // history
- Dictionary opts = ARRAY_DICT_INIT;
- PUT(opts, "verbose", BOOLEAN_OBJ(true));
- ADD(args, DICTIONARY_OBJ(opts));
+ ADD_C(info, STRING_OBJ(str));
+ ADD_C(chunks, ARRAY_OBJ(info));
+ MAXSIZE_TEMP_ARRAY(end_fold, 2);
+ ADD_C(end_fold, CSTR_AS_OBJ("}}}\n"));
+ ADD_C(end_fold, CSTR_AS_OBJ("Title"));
+ ADD_C(chunks, ARRAY_OBJ(end_fold));
+
+ MAXSIZE_TEMP_ARRAY(args, 3);
+ ADD_C(args, ARRAY_OBJ(chunks));
+ ADD_C(args, BOOLEAN_OBJ(true)); // history
+ MAXSIZE_TEMP_DICT(opts, 1);
+ PUT_C(opts, "verbose", BOOLEAN_OBJ(true));
+ ADD_C(args, DICTIONARY_OBJ(opts));
rpc_send_event(ui_client_channel_id, "nvim_echo", args);
- api_free_array(args);
+ xfree(str.data);
}
void tui_suspend(TUIData *tui)
@@ -1440,7 +1475,7 @@ void tui_suspend(TUIData *tui)
tui_mouse_on(tui);
}
stream_set_blocking(tui->input.in_fd, false); // libuv expects this
- ui_client_attach(tui->width, tui->height, tui->term);
+ ui_client_attach(tui->width, tui->height, tui->term, tui->rgb);
#endif
}
@@ -1530,6 +1565,14 @@ void tui_option_set(TUIData *tui, String name, Object value)
}
}
+void tui_chdir(TUIData *tui, String path)
+{
+ int err = uv_chdir(path.data);
+ if (err != 0) {
+ ELOG("Failed to chdir to %s: %s", path.data, strerror(err));
+ }
+}
+
void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, Integer endcol,
Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk,
const sattr_T *attrs)
@@ -1597,11 +1640,21 @@ static void invalidate(TUIData *tui, int top, int bot, int left, int right)
}
}
+static void ensure_space_buf_size(TUIData *tui, size_t len)
+{
+ if (len > tui->space_buf_len) {
+ tui->space_buf = xrealloc(tui->space_buf, len * sizeof *tui->space_buf);
+ memset(tui->space_buf + tui->space_buf_len, ' ', len - tui->space_buf_len);
+ tui->space_buf_len = len;
+ }
+}
+
/// Tries to get the user's wanted dimensions (columns and rows) for the entire
/// application (i.e., the host terminal).
void tui_guess_size(TUIData *tui)
{
- int width = 0, height = 0;
+ int width = 0;
+ int height = 0;
// 1 - try from a system call(ioctl/TIOCGWINSZ on unix)
if (tui->out_isatty
@@ -1632,6 +1685,7 @@ void tui_guess_size(TUIData *tui)
tui->width = width;
tui->height = height;
+ ensure_space_buf_size(tui, (size_t)tui->width);
// Redraw on SIGWINCH event if size didn't change. #23411
ui_client_set_size(width, height);
@@ -1753,6 +1807,44 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name)
return -1;
}
+/// Determine if the terminal supports truecolor or not:
+///
+/// 1. If $COLORTERM is "24bit" or "truecolor", return true
+/// 2. Else, check terminfo for Tc, RGB, setrgbf, or setrgbb capabilities. If
+/// found, return true
+/// 3. Else, return false
+static bool term_has_truecolor(TUIData *tui, const char *colorterm)
+{
+ // Check $COLORTERM
+ if (strequal(colorterm, "truecolor") || strequal(colorterm, "24bit")) {
+ return true;
+ }
+
+ // Check for Tc and RGB
+ for (size_t i = 0; i < unibi_count_ext_bool(tui->ut); i++) {
+ const char *n = unibi_get_ext_bool_name(tui->ut, i);
+ if (n && (!strcmp(n, "Tc") || !strcmp(n, "RGB"))) {
+ return true;
+ }
+ }
+
+ // Check for setrgbf and setrgbb
+ bool setrgbf = false;
+ bool setrgbb = false;
+ for (size_t i = 0; i < unibi_count_ext_str(tui->ut) && (!setrgbf || !setrgbb); i++) {
+ const char *n = unibi_get_ext_str_name(tui->ut, i);
+ if (n) {
+ if (!setrgbf && !strcmp(n, "setrgbf")) {
+ setrgbf = true;
+ } else if (!setrgbb && !strcmp(n, "setrgbb")) {
+ setrgbb = true;
+ }
+ }
+ }
+
+ return setrgbf && setrgbb;
+}
+
/// Patches the terminfo records after loading from system or built-in db.
/// Several entries in terminfo are known to be deficient or outright wrong;
/// and several terminal emulators falsely announce incorrect terminal types.
@@ -2254,23 +2346,103 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in
}
}
+static bool should_invisible(TUIData *tui)
+{
+ return tui->busy || tui->want_invisible;
+}
+
+/// Write the sequence to begin flushing output to `buf`.
+/// If 'termsync' is set and the terminal supports synchronized output, begin synchronized update.
+/// Otherwise, hide the cursor to avoid cursor jumping.
+///
+/// @param buf the buffer to write the sequence to
+/// @param len the length of `buf`
+static size_t flush_buf_start(TUIData *tui, char *buf, size_t len)
+ FUNC_ATTR_NONNULL_ALL
+{
+ unibi_var_t params[9]; // Don't use tui->params[] as they may already be in use.
+
+ const char *str = NULL;
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(params[0], 1);
+ str = unibi_get_ext_str(tui->ut, (size_t)tui->unibi_ext.sync);
+ } else if (!tui->is_invisible) {
+ str = unibi_get_str(tui->ut, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
+
+ if (str == NULL) {
+ return 0;
+ }
+
+ return unibi_run(str, params, buf, len);
+}
+
+/// Write the sequence to end flushing output to `buf`.
+/// If 'termsync' is set and the terminal supports synchronized output, end synchronized update.
+/// Otherwise, make the cursor visible again.
+///
+/// @param buf the buffer to write the sequence to
+/// @param len the length of `buf`
+static size_t flush_buf_end(TUIData *tui, char *buf, size_t len)
+ FUNC_ATTR_NONNULL_ALL
+{
+ unibi_var_t params[9]; // Don't use tui->params[] as they may already be in use.
+
+ size_t offset = 0;
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(params[0], 0);
+ const char *str = unibi_get_ext_str(tui->ut, (size_t)tui->unibi_ext.sync);
+ offset = unibi_run(str, params, buf, len);
+ }
+
+ const char *str = NULL;
+ if (tui->is_invisible && !should_invisible(tui)) {
+ str = unibi_get_str(tui->ut, unibi_cursor_normal);
+ tui->is_invisible = false;
+ } else if (!tui->is_invisible && should_invisible(tui)) {
+ str = unibi_get_str(tui->ut, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
+
+ if (str != NULL) {
+ assert(len >= offset);
+ offset += unibi_run(str, params, buf + offset, len - offset);
+ }
+
+ return offset;
+}
+
+/// Flushes the rendered buffer to the TTY.
+///
+/// @see tui_flush
static void flush_buf(TUIData *tui)
{
uv_write_t req;
- uv_buf_t buf;
+ uv_buf_t bufs[3];
+ char pre[32];
+ char post[32];
- if (tui->bufpos <= 0) {
+ if (tui->bufpos <= 0 && tui->is_invisible == should_invisible(tui)) {
return;
}
- buf.base = tui->buf;
- buf.len = UV_BUF_LEN(tui->bufpos);
+ bufs[0].base = pre;
+ bufs[0].len = UV_BUF_LEN(flush_buf_start(tui, pre, sizeof(pre)));
+
+ bufs[1].base = tui->buf;
+ bufs[1].len = UV_BUF_LEN(tui->bufpos);
+
+ bufs[2].base = post;
+ bufs[2].len = UV_BUF_LEN(flush_buf_end(tui, post, sizeof(post)));
if (tui->screenshot) {
- fwrite(buf.base, buf.len, 1, tui->screenshot);
+ for (size_t i = 0; i < ARRAY_SIZE(bufs); i++) {
+ fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot);
+ }
} else {
int ret
- = uv_write(&req, (uv_stream_t *)&tui->output_handle, &buf, 1, NULL);
+ = uv_write(&req, (uv_stream_t *)&tui->output_handle, bufs, ARRAY_SIZE(bufs), NULL);
if (ret) {
ELOG("uv_write failed: %s", uv_strerror(ret));
}