} window_t;
typedef struct {
- char *value;
- char *desc;
+ const char *value;
+ const char *desc;
} item_t;
typedef struct {
static int theme;
static int abbrev;
static int draw;
-static char *watch;
static int view;
+static char *watch;
static char cmd_buf[4096];
static int cmd_pos;
static short color_watch;
static short color_error;
-static const char *themes[] = {
- [THEME_NORMAL] "normal",
- [THEME_DARK] "dark",
- [THEME_LIGHT] "light",
+static const char *themes[][2] = {
+ [THEME_NORMAL] { "normal", "Default theme" },
+ [THEME_DARK] { "dark", "Dark background" },
+ [THEME_LIGHT] { "light", "Light background" },
};
-static const char *abbrevs[] = {
- [ABBREV_NONE] "none",
- [ABBREV_FIRST] "first",
- [ABBREV_LAST] "last",
- [ABBREV_FLAST] "flast",
- [ABBREV_INITIALS] "initials",
+static const char *abbrevs[][2] = {
+ [ABBREV_NONE] { "none", "Do not abbreivate" },
+ [ABBREV_FIRST] { "first", "First name only" },
+ [ABBREV_LAST] { "last", "Last name only" },
+ [ABBREV_FLAST] { "flast", "First initial last name" },
+ [ABBREV_INITIALS] { "initials", "Initials only" },
};
/* Helper functions */
return prev;
}
-static void cycle_channel(void)
+static channel_t *cycle_channel(window_t *win)
{
/* Clear seen flags */
for (int i = 0; i < history; i++)
if (msg->channel->seen)
continue;
msg->channel->seen = 1;
- if (!in_window(msg, focus))
+ if (!in_window(msg, win))
continue;
if (cur)
next = msg->channel;
- if (msg->channel == focus->dest)
+ if (msg->channel == win->dest)
cur = msg->channel;
if (!first)
first = msg->channel;
}
- focus->dest = next ?: first ?: &sys_chan;
+ return next ?: first ?: &sys_chan;
}
-static void last_channel(void)
+static channel_t *last_channel(window_t *win)
{
- if (focus->dest == &sys_chan &&
- focus->saved != &sys_chan) {
- focus->dest = focus->saved;
- return;
- }
- for (int i = history-1; i>=0; i--) {
- if (in_window(&messages[i], focus)) {
- focus->dest = messages[i].channel;
- return;
- }
- }
+ if (win->dest == &sys_chan &&
+ win->saved != &sys_chan)
+ return win->saved;
+ for (int i = history-1; i>=0; i--)
+ if (in_window(&messages[i], win))
+ return messages[i].channel;
+ return win->dest;
}
static void update_windows(void)
}
/* Menu items */
-static void add_item(char *value, char *desc)
+static void add_item(const char *prefix, const char *value, const char *desc)
{
item_t item = {
.value = value,
.desc = desc,
};
append(&item_buf, (void*)&item, sizeof(item));
+ if (prefix)
+ item_pos = prefix - cmd_buf;
}
static int num_items(void)
{
const item_t *a = _a;
const item_t *b = _b;
- return strcmp(a->value, b->value) ?:
- strcmp(a->desc, b->desc);
+ return compare(a->value, b->value) ?:
+ compare(a->desc, b->desc);
}
static void sort_items(void)
}
/* Commands */
+static void send_complete(const char *text)
+{
+ const char *arg;
+ if (prefix(text, "/theme ", &arg)) {
+ complete_array(arg, themes, N_ELEMENTS(themes));
+ }
+ else if (prefix(text, "/abbrev ", &arg)) {
+ complete_array(arg, abbrevs, N_ELEMENTS(abbrevs));
+ }
+ else if (prefix(text, "/server ", &arg)) {
+ complete_server(arg);
+ }
+ else if (prefix(text, "/channel ", &arg)) {
+ complete_channel(arg);
+ }
+ else {
+ complete_args(text,
+ "/quit", "Quit the program",
+ "/sort", "Sort chat history",
+ "/theme ", "Change the theme",
+ "/abbrev ", "Abbreviate names",
+ "/watch ", "Watch for regex",
+ "/open", "Open window",
+ "/close", "Close window",
+ "/name ", "Set window name",
+ "/server ", "Set window server",
+ "/channel ", "Set window channel",
+ "/filter ", "Set window filter",
+ NULL);
+ }
+
+ chat_complete(last_channel(focus), text);
+}
+
static int send_command(const char *text)
{
const char *arg;
}
else if (prefix(text, "/theme", &arg)) {
for (int i = 0; i < N_ELEMENTS(themes); i++)
- if (match(themes[i], arg))
+ if (match(themes[i][0], arg))
theme = i;
}
else if (prefix(text, "/abbrev", &arg)) {
for (int i = 0; i < N_ELEMENTS(abbrevs); i++)
- if (match(abbrevs[i], arg))
+ if (match(abbrevs[i][0], arg))
abbrev = i;
}
else if (prefix(text, "/watch", &arg)) {
}
}
+static void insert_mb(wint_t chr)
+{
+ char buf[MB_CUR_MAX];
+ int n = wctomb(buf, chr);
+ insert(buf, n);
+}
+
static void delete(int before, int after)
{
if (before > 0 && (cmd_pos-before) >= 0) {
}
/* Tab completion */
-static void complete_user(const char *prefix)
-{
- int len = strlen(prefix);
- for (user_t *cur = users; cur; cur = cur->next) {
- user_t *alias = cur;
- while (alias->alias)
- alias = alias->alias;
- if (!strncmp(prefix, cur->name, len) ||
- !strncmp(prefix, alias->name, len))
- add_item(alias->name, cur->name);
- }
-}
-
-static void complete_channel(const char *prefix)
-{
- int len = strlen(prefix);
- for (channel_t *cur = channels; cur; cur = cur->next)
- if (!strncmp(prefix, cur->name, len))
- add_item(cur->name, cur->topic ?: "No Topic");
-}
-
static void show_completion(void)
{
debug("complete: show");
/* Apply completion */
- char *pick = get_item(item_idx)->value;
- char *save = &save_buf[save_pos];
- cmd_pos = item_pos;
- cmd_len = item_pos;
+ const char *pick = get_item(item_idx)->value;
+ const char *save = &save_buf[save_pos];
+ cmd_pos = item_pos;
+ cmd_len = item_pos;
insert(pick, strlen(pick));
insert(save, save_len-save_pos);
cmd_pos -= save_len-save_pos;
{
debug("complete: start");
- /* Null terminate cmd_buf */
- static char cmd[sizeof(cmd_buf)];
- memcpy(cmd, cmd_buf, cmd_pos);
- cmd[cmd_pos] = '\0';
-
/* Reset item buffers */
reset(&item_buf);
item_idx = 0;
item_scroll = 0;
/* Run completion */
- const char *head = NULL;
- if (prefix(cmd, "/query", &head))
- complete_user(head ?: "");
- if (prefix(cmd, "/join", &head))
- complete_channel(head ?: "");
- if (head)
- item_pos = head - cmd;
+ char tmp = cmd_buf[cmd_pos];
+ cmd_buf[cmd_pos] = '\0';
+ send_complete(cmd_buf);
+ cmd_buf[cmd_pos] = tmp;
/* Sort matches */
sort_items();
focus = prev_window(focus) ?: focus;
}
else if (chr == KEY_CTRL_X) {
- cycle_channel();
+ focus->dest = cycle_channel(focus);
}
else if (chr == KEY_CTRL_Y) {
focus->scroll -= 1;
}
else if (chr == KEY_TAB) {
if (cmd_pos == 0)
- last_channel();
+ focus->dest = last_channel(focus);
else
start_completion();
}
delete(0, 1);
}
else if (iswprint(chr)) {
- char buf[MB_CUR_MAX];
- int n = wctomb(buf, chr);
- insert(buf, n);
+ insert_mb(chr);
}
/* Unknown control character */
static void input_menu(wint_t chr)
{
- if (chr == 'j' ||
+ if (chr == KEY_CTRL_N ||
chr == KEY_DOWN) {
if (item_idx+1 < num_items())
item_idx++;
show_completion();
}
- else if (chr == 'k' ||
+ else if (chr == KEY_CTRL_P ||
chr == KEY_UP) {
if (item_idx > 0)
item_idx--;
show_completion();
}
- else if (chr == KEY_RETURN) {
- debug("complete: chat");
- view = VIEW_CHAT;
+ else if (chr == KEY_TAB) {
+ start_completion();
}
- else if (chr == 'q' ||
- chr == KEY_ESCAPE) {
- debug("complete: chat");
+ else if (chr == KEY_ESCAPE) {
+ debug("complete: escape");
memcpy(cmd_buf, save_buf, save_len);
cmd_pos = save_pos;
cmd_len = save_len;
view = VIEW_CHAT;
}
+ else {
+ debug("complete: accept");
+ view = VIEW_CHAT;
+ input_chat(chr);
+ }
}
/* View init */
if (match(group, "general")) {
if (match(key, "theme"))
- theme = get_map(value, themes);
+ theme = get_mapv(value, themes);
else if (match(key, "abbrev"))
- abbrev = get_map(value, abbrevs);
+ abbrev = get_mapv(value, abbrevs);
else if (match(key, "defocus"))
defocus = get_number(value);
else if (match(key, "watch"))
while (get_wch(&wch) != ERR) {
if (view == VIEW_CHAT)
input_chat(wch);
- if (view == VIEW_MENU)
+ else if (view == VIEW_MENU)
input_menu(wch);
deadline = time(NULL) + defocus;
draw = 1;
draw_header();
if (view == VIEW_CHAT)
draw_chat();
- if (view == VIEW_MENU)
+ else if (view == VIEW_MENU)
draw_menu();
move(cmd_row, cmd_col);
return;
endwin();
}
+
+/* Completion */
+void complete_item(const char *prefix, const char *value, const char *desc)
+{
+ add_item(prefix, value, desc ?: "");
+}
+
+void complete_user(const char *prefix)
+{
+ for (user_t *cur = users; cur; cur = cur->next) {
+ user_t *alias = cur;
+ while (alias->alias)
+ alias = alias->alias;
+ if (starts(prefix, cur->name) ||
+ starts(prefix, alias->name))
+ add_item(prefix, alias->name, cur->name);
+ }
+}
+
+void complete_channel(const char *prefix)
+{
+ for (channel_t *cur = channels; cur; cur = cur->next)
+ if (starts(prefix, cur->name))
+ add_item(prefix, cur->name, cur->topic);
+}
+
+void complete_server(const char *prefix)
+{
+ for (server_t *cur = servers; cur; cur = cur->next)
+ if (starts(prefix, cur->name))
+ add_item(prefix, cur->name, NULL);
+}
+
+void complete_array(const char *prefix, const char *list[][2], int n)
+{
+ for (int i = 0; i < n; i++)
+ if (starts(prefix, list[i][0]))
+ add_item(prefix, list[i][0], list[i][1]);
+}
+
+void complete_args(const char *prefix, ...)
+{
+ const char *value, *desc;
+ va_list ap;
+ va_start(ap, prefix);
+ while (1) {
+ value = va_arg(ap, const char *);
+ if (!value)
+ break;
+ desc = va_arg(ap, const char *);
+ if (!starts(prefix, value))
+ continue;
+ add_item(prefix, value, desc);
+ }
+ va_end(ap);
+}