Filter: handle yyyy-mm-dd dates
[cmus.git] / command_mode.c
blob420c7887dc6ee3b1323fb950515ceae231930e84
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "command_mode.h"
21 #include "search_mode.h"
22 #include "cmdline.h"
23 #include "options.h"
24 #include "ui_curses.h"
25 #include "history.h"
26 #include "tabexp.h"
27 #include "tabexp_file.h"
28 #include "browser.h"
29 #include "filters.h"
30 #include "player.h"
31 #include "editable.h"
32 #include "lib.h"
33 #include "pl.h"
34 #include "play_queue.h"
35 #include "cmus.h"
36 #include "worker.h"
37 #include "keys.h"
38 #include "xmalloc.h"
39 #include "xstrjoin.h"
40 #include "misc.h"
41 #include "path.h"
42 #include "format_print.h"
43 #include "spawn.h"
44 #include "utils.h"
45 #include "list.h"
46 #include "debug.h"
47 #include "load_dir.h"
48 #include "config/datadir.h"
49 #include "help.h"
51 #include <stdlib.h>
52 #include <ctype.h>
53 #include <sys/types.h>
54 #include <sys/wait.h>
55 #include <dirent.h>
56 #include <pwd.h>
58 #if defined(__sun__)
59 #include <ncurses.h>
60 #else
61 #include <curses.h>
62 #endif
64 static struct history cmd_history;
65 static char *cmd_history_filename;
66 static char *history_search_text = NULL;
67 static int arg_expand_cmd = -1;
69 static char *get_home_dir(const char *username)
71 struct passwd *passwd;
73 if (username == NULL)
74 return xstrdup(home_dir);
75 passwd = getpwnam(username);
76 if (passwd == NULL)
77 return NULL;
78 /* don't free passwd */
79 return xstrdup(passwd->pw_dir);
82 static char *expand_filename(const char *name)
84 if (name[0] == '~') {
85 char *slash;
87 slash = strchr(name, '/');
88 if (slash) {
89 char *username, *home;
91 if (slash - name - 1 > 0) {
92 /* ~user/... */
93 username = xstrndup(name + 1, slash - name - 1);
94 } else {
95 /* ~/... */
96 username = NULL;
98 home = get_home_dir(username);
99 free(username);
100 if (home) {
101 char *expanded;
103 expanded = xstrjoin(home, slash);
104 free(home);
105 return expanded;
106 } else {
107 return xstrdup(name);
109 } else {
110 if (name[1] == 0) {
111 return xstrdup(home_dir);
112 } else {
113 char *home;
115 home = get_home_dir(name + 1);
116 if (home)
117 return home;
118 return xstrdup(name);
121 } else {
122 return xstrdup(name);
126 /* view {{{ */
128 void view_clear(int view)
130 switch (view) {
131 case TREE_VIEW:
132 case SORTED_VIEW:
133 worker_remove_jobs(JOB_TYPE_LIB);
134 editable_lock();
135 editable_clear(&lib_editable);
137 /* FIXME: make this optional? */
138 lib_clear_store();
140 editable_unlock();
141 break;
142 case PLAYLIST_VIEW:
143 worker_remove_jobs(JOB_TYPE_PL);
144 editable_lock();
145 editable_clear(&pl_editable);
146 editable_unlock();
147 break;
148 case QUEUE_VIEW:
149 worker_remove_jobs(JOB_TYPE_QUEUE);
150 editable_lock();
151 editable_clear(&pq_editable);
152 editable_unlock();
153 break;
154 default:
155 info_msg(":clear only works in views 1-4");
159 void view_add(int view, char *arg, int prepend)
161 char *tmp, *name;
162 enum file_type ft;
164 tmp = expand_filename(arg);
165 ft = cmus_detect_ft(tmp, &name);
166 if (ft == FILE_TYPE_INVALID) {
167 error_msg("adding '%s': %s", tmp, strerror(errno));
168 free(tmp);
169 return;
171 free(tmp);
173 switch (view) {
174 case TREE_VIEW:
175 case SORTED_VIEW:
176 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
177 break;
178 case PLAYLIST_VIEW:
179 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
180 break;
181 case QUEUE_VIEW:
182 if (prepend) {
183 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
184 } else {
185 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
187 break;
188 default:
189 info_msg(":add only works in views 1-4");
191 free(name);
194 void view_load(int view, char *arg)
196 char *tmp, *name;
197 enum file_type ft;
199 tmp = expand_filename(arg);
200 ft = cmus_detect_ft(tmp, &name);
201 if (ft == FILE_TYPE_INVALID) {
202 error_msg("loading '%s': %s", tmp, strerror(errno));
203 free(tmp);
204 return;
206 free(tmp);
208 if (ft == FILE_TYPE_FILE)
209 ft = FILE_TYPE_PL;
210 if (ft != FILE_TYPE_PL) {
211 error_msg("loading '%s': not a playlist file", name);
212 free(name);
213 return;
216 switch (view) {
217 case TREE_VIEW:
218 case SORTED_VIEW:
219 worker_remove_jobs(JOB_TYPE_LIB);
220 editable_lock();
221 editable_clear(&lib_editable);
222 editable_unlock();
223 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
224 free(lib_filename);
225 lib_filename = name;
226 break;
227 case PLAYLIST_VIEW:
228 worker_remove_jobs(JOB_TYPE_PL);
229 editable_lock();
230 editable_clear(&pl_editable);
231 editable_unlock();
232 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
233 free(pl_filename);
234 pl_filename = name;
235 break;
236 default:
237 info_msg(":load only works in views 1-3");
238 free(name);
242 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
244 char *filename = *filenamep;
246 if (arg) {
247 free(filename);
248 filename = xstrdup(arg);
249 *filenamep = filename;
252 editable_lock();
253 if (cmus_save(for_each_ti, filename) == -1)
254 error_msg("saving '%s': %s", filename, strerror(errno));
255 editable_unlock();
258 void view_save(int view, char *arg)
260 if (arg) {
261 char *tmp;
263 tmp = expand_filename(arg);
264 arg = path_absolute(tmp);
265 free(tmp);
268 switch (view) {
269 case TREE_VIEW:
270 case SORTED_VIEW:
271 do_save(lib_for_each, arg, &lib_filename);
272 break;
273 case PLAYLIST_VIEW:
274 do_save(pl_for_each, arg, &pl_filename);
275 break;
276 default:
277 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
279 free(arg);
282 /* }}} */
284 /* only returns the last flag which is enough for it's callers */
285 static int parse_flags(const char **strp, const char *flags)
287 const char *str = *strp;
288 int flag = 0;
290 if (str == NULL)
291 return flag;
293 while (*str) {
294 if (*str != '-')
295 break;
297 // "-"
298 if (str[1] == 0)
299 break;
301 // "--" or "-- "
302 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
303 str += 2;
304 break;
307 // not "-?" or "-? "
308 if (str[2] && str[2] != ' ')
309 break;
311 flag = str[1];
312 if (!strchr(flags, flag)) {
313 error_msg("invalid option -%c", flag);
314 return -1;
317 str += 2;
319 while (*str == ' ')
320 str++;
322 while (*str == ' ')
323 str++;
324 if (*str == 0)
325 str = NULL;
326 *strp = str;
327 return flag;
330 static int flag_to_view(int flag)
332 switch (flag) {
333 case 'l':
334 return TREE_VIEW;
335 case 'p':
336 return PLAYLIST_VIEW;
337 case 'q':
338 case 'Q':
339 return QUEUE_VIEW;
340 default:
341 return cur_view;
345 static void cmd_add(char *arg)
347 int flag = parse_flags((const char **)&arg, "lpqQ");
349 if (flag == -1)
350 return;
351 if (arg == NULL) {
352 error_msg("not enough arguments\n");
353 return;
355 view_add(flag_to_view(flag), arg, flag == 'Q');
358 static void cmd_clear(char *arg)
360 int flag = parse_flags((const char **)&arg, "lpq");
362 if (flag == -1)
363 return;
364 if (arg) {
365 error_msg("too many arguments\n");
366 return;
368 view_clear(flag_to_view(flag));
371 static void cmd_load(char *arg)
373 int flag = parse_flags((const char **)&arg, "lp");
375 if (flag == -1)
376 return;
377 if (arg == NULL) {
378 error_msg("not enough arguments\n");
379 return;
381 view_load(flag_to_view(flag), arg);
384 static void cmd_save(char *arg)
386 int flag = parse_flags((const char **)&arg, "lp");
388 if (flag == -1)
389 return;
390 view_save(flag_to_view(flag), arg);
393 static void cmd_set(char *arg)
395 char *value = NULL;
396 int i;
398 for (i = 0; arg[i]; i++) {
399 if (arg[i] == '=') {
400 arg[i] = 0;
401 value = &arg[i + 1];
402 break;
405 if (value) {
406 option_set(arg, value);
407 help_win->changed = 1;
408 } else {
409 struct cmus_opt *opt;
410 char buf[OPTION_MAX_SIZE];
412 /* support "set <option>?" */
413 i--;
414 if (arg[i] == '?')
415 arg[i] = 0;
417 opt = option_find(arg);
418 if (opt) {
419 opt->get(opt->id, buf);
420 info_msg("setting: '%s=%s'", arg, buf);
425 static void cmd_toggle(char *arg)
427 struct cmus_opt *opt = option_find(arg);
429 if (opt == NULL)
430 return;
432 if (opt->toggle == NULL) {
433 error_msg("%s is not toggle option", opt->name);
434 return;
436 opt->toggle(opt->id);
437 help_win->changed = 1;
440 static int get_number(char *str, char **end)
442 int val = 0;
444 while (*str >= '0' && *str <= '9') {
445 val *= 10;
446 val += *str++ - '0';
448 *end = str;
449 return val;
452 static void cmd_seek(char *arg)
454 int relative = 0;
455 int seek = 0, sign = 1, count;
457 switch (*arg) {
458 case '-':
459 sign = -1;
460 case '+':
461 relative = 1;
462 arg++;
463 break;
466 count = 0;
467 goto inside;
469 do {
470 int num;
471 char *end;
473 if (*arg != ':')
474 break;
475 arg++;
476 inside:
477 num = get_number(arg, &end);
478 if (arg == end)
479 break;
480 arg = end;
481 seek = seek * 60 + num;
482 } while (++count < 3);
484 seek *= sign;
485 if (!count)
486 goto err;
488 if (count == 1) {
489 switch (tolower(*arg)) {
490 case 'h':
491 seek *= 60;
492 case 'm':
493 seek *= 60;
494 case 's':
495 arg++;
496 break;
500 if (!*arg) {
501 player_seek(seek, relative);
502 return;
504 err:
505 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
508 static void cmd_factivate(char *arg)
510 editable_lock();
511 filters_activate_names(arg);
512 editable_unlock();
515 static void cmd_filter(char *arg)
517 editable_lock();
518 filters_set_anonymous(arg);
519 editable_unlock();
522 static void cmd_fset(char *arg)
524 filters_set_filter(arg);
527 static void cmd_invert(char *arg)
529 editable_lock();
530 switch (cur_view) {
531 case SORTED_VIEW:
532 editable_invert_marks(&lib_editable);
533 break;
534 case PLAYLIST_VIEW:
535 editable_invert_marks(&pl_editable);
536 break;
537 case QUEUE_VIEW:
538 editable_invert_marks(&pq_editable);
539 break;
540 default:
541 info_msg(":invert only works in views 2-4");
543 editable_unlock();
546 static void cmd_mark(char *arg)
548 editable_lock();
549 switch (cur_view) {
550 case SORTED_VIEW:
551 editable_mark(&lib_editable, arg);
552 break;
553 case PLAYLIST_VIEW:
554 editable_mark(&pl_editable, arg);
555 break;
556 case QUEUE_VIEW:
557 editable_mark(&pq_editable, arg);
558 break;
559 default:
560 info_msg(":mark only works in views 2-4");
562 editable_unlock();
565 static void cmd_unmark(char *arg)
567 editable_lock();
568 switch (cur_view) {
569 case SORTED_VIEW:
570 editable_unmark(&lib_editable);
571 break;
572 case PLAYLIST_VIEW:
573 editable_unmark(&pl_editable);
574 break;
575 case QUEUE_VIEW:
576 editable_unmark(&pq_editable);
577 break;
578 default:
579 info_msg(":unmark only works in views 2-4");
581 editable_unlock();
584 static void cmd_cd(char *arg)
586 if (arg) {
587 char *dir, *absolute;
589 dir = expand_filename(arg);
590 absolute = path_absolute(dir);
591 if (chdir(dir) == -1) {
592 error_msg("could not cd to '%s': %s", dir, strerror(errno));
593 } else {
594 browser_chdir(absolute);
596 free(absolute);
597 free(dir);
598 } else {
599 if (chdir(home_dir) == -1) {
600 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
601 } else {
602 browser_chdir(home_dir);
607 static void cmd_bind(char *arg)
609 int flag = parse_flags((const char **)&arg, "f");
610 char *key, *func;
612 if (flag == -1)
613 return;
615 if (arg == NULL)
616 goto err;
618 key = strchr(arg, ' ');
619 if (key == NULL)
620 goto err;
621 *key++ = 0;
622 while (*key == ' ')
623 key++;
625 func = strchr(key, ' ');
626 if (func == NULL)
627 goto err;
628 *func++ = 0;
629 while (*func == ' ')
630 func++;
631 if (*func == 0)
632 goto err;
634 key_bind(arg, key, func, flag == 'f');
635 return;
636 err:
637 error_msg("expecting 3 arguments (context, key and function)\n");
640 static void cmd_unbind(char *arg)
642 int flag = parse_flags((const char **)&arg, "f");
643 char *key;
645 if (flag == -1)
646 return;
648 if (arg == NULL)
649 goto err;
651 key = strchr(arg, ' ');
652 if (key == NULL)
653 goto err;
654 *key++ = 0;
655 while (*key == ' ')
656 key++;
657 if (*key == 0)
658 goto err;
660 /* FIXME: remove spaces at end */
662 key_unbind(arg, key, flag == 'f');
663 return;
664 err:
665 error_msg("expecting 2 arguments (context and key)\n");
668 static void cmd_showbind(char *arg)
670 char *key;
672 key = strchr(arg, ' ');
673 if (key == NULL)
674 goto err;
675 *key++ = 0;
676 while (*key == ' ')
677 key++;
678 if (*key == 0)
679 goto err;
681 /* FIXME: remove spaces at end */
683 show_binding(arg, key);
684 return;
685 err:
686 error_msg("expecting 2 arguments (context and key)\n");
689 static void cmd_quit(char *arg)
691 quit();
694 static void cmd_reshuffle(char *arg)
696 editable_lock();
697 lib_reshuffle();
698 pl_reshuffle();
699 editable_unlock();
702 static void cmd_source(char *arg)
704 char *filename = expand_filename(arg);
706 if (source_file(filename) == -1)
707 error_msg("sourcing %s: %s", filename, strerror(errno));
708 free(filename);
711 static void cmd_colorscheme(char *arg)
713 char filename[512];
715 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
716 if (source_file(filename) == -1) {
717 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
718 if (source_file(filename) == -1)
719 error_msg("sourcing %s: %s", filename, strerror(errno));
724 * \" inside double-quotes becomes "
725 * \\ inside double-quotes becomes \
727 static char *parse_quoted(const char **strp)
729 const char *str = *strp;
730 const char *start;
731 char *ret, *dst;
733 str++;
734 start = str;
735 while (1) {
736 int c = *str++;
738 if (c == 0)
739 goto error;
740 if (c == '"')
741 break;
742 if (c == '\\') {
743 if (*str++ == 0)
744 goto error;
747 *strp = str;
748 ret = xnew(char, str - start);
749 str = start;
750 dst = ret;
751 while (1) {
752 int c = *str++;
754 if (c == '"')
755 break;
756 if (c == '\\') {
757 c = *str++;
758 if (c != '"' && c != '\\')
759 *dst++ = '\\';
761 *dst++ = c;
763 *dst = 0;
764 return ret;
765 error:
766 error_msg("`\"' expected");
767 return NULL;
770 static char *parse_escaped(const char **strp)
772 const char *str = *strp;
773 const char *start;
774 char *ret, *dst;
776 start = str;
777 while (1) {
778 int c = *str;
780 if (c == 0 || c == ' ' || c == '\'' || c == '"')
781 break;
783 str++;
784 if (c == '\\') {
785 c = *str;
786 if (c == 0)
787 break;
788 str++;
791 *strp = str;
792 ret = xnew(char, str - start + 1);
793 str = start;
794 dst = ret;
795 while (1) {
796 int c = *str;
798 if (c == 0 || c == ' ' || c == '\'' || c == '"')
799 break;
801 str++;
802 if (c == '\\') {
803 c = *str;
804 if (c == 0) {
805 *dst++ = '\\';
806 break;
808 str++;
810 *dst++ = c;
812 *dst = 0;
813 return ret;
816 static char *parse_one(const char **strp)
818 const char *str = *strp;
819 char *ret = NULL;
821 while (1) {
822 char *part;
823 int c = *str;
825 if (!c || c == ' ')
826 break;
827 if (c == '"') {
828 part = parse_quoted(&str);
829 if (part == NULL)
830 goto error;
831 } else if (c == '\'') {
832 /* backslashes are normal chars inside single-quotes */
833 const char *end;
835 str++;
836 end = strchr(str, '\'');
837 if (end == NULL)
838 goto sq_missing;
839 part = xstrndup(str, end - str);
840 str = end + 1;
841 } else {
842 part = parse_escaped(&str);
845 if (ret == NULL) {
846 ret = part;
847 } else {
848 char *tmp = xstrjoin(ret, part);
849 free(ret);
850 ret = tmp;
853 *strp = str;
854 return ret;
855 sq_missing:
856 error_msg("`'' expected");
857 error:
858 free(ret);
859 return NULL;
862 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
864 char **av = NULL;
865 int nr = 0;
866 int alloc = 0;
868 while (*cmd) {
869 char *arg;
871 /* there can't be spaces at start of command
872 * and there is at least one argument */
873 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
874 /* {} is replaced with file arguments */
875 if (*args_idx != -1)
876 goto only_once_please;
877 *args_idx = nr;
878 cmd += 2;
879 goto skip_spaces;
880 } else {
881 arg = parse_one(&cmd);
882 if (arg == NULL)
883 goto error;
886 if (nr == alloc) {
887 alloc = alloc ? alloc * 2 : 4;
888 av = xrenew(char *, av, alloc + 1);
890 av[nr++] = arg;
891 skip_spaces:
892 while (*cmd == ' ')
893 cmd++;
895 av[nr] = NULL;
896 *ac = nr;
897 return av;
898 only_once_please:
899 error_msg("{} can be used only once");
900 error:
901 while (nr > 0)
902 free(av[--nr]);
903 free(av);
904 return NULL;
907 static struct track_info **sel_tis;
908 static int sel_tis_alloc;
909 static int sel_tis_nr;
911 static int add_ti(void *data, struct track_info *ti)
913 if (sel_tis_nr == sel_tis_alloc) {
914 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
915 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
917 track_info_ref(ti);
918 sel_tis[sel_tis_nr++] = ti;
919 return 0;
922 static void cmd_run(char *arg)
924 char **av, **argv;
925 int ac, argc, i, run, files_idx = -1;
927 if (cur_view > QUEUE_VIEW) {
928 info_msg("Command execution is supported only in views 1-4");
929 return;
932 av = parse_cmd(arg, &files_idx, &ac);
933 if (av == NULL) {
934 return;
937 /* collect selected files (struct track_info) */
938 sel_tis = NULL;
939 sel_tis_alloc = 0;
940 sel_tis_nr = 0;
942 editable_lock();
943 switch (cur_view) {
944 case TREE_VIEW:
945 __tree_for_each_sel(add_ti, NULL, 0);
946 break;
947 case SORTED_VIEW:
948 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
949 break;
950 case PLAYLIST_VIEW:
951 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
952 break;
953 case QUEUE_VIEW:
954 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
955 break;
957 editable_unlock();
959 if (sel_tis_nr == 0) {
960 /* no files selected, do nothing */
961 free_str_array(av);
962 return;
964 sel_tis[sel_tis_nr] = NULL;
966 /* build argv */
967 argv = xnew(char *, ac + sel_tis_nr + 1);
968 argc = 0;
969 if (files_idx == -1) {
970 /* add selected files after rest of the args */
971 for (i = 0; i < ac; i++)
972 argv[argc++] = av[i];
973 for (i = 0; i < sel_tis_nr; i++)
974 argv[argc++] = sel_tis[i]->filename;
975 } else {
976 for (i = 0; i < files_idx; i++)
977 argv[argc++] = av[i];
978 for (i = 0; i < sel_tis_nr; i++)
979 argv[argc++] = sel_tis[i]->filename;
980 for (i = files_idx; i < ac; i++)
981 argv[argc++] = av[i];
983 argv[argc] = NULL;
985 for (i = 0; argv[i]; i++)
986 d_print("ARG: '%s'\n", argv[i]);
988 run = 1;
989 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
990 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
991 info_msg("Aborted");
992 run = 0;
995 if (run) {
996 int status;
998 if (spawn(argv, &status)) {
999 error_msg("executing %s: %s", argv[0], strerror(errno));
1000 } else {
1001 if (WIFEXITED(status)) {
1002 int rc = WEXITSTATUS(status);
1004 if (rc)
1005 error_msg("%s returned %d", argv[0], rc);
1007 if (WIFSIGNALED(status))
1008 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1010 switch (cur_view) {
1011 case TREE_VIEW:
1012 case SORTED_VIEW:
1013 /* this must be done before sel_tis are unreffed */
1014 free_str_array(av);
1015 free(argv);
1017 /* remove non-existed files, update tags for changed files */
1018 cmus_update_tis(sel_tis, sel_tis_nr);
1020 /* we don't own sel_tis anymore! */
1021 return;
1025 free_str_array(av);
1026 free(argv);
1027 for (i = 0; sel_tis[i]; i++)
1028 track_info_unref(sel_tis[i]);
1029 free(sel_tis);
1032 static int get_one_ti(void *data, struct track_info *ti)
1034 struct track_info **sel_ti = data;
1036 track_info_ref(ti);
1037 *sel_ti = ti;
1038 /* stop the for each loop, we need only the first selected track */
1039 return 1;
1042 static void cmd_echo(char *arg)
1044 struct track_info *sel_ti;
1045 char *ptr = arg;
1047 while (1) {
1048 ptr = strchr(ptr, '{');
1049 if (ptr == NULL)
1050 break;
1051 if (ptr[1] == '}')
1052 break;
1053 ptr++;
1056 if (ptr == NULL) {
1057 info_msg("%s", arg);
1058 return;
1061 if (cur_view > QUEUE_VIEW) {
1062 info_msg("echo with {} in its arguments is supported only in views 1-4");
1063 return;
1066 *ptr = 0;
1067 ptr += 2;
1069 /* get only the first selected track */
1070 sel_ti = NULL;
1072 editable_lock();
1073 switch (cur_view) {
1074 case TREE_VIEW:
1075 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1076 break;
1077 case SORTED_VIEW:
1078 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1079 break;
1080 case PLAYLIST_VIEW:
1081 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1082 break;
1083 case QUEUE_VIEW:
1084 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1085 break;
1087 editable_unlock();
1089 if (sel_ti == NULL)
1090 return;
1092 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1093 track_info_unref(sel_ti);
1096 #define VF_RELATIVE 0x01
1097 #define VF_PERCENTAGE 0x02
1099 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1101 unsigned int f = 0;
1102 int ch, val = 0, digits = 0, sign = 1;
1104 if (*arg == '-') {
1105 arg++;
1106 f |= VF_RELATIVE;
1107 sign = -1;
1108 } else if (*arg == '+') {
1109 arg++;
1110 f |= VF_RELATIVE;
1113 while (1) {
1114 ch = *arg++;
1115 if (ch < '0' || ch > '9')
1116 break;
1117 val *= 10;
1118 val += ch - '0';
1119 digits++;
1121 if (digits == 0)
1122 goto err;
1124 if (ch == '%') {
1125 f |= VF_PERCENTAGE;
1126 ch = *arg;
1128 if (ch)
1129 goto err;
1131 *value = sign * val;
1132 *flags = f;
1133 return 0;
1134 err:
1135 return -1;
1138 static int calc_vol(int val, int old, unsigned int flags)
1140 if (flags & VF_RELATIVE) {
1141 if (flags & VF_PERCENTAGE)
1142 val = scale_from_percentage(val, volume_max);
1143 val += old;
1144 } else if (flags & VF_PERCENTAGE) {
1145 val = scale_from_percentage(val, volume_max);
1147 return clamp(val, 0, volume_max);
1151 * :vol value [value]
1153 * where value is [-+]?[0-9]+%?
1155 static void cmd_vol(char *arg)
1157 char **values = get_words(arg);
1158 unsigned int lf, rf;
1159 int l, r, ol, or;
1161 if (values[1] && values[2])
1162 goto err;
1164 if (parse_vol_arg(values[0], &l, &lf))
1165 goto err;
1167 r = l;
1168 rf = lf;
1169 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1170 goto err;
1172 free_str_array(values);
1174 player_get_volume(&ol, &or);
1175 l = calc_vol(l, ol, lf);
1176 r = calc_vol(r, or, rf);
1177 player_set_volume(l, r);
1178 return;
1179 err:
1180 free_str_array(values);
1181 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1184 static void cmd_view(char *arg)
1186 int view;
1188 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view))
1189 set_view(view - 1);
1192 static void cmd_p_next(char *arg)
1194 cmus_next();
1197 static void cmd_p_pause(char *arg)
1199 player_pause();
1202 static void cmd_p_play(char *arg)
1204 if (arg) {
1205 player_play_file(arg);
1206 } else {
1207 player_play();
1211 static void cmd_p_prev(char *arg)
1213 cmus_prev();
1216 static void cmd_p_stop(char *arg)
1218 player_stop();
1221 static void cmd_search_next(char *arg)
1223 if (search_str) {
1224 if (!search_next(searchable, search_str, search_direction))
1225 search_not_found();
1229 static void cmd_search_prev(char *arg)
1231 if (search_str) {
1232 if (!search_next(searchable, search_str, !search_direction))
1233 search_not_found();
1237 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1239 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1242 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1244 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1247 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1249 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1252 static for_each_sel_ti_cb view_for_each_sel[4] = {
1253 tree_for_each_sel,
1254 sorted_for_each_sel,
1255 pl_for_each_sel,
1256 pq_for_each_sel
1259 /* wrapper for void lib_add_track(struct track_info *) etc. */
1260 static int wrapper_cb(void *data, struct track_info *ti)
1262 add_ti_cb add = data;
1264 add(ti);
1265 return 0;
1268 static void add_from_browser(add_ti_cb add, int job_type)
1270 char *sel = browser_get_sel();
1272 if (sel) {
1273 enum file_type ft;
1274 char *ret;
1276 ft = cmus_detect_ft(sel, &ret);
1277 if (ft != FILE_TYPE_INVALID) {
1278 cmus_add(add, ret, ft, job_type);
1279 window_down(browser_win, 1);
1281 free(ret);
1282 free(sel);
1286 static void cmd_win_add_l(char *arg)
1288 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1289 return;
1291 if (cur_view <= QUEUE_VIEW) {
1292 editable_lock();
1293 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1294 editable_unlock();
1295 } else if (cur_view == BROWSER_VIEW) {
1296 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1300 static void cmd_win_add_p(char *arg)
1302 /* could allow adding dups? */
1303 if (cur_view == PLAYLIST_VIEW)
1304 return;
1306 if (cur_view <= QUEUE_VIEW) {
1307 editable_lock();
1308 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1309 editable_unlock();
1310 } else if (cur_view == BROWSER_VIEW) {
1311 add_from_browser(pl_add_track, JOB_TYPE_PL);
1315 static void cmd_win_add_Q(char *arg)
1317 if (cur_view == QUEUE_VIEW)
1318 return;
1320 if (cur_view <= QUEUE_VIEW) {
1321 editable_lock();
1322 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1323 editable_unlock();
1324 } else if (cur_view == BROWSER_VIEW) {
1325 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1329 static void cmd_win_add_q(char *arg)
1331 if (cur_view == QUEUE_VIEW)
1332 return;
1334 if (cur_view <= QUEUE_VIEW) {
1335 editable_lock();
1336 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1337 editable_unlock();
1338 } else if (cur_view == BROWSER_VIEW) {
1339 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1343 static void cmd_win_activate(char *arg)
1345 struct track_info *info = NULL;
1347 editable_lock();
1348 switch (cur_view) {
1349 case TREE_VIEW:
1350 info = tree_set_selected();
1351 break;
1352 case SORTED_VIEW:
1353 info = sorted_set_selected();
1354 break;
1355 case PLAYLIST_VIEW:
1356 info = pl_set_selected();
1357 break;
1358 case QUEUE_VIEW:
1359 break;
1360 case BROWSER_VIEW:
1361 browser_enter();
1362 break;
1363 case FILTERS_VIEW:
1364 filters_activate();
1365 break;
1366 case HELP_VIEW:
1367 help_select();
1368 break;
1370 editable_unlock();
1372 if (info) {
1373 /* update lib/pl mode */
1374 if (cur_view < 2)
1375 play_library = 1;
1376 if (cur_view == 2)
1377 play_library = 0;
1379 player_play_file(info->filename);
1380 track_info_unref(info);
1384 static void cmd_win_mv_after(char *arg)
1386 editable_lock();
1387 switch (cur_view) {
1388 case TREE_VIEW:
1389 break;
1390 case SORTED_VIEW:
1391 editable_move_after(&lib_editable);
1392 break;
1393 case PLAYLIST_VIEW:
1394 editable_move_after(&pl_editable);
1395 break;
1396 case QUEUE_VIEW:
1397 editable_move_after(&pq_editable);
1398 break;
1399 case BROWSER_VIEW:
1400 break;
1401 case FILTERS_VIEW:
1402 break;
1403 case HELP_VIEW:
1404 break;
1406 editable_unlock();
1409 static void cmd_win_mv_before(char *arg)
1411 editable_lock();
1412 switch (cur_view) {
1413 case TREE_VIEW:
1414 break;
1415 case SORTED_VIEW:
1416 editable_move_before(&lib_editable);
1417 break;
1418 case PLAYLIST_VIEW:
1419 editable_move_before(&pl_editable);
1420 break;
1421 case QUEUE_VIEW:
1422 editable_move_before(&pq_editable);
1423 break;
1424 case BROWSER_VIEW:
1425 break;
1426 case FILTERS_VIEW:
1427 break;
1428 case HELP_VIEW:
1429 break;
1431 editable_unlock();
1434 static void cmd_win_remove(char *arg)
1436 editable_lock();
1437 switch (cur_view) {
1438 case TREE_VIEW:
1439 tree_remove_sel();
1440 break;
1441 case SORTED_VIEW:
1442 editable_remove_sel(&lib_editable);
1443 break;
1444 case PLAYLIST_VIEW:
1445 editable_remove_sel(&pl_editable);
1446 break;
1447 case QUEUE_VIEW:
1448 editable_remove_sel(&pq_editable);
1449 break;
1450 case BROWSER_VIEW:
1451 browser_delete();
1452 break;
1453 case FILTERS_VIEW:
1454 filters_delete_filter();
1455 break;
1456 case HELP_VIEW:
1457 help_remove();
1458 break;
1460 editable_unlock();
1463 static void cmd_win_sel_cur(char *arg)
1465 editable_lock();
1466 switch (cur_view) {
1467 case TREE_VIEW:
1468 tree_sel_current();
1469 break;
1470 case SORTED_VIEW:
1471 sorted_sel_current();
1472 break;
1473 case PLAYLIST_VIEW:
1474 pl_sel_current();
1475 break;
1476 case QUEUE_VIEW:
1477 break;
1478 case BROWSER_VIEW:
1479 break;
1480 case FILTERS_VIEW:
1481 break;
1482 case HELP_VIEW:
1483 break;
1485 editable_unlock();
1488 static void cmd_win_toggle(char *arg)
1490 switch (cur_view) {
1491 case TREE_VIEW:
1492 editable_lock();
1493 tree_toggle_expand_artist();
1494 editable_unlock();
1495 break;
1496 case SORTED_VIEW:
1497 editable_lock();
1498 editable_toggle_mark(&lib_editable);
1499 editable_unlock();
1500 break;
1501 case PLAYLIST_VIEW:
1502 editable_lock();
1503 editable_toggle_mark(&pl_editable);
1504 editable_unlock();
1505 break;
1506 case QUEUE_VIEW:
1507 editable_lock();
1508 editable_toggle_mark(&pq_editable);
1509 editable_unlock();
1510 break;
1511 case BROWSER_VIEW:
1512 break;
1513 case FILTERS_VIEW:
1514 filters_toggle_filter();
1515 break;
1516 case HELP_VIEW:
1517 help_toggle();
1518 break;
1522 static struct window *current_win(void)
1524 switch (cur_view) {
1525 case TREE_VIEW:
1526 return lib_cur_win;
1527 case SORTED_VIEW:
1528 return lib_editable.win;
1529 case PLAYLIST_VIEW:
1530 return pl_editable.win;
1531 case QUEUE_VIEW:
1532 return pq_editable.win;
1533 case BROWSER_VIEW:
1534 return browser_win;
1535 case HELP_VIEW:
1536 return help_win;
1537 case FILTERS_VIEW:
1538 default:
1539 return filters_win;
1543 static void cmd_win_bottom(char *arg)
1545 editable_lock();
1546 window_goto_bottom(current_win());
1547 editable_unlock();
1550 static void cmd_win_down(char *arg)
1552 editable_lock();
1553 window_down(current_win(), 1);
1554 editable_unlock();
1557 static void cmd_win_next(char *arg)
1559 if (cur_view == TREE_VIEW) {
1560 editable_lock();
1561 tree_toggle_active_window();
1562 editable_unlock();
1566 static void cmd_win_pg_down(char *arg)
1568 editable_lock();
1569 window_page_down(current_win());
1570 editable_unlock();
1573 static void cmd_win_pg_up(char *arg)
1575 editable_lock();
1576 window_page_up(current_win());
1577 editable_unlock();
1580 static void cmd_win_top(char *arg)
1582 editable_lock();
1583 window_goto_top(current_win());
1584 editable_unlock();
1587 static void cmd_win_up(char *arg)
1589 editable_lock();
1590 window_up(current_win(), 1);
1591 editable_unlock();
1594 static void cmd_win_update(char *arg)
1596 switch (cur_view) {
1597 case TREE_VIEW:
1598 case SORTED_VIEW:
1599 cmus_update_lib();
1600 break;
1601 case BROWSER_VIEW:
1602 browser_reload();
1603 break;
1607 static void cmd_browser_up(char *arg)
1609 browser_up();
1612 static void cmd_refresh(char *arg)
1614 clearok(curscr, TRUE);
1615 refresh();
1618 /* tab exp {{{
1620 * these functions fill tabexp struct, which is resetted beforehand
1623 /* buffer used for tab expansion */
1624 static char expbuf[512];
1626 static int filter_directories(const char *name, const struct stat *s)
1628 return S_ISDIR(s->st_mode);
1631 static int filter_any(const char *name, const struct stat *s)
1633 return 1;
1636 static int filter_playable(const char *name, const struct stat *s)
1638 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1641 static int filter_playlist(const char *name, const struct stat *s)
1643 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1646 static int filter_supported(const char *name, const struct stat *s)
1648 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1651 static void expand_files(const char *str)
1653 expand_files_and_dirs(str, filter_any);
1656 static void expand_directories(const char *str)
1658 expand_files_and_dirs(str, filter_directories);
1661 static void expand_playable(const char *str)
1663 expand_files_and_dirs(str, filter_playable);
1666 static void expand_playlist(const char *str)
1668 expand_files_and_dirs(str, filter_playlist);
1671 static void expand_supported(const char *str)
1673 expand_files_and_dirs(str, filter_supported);
1676 static void expand_add(const char *str)
1678 int flag = parse_flags(&str, "lpqQ");
1680 if (flag == -1)
1681 return;
1682 if (str == NULL)
1683 str = "";
1684 expand_supported(str);
1686 if (tabexp.head && flag) {
1687 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1688 free(tabexp.head);
1689 tabexp.head = xstrdup(expbuf);
1693 static void expand_load_save(const char *str)
1695 int flag = parse_flags(&str, "lp");
1697 if (flag == -1)
1698 return;
1699 if (str == NULL)
1700 str = "";
1701 expand_playlist(str);
1703 if (tabexp.head && flag) {
1704 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1705 free(tabexp.head);
1706 tabexp.head = xstrdup(expbuf);
1710 static void expand_key_context(const char *str, const char *force)
1712 int pos, i, len = strlen(str);
1713 char **tails;
1715 tails = xnew(char *, NR_CTXS + 1);
1716 pos = 0;
1717 for (i = 0; key_context_names[i]; i++) {
1718 int cmp = strncmp(str, key_context_names[i], len);
1719 if (cmp > 0)
1720 continue;
1721 if (cmp < 0)
1722 break;
1723 tails[pos++] = xstrdup(key_context_names[i] + len);
1726 if (pos == 0) {
1727 free(tails);
1728 return;
1730 if (pos == 1) {
1731 char *tmp = xstrjoin(tails[0], " ");
1732 free(tails[0]);
1733 tails[0] = tmp;
1735 tails[pos] = NULL;
1736 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1737 tabexp.head = xstrdup(expbuf);
1738 tabexp.tails = tails;
1741 static int get_context(const char *str, int len)
1743 int i, c = -1, count = 0;
1745 for (i = 0; key_context_names[i]; i++) {
1746 if (strncmp(str, key_context_names[i], len) == 0) {
1747 if (key_context_names[i][len] == 0) {
1748 /* exact */
1749 return i;
1751 c = i;
1752 count++;
1755 if (count == 1)
1756 return c;
1757 return -1;
1760 static void expand_command_line(const char *str);
1762 static void expand_bind_args(const char *str)
1764 /* :bind context key function
1766 * possible values for str:
1768 * context k
1769 * context key f
1771 * you need to know context before you can expand function
1773 /* start and end pointers for context, key and function */
1774 const char *cs, *ce, *ks, *ke, *fs;
1775 int i, c, k, count;
1776 int flag = parse_flags((const char **)&str, "f");
1777 const char *force = "";
1779 if (flag == -1)
1780 return;
1781 if (str == NULL)
1782 str = "";
1784 if (flag == 'f')
1785 force = "-f ";
1787 cs = str;
1788 ce = strchr(cs, ' ');
1789 if (ce == NULL) {
1790 expand_key_context(cs, force);
1791 return;
1794 /* context must be expandable */
1795 c = get_context(cs, ce - cs);
1796 if (c == -1) {
1797 /* context is ambiguous or invalid */
1798 return;
1801 ks = ce;
1802 while (*ks == ' ')
1803 ks++;
1804 ke = strchr(ks, ' ');
1805 if (ke == NULL) {
1806 /* expand key */
1807 int len = strlen(ks);
1808 PTR_ARRAY(array);
1810 for (i = 0; key_table[i].name; i++) {
1811 int cmp = strncmp(ks, key_table[i].name, len);
1812 if (cmp > 0)
1813 continue;
1814 if (cmp < 0)
1815 break;
1816 ptr_array_add(&array, xstrdup(key_table[i].name + len));
1819 if (!array.count)
1820 return;
1822 if (array.count == 1) {
1823 char **ptrs = array.ptrs;
1824 char *tmp = xstrjoin(ptrs[0], " ");
1825 free(ptrs[0]);
1826 ptrs[0] = tmp;
1829 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
1831 ptr_array_plug(&array);
1832 tabexp.head = xstrdup(expbuf);
1833 tabexp.tails = array.ptrs;
1834 return;
1837 /* key must be expandable */
1838 k = -1;
1839 count = 0;
1840 for (i = 0; key_table[i].name; i++) {
1841 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
1842 if (key_table[i].name[ke - ks] == 0) {
1843 /* exact */
1844 k = i;
1845 count = 1;
1846 break;
1848 k = i;
1849 count++;
1852 if (count != 1) {
1853 /* key is ambiguous or invalid */
1854 return;
1857 fs = ke;
1858 while (*fs == ' ')
1859 fs++;
1861 if (*fs == ':')
1862 fs++;
1864 /* expand com [arg...] */
1865 expand_command_line(fs);
1866 if (tabexp.head == NULL) {
1867 /* command expand failed */
1868 return;
1872 * tabexp.head is now "com"
1873 * tabexp.tails is [ mand1 mand2 ... ]
1875 * need to change tabexp.head to "context key com"
1878 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
1879 key_table[k].name, tabexp.head);
1880 free(tabexp.head);
1881 tabexp.head = xstrdup(expbuf);
1884 static void expand_unbind_args(const char *str)
1886 /* :unbind context key */
1887 /* start and end pointers for context and key */
1888 const char *cs, *ce, *ks;
1889 const struct binding *b;
1890 PTR_ARRAY(array);
1891 int c, len;
1893 cs = str;
1894 ce = strchr(cs, ' ');
1895 if (ce == NULL) {
1896 expand_key_context(cs, "");
1897 return;
1900 /* context must be expandable */
1901 c = get_context(cs, ce - cs);
1902 if (c == -1) {
1903 /* context is ambiguous or invalid */
1904 return;
1907 ks = ce;
1908 while (*ks == ' ')
1909 ks++;
1911 /* expand key */
1912 len = strlen(ks);
1913 b = key_bindings[c];
1914 while (b) {
1915 if (!strncmp(ks, b->key->name, len))
1916 ptr_array_add(&array, xstrdup(b->key->name + len));
1917 b = b->next;
1919 if (!array.count)
1920 return;
1922 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
1924 ptr_array_plug(&array);
1925 tabexp.head = xstrdup(expbuf);
1926 tabexp.tails = array.ptrs;
1929 static void expand_factivate(const char *str)
1931 /* "name1 name2 name3", expand only name3 */
1932 struct filter_entry *e;
1933 const char *name;
1934 PTR_ARRAY(array);
1935 int str_len, len, i;
1937 str_len = strlen(str);
1938 i = str_len;
1939 while (i > 0) {
1940 if (str[i - 1] == ' ')
1941 break;
1942 i--;
1944 len = str_len - i;
1945 name = str + i;
1947 list_for_each_entry(e, &filters_head, node) {
1948 if (!strncmp(name, e->name, len))
1949 ptr_array_add(&array, xstrdup(e->name + len));
1951 if (!array.count)
1952 return;
1954 ptr_array_plug(&array);
1955 tabexp.head = xstrdup(str);
1956 tabexp.tails = array.ptrs;
1959 static void expand_options(const char *str)
1961 struct cmus_opt *opt;
1962 int len;
1963 char **tails;
1965 /* tabexp is resetted */
1966 len = strlen(str);
1967 if (len > 1 && str[len - 1] == '=') {
1968 /* expand value */
1969 char *var = xstrndup(str, len - 1);
1971 list_for_each_entry(opt, &option_head, node) {
1972 if (strcmp(var, opt->name) == 0) {
1973 char buf[OPTION_MAX_SIZE];
1975 tails = xnew(char *, 2);
1977 buf[0] = 0;
1978 opt->get(opt->id, buf);
1979 tails[0] = xstrdup(buf);
1980 tails[1] = NULL;
1982 tabexp.head = xstrdup(str);
1983 tabexp.tails = tails;
1984 free(var);
1985 return;
1988 free(var);
1989 } else {
1990 /* expand variable */
1991 int pos;
1993 tails = xnew(char *, nr_options + 1);
1994 pos = 0;
1995 list_for_each_entry(opt, &option_head, node) {
1996 if (strncmp(str, opt->name, len) == 0)
1997 tails[pos++] = xstrdup(opt->name + len);
1999 if (pos > 0) {
2000 if (pos == 1) {
2001 /* only one variable matches, add '=' */
2002 char *tmp = xstrjoin(tails[0], "=");
2004 free(tails[0]);
2005 tails[0] = tmp;
2008 tails[pos] = NULL;
2009 tabexp.head = xstrdup(str);
2010 tabexp.tails = tails;
2011 } else {
2012 free(tails);
2017 static void expand_toptions(const char *str)
2019 struct cmus_opt *opt;
2020 int len, pos;
2021 char **tails;
2023 tails = xnew(char *, nr_options + 1);
2024 len = strlen(str);
2025 pos = 0;
2026 list_for_each_entry(opt, &option_head, node) {
2027 if (opt->toggle == NULL)
2028 continue;
2029 if (strncmp(str, opt->name, len) == 0)
2030 tails[pos++] = xstrdup(opt->name + len);
2032 if (pos > 0) {
2033 tails[pos] = NULL;
2034 tabexp.head = xstrdup(str);
2035 tabexp.tails = tails;
2036 } else {
2037 free(tails);
2041 static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
2043 struct directory dir;
2044 const char *name, *dot;
2045 int len = strlen(str);
2047 if (dir_open(&dir, dirname))
2048 return;
2050 while ((name = dir_read(&dir))) {
2051 if (!S_ISREG(dir.st.st_mode))
2052 continue;
2053 if (strncmp(name, str, len))
2054 continue;
2055 dot = strrchr(name, '.');
2056 if (dot == NULL || strcmp(dot, ".theme"))
2057 continue;
2058 if (dot - name < len)
2059 /* str is "foo.th"
2060 * matches "foo.theme"
2061 * which also ends with ".theme"
2063 continue;
2064 ptr_array_add(array, xstrndup(name + len, dot - name - len));
2066 dir_close(&dir);
2069 static void expand_colorscheme(const char *str)
2071 PTR_ARRAY(array);
2073 load_themes(cmus_config_dir, str, &array);
2074 load_themes(DATADIR "/cmus", str, &array);
2076 if (array.count) {
2077 ptr_array_sort(&array, strptrcmp);
2079 ptr_array_plug(&array);
2080 tabexp.head = xstrdup(str);
2081 tabexp.tails = array.ptrs;
2085 /* tab exp }}} */
2087 /* sort by name */
2088 struct command commands[] = {
2089 { "add", cmd_add, 1, 1, expand_add, 0, 0 },
2090 { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
2091 { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
2092 { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
2093 { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
2094 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
2095 { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
2096 { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
2097 { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
2098 { "fset", cmd_fset, 1, 1, NULL, 0, 0 },
2099 { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
2100 { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
2101 { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
2102 { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
2103 { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
2104 { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
2105 { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
2106 { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
2107 { "quit", cmd_quit, 0, 0, NULL, 0, 0 },
2108 { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
2109 { "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
2110 { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
2111 { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
2112 { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
2113 { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
2114 { "set", cmd_set, 1, 1, expand_options, 0, 0 },
2115 { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
2116 { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
2117 { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
2118 { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
2119 { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
2120 { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
2121 { "view", cmd_view, 1, 1, NULL, 0, 0 },
2122 { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
2123 { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
2124 { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
2125 { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
2126 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
2127 { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
2128 { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
2129 { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
2130 { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
2131 { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
2132 { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
2133 { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
2134 { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
2135 { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
2136 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
2137 { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
2138 { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
2139 { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
2140 { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
2141 { NULL, NULL, 0, 0, 0, 0, 0 }
2144 /* fills tabexp struct */
2145 static void expand_commands(const char *str)
2147 int i, len, pos;
2148 char **tails;
2150 /* tabexp is resetted */
2151 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2152 len = strlen(str);
2153 pos = 0;
2154 for (i = 0; commands[i].name; i++) {
2155 if (strncmp(str, commands[i].name, len) == 0)
2156 tails[pos++] = xstrdup(commands[i].name + len);
2158 if (pos > 0) {
2159 if (pos == 1) {
2160 /* only one command matches, add ' ' */
2161 char *tmp = xstrjoin(tails[0], " ");
2163 free(tails[0]);
2164 tails[0] = tmp;
2166 tails[pos] = NULL;
2167 tabexp.head = xstrdup(str);
2168 tabexp.tails = tails;
2169 } else {
2170 free(tails);
2174 struct command *get_command(const char *str)
2176 int i, len;
2178 while (*str == ' ')
2179 str++;
2180 for (len = 0; str[len] && str[len] != ' '; len++)
2183 for (i = 0; commands[i].name; i++) {
2184 if (strncmp(str, commands[i].name, len))
2185 continue;
2187 if (commands[i].name[len] == 0) {
2188 /* exact */
2189 return &commands[i];
2192 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2193 /* ambiguous */
2194 return NULL;
2196 return &commands[i];
2198 return NULL;
2201 /* fills tabexp struct */
2202 static void expand_command_line(const char *str)
2204 /* :command [arg]...
2206 * examples:
2208 * str expanded value (tabexp.head)
2209 * -------------------------------------
2210 * fs fset
2211 * b c bind common
2212 * se se (tabexp.tails = [ ek t ])
2214 /* command start/end, argument start */
2215 const char *cs, *ce, *as;
2216 const struct command *cmd;
2218 cs = str;
2219 ce = strchr(cs, ' ');
2220 if (ce == NULL) {
2221 /* expand command */
2222 expand_commands(cs);
2223 return;
2226 /* command must be expandable */
2227 cmd = get_command(cs);
2228 if (cmd == NULL) {
2229 /* command ambiguous or invalid */
2230 return;
2233 if (cmd->expand == NULL) {
2234 /* can't expand argument */
2235 return;
2238 as = ce;
2239 while (*as == ' ')
2240 as++;
2242 /* expand argument */
2243 cmd->expand(as);
2244 if (tabexp.head == NULL) {
2245 /* argument expansion failed */
2246 return;
2249 /* tabexp.head is now start of the argument string */
2250 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2251 free(tabexp.head);
2252 tabexp.head = xstrdup(expbuf);
2255 static void tab_expand(void)
2257 char *s1, *s2, *tmp;
2258 int pos;
2260 /* strip white space */
2261 pos = 0;
2262 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2263 pos++;
2265 /* string to expand */
2266 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2268 /* tail */
2269 s2 = xstrdup(cmdline.line + cmdline.bpos);
2271 tmp = tabexp_expand(s1, expand_command_line);
2272 if (tmp) {
2273 /* tmp.s2 */
2274 int l1, l2;
2276 l1 = strlen(tmp);
2277 l2 = strlen(s2);
2278 cmdline.blen = l1 + l2;
2279 if (cmdline.blen >= cmdline.size) {
2280 while (cmdline.blen >= cmdline.size)
2281 cmdline.size *= 2;
2282 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2284 sprintf(cmdline.line, "%s%s", tmp, s2);
2285 cmdline.bpos = l1;
2286 cmdline.cpos = u_strlen(tmp);
2287 cmdline.clen = u_strlen(cmdline.line);
2288 free(tmp);
2290 free(s1);
2291 free(s2);
2294 static void reset_tab_expansion(void)
2296 tabexp_reset();
2297 arg_expand_cmd = -1;
2300 int run_only_safe_commands;
2302 /* FIXME: parse all arguments */
2303 void run_command(const char *buf)
2305 char *cmd, *arg;
2306 int cmd_start, cmd_end, cmd_len;
2307 int arg_start, arg_end;
2308 int i;
2310 i = 0;
2311 while (buf[i] && buf[i] == ' ')
2312 i++;
2314 if (buf[i] == '#')
2315 return;
2317 cmd_start = i;
2318 while (buf[i] && buf[i] != ' ')
2319 i++;
2320 cmd_end = i;
2321 while (buf[i] && buf[i] == ' ')
2322 i++;
2323 arg_start = i;
2324 while (buf[i])
2325 i++;
2326 arg_end = i;
2328 cmd_len = cmd_end - cmd_start;
2329 if (cmd_len == 0)
2330 return;
2332 cmd = xstrndup(buf + cmd_start, cmd_len);
2333 if (arg_start == arg_end) {
2334 arg = NULL;
2335 } else {
2336 arg = xstrndup(buf + arg_start, arg_end - arg_start);
2338 i = 0;
2339 while (1) {
2340 const struct command *c = &commands[i];
2342 if (c->name == NULL) {
2343 error_msg("unknown command\n");
2344 break;
2346 if (strncmp(cmd, c->name, cmd_len) == 0) {
2347 const char *next = commands[i + 1].name;
2348 int exact = c->name[cmd_len] == 0;
2350 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2351 error_msg("ambiguous command\n");
2352 break;
2354 if (c->min_args > 0 && arg == NULL) {
2355 error_msg("not enough arguments\n");
2356 break;
2358 if (c->max_args == 0 && arg) {
2359 error_msg("too many arguments\n");
2360 break;
2362 if (run_only_safe_commands && c->flags & CMD_UNSAFE) {
2363 d_print("trying to execute unsafe command over net\n");
2364 break;
2366 c->func(arg);
2367 break;
2369 i++;
2371 free(arg);
2372 free(cmd);
2375 static void reset_history_search(void)
2377 history_reset_search(&cmd_history);
2378 free(history_search_text);
2379 history_search_text = NULL;
2382 static void backspace(void)
2384 if (cmdline.clen > 0) {
2385 cmdline_backspace();
2386 } else {
2387 input_mode = NORMAL_MODE;
2391 void command_mode_ch(uchar ch)
2393 switch (ch) {
2394 case 0x01: // ^A
2395 cmdline_move_home();
2396 break;
2397 case 0x02: // ^B
2398 cmdline_move_left();
2399 break;
2400 case 0x04: // ^D
2401 cmdline_delete_ch();
2402 break;
2403 case 0x05: // ^E
2404 cmdline_move_end();
2405 break;
2406 case 0x06: // ^F
2407 cmdline_move_right();
2408 break;
2409 case 0x03: // ^C
2410 case 0x07: // ^G
2411 case 0x1B: // ESC
2412 if (cmdline.blen) {
2413 history_add_line(&cmd_history, cmdline.line);
2414 cmdline_clear();
2416 input_mode = NORMAL_MODE;
2417 break;
2418 case 0x0A:
2419 if (cmdline.blen) {
2420 run_command(cmdline.line);
2421 history_add_line(&cmd_history, cmdline.line);
2422 cmdline_clear();
2424 input_mode = NORMAL_MODE;
2425 break;
2426 case 0x0B:
2427 cmdline_clear_end();
2428 break;
2429 case 0x09:
2430 /* tab expansion should not complain */
2431 display_errors = 0;
2433 tab_expand();
2434 break;
2435 case 0x15:
2436 cmdline_backspace_to_bol();
2437 break;
2438 case 127:
2439 backspace();
2440 break;
2441 default:
2442 cmdline_insert_ch(ch);
2444 reset_history_search();
2445 if (ch != 0x09)
2446 reset_tab_expansion();
2449 void command_mode_key(int key)
2451 reset_tab_expansion();
2452 switch (key) {
2453 case KEY_DC:
2454 cmdline_delete_ch();
2455 break;
2456 case KEY_BACKSPACE:
2457 backspace();
2458 break;
2459 case KEY_LEFT:
2460 cmdline_move_left();
2461 return;
2462 case KEY_RIGHT:
2463 cmdline_move_right();
2464 return;
2465 case KEY_HOME:
2466 cmdline_move_home();
2467 return;
2468 case KEY_END:
2469 cmdline_move_end();
2470 return;
2471 case KEY_UP:
2473 const char *s;
2475 if (history_search_text == NULL)
2476 history_search_text = xstrdup(cmdline.line);
2477 s = history_search_forward(&cmd_history, history_search_text);
2478 if (s)
2479 cmdline_set_text(s);
2481 return;
2482 case KEY_DOWN:
2483 if (history_search_text) {
2484 const char *s;
2486 s = history_search_backward(&cmd_history, history_search_text);
2487 if (s) {
2488 cmdline_set_text(s);
2489 } else {
2490 cmdline_set_text(history_search_text);
2493 return;
2494 default:
2495 d_print("key = %c (%d)\n", key, key);
2497 reset_history_search();
2500 void commands_init(void)
2502 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2503 history_load(&cmd_history, cmd_history_filename, 2000);
2506 void commands_exit(void)
2508 history_save(&cmd_history);
2509 free(cmd_history_filename);
2510 tabexp_reset();