xF: deduplicate transcript logic
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success

After the last commit it got a bit ridiculous.
This commit is contained in:
2026-08-07 15:05:21 +02:00
parent f6a41d7c51
commit 087091577c
+149 -162
View File
@@ -83,7 +83,7 @@ struct buffer_line
bool is_unimportant;
bool is_highlight;
enum relay_rendition rendition;
uint64_t when; ///< Unix timestamp in milliseconds
uint64_t when; ///< Unix timestamp in ms, for log = 0
struct buffer_line_item *items;
struct buffer_line_item *items_tail;
@@ -2687,6 +2687,15 @@ app_make_line_timestamp (const struct buffer_line *line)
static void
app_wrap_buffer_line_to_width (struct buffer_line *line, int width)
{
if (!line->when)
{
struct widget *padding = app_padding (ATTRIBUTE_NORMAL, 0.25);
int available = MAX (0, width - 2 * padding->width);
app_wrap_buffer_line (line, available, available);
widget_destroy (padding);
return;
}
struct widget *timestamp = app_make_line_timestamp (line);
struct widget *time_padding = app_padding (ATTRIBUTE_TIMESTAMP, 0.25);
struct widget *mark = app_make_mark (line);
@@ -2704,15 +2713,6 @@ app_wrap_buffer_line_to_width (struct buffer_line *line, int width)
widget_destroy (padding);
}
static void
app_wrap_log_line_to_width (struct buffer_line *line, int width)
{
struct widget *padding = app_padding (ATTRIBUTE_NORMAL, 0.25);
int available = MAX (0, width - 2 * padding->width);
app_wrap_buffer_line (line, available, available);
widget_destroy (padding);
}
static int
app_wrapped_line_height (const struct buffer_line *line)
{
@@ -2732,11 +2732,24 @@ app_buffer_line_height (struct buffer_line *line)
static struct widget *
app_make_line (struct buffer_line *line)
{
struct layout rows = {};
if (!line->when)
{
for (size_t i = 0; i < line->wraps_len; i++)
{
struct layout row = {};
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.25));
app_append_line_span (&row, 0, line, &line->wraps[i]);
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.25));
app_push (&rows, xui_hbox (row.head));
}
return xui_vbox (rows.head);
}
// XXX: We still use too much ATTRIBUTE_NORMAL, which basically names
// the <-1, -1> colour pair. We don't use it with line items as a default
// because then links attribute application would get blocked.
chtype base = line->leaked ? g_attrs[ATTRIBUTE_EXTERNAL].attrs : 0;
struct layout rows = {};
for (size_t i = 0; i < line->wraps_len; i++)
{
struct layout row = {};
@@ -2794,19 +2807,50 @@ app_make_read_marker (void)
return marker;
}
static struct widget *
app_make_log_line (const struct buffer_line *line)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
enum transcript_visit_kind { VISIT_LINE, VISIT_READ, VISIT_DATE };
typedef void (*transcript_visit_fn)
(enum transcript_visit_kind, struct buffer_line *, time_t, int, void *);
static void
app_walk_transcript (time_t now, transcript_visit_fn visit, void *user_data)
{
struct layout rows = {};
for (size_t i = 0; i < line->wraps_len; i++)
if (g.log.visible)
{
struct layout row = {};
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.25));
app_append_line_span (&row, 0, line, &line->wraps[i]);
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.25));
app_push (&rows, xui_hbox (row.head));
for (size_t i = 0; i < g.log.lines_len; i++)
visit (VISIT_LINE, g.log.lines[i], 0,
app_wrapped_line_height (g.log.lines[i]), user_data);
return;
}
return xui_vbox (rows.head);
struct buffer *b = g.buffer_current;
if (!b)
return;
uint64_t unread = (uint64_t) b->new_messages + b->new_unimportant_messages;
const struct buffer_line *marker_before = NULL;
for (const struct buffer_line *line = b->lines_tail;
line && unread--; line = line->prev)
marker_before = line;
struct tm last_date = {};
bool have_last_date = false;
LIST_FOR_EACH (struct buffer_line, line, b->lines)
{
if (line == marker_before)
visit (VISIT_READ, NULL, line->when, READ_MARKER_HEIGHT, user_data);
if (!buffer_line_is_visible (b, line))
continue;
time_t when = line->when / 1000;
if (app_date_changed (when, &last_date, &have_last_date))
visit (VISIT_DATE, line, when, g_xui.vunit, user_data);
visit (VISIT_LINE, line, when, app_wrapped_line_height (line), user_data);
}
if (have_last_date && app_date_changed (now, &last_date, &have_last_date))
visit (VISIT_DATE, NULL, now, g_xui.vunit, user_data);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2823,8 +2867,7 @@ struct transcript_slice
static struct transcript_slice
app_make_transcript_slice (void)
{
struct app_scroll_view view =
app_scroll_view (WIDGET_TRANSCRIPT_SCROLLBAR);
struct app_scroll_view view = app_scroll_view (WIDGET_TRANSCRIPT_SCROLLBAR);
int viewport = app_viewport_height ();
int view_top = app_scroll_view_origin (&view);
return (struct transcript_slice)
@@ -2854,113 +2897,79 @@ app_transcript_slice_offset (const struct transcript_slice *slice)
return slice->first_y < 0 ? 0 : slice->first_y - slice->view_top;
}
static int
app_append_log_rows (struct layout *rows)
{
struct transcript_slice slice = app_make_transcript_slice ();
for (size_t i = 0; i < g.log.lines_len; i++)
{
struct buffer_line *line = g.log.lines[i];
if (app_transcript_row_is_visible
(&slice, app_wrapped_line_height (line)))
app_push (rows, app_make_log_line (line));
}
return app_transcript_slice_offset (&slice);
}
static int
app_append_buffer_rows (struct layout *rows, time_t now)
{
struct buffer *b = g.buffer_current;
if (!b)
return 0;
uint64_t unread = (uint64_t) b->new_messages + b->new_unimportant_messages;
const struct buffer_line *marker_before = NULL;
for (const struct buffer_line *line = b->lines_tail;
line && unread--; line = line->prev)
marker_before = line;
struct transcript_slice slice = app_make_transcript_slice ();
struct tm last_date = {};
bool have_last_date = false;
LIST_FOR_EACH (struct buffer_line, line, b->lines)
{
if (line == marker_before
&& app_transcript_row_is_visible (&slice, READ_MARKER_HEIGHT))
app_push (rows, app_make_read_marker ());
if (!buffer_line_is_visible (b, line))
continue;
time_t when = line->when / 1000;
if (app_date_changed (when, &last_date, &have_last_date)
&& app_transcript_row_is_visible (&slice, g_xui.vunit))
app_push (rows, app_make_date_change_marker (when));
if (app_transcript_row_is_visible
(&slice, app_wrapped_line_height (line)))
app_push (rows, app_make_line (line));
}
if (have_last_date && app_date_changed (now, &last_date, &have_last_date)
&& app_transcript_row_is_visible (&slice, g_xui.vunit))
app_push (rows, app_make_date_change_marker (now));
return app_transcript_slice_offset (&slice);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct transcript_layout_ctx
{
struct transcript_slice slice;
struct layout rows;
};
static void
transcript_layout_visit (enum transcript_visit_kind kind,
struct buffer_line *line, time_t when, int height, void *user_data)
{
struct transcript_layout_ctx *c = user_data;
if (!app_transcript_row_is_visible (&c->slice, height))
return;
switch (kind)
{
break; case VISIT_LINE:
app_push (&c->rows, app_make_line (line));
break; case VISIT_READ:
app_push (&c->rows, app_make_read_marker ());
break; case VISIT_DATE:
app_push (&c->rows, app_make_date_change_marker (when));
}
}
static struct widget *
app_make_transcript (time_t now)
{
struct layout rows = {};
int offset = g.log.visible
? app_append_log_rows (&rows)
: app_append_buffer_rows (&rows, now);
struct transcript_layout_ctx ctx =
{ .slice = app_make_transcript_slice () };
app_walk_transcript (now, transcript_layout_visit, &ctx);
struct widget *transcript = xui_vbox (rows.head);
struct widget *transcript = xui_vbox (ctx.rows.head);
transcript->on_allocated = app_on_viewport_allocated;
transcript->widget_id = WIDGET_TRANSCRIPT;
transcript->userdata = offset;
transcript->userdata = app_transcript_slice_offset (&ctx.slice);
transcript->width = transcript->height = -1;
return transcript;
}
struct transcript_height_ctx
{
int width;
uint64_t height;
};
static void
transcript_height_visit (enum transcript_visit_kind kind,
struct buffer_line *line, time_t when, int height, void *user_data)
{
struct transcript_height_ctx *c = user_data;
switch (kind)
{
break; case VISIT_LINE:
app_wrap_buffer_line_to_width (line, c->width);
c->height += app_wrapped_line_height (line);
break; default:
c->height += height;
}
}
static int
app_transcript_height_at (int width, time_t now)
{
uint64_t height = 0;
if (g.log.visible)
{
for (size_t i = 0; i < g.log.lines_len; i++)
{
app_wrap_log_line_to_width (g.log.lines[i], width);
height += app_wrapped_line_height (g.log.lines[i]);
}
return MIN ((uint64_t) INT_MAX, height);
}
struct buffer *buffer = g.buffer_current;
if (!buffer)
return 0;
height = buffer_has_read_marker (buffer) ? READ_MARKER_HEIGHT : 0;
struct tm last_date = {};
bool have_last_date = false;
LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
{
if (!buffer_line_is_visible (buffer, line))
continue;
if (app_date_changed (line->when / 1000, &last_date, &have_last_date))
height += g_xui.vunit;
app_wrap_buffer_line_to_width (line, width);
height += app_wrapped_line_height (line);
}
if (have_last_date && app_date_changed (now, &last_date, &have_last_date))
height += g_xui.vunit;
return MIN ((uint64_t) INT_MAX, height);
struct transcript_height_ctx ctx = { .width = width, .height = 0 };
app_walk_transcript (now, transcript_height_visit, &ctx);
return MIN ((uint64_t) INT_MAX, ctx.height);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct widget *
app_make_middle (void)
{
@@ -3507,52 +3516,6 @@ app_external_editor_make_file (const char *template_name, const char *contents,
return template;
}
static void
app_external_editor_append_line (struct str *output,
const struct buffer_line *line, bool decorated)
{
if (decorated)
{
str_append (output, app_format_line_timestamp (line));
str_append (output, app_line_mark (line->rendition));
}
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
str_append (output, item->text);
str_append_c (output, '\n');
}
static struct str
app_external_editor_make_transcript (void)
{
struct str transcript = str_make ();
if (g.log.visible)
{
for (size_t i = 0; i < g.log.lines_len; i++)
app_external_editor_append_line
(&transcript, g.log.lines[i], false);
return transcript;
}
struct buffer *buffer = g.buffer_current;
struct tm last_date = {};
bool have_last_date = false;
LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
{
if (!buffer_line_is_visible (buffer, line))
continue;
time_t when = line->when / 1000;
if (app_date_changed (when, &last_date, &have_last_date))
str_append_printf (&transcript, "%s\n", app_format_date (when));
app_external_editor_append_line (&transcript, line, true);
}
time_t now = time (NULL);
if (have_last_date && app_date_changed (now, &last_date, &have_last_date))
str_append_printf (&transcript, "%s\n", app_format_date (now));
return transcript;
}
static void
app_external_editor_process_file (void)
{
@@ -3678,6 +3641,29 @@ app_external_editor_launch (struct error **e)
return true;
}
static void
transcript_text_visit (enum transcript_visit_kind kind,
struct buffer_line *line, time_t when, int height, void *user_data)
{
struct str *s = user_data;
switch (kind)
{
break; case VISIT_LINE:
if (when)
{
str_append (s, app_format_line_timestamp (line));
str_append (s, app_line_mark (line->rendition));
}
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
str_append (s, item->text);
str_append_c (s, '\n');
break; case VISIT_READ:
str_append (s, "---\n");
break; case VISIT_DATE:
str_append_printf (s, "%s\n", app_format_date (when));
}
}
static bool
app_launch_external_editor (bool view_transcript)
{
@@ -3689,9 +3675,10 @@ app_launch_external_editor (bool view_transcript)
const char *template_name = "input.XXXXXX.txt";
if (view_transcript)
{
struct str transcript = app_external_editor_make_transcript ();
contents_len = transcript.len;
contents = str_steal (&transcript);
struct str text = str_make ();
app_walk_transcript (time (NULL), transcript_text_visit, &text);
contents_len = text.len;
contents = str_steal (&text);
template_name = "transcript.XXXXXX.txt";
}
else