/* * Copyright 2008-2013 Various Authors * Copyright 2006 * * heavily based on filters.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "help.h" #include "window.h" #include "search.h" #include "misc.h" #include "xmalloc.h" #include "keys.h" #include "command_mode.h" #include "ui_curses.h" #include "options.h" #include "cmdline.h" #include struct window *help_win; struct searchable *help_searchable; static LIST_HEAD(help_head); static struct list_head *bound_head; static struct list_head *bound_tail; static struct list_head *unbound_head; static struct list_head *unbound_tail; static inline void help_entry_to_iter(struct help_entry *e, struct iter *iter) { iter->data0 = &help_head; iter->data1 = e; iter->data2 = NULL; } static GENERIC_ITER_PREV(help_get_prev, struct help_entry, node) static GENERIC_ITER_NEXT(help_get_next, struct help_entry, node) static int help_search_get_current(void *data, struct iter *iter, enum search_direction dir) { return window_get_sel(help_win, iter); } static int help_search_matches(void *data, struct iter *iter, const char *text) { int matched = 0; char **words = get_words(text); if (words[0] != NULL) { struct help_entry *ent; int i; ent = iter_to_help_entry(iter); for (i = 0; ; i++) { if (words[i] == NULL) { window_set_sel(help_win, iter); matched = 1; break; } if (ent->type == HE_TEXT) { if (!u_strcasestr(ent->text, words[i])) break; } else if (ent->type == HE_BOUND) { if (!u_strcasestr(ent->binding->cmd, words[i]) && !u_strcasestr(ent->binding->key->name, words[i])) break; } else if (ent->type == HE_UNBOUND) { if (!u_strcasestr(ent->command->name, words[i])) break; } else if (ent->type == HE_OPTION) { if (!u_strcasestr(ent->option->name, words[i])) break; } } } free_str_array(words); return matched; } static const struct searchable_ops help_search_ops = { .get_prev = help_get_prev, .get_next = help_get_next, .get_current = help_search_get_current, .matches = help_search_matches }; static void help_add_text(const char *s) { struct help_entry *ent; ent = xnew(struct help_entry, 1); ent->type = HE_TEXT; ent->text = s; list_add_tail(&ent->node, &help_head); } static void help_add_defaults(void) { struct cmus_opt *opt; help_add_text("Keybindings"); help_add_text("-----------"); bound_head = help_head.prev; help_add_text(""); help_add_text("Unbound Commands"); help_add_text("----------------"); unbound_head = help_head.prev; help_add_text(""); help_add_text("Options"); help_add_text("-------"); list_for_each_entry(opt, &option_head, node) { struct help_entry *ent = xnew(struct help_entry, 1); ent->type = HE_OPTION; ent->option = opt; list_add_tail(&ent->node, &help_head); } bound_tail = bound_head->next; unbound_tail = unbound_head->next; } void help_remove_unbound(struct command *cmd) { struct help_entry *ent; struct iter i; list_for_each_entry(ent, &help_head, node) { if (ent->type != HE_UNBOUND) continue; if (ent->command == cmd) { help_entry_to_iter(ent, &i); window_row_vanishes(help_win, &i); list_del(&ent->node); free(ent); return; } } } static void list_add_sorted(struct list_head *new, struct list_head *head, struct list_head *tail, int (*cmp)(struct list_head *, struct list_head *)) { struct list_head *item = tail->prev; while (item != head) { if (cmp(new, item) >= 0) break; item = item->prev; } /* add after item */ list_add(new, item); } static int bound_cmp(struct list_head *ai, struct list_head *bi) { struct help_entry *a = container_of(ai, struct help_entry, node); struct help_entry *b = container_of(bi, struct help_entry, node); int ret = a->binding->ctx - b->binding->ctx; if (!ret) ret = strcmp(a->binding->key->name, b->binding->key->name); return ret; } static int unbound_cmp(struct list_head *ai, struct list_head *bi) { struct help_entry *a = container_of(ai, struct help_entry, node); struct help_entry *b = container_of(bi, struct help_entry, node); return strcmp(a->command->name, b->command->name); } void help_add_unbound(struct command *cmd) { struct help_entry *ent; ent = xnew(struct help_entry, 1); ent->type = HE_UNBOUND; ent->command = cmd; list_add_sorted(&ent->node, unbound_head, unbound_tail, unbound_cmp); } void help_add_all_unbound(void) { int i; for (i = 0; commands[i].name; ++i) if (!commands[i].bc) help_add_unbound(&commands[i]); } void help_select(void) { struct iter sel; struct help_entry *ent; char buf[OPTION_MAX_SIZE]; if (!window_get_sel(help_win, &sel)) return; ent = iter_to_help_entry(&sel); switch (ent->type) { case HE_BOUND: snprintf(buf, sizeof(buf), "bind -f %s %s %s", key_context_names[ent->binding->ctx], ent->binding->key->name, ent->binding->cmd); cmdline_set_text(buf); enter_command_mode(); break; case HE_UNBOUND: snprintf(buf, sizeof(buf), "bind common %s", ent->command->name); cmdline_set_text(buf); enter_command_mode(); break; case HE_OPTION: snprintf(buf, sizeof(buf), "set %s=", ent->option->name); size_t len = strlen(buf); ent->option->get(ent->option->data, buf + len, sizeof(buf) - len); cmdline_set_text(buf); enter_command_mode(); break; default: break; } } void help_toggle(void) { struct iter sel; struct help_entry *ent; if (!window_get_sel(help_win, &sel)) return; ent = iter_to_help_entry(&sel); switch (ent->type) { case HE_OPTION: if (ent->option->toggle) { ent->option->toggle(ent->option->data); help_win->changed = 1; } break; default: break; } } void help_remove(void) { struct iter sel; struct help_entry *ent; if (!window_get_sel(help_win, &sel)) return; ent = iter_to_help_entry(&sel); switch (ent->type) { case HE_BOUND: if (yes_no_query("Remove selected binding? [y/N]") == UI_QUERY_ANSWER_YES) key_unbind(key_context_names[ent->binding->ctx], ent->binding->key->name, 0); break; default: break; } } void help_add_bound(const struct binding *bind) { struct help_entry *ent; ent = xnew(struct help_entry, 1); ent->type = HE_BOUND; ent->binding = bind; list_add_sorted(&ent->node, bound_head, bound_tail, bound_cmp); } void help_remove_bound(const struct binding *bind) { struct help_entry *ent; struct iter i; list_for_each_entry(ent, &help_head, node) { if (ent->binding == bind) { help_entry_to_iter(ent, &i); window_row_vanishes(help_win, &i); list_del(&ent->node); free(ent); return; } } } void help_init(void) { struct iter iter; help_win = window_new(help_get_prev, help_get_next); window_set_contents(help_win, &help_head); window_changed(help_win); help_add_defaults(); iter.data0 = &help_head; iter.data1 = NULL; iter.data2 = NULL; help_searchable = searchable_new(NULL, &iter, &help_search_ops); } void help_exit(void) { searchable_free(help_searchable); window_free(help_win); }