xF: add link detection and opening
This commit is contained in:
@@ -42,6 +42,8 @@ enum buffer_line_item_style
|
||||
BUFFER_LINE_ITEM_INVERSE = 1 << 3,
|
||||
BUFFER_LINE_ITEM_CROSSED_OUT = 1 << 4,
|
||||
BUFFER_LINE_ITEM_MONOSPACE = 1 << 5,
|
||||
BUFFER_LINE_ITEM_LINK = 1 << 6,
|
||||
BUFFER_LINE_ITEM_LINK_START = 1 << 7,
|
||||
};
|
||||
|
||||
// See: union relay_item_data
|
||||
@@ -72,30 +74,69 @@ struct buffer_line
|
||||
};
|
||||
|
||||
static void
|
||||
buffer_line_item_add (struct buffer_line_item **head,
|
||||
struct buffer_line_item **tail, const char *text, int16_t fg, int16_t bg,
|
||||
unsigned style)
|
||||
buffer_line_add_item (struct buffer_line *line, const char *text,
|
||||
int16_t fg, int16_t bg, unsigned style)
|
||||
{
|
||||
size_t len = strlen (text);
|
||||
|
||||
struct buffer_line_item *item = xcalloc (1, sizeof *item + len + 1);
|
||||
item->fg = fg;
|
||||
item->bg = bg;
|
||||
item->style = style;
|
||||
memcpy (item->text, text, len + 1);
|
||||
LIST_APPEND_WITH_TAIL (*head, *tail, item);
|
||||
|
||||
LIST_APPEND_WITH_TAIL (line->items, line->items_tail, item);
|
||||
}
|
||||
|
||||
static struct buffer_line_item *
|
||||
buffer_line_split_item (struct buffer_line *line,
|
||||
struct buffer_line_item *item, size_t offset)
|
||||
{
|
||||
size_t len = strlen (item->text);
|
||||
hard_assert (offset && offset < len);
|
||||
|
||||
struct buffer_line_item *suffix =
|
||||
xcalloc (1, sizeof *suffix + len - offset + 1);
|
||||
suffix->fg = item->fg;
|
||||
suffix->bg = item->bg;
|
||||
suffix->style = item->style;
|
||||
memcpy (suffix->text, item->text + offset, len - offset + 1);
|
||||
|
||||
item->text[offset] = 0;
|
||||
|
||||
struct buffer_line_item *following = item->next;
|
||||
LIST_INSERT_WITH_TAIL (line->items, line->items_tail, suffix, following);
|
||||
return suffix;
|
||||
}
|
||||
|
||||
static struct buffer_line_item *
|
||||
buffer_line_split_at (struct buffer_line *line, size_t offset)
|
||||
{
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
|
||||
{
|
||||
size_t len = strlen (item->text);
|
||||
if (!offset)
|
||||
return item;
|
||||
if (offset < len)
|
||||
return buffer_line_split_item (line, item, offset);
|
||||
offset -= len;
|
||||
}
|
||||
hard_assert (!offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_line_items_free (struct buffer_line_item *items)
|
||||
buffer_line_reset (struct buffer_line *line)
|
||||
{
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, items)
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
|
||||
free (item);
|
||||
line->items = line->items_tail = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_line_free (struct buffer_line *self)
|
||||
buffer_line_destroy (struct buffer_line *self)
|
||||
{
|
||||
buffer_line_items_free (self->items);
|
||||
buffer_line_reset (self);
|
||||
free (self);
|
||||
}
|
||||
|
||||
@@ -109,7 +150,7 @@ buffer_line_clone (const struct buffer_line *line)
|
||||
self->is_unimportant = line->is_unimportant;
|
||||
self->is_highlight = line->is_highlight;
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
|
||||
buffer_line_item_add (&self->items, &self->items_tail,
|
||||
buffer_line_add_item (self,
|
||||
item->text, item->fg, item->bg, item->style);
|
||||
return self;
|
||||
}
|
||||
@@ -175,7 +216,7 @@ static void
|
||||
buffer_clear (struct buffer *self)
|
||||
{
|
||||
LIST_FOR_EACH (struct buffer_line, iter, self->lines)
|
||||
buffer_line_free (iter);
|
||||
buffer_line_destroy (iter);
|
||||
self->lines = self->lines_tail = NULL;
|
||||
}
|
||||
|
||||
@@ -194,7 +235,7 @@ buffer_pop_excess_lines (struct buffer *self)
|
||||
{
|
||||
struct buffer_line *line = self->lines;
|
||||
LIST_UNLINK_WITH_TAIL (self->lines, self->lines_tail, line);
|
||||
buffer_line_free (line);
|
||||
buffer_line_destroy (line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +244,8 @@ buffer_destroy (struct buffer *self)
|
||||
{
|
||||
free (self->buffer_name);
|
||||
free (self->server_name);
|
||||
buffer_line_items_free (self->topic);
|
||||
buffer_line_reset (&(struct buffer_line)
|
||||
{ .items = self->topic, .items_tail = self->topic_tail });
|
||||
free (self->modes);
|
||||
free (self->input);
|
||||
strv_free (&self->history);
|
||||
@@ -313,6 +355,7 @@ struct binding
|
||||
XX (ERROR, 1, -1, A_BOLD) \
|
||||
XX (ACTION, 1, -1, 0) \
|
||||
XX (LEAKED, 248, -1, 0) \
|
||||
XX (LINK, 21, -1, A_UNDERLINE) \
|
||||
XX (PROMPT, -1, 255, A_BOLD) \
|
||||
XX (SCROLLBAR, 250, -1, 0) \
|
||||
XX (IRC_0, 16, -1, 0) \
|
||||
@@ -358,6 +401,7 @@ enum app_widget_id
|
||||
{
|
||||
WIDGET_NONE,
|
||||
WIDGET_BUTTON,
|
||||
WIDGET_LINK,
|
||||
WIDGET_BUFFER,
|
||||
WIDGET_BUFFER_LIST,
|
||||
WIDGET_TRANSCRIPT,
|
||||
@@ -450,6 +494,7 @@ static struct
|
||||
struct buffer_line *local_lines_tail;
|
||||
struct completion_request completion;
|
||||
struct log_view log;
|
||||
regex_t *link_re; ///< Finds links in buffer line text
|
||||
|
||||
// User interface:
|
||||
|
||||
@@ -576,7 +621,7 @@ app_queue_local_line (enum relay_rendition rendition, const char *text)
|
||||
struct buffer_line *line = xcalloc (1, sizeof *line);
|
||||
line->rendition = rendition;
|
||||
line->when = when;
|
||||
buffer_line_item_add (&line->items, &line->items_tail, text, -1, -1, 0);
|
||||
buffer_line_add_item (line, text, -1, -1, 0);
|
||||
LIST_APPEND_WITH_TAIL (g.local_lines, g.local_lines_tail, line);
|
||||
app_flush_local_lines ();
|
||||
}
|
||||
@@ -958,8 +1003,37 @@ buffer_find (const char *name)
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_line_items_convert (const union relay_item_data *items, uint32_t len,
|
||||
struct buffer_line_item **head, struct buffer_line_item **tail)
|
||||
buffer_line_linkify (struct buffer_line *line)
|
||||
{
|
||||
struct str text = str_make ();
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
|
||||
str_append (&text, item->text);
|
||||
|
||||
regmatch_t match = {};
|
||||
size_t offset = 0;
|
||||
int result = 0;
|
||||
while (!(result = regexec (g.link_re, text.str + offset, 1, &match, 0)))
|
||||
{
|
||||
size_t start = offset + match.rm_so;
|
||||
size_t end = offset + match.rm_eo;
|
||||
offset = end;
|
||||
|
||||
struct buffer_line_item *first = buffer_line_split_at (line, start);
|
||||
struct buffer_line_item *after = buffer_line_split_at (line, end);
|
||||
hard_assert (first && start < end);
|
||||
|
||||
first->style |= BUFFER_LINE_ITEM_LINK_START;
|
||||
for (struct buffer_line_item *item = first;
|
||||
item != after; item = item->next)
|
||||
item->style |= BUFFER_LINE_ITEM_LINK;
|
||||
}
|
||||
soft_assert (result == REG_NOMATCH);
|
||||
str_free (&text);
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_line_convert_items (struct buffer_line *line,
|
||||
const union relay_item_data *items, uint32_t len)
|
||||
{
|
||||
int16_t fg = -1, bg = -1;
|
||||
unsigned style = 0;
|
||||
@@ -969,8 +1043,7 @@ buffer_line_items_convert (const union relay_item_data *items, uint32_t len,
|
||||
switch (item->kind)
|
||||
{
|
||||
case RELAY_ITEM_TEXT:
|
||||
buffer_line_item_add (head, tail,
|
||||
item->text.text.str, fg, bg, style);
|
||||
buffer_line_add_item (line, item->text.text.str, fg, bg, style);
|
||||
break;
|
||||
case RELAY_ITEM_RESET:
|
||||
fg = bg = -1;
|
||||
@@ -1004,6 +1077,7 @@ buffer_line_items_convert (const union relay_item_data *items, uint32_t len,
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer_line_linkify (line);
|
||||
}
|
||||
|
||||
static struct buffer_line *
|
||||
@@ -1015,8 +1089,7 @@ buffer_line_new (const struct relay_event_data_buffer_line *m)
|
||||
self->is_unimportant = m->is_unimportant;
|
||||
self->is_highlight = m->is_highlight;
|
||||
|
||||
buffer_line_items_convert (m->items, m->items_len,
|
||||
&self->items, &self->items_tail);
|
||||
buffer_line_convert_items (self, m->items, m->items_len);
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -1113,17 +1186,20 @@ relay_process_buffer_update (struct buffer *b,
|
||||
if (server_name)
|
||||
b->server_name = xstrdup (server_name);
|
||||
|
||||
buffer_line_items_free (b->topic);
|
||||
b->topic = b->topic_tail = NULL;
|
||||
struct buffer_line topic =
|
||||
{ .items = b->topic, .items_tail = b->topic_tail };
|
||||
buffer_line_reset (&topic);
|
||||
cstr_set (&b->modes, NULL);
|
||||
if (b->kind == RELAY_BUFFER_KIND_CHANNEL)
|
||||
{
|
||||
const struct relay_buffer_context_channel *channel =
|
||||
&e->context.channel;
|
||||
buffer_line_items_convert (channel->topic, channel->topic_len,
|
||||
&b->topic, &b->topic_tail);
|
||||
buffer_line_convert_items
|
||||
(&topic, channel->topic, channel->topic_len);
|
||||
b->modes = xstrdup (channel->modes.str);
|
||||
}
|
||||
b->topic = topic.items;
|
||||
b->topic_tail = topic.items_tail;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2032,10 +2108,9 @@ app_line_item_color (int16_t color)
|
||||
}
|
||||
|
||||
static chtype
|
||||
app_line_item_attrs (enum app_attribute attribute, bool leaked,
|
||||
app_line_item_attrs (chtype attrs, bool leaked,
|
||||
const struct buffer_line_item *item)
|
||||
{
|
||||
chtype attrs = g_attrs[attribute].attrs;
|
||||
if (item->style & BUFFER_LINE_ITEM_BOLD)
|
||||
attrs |= A_BOLD;
|
||||
#ifdef A_ITALIC
|
||||
@@ -2078,6 +2153,46 @@ app_line_item_attrs (enum app_attribute attribute, bool leaked,
|
||||
return attrs;
|
||||
}
|
||||
|
||||
static chtype
|
||||
app_link_attrs (chtype attrs)
|
||||
{
|
||||
chtype link = g_attrs[ATTRIBUTE_LINK].attrs;
|
||||
if (attrs & A_COLOR)
|
||||
link &= ~A_COLOR;
|
||||
return attrs | link;
|
||||
}
|
||||
|
||||
static void
|
||||
app_append_line_items (struct layout *row, chtype base,
|
||||
const struct buffer_line *line)
|
||||
{
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
|
||||
{
|
||||
chtype attrs = app_line_item_attrs (base, line->leaked, item);
|
||||
unsigned extended = item->style & BUFFER_LINE_ITEM_MONOSPACE
|
||||
? XUI_ATTR_MONOSPACE
|
||||
: 0;
|
||||
|
||||
bool link = item->style & BUFFER_LINE_ITEM_LINK;
|
||||
if (link)
|
||||
attrs = app_link_attrs (attrs);
|
||||
|
||||
struct widget *widget =
|
||||
app_label_attrs (attrs, extended, item->text, !item->next && !link);
|
||||
if (link)
|
||||
{
|
||||
widget->widget_id = WIDGET_LINK;
|
||||
widget->userdata = item->style;
|
||||
}
|
||||
app_push (row, widget);
|
||||
}
|
||||
|
||||
// We don't want the "hitbox" of links to extend until the end of line.
|
||||
if (!line->items_tail
|
||||
|| line->items_tail->style & BUFFER_LINE_ITEM_LINK)
|
||||
app_push (row, app_label_attrs (base, 0, "", true));
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static struct widget *
|
||||
@@ -2099,15 +2214,8 @@ app_make_title (void)
|
||||
struct layout row = {};
|
||||
app_push (&row, app_padding (ATTRIBUTE_CHROME, 0.25));
|
||||
if (g.buffer_current && g.buffer_current->topic)
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, g.buffer_current->topic)
|
||||
{
|
||||
unsigned extended = item->style & BUFFER_LINE_ITEM_MONOSPACE
|
||||
? XUI_ATTR_MONOSPACE
|
||||
: 0;
|
||||
chtype attrs = app_line_item_attrs (ATTRIBUTE_CHROME, false, item);
|
||||
app_push (&row, app_label_attrs (attrs, extended, item->text,
|
||||
item == g.buffer_current->topic_tail));
|
||||
}
|
||||
app_append_line_items (&row, g_attrs[ATTRIBUTE_CHROME].attrs,
|
||||
&(struct buffer_line) { .items = g.buffer_current->topic });
|
||||
else
|
||||
app_push (&row, app_label (ATTRIBUTE_CHROME, "", true));
|
||||
|
||||
@@ -2228,7 +2336,7 @@ static struct widget *
|
||||
app_make_mark (const struct buffer_line *line)
|
||||
{
|
||||
chtype attrs = line->leaked
|
||||
? ATTRIBUTE_LEAKED
|
||||
? g_attrs[ATTRIBUTE_LEAKED].attrs
|
||||
: g_attrs[app_mark_attribute (line->rendition)].attrs;
|
||||
return app_label_attrs (attrs, XUI_ATTR_MONOSPACE,
|
||||
app_line_mark (line->rendition), false);
|
||||
@@ -2243,27 +2351,16 @@ app_make_line (const struct buffer_line *line)
|
||||
if (localtime_r (&when, &tm))
|
||||
strftime (timestamp, sizeof timestamp, "%H:%M:%S", &tm);
|
||||
|
||||
enum app_attribute attribute = ATTRIBUTE_NORMAL;
|
||||
if (line->leaked)
|
||||
attribute = ATTRIBUTE_LEAKED;
|
||||
|
||||
// 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.
|
||||
struct layout row = {};
|
||||
app_push (&row, app_padding (ATTRIBUTE_TIME, 0.25));
|
||||
app_push (&row, app_label (ATTRIBUTE_TIME, timestamp, false));
|
||||
app_push (&row, app_padding (ATTRIBUTE_TIME, 0.25));
|
||||
app_push (&row, app_make_mark (line));
|
||||
|
||||
LIST_FOR_EACH (struct buffer_line_item, item, line->items)
|
||||
{
|
||||
unsigned extended = item->style & BUFFER_LINE_ITEM_MONOSPACE
|
||||
? XUI_ATTR_MONOSPACE
|
||||
: 0;
|
||||
chtype attrs = app_line_item_attrs (attribute, line->leaked, item);
|
||||
app_push (&row, app_label_attrs (attrs, extended, item->text,
|
||||
item == line->items_tail));
|
||||
}
|
||||
if (!line->items)
|
||||
app_push (&row, app_label (attribute, "", true));
|
||||
app_append_line_items (&row,
|
||||
line->leaked ? g_attrs[ATTRIBUTE_LEAKED].attrs : 0, line);
|
||||
app_push (&row, app_padding (ATTRIBUTE_NORMAL, 0.25));
|
||||
return xui_hbox (row.head);
|
||||
}
|
||||
@@ -2302,10 +2399,15 @@ app_make_unread_marker (void)
|
||||
static struct widget *
|
||||
app_make_log_line (const char *text)
|
||||
{
|
||||
struct buffer_line model = {};
|
||||
buffer_line_add_item (&model, text, -1, -1, 0);
|
||||
buffer_line_linkify (&model);
|
||||
|
||||
struct layout line = {};
|
||||
app_push (&line, app_padding (ATTRIBUTE_NORMAL, 0.25));
|
||||
app_push (&line, app_label (ATTRIBUTE_NORMAL, text, true));
|
||||
app_append_line_items (&line, 0, &model);
|
||||
app_push (&line, app_padding (ATTRIBUTE_NORMAL, 0.25));
|
||||
buffer_line_reset (&model);
|
||||
return xui_hbox (line.head);
|
||||
}
|
||||
|
||||
@@ -2699,6 +2801,24 @@ app_widget_by_id (struct widget *list, int widget_id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
app_link_from_widget (struct widget *w)
|
||||
{
|
||||
// No need to store it extra when we can collect it naturally.
|
||||
while (w && w->widget_id == WIDGET_LINK
|
||||
&& !(w->userdata & BUFFER_LINE_ITEM_LINK_START))
|
||||
w = w->prev;
|
||||
if (!w || w->widget_id != WIDGET_LINK
|
||||
|| !(w->userdata & BUFFER_LINE_ITEM_LINK_START))
|
||||
return NULL;
|
||||
|
||||
struct str url = str_make ();
|
||||
for (; w && w->widget_id == WIDGET_LINK
|
||||
&& (w->userdata & BUFFER_LINE_ITEM_LINK); w = w->next)
|
||||
str_append (&url, w->text);
|
||||
return str_steal (&url);
|
||||
}
|
||||
|
||||
static bool
|
||||
app_scrollbar_seek (struct widget *scrollbar, int y)
|
||||
{
|
||||
@@ -2847,6 +2967,37 @@ app_toggle_log (void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
app_open (const char *object)
|
||||
{
|
||||
pid_t child = fork ();
|
||||
if (child < 0)
|
||||
{
|
||||
print_error ("failed to open link: %s", strerror (errno));
|
||||
return;
|
||||
}
|
||||
if (!child)
|
||||
{
|
||||
if (setsid () < 0)
|
||||
_exit (EXIT_FAILURE);
|
||||
|
||||
pid_t grandchild = fork ();
|
||||
if (grandchild < 0)
|
||||
_exit (EXIT_FAILURE);
|
||||
if (grandchild)
|
||||
_exit (EXIT_SUCCESS);
|
||||
|
||||
#ifdef __APPLE__
|
||||
execlp ("open", "open", object, NULL);
|
||||
#else
|
||||
execlp ("xdg-open", "xdg-open", object, NULL);
|
||||
#endif
|
||||
_exit (EXIT_FAILURE);
|
||||
}
|
||||
while (waitpid (child, NULL, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
}
|
||||
|
||||
// ~~~ Action and mouse dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
static const enum line_editor_action g_editor_actions[] =
|
||||
@@ -2983,7 +3134,9 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
||||
int modifiers)
|
||||
{
|
||||
(void) modifiers;
|
||||
|
||||
app_notify_active ();
|
||||
|
||||
if (type == TERMO_MOUSE_RELEASE)
|
||||
{
|
||||
g.dragged_scrollbar = WIDGET_NONE;
|
||||
@@ -2996,8 +3149,9 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
||||
|
||||
struct widget *scrollbar =
|
||||
app_widget_by_id (g_xui.widgets, g.dragged_scrollbar);
|
||||
return app_scrollbar_seek (scrollbar,
|
||||
scrollbar ? y - scrollbar->y : 0);
|
||||
if (scrollbar)
|
||||
return app_scrollbar_seek (scrollbar, y - scrollbar->y);
|
||||
return false;
|
||||
}
|
||||
if (type != TERMO_MOUSE_PRESS)
|
||||
return true;
|
||||
@@ -3020,15 +3174,31 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
||||
return false;
|
||||
|
||||
struct widget *target = app_interactive_widget_at (g_xui.widgets, x, y);
|
||||
if (target && (target->widget_id == WIDGET_BUFFER_LIST_SCROLLBAR
|
||||
|| target->widget_id == WIDGET_TRANSCRIPT_SCROLLBAR))
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (target->widget_id == WIDGET_BUFFER_LIST_SCROLLBAR
|
||||
|| target->widget_id == WIDGET_TRANSCRIPT_SCROLLBAR)
|
||||
{
|
||||
g.dragged_scrollbar = target->widget_id;
|
||||
return app_scrollbar_seek (target, y - target->y);
|
||||
}
|
||||
if (target && target->widget_id == WIDGET_BUTTON)
|
||||
if (target->widget_id == WIDGET_LINK)
|
||||
{
|
||||
char *url = app_link_from_widget (target);
|
||||
if (!url)
|
||||
return false;
|
||||
|
||||
#ifdef WITH_X11
|
||||
if (g_xui.ui == &x11_ui)
|
||||
app_open (url);
|
||||
#endif // WITH_X11
|
||||
free (url);
|
||||
return true;
|
||||
}
|
||||
if (target->widget_id == WIDGET_BUTTON)
|
||||
return app_process_action (target->userdata);
|
||||
if (target && target->widget_id == WIDGET_BUFFER)
|
||||
if (target->widget_id == WIDGET_BUFFER)
|
||||
{
|
||||
app_send_buffer_activate (app_buffer_by_index (target->userdata));
|
||||
return true;
|
||||
@@ -3279,11 +3449,19 @@ signals_on_readable (const struct pollfd *fd, void *user_data)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static const char g_link_pattern[] =
|
||||
"https?://"
|
||||
"([^][(){}<>\"'[:space:]]|\\([^][(){}<>\"'[:space:]]*\\))+"
|
||||
"([^][(){}<>\"'[:space:],.:]|\\([^][(){}<>\"'[:space:]]*\\))";
|
||||
|
||||
static void
|
||||
app_init_context (const char *host, const char *port)
|
||||
{
|
||||
poller_init (&g.poller);
|
||||
app_editor_init ();
|
||||
struct error *e = NULL;
|
||||
if (!(g.link_re = regex_compile (g_link_pattern, REG_EXTENDED, &e)))
|
||||
exit_fatal ("Failed to compile link regex: %s", e->message);
|
||||
|
||||
g.relay.host = xstrdup (host);
|
||||
g.relay.port = xstrdup (port);
|
||||
@@ -3302,7 +3480,7 @@ app_free_context (void)
|
||||
{
|
||||
app_reset_relay_state ();
|
||||
LIST_FOR_EACH (struct buffer_line, iter, g.local_lines)
|
||||
buffer_line_free (iter);
|
||||
buffer_line_destroy (iter);
|
||||
g.local_lines = g.local_lines_tail = NULL;
|
||||
|
||||
log_view_free (&g.log);
|
||||
@@ -3315,6 +3493,7 @@ app_free_context (void)
|
||||
str_free (&g.relay.read_buffer);
|
||||
str_free (&g.relay.write_buffer);
|
||||
free (g.relay.session_id);
|
||||
regex_free (g.link_re);
|
||||
|
||||
free (g.keys);
|
||||
app_editor_free ();
|
||||
|
||||
Reference in New Issue
Block a user