Add xterm-white.theme
[cmus.git] / command_mode.c
blobb98376e9d3ef29f5e2ae5fcbf197b1f5aca5a85f
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 "config/datadir.h"
49 #include <stdlib.h>
50 #include <curses.h>
51 #include <ctype.h>
52 #include <sys/types.h>
53 #include <sys/wait.h>
54 #include <dirent.h>
55 #include <pwd.h>
57 static struct history cmd_history;
58 static char *cmd_history_filename;
59 static char *history_search_text = NULL;
60 static int arg_expand_cmd = -1;
62 static char *get_home_dir(const char *username)
64 struct passwd *passwd;
66 if (username == NULL)
67 return xstrdup(home_dir);
68 passwd = getpwnam(username);
69 if (passwd == NULL)
70 return NULL;
71 /* don't free passwd */
72 return xstrdup(passwd->pw_dir);
75 static char *expand_filename(const char *name)
77 if (name[0] == '~') {
78 char *slash;
80 slash = strchr(name, '/');
81 if (slash) {
82 char *username, *home;
84 if (slash - name - 1 > 0) {
85 /* ~user/... */
86 username = xstrndup(name + 1, slash - name - 1);
87 } else {
88 /* ~/... */
89 username = NULL;
91 home = get_home_dir(username);
92 free(username);
93 if (home) {
94 char *expanded;
96 expanded = xstrjoin(home, slash);
97 free(home);
98 return expanded;
99 } else {
100 return xstrdup(name);
102 } else {
103 if (name[1] == 0) {
104 return xstrdup(home_dir);
105 } else {
106 char *home;
108 home = get_home_dir(name + 1);
109 if (home)
110 return home;
111 return xstrdup(name);
114 } else {
115 return xstrdup(name);
119 /* view {{{ */
121 void view_clear(int view)
123 switch (view) {
124 case TREE_VIEW:
125 case SORTED_VIEW:
126 worker_remove_jobs(JOB_TYPE_LIB);
127 editable_lock();
128 editable_clear(&lib_editable);
130 /* FIXME: make this optional? */
131 lib_clear_store();
133 editable_unlock();
134 break;
135 case PLAYLIST_VIEW:
136 worker_remove_jobs(JOB_TYPE_PL);
137 editable_lock();
138 editable_clear(&pl_editable);
139 editable_unlock();
140 break;
141 case QUEUE_VIEW:
142 worker_remove_jobs(JOB_TYPE_QUEUE);
143 editable_lock();
144 editable_clear(&pq_editable);
145 editable_unlock();
146 break;
147 default:
148 info_msg(":clear only works in views 1-4");
152 void view_add(int view, char *arg, int prepend)
154 char *tmp, *name;
155 enum file_type ft;
157 tmp = expand_filename(arg);
158 ft = cmus_detect_ft(tmp, &name);
159 if (ft == FILE_TYPE_INVALID) {
160 error_msg("adding '%s': %s", tmp, strerror(errno));
161 free(tmp);
162 return;
164 free(tmp);
166 switch (view) {
167 case TREE_VIEW:
168 case SORTED_VIEW:
169 cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB);
170 break;
171 case PLAYLIST_VIEW:
172 cmus_add(pl_add_track, name, ft, JOB_TYPE_PL);
173 break;
174 case QUEUE_VIEW:
175 if (prepend) {
176 cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE);
177 } else {
178 cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE);
180 break;
181 default:
182 info_msg(":add only works in views 1-4");
184 free(name);
187 void view_load(int view, char *arg)
189 char *tmp, *name;
190 enum file_type ft;
192 tmp = expand_filename(arg);
193 ft = cmus_detect_ft(tmp, &name);
194 if (ft == FILE_TYPE_INVALID) {
195 error_msg("loading '%s': %s", tmp, strerror(errno));
196 free(tmp);
197 return;
199 free(tmp);
201 if (ft == FILE_TYPE_FILE)
202 ft = FILE_TYPE_PL;
203 if (ft != FILE_TYPE_PL) {
204 error_msg("loading '%s': not a playlist file", name);
205 free(name);
206 return;
209 switch (view) {
210 case TREE_VIEW:
211 case SORTED_VIEW:
212 worker_remove_jobs(JOB_TYPE_LIB);
213 editable_lock();
214 editable_clear(&lib_editable);
215 editable_unlock();
216 cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB);
217 free(lib_filename);
218 lib_filename = name;
219 break;
220 case PLAYLIST_VIEW:
221 worker_remove_jobs(JOB_TYPE_PL);
222 editable_lock();
223 editable_clear(&pl_editable);
224 editable_unlock();
225 cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL);
226 free(pl_filename);
227 pl_filename = name;
228 break;
229 default:
230 info_msg(":load only works in views 1-3");
231 free(name);
235 static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep)
237 char *filename = *filenamep;
239 if (arg) {
240 free(filename);
241 filename = xstrdup(arg);
242 *filenamep = filename;
245 editable_lock();
246 if (cmus_save(for_each_ti, filename) == -1)
247 error_msg("saving '%s': %s", filename, strerror(errno));
248 editable_unlock();
251 void view_save(int view, char *arg)
253 if (arg) {
254 char *tmp;
256 tmp = expand_filename(arg);
257 arg = path_absolute(tmp);
258 free(tmp);
261 switch (view) {
262 case TREE_VIEW:
263 case SORTED_VIEW:
264 do_save(lib_for_each, arg, &lib_filename);
265 break;
266 case PLAYLIST_VIEW:
267 do_save(pl_for_each, arg, &pl_filename);
268 break;
269 default:
270 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
272 free(arg);
275 /* }}} */
277 /* only returns the last flag which is enough for it's callers */
278 static int parse_flags(const char **strp, const char *flags)
280 const char *str = *strp;
281 int flag = 0;
283 if (str == NULL)
284 return flag;
286 while (*str) {
287 if (*str != '-')
288 break;
290 // "-"
291 if (str[1] == 0)
292 break;
294 // "--" or "-- "
295 if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
296 str += 2;
297 break;
300 // not "-?" or "-? "
301 if (str[2] && str[2] != ' ')
302 break;
304 flag = str[1];
305 if (!strchr(flags, flag)) {
306 error_msg("invalid option -%c", flag);
307 return -1;
310 str += 2;
312 while (*str == ' ')
313 str++;
315 while (*str == ' ')
316 str++;
317 if (*str == 0)
318 str = NULL;
319 *strp = str;
320 return flag;
323 static int flag_to_view(int flag)
325 switch (flag) {
326 case 'l':
327 return TREE_VIEW;
328 case 'p':
329 return PLAYLIST_VIEW;
330 case 'q':
331 case 'Q':
332 return QUEUE_VIEW;
333 default:
334 return cur_view;
338 static void cmd_add(char *arg)
340 int flag = parse_flags((const char **)&arg, "lpqQ");
342 if (flag == -1)
343 return;
344 if (arg == NULL) {
345 error_msg("not enough arguments\n");
346 return;
348 view_add(flag_to_view(flag), arg, flag == 'Q');
351 static void cmd_clear(char *arg)
353 int flag = parse_flags((const char **)&arg, "lpq");
355 if (flag == -1)
356 return;
357 if (arg) {
358 error_msg("too many arguments\n");
359 return;
361 view_clear(flag_to_view(flag));
364 static void cmd_load(char *arg)
366 int flag = parse_flags((const char **)&arg, "lp");
368 if (flag == -1)
369 return;
370 if (arg == NULL) {
371 error_msg("not enough arguments\n");
372 return;
374 view_load(flag_to_view(flag), arg);
377 static void cmd_save(char *arg)
379 int flag = parse_flags((const char **)&arg, "lp");
381 if (flag == -1)
382 return;
383 view_save(flag_to_view(flag), arg);
386 static void cmd_set(char *arg)
388 char *value = NULL;
389 int i;
391 for (i = 0; arg[i]; i++) {
392 if (arg[i] == '=') {
393 arg[i] = 0;
394 value = &arg[i + 1];
395 break;
398 if (value) {
399 option_set(arg, value);
400 } else {
401 struct cmus_opt *opt;
402 char buf[OPTION_MAX_SIZE];
404 /* support "set <option>?" */
405 i--;
406 if (arg[i] == '?')
407 arg[i] = 0;
409 opt = option_find(arg);
410 if (opt) {
411 opt->get(opt->id, buf);
412 info_msg("setting: '%s=%s'", arg, buf);
417 static void cmd_toggle(char *arg)
419 struct cmus_opt *opt = option_find(arg);
421 if (opt == NULL)
422 return;
424 if (opt->toggle == NULL) {
425 error_msg("%s is not toggle option", opt->name);
426 return;
428 opt->toggle(opt->id);
431 static int get_number(char *str, char **end)
433 int val = 0;
435 while (*str >= '0' && *str <= '9') {
436 val *= 10;
437 val += *str++ - '0';
439 *end = str;
440 return val;
443 static void cmd_seek(char *arg)
445 int seek_mode = SEEK_SET;
446 int seek = 0, sign = 1, count;
448 switch (*arg) {
449 case '-':
450 sign = -1;
451 case '+':
452 seek_mode = SEEK_CUR;
453 arg++;
454 break;
457 count = 0;
458 goto inside;
460 do {
461 int num;
462 char *end;
464 if (*arg != ':')
465 break;
466 arg++;
467 inside:
468 num = get_number(arg, &end);
469 if (arg == end)
470 break;
471 arg = end;
472 seek = seek * 60 + num;
473 } while (++count < 3);
475 seek *= sign;
476 if (!count)
477 goto err;
479 if (count == 1) {
480 switch (tolower(*arg)) {
481 case 'h':
482 seek *= 60;
483 case 'm':
484 seek *= 60;
485 case 's':
486 arg++;
487 break;
491 if (!*arg) {
492 player_seek(seek, seek_mode);
493 return;
495 err:
496 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
499 static void cmd_factivate(char *arg)
501 editable_lock();
502 filters_activate_names(arg);
503 editable_unlock();
506 static void cmd_filter(char *arg)
508 editable_lock();
509 filters_set_anonymous(arg);
510 editable_unlock();
513 static void cmd_fset(char *arg)
515 filters_set_filter(arg);
518 static void cmd_invert(char *arg)
520 editable_lock();
521 switch (cur_view) {
522 case SORTED_VIEW:
523 editable_invert_marks(&lib_editable);
524 break;
525 case PLAYLIST_VIEW:
526 editable_invert_marks(&pl_editable);
527 break;
528 case QUEUE_VIEW:
529 editable_invert_marks(&pq_editable);
530 break;
531 default:
532 info_msg(":invert only works in views 2-4");
534 editable_unlock();
537 static void cmd_mark(char *arg)
539 editable_lock();
540 switch (cur_view) {
541 case SORTED_VIEW:
542 editable_mark(&lib_editable, arg);
543 break;
544 case PLAYLIST_VIEW:
545 editable_mark(&pl_editable, arg);
546 break;
547 case QUEUE_VIEW:
548 editable_mark(&pq_editable, arg);
549 break;
550 default:
551 info_msg(":mark only works in views 2-4");
553 editable_unlock();
556 static void cmd_unmark(char *arg)
558 editable_lock();
559 switch (cur_view) {
560 case SORTED_VIEW:
561 editable_unmark(&lib_editable);
562 break;
563 case PLAYLIST_VIEW:
564 editable_unmark(&pl_editable);
565 break;
566 case QUEUE_VIEW:
567 editable_unmark(&pq_editable);
568 break;
569 default:
570 info_msg(":unmark only works in views 2-4");
572 editable_unlock();
575 static void cmd_cd(char *arg)
577 if (arg) {
578 char *dir, *absolute;
580 dir = expand_filename(arg);
581 absolute = path_absolute(dir);
582 if (chdir(dir) == -1) {
583 error_msg("could not cd to '%s': %s", dir, strerror(errno));
584 } else {
585 browser_chdir(absolute);
587 free(absolute);
588 free(dir);
589 } else {
590 if (chdir(home_dir) == -1) {
591 error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
592 } else {
593 browser_chdir(home_dir);
598 static void cmd_bind(char *arg)
600 int flag = parse_flags((const char **)&arg, "f");
601 char *key, *func;
603 if (flag == -1)
604 return;
606 if (arg == NULL)
607 goto err;
609 key = strchr(arg, ' ');
610 if (key == NULL)
611 goto err;
612 *key++ = 0;
613 while (*key == ' ')
614 key++;
616 func = strchr(key, ' ');
617 if (func == NULL)
618 goto err;
619 *func++ = 0;
620 while (*func == ' ')
621 func++;
622 if (*func == 0)
623 goto err;
625 key_bind(arg, key, func, flag == 'f');
626 return;
627 err:
628 error_msg("expecting 3 arguments (context, key and function)\n");
631 static void cmd_unbind(char *arg)
633 int flag = parse_flags((const char **)&arg, "f");
634 char *key;
636 if (flag == -1)
637 return;
639 if (arg == NULL)
640 goto err;
642 key = strchr(arg, ' ');
643 if (key == NULL)
644 goto err;
645 *key++ = 0;
646 while (*key == ' ')
647 key++;
648 if (*key == 0)
649 goto err;
651 /* FIXME: remove spaces at end */
653 key_unbind(arg, key, flag == 'f');
654 return;
655 err:
656 error_msg("expecting 2 arguments (context and key)\n");
659 static void cmd_showbind(char *arg)
661 char *key;
663 key = strchr(arg, ' ');
664 if (key == NULL)
665 goto err;
666 *key++ = 0;
667 while (*key == ' ')
668 key++;
669 if (*key == 0)
670 goto err;
672 /* FIXME: remove spaces at end */
674 show_binding(arg, key);
675 return;
676 err:
677 error_msg("expecting 2 arguments (context and key)\n");
680 static void cmd_quit(char *arg)
682 quit();
685 static void cmd_reshuffle(char *arg)
687 editable_lock();
688 lib_reshuffle();
689 pl_reshuffle();
690 editable_unlock();
693 static void cmd_source(char *arg)
695 char *filename = expand_filename(arg);
697 if (source_file(filename) == -1)
698 error_msg("sourcing %s: %s", filename, strerror(errno));
699 free(filename);
702 static void cmd_colorscheme(char *arg)
704 char filename[512];
706 snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
707 if (source_file(filename) == -1) {
708 snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
709 if (source_file(filename) == -1)
710 error_msg("sourcing %s: %s", filename, strerror(errno));
715 * \" inside double-quotes becomes "
716 * \\ inside double-quotes becomes \
718 static char *parse_quoted(const char **strp)
720 const char *str = *strp;
721 const char *start;
722 char *ret, *dst;
724 str++;
725 start = str;
726 while (1) {
727 int c = *str++;
729 if (c == 0)
730 goto error;
731 if (c == '"')
732 break;
733 if (c == '\\') {
734 if (*str++ == 0)
735 goto error;
738 *strp = str;
739 ret = xnew(char, str - start);
740 str = start;
741 dst = ret;
742 while (1) {
743 int c = *str++;
745 if (c == '"')
746 break;
747 if (c == '\\') {
748 c = *str++;
749 if (c != '"' && c != '\\')
750 *dst++ = '\\';
752 *dst++ = c;
754 *dst = 0;
755 return ret;
756 error:
757 error_msg("`\"' expected");
758 return NULL;
761 static char *parse_escaped(const char **strp)
763 const char *str = *strp;
764 const char *start;
765 char *ret, *dst;
767 start = str;
768 while (1) {
769 int c = *str;
771 if (c == 0 || c == ' ' || c == '\'' || c == '"')
772 break;
774 str++;
775 if (c == '\\') {
776 c = *str;
777 if (c == 0)
778 break;
779 str++;
782 *strp = str;
783 ret = xnew(char, str - start + 1);
784 str = start;
785 dst = ret;
786 while (1) {
787 int c = *str;
789 if (c == 0 || c == ' ' || c == '\'' || c == '"')
790 break;
792 str++;
793 if (c == '\\') {
794 c = *str;
795 if (c == 0) {
796 *dst++ = '\\';
797 break;
799 str++;
801 *dst++ = c;
803 *dst = 0;
804 return ret;
807 static char *parse_one(const char **strp)
809 const char *str = *strp;
810 char *ret = NULL;
812 while (1) {
813 char *part;
814 int c = *str;
816 if (!c || c == ' ')
817 break;
818 if (c == '"') {
819 part = parse_quoted(&str);
820 if (part == NULL)
821 goto error;
822 } else if (c == '\'') {
823 /* backslashes are normal chars inside single-quotes */
824 const char *end;
826 str++;
827 end = strchr(str, '\'');
828 if (end == NULL)
829 goto sq_missing;
830 part = xstrndup(str, end - str);
831 str = end + 1;
832 } else {
833 part = parse_escaped(&str);
836 if (ret == NULL) {
837 ret = part;
838 } else {
839 char *tmp = xstrjoin(ret, part);
840 free(ret);
841 ret = tmp;
844 *strp = str;
845 return ret;
846 sq_missing:
847 error_msg("`'' expected");
848 error:
849 free(ret);
850 return NULL;
853 static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
855 char **av = NULL;
856 int nr = 0;
857 int alloc = 0;
859 while (*cmd) {
860 char *arg;
862 /* there can't be spaces at start of command
863 * and there is at least one argument */
864 if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
865 /* {} is replaced with file arguments */
866 if (*args_idx != -1)
867 goto only_once_please;
868 *args_idx = nr;
869 cmd += 2;
870 goto skip_spaces;
871 } else {
872 arg = parse_one(&cmd);
873 if (arg == NULL)
874 goto error;
877 if (nr == alloc) {
878 alloc = alloc ? alloc * 2 : 4;
879 av = xrenew(char *, av, alloc + 1);
881 av[nr++] = arg;
882 skip_spaces:
883 while (*cmd == ' ')
884 cmd++;
886 av[nr] = NULL;
887 *ac = nr;
888 return av;
889 only_once_please:
890 error_msg("{} can be used only once");
891 error:
892 while (nr > 0)
893 free(av[--nr]);
894 free(av);
895 return NULL;
898 static struct track_info **sel_tis;
899 static int sel_tis_alloc;
900 static int sel_tis_nr;
902 static int add_ti(void *data, struct track_info *ti)
904 if (sel_tis_nr == sel_tis_alloc) {
905 sel_tis_alloc = sel_tis_alloc ? sel_tis_alloc * 2 : 8;
906 sel_tis = xrenew(struct track_info *, sel_tis, sel_tis_alloc);
908 track_info_ref(ti);
909 sel_tis[sel_tis_nr++] = ti;
910 return 0;
913 static void cmd_run(char *arg)
915 char **av, **argv;
916 int ac, argc, i, run, files_idx = -1;
918 if (cur_view > QUEUE_VIEW) {
919 info_msg("Command execution is supported only in views 1-4");
920 return;
923 av = parse_cmd(arg, &files_idx, &ac);
924 if (av == NULL) {
925 return;
928 /* collect selected files (struct track_info) */
929 sel_tis = NULL;
930 sel_tis_alloc = 0;
931 sel_tis_nr = 0;
933 editable_lock();
934 switch (cur_view) {
935 case TREE_VIEW:
936 __tree_for_each_sel(add_ti, NULL, 0);
937 break;
938 case SORTED_VIEW:
939 __editable_for_each_sel(&lib_editable, add_ti, NULL, 0);
940 break;
941 case PLAYLIST_VIEW:
942 __editable_for_each_sel(&pl_editable, add_ti, NULL, 0);
943 break;
944 case QUEUE_VIEW:
945 __editable_for_each_sel(&pq_editable, add_ti, NULL, 0);
946 break;
948 editable_unlock();
950 if (sel_tis_nr == 0) {
951 /* no files selected, do nothing */
952 free_str_array(av);
953 return;
955 sel_tis[sel_tis_nr] = NULL;
957 /* build argv */
958 argv = xnew(char *, ac + sel_tis_nr + 1);
959 argc = 0;
960 if (files_idx == -1) {
961 /* add selected files after rest of the args */
962 for (i = 0; i < ac; i++)
963 argv[argc++] = av[i];
964 for (i = 0; i < sel_tis_nr; i++)
965 argv[argc++] = sel_tis[i]->filename;
966 } else {
967 for (i = 0; i < files_idx; i++)
968 argv[argc++] = av[i];
969 for (i = 0; i < sel_tis_nr; i++)
970 argv[argc++] = sel_tis[i]->filename;
971 for (i = files_idx; i < ac; i++)
972 argv[argc++] = av[i];
974 argv[argc] = NULL;
976 free(av);
978 for (i = 0; argv[i]; i++)
979 d_print("ARG: '%s'\n", argv[i]);
981 run = 1;
982 if (confirm_run && (sel_tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
983 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel_tis_nr)) {
984 info_msg("Aborted");
985 run = 0;
988 if (run) {
989 int status;
991 if (spawn(argv, &status)) {
992 error_msg("executing %s: %s", argv[0], strerror(errno));
993 } else {
994 if (WIFEXITED(status)) {
995 int rc = WEXITSTATUS(status);
997 if (rc)
998 error_msg("%s returned %d", argv[0], rc);
1000 if (WIFSIGNALED(status))
1001 error_msg("%s received signal %d", argv[0], WTERMSIG(status));
1003 switch (cur_view) {
1004 case TREE_VIEW:
1005 case SORTED_VIEW:
1006 /* this must be done before sel_tis are unreffed */
1007 free_str_array(argv);
1009 /* remove non-existed files, update tags for changed files */
1010 cmus_update_tis(sel_tis, sel_tis_nr);
1012 /* we don't own sel_tis anymore! */
1013 return;
1017 free_str_array(argv);
1018 for (i = 0; sel_tis[i]; i++)
1019 track_info_unref(sel_tis[i]);
1020 free(sel_tis);
1023 static int get_one_ti(void *data, struct track_info *ti)
1025 struct track_info **sel_ti = data;
1027 track_info_ref(ti);
1028 *sel_ti = ti;
1029 /* stop the for each loop, we need only the first selected track */
1030 return 1;
1033 static void cmd_echo(char *arg)
1035 struct track_info *sel_ti;
1036 char *ptr = arg;
1038 while (1) {
1039 ptr = strchr(ptr, '{');
1040 if (ptr == NULL)
1041 break;
1042 if (ptr[1] == '}')
1043 break;
1044 ptr++;
1047 if (ptr == NULL) {
1048 info_msg("%s", arg);
1049 return;
1052 if (cur_view > QUEUE_VIEW) {
1053 info_msg("echo with {} in its arguments is supported only in views 1-4");
1054 return;
1057 *ptr = 0;
1058 ptr += 2;
1060 /* get only the first selected track */
1061 sel_ti = NULL;
1063 editable_lock();
1064 switch (cur_view) {
1065 case TREE_VIEW:
1066 __tree_for_each_sel(get_one_ti, &sel_ti, 0);
1067 break;
1068 case SORTED_VIEW:
1069 __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
1070 break;
1071 case PLAYLIST_VIEW:
1072 __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
1073 break;
1074 case QUEUE_VIEW:
1075 __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
1076 break;
1078 editable_unlock();
1080 if (sel_ti == NULL)
1081 return;
1083 info_msg("%s%s%s", arg, sel_ti->filename, ptr);
1084 track_info_unref(sel_ti);
1087 #define VF_RELATIVE 0x01
1088 #define VF_PERCENTAGE 0x02
1090 static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
1092 unsigned int f = 0;
1093 int ch, val = 0, digits = 0, sign = 1;
1095 if (*arg == '-') {
1096 arg++;
1097 f |= VF_RELATIVE;
1098 sign = -1;
1099 } else if (*arg == '+') {
1100 arg++;
1101 f |= VF_RELATIVE;
1104 while (1) {
1105 ch = *arg++;
1106 if (ch < '0' || ch > '9')
1107 break;
1108 val *= 10;
1109 val += ch - '0';
1110 digits++;
1112 if (digits == 0)
1113 goto err;
1115 if (ch == '%') {
1116 f |= VF_PERCENTAGE;
1117 ch = *arg;
1119 if (ch)
1120 goto err;
1122 *value = sign * val;
1123 *flags = f;
1124 return 0;
1125 err:
1126 return -1;
1129 static int calc_vol(int val, int old, unsigned int flags)
1131 if (flags & VF_RELATIVE) {
1132 if (flags & VF_PERCENTAGE)
1133 val = scale_from_percentage(val, volume_max);
1134 val += old;
1135 } else if (flags & VF_PERCENTAGE) {
1136 val = scale_from_percentage(val, volume_max);
1138 return clamp(val, 0, volume_max);
1142 * :vol value [value]
1144 * where value is [-+]?[0-9]+%?
1146 static void cmd_vol(char *arg)
1148 char **values = get_words(arg);
1149 unsigned int lf, rf;
1150 int l, r, ol, or;
1152 if (values[1] && values[2])
1153 goto err;
1155 if (parse_vol_arg(values[0], &l, &lf))
1156 goto err;
1158 r = l;
1159 rf = lf;
1160 if (values[1] && parse_vol_arg(values[1], &r, &rf))
1161 goto err;
1163 free_str_array(values);
1165 player_get_volume(&ol, &or);
1166 l = calc_vol(l, ol, lf);
1167 r = calc_vol(r, or, rf);
1168 player_set_volume(l, r);
1169 return;
1170 err:
1171 free_str_array(values);
1172 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1175 static void cmd_view(char *arg)
1177 int view;
1179 if (parse_enum(arg, 1, NR_VIEWS, view_names, &view))
1180 set_view(view - 1);
1183 static void cmd_p_next(char *arg)
1185 cmus_next();
1188 static void cmd_p_pause(char *arg)
1190 player_pause();
1193 static void cmd_p_play(char *arg)
1195 if (arg) {
1196 player_play_file(arg);
1197 } else {
1198 player_play();
1202 static void cmd_p_prev(char *arg)
1204 cmus_prev();
1207 static void cmd_p_stop(char *arg)
1209 player_stop();
1212 static void cmd_search_next(char *arg)
1214 if (search_str) {
1215 if (!search_next(searchable, search_str, search_direction))
1216 search_not_found();
1220 static void cmd_search_prev(char *arg)
1222 if (search_str) {
1223 if (!search_next(searchable, search_str, !search_direction))
1224 search_not_found();
1228 static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1230 return editable_for_each_sel(&lib_editable, cb, data, reverse);
1233 static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1235 return editable_for_each_sel(&pl_editable, cb, data, reverse);
1238 static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
1240 return editable_for_each_sel(&pq_editable, cb, data, reverse);
1243 static for_each_sel_ti_cb view_for_each_sel[4] = {
1244 tree_for_each_sel,
1245 sorted_for_each_sel,
1246 pl_for_each_sel,
1247 pq_for_each_sel
1250 /* wrapper for void lib_add_track(struct track_info *) etc. */
1251 static int wrapper_cb(void *data, struct track_info *ti)
1253 add_ti_cb add = data;
1255 add(ti);
1256 return 0;
1259 static void add_from_browser(add_ti_cb add, int job_type)
1261 char *sel = browser_get_sel();
1263 if (sel) {
1264 enum file_type ft;
1265 char *ret;
1267 ft = cmus_detect_ft(sel, &ret);
1268 if (ft != FILE_TYPE_INVALID) {
1269 cmus_add(add, ret, ft, job_type);
1270 window_down(browser_win, 1);
1272 free(ret);
1273 free(sel);
1277 static void cmd_win_add_l(char *arg)
1279 if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
1280 return;
1282 if (cur_view <= QUEUE_VIEW) {
1283 editable_lock();
1284 view_for_each_sel[cur_view](wrapper_cb, lib_add_track, 0);
1285 editable_unlock();
1286 } else if (cur_view == BROWSER_VIEW) {
1287 add_from_browser(lib_add_track, JOB_TYPE_LIB);
1291 static void cmd_win_add_p(char *arg)
1293 /* could allow adding dups? */
1294 if (cur_view == PLAYLIST_VIEW)
1295 return;
1297 if (cur_view <= QUEUE_VIEW) {
1298 editable_lock();
1299 view_for_each_sel[cur_view](wrapper_cb, pl_add_track, 0);
1300 editable_unlock();
1301 } else if (cur_view == BROWSER_VIEW) {
1302 add_from_browser(pl_add_track, JOB_TYPE_PL);
1306 static void cmd_win_add_Q(char *arg)
1308 if (cur_view == QUEUE_VIEW)
1309 return;
1311 if (cur_view <= QUEUE_VIEW) {
1312 editable_lock();
1313 view_for_each_sel[cur_view](wrapper_cb, play_queue_prepend, 1);
1314 editable_unlock();
1315 } else if (cur_view == BROWSER_VIEW) {
1316 add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
1320 static void cmd_win_add_q(char *arg)
1322 if (cur_view == QUEUE_VIEW)
1323 return;
1325 if (cur_view <= QUEUE_VIEW) {
1326 editable_lock();
1327 view_for_each_sel[cur_view](wrapper_cb, play_queue_append, 0);
1328 editable_unlock();
1329 } else if (cur_view == BROWSER_VIEW) {
1330 add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
1334 static void cmd_win_activate(char *arg)
1336 struct track_info *info = NULL;
1338 editable_lock();
1339 switch (cur_view) {
1340 case TREE_VIEW:
1341 info = tree_set_selected();
1342 break;
1343 case SORTED_VIEW:
1344 info = sorted_set_selected();
1345 break;
1346 case PLAYLIST_VIEW:
1347 info = pl_set_selected();
1348 break;
1349 case QUEUE_VIEW:
1350 break;
1351 case BROWSER_VIEW:
1352 browser_enter();
1353 break;
1354 case FILTERS_VIEW:
1355 filters_activate();
1356 break;
1358 editable_unlock();
1360 if (info) {
1361 /* update lib/pl mode */
1362 if (cur_view < 2)
1363 play_library = 1;
1364 if (cur_view == 2)
1365 play_library = 0;
1367 player_play_file(info->filename);
1368 track_info_unref(info);
1372 static void cmd_win_mv_after(char *arg)
1374 editable_lock();
1375 switch (cur_view) {
1376 case TREE_VIEW:
1377 break;
1378 case SORTED_VIEW:
1379 editable_move_after(&lib_editable);
1380 break;
1381 case PLAYLIST_VIEW:
1382 editable_move_after(&pl_editable);
1383 break;
1384 case QUEUE_VIEW:
1385 editable_move_after(&pq_editable);
1386 break;
1387 case BROWSER_VIEW:
1388 break;
1389 case FILTERS_VIEW:
1390 break;
1392 editable_unlock();
1395 static void cmd_win_mv_before(char *arg)
1397 editable_lock();
1398 switch (cur_view) {
1399 case TREE_VIEW:
1400 break;
1401 case SORTED_VIEW:
1402 editable_move_before(&lib_editable);
1403 break;
1404 case PLAYLIST_VIEW:
1405 editable_move_before(&pl_editable);
1406 break;
1407 case QUEUE_VIEW:
1408 editable_move_before(&pq_editable);
1409 break;
1410 case BROWSER_VIEW:
1411 break;
1412 case FILTERS_VIEW:
1413 break;
1415 editable_unlock();
1418 static void cmd_win_remove(char *arg)
1420 editable_lock();
1421 switch (cur_view) {
1422 case TREE_VIEW:
1423 tree_remove_sel();
1424 break;
1425 case SORTED_VIEW:
1426 editable_remove_sel(&lib_editable);
1427 break;
1428 case PLAYLIST_VIEW:
1429 editable_remove_sel(&pl_editable);
1430 break;
1431 case QUEUE_VIEW:
1432 editable_remove_sel(&pq_editable);
1433 break;
1434 case BROWSER_VIEW:
1435 browser_delete();
1436 break;
1437 case FILTERS_VIEW:
1438 filters_delete_filter();
1439 break;
1441 editable_unlock();
1444 static void cmd_win_sel_cur(char *arg)
1446 editable_lock();
1447 switch (cur_view) {
1448 case TREE_VIEW:
1449 tree_sel_current();
1450 break;
1451 case SORTED_VIEW:
1452 sorted_sel_current();
1453 break;
1454 case PLAYLIST_VIEW:
1455 pl_sel_current();
1456 break;
1457 case QUEUE_VIEW:
1458 break;
1459 case BROWSER_VIEW:
1460 break;
1461 case FILTERS_VIEW:
1462 break;
1464 editable_unlock();
1467 static void cmd_win_toggle(char *arg)
1469 editable_lock();
1470 switch (cur_view) {
1471 case TREE_VIEW:
1472 tree_toggle_expand_artist();
1473 break;
1474 case SORTED_VIEW:
1475 editable_toggle_mark(&lib_editable);
1476 break;
1477 case PLAYLIST_VIEW:
1478 editable_toggle_mark(&pl_editable);
1479 break;
1480 case QUEUE_VIEW:
1481 editable_toggle_mark(&pq_editable);
1482 break;
1483 case BROWSER_VIEW:
1484 break;
1485 case FILTERS_VIEW:
1486 filters_toggle_filter();
1487 break;
1489 editable_unlock();
1492 static struct window *current_win(void)
1494 switch (cur_view) {
1495 case TREE_VIEW:
1496 return lib_cur_win;
1497 case SORTED_VIEW:
1498 return lib_editable.win;
1499 case PLAYLIST_VIEW:
1500 return pl_editable.win;
1501 case QUEUE_VIEW:
1502 return pq_editable.win;
1503 case BROWSER_VIEW:
1504 return browser_win;
1505 case FILTERS_VIEW:
1506 default:
1507 return filters_win;
1511 static void cmd_win_bottom(char *arg)
1513 editable_lock();
1514 window_goto_bottom(current_win());
1515 editable_unlock();
1518 static void cmd_win_down(char *arg)
1520 editable_lock();
1521 window_down(current_win(), 1);
1522 editable_unlock();
1525 static void cmd_win_next(char *arg)
1527 if (cur_view == TREE_VIEW) {
1528 editable_lock();
1529 tree_toggle_active_window();
1530 editable_unlock();
1534 static void cmd_win_pg_down(char *arg)
1536 editable_lock();
1537 window_page_down(current_win());
1538 editable_unlock();
1541 static void cmd_win_pg_up(char *arg)
1543 editable_lock();
1544 window_page_up(current_win());
1545 editable_unlock();
1548 static void cmd_win_top(char *arg)
1550 editable_lock();
1551 window_goto_top(current_win());
1552 editable_unlock();
1555 static void cmd_win_up(char *arg)
1557 editable_lock();
1558 window_up(current_win(), 1);
1559 editable_unlock();
1562 static void cmd_win_update(char *arg)
1564 switch (cur_view) {
1565 case TREE_VIEW:
1566 case SORTED_VIEW:
1567 cmus_update_lib();
1568 break;
1569 case BROWSER_VIEW:
1570 browser_reload();
1571 break;
1575 static void cmd_browser_up(char *arg)
1577 browser_up();
1580 static void cmd_refresh(char *arg)
1582 clearok(curscr, TRUE);
1583 refresh();
1586 /* tab exp {{{
1588 * these functions fill tabexp struct, which is resetted beforehand
1591 /* buffer used for tab expansion */
1592 static char expbuf[512];
1594 static int filter_directories(const char *name, const struct stat *s)
1596 return S_ISDIR(s->st_mode);
1599 static int filter_any(const char *name, const struct stat *s)
1601 return 1;
1604 static int filter_playable(const char *name, const struct stat *s)
1606 return S_ISDIR(s->st_mode) || cmus_is_playable(name);
1609 static int filter_playlist(const char *name, const struct stat *s)
1611 return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
1614 static int filter_supported(const char *name, const struct stat *s)
1616 return S_ISDIR(s->st_mode) || cmus_is_supported(name);
1619 static void expand_files(const char *str)
1621 expand_files_and_dirs(str, filter_any);
1624 static void expand_directories(const char *str)
1626 expand_files_and_dirs(str, filter_directories);
1629 static void expand_playable(const char *str)
1631 expand_files_and_dirs(str, filter_playable);
1634 static void expand_playlist(const char *str)
1636 expand_files_and_dirs(str, filter_playlist);
1639 static void expand_supported(const char *str)
1641 expand_files_and_dirs(str, filter_supported);
1644 static void expand_add(const char *str)
1646 int flag = parse_flags(&str, "lpqQ");
1648 if (flag == -1)
1649 return;
1650 if (str == NULL)
1651 str = "";
1652 expand_supported(str);
1654 if (tabexp.head && flag) {
1655 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1656 free(tabexp.head);
1657 tabexp.head = xstrdup(expbuf);
1661 static void expand_load_save(const char *str)
1663 int flag = parse_flags(&str, "lp");
1665 if (flag == -1)
1666 return;
1667 if (str == NULL)
1668 str = "";
1669 expand_playlist(str);
1671 if (tabexp.head && flag) {
1672 snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
1673 free(tabexp.head);
1674 tabexp.head = xstrdup(expbuf);
1678 static void expand_key_context(const char *str, const char *force)
1680 int pos, i, len = strlen(str);
1681 char **tails;
1683 tails = xnew(char *, NR_CTXS + 1);
1684 pos = 0;
1685 for (i = 0; key_context_names[i]; i++) {
1686 int cmp = strncmp(str, key_context_names[i], len);
1687 if (cmp > 0)
1688 continue;
1689 if (cmp < 0)
1690 break;
1691 tails[pos++] = xstrdup(key_context_names[i] + len);
1694 if (pos == 0) {
1695 free(tails);
1696 return;
1698 if (pos == 1) {
1699 char *tmp = xstrjoin(tails[0], " ");
1700 free(tails[0]);
1701 tails[0] = tmp;
1703 tails[pos] = NULL;
1704 snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
1705 tabexp.head = xstrdup(expbuf);
1706 tabexp.tails = tails;
1707 tabexp.nr_tails = pos;
1710 static int get_context(const char *str, int len)
1712 int i, c = -1, count = 0;
1714 for (i = 0; key_context_names[i]; i++) {
1715 if (strncmp(str, key_context_names[i], len) == 0) {
1716 if (key_context_names[i][len] == 0) {
1717 /* exact */
1718 return i;
1720 c = i;
1721 count++;
1724 if (count == 1)
1725 return c;
1726 return -1;
1729 static void expand_command_line(const char *str);
1731 static void expand_bind_args(const char *str)
1733 /* :bind context key function
1735 * possible values for str:
1737 * context k
1738 * context key f
1740 * you need to know context before you can expand function
1742 /* start and end pointers for context, key and function */
1743 const char *cs, *ce, *ks, *ke, *fs;
1744 char *tmp, **tails;
1745 int i, c, k, len, pos, alloc, count;
1746 int flag = parse_flags((const char **)&str, "f");
1747 const char *force = "";
1749 if (flag == -1)
1750 return;
1751 if (str == NULL)
1752 str = "";
1754 if (flag == 'f')
1755 force = "-f ";
1757 cs = str;
1758 ce = strchr(cs, ' ');
1759 if (ce == NULL) {
1760 expand_key_context(cs, force);
1761 return;
1764 /* context must be expandable */
1765 c = get_context(cs, ce - cs);
1766 if (c == -1) {
1767 /* context is ambiguous or invalid */
1768 return;
1771 ks = ce;
1772 while (*ks == ' ')
1773 ks++;
1774 ke = strchr(ks, ' ');
1775 if (ke == NULL) {
1776 /* expand key */
1777 len = strlen(ks);
1778 tails = NULL;
1779 alloc = 0;
1780 pos = 0;
1781 for (i = 0; key_table[i].name; i++) {
1782 int cmp = strncmp(ks, key_table[i].name, len);
1783 if (cmp > 0)
1784 continue;
1785 if (cmp < 0)
1786 break;
1787 tails = str_array_add(tails, &alloc, &pos, xstrdup(key_table[i].name + len));
1790 if (pos == 0) {
1791 return;
1793 if (pos == 1) {
1794 tmp = xstrjoin(tails[0], " ");
1795 free(tails[0]);
1796 tails[0] = tmp;
1799 snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
1801 tails[pos] = NULL;
1802 tabexp.head = xstrdup(expbuf);
1803 tabexp.tails = tails;
1804 tabexp.nr_tails = pos;
1805 return;
1808 /* key must be expandable */
1809 k = -1;
1810 count = 0;
1811 for (i = 0; key_table[i].name; i++) {
1812 if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
1813 if (key_table[i].name[ke - ks] == 0) {
1814 /* exact */
1815 k = i;
1816 count = 1;
1817 break;
1819 k = i;
1820 count++;
1823 if (count != 1) {
1824 /* key is ambiguous or invalid */
1825 return;
1828 fs = ke;
1829 while (*fs == ' ')
1830 fs++;
1832 if (*fs == ':')
1833 fs++;
1835 /* expand com [arg...] */
1836 expand_command_line(fs);
1837 if (tabexp.head == NULL) {
1838 /* command expand failed */
1839 return;
1843 * tabexp.head is now "com"
1844 * tabexp.tails is [ mand1 mand2 ... ]
1846 * need to change tabexp.head to "context key com"
1849 snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
1850 key_table[k].name, tabexp.head);
1851 free(tabexp.head);
1852 tabexp.head = xstrdup(expbuf);
1855 static void expand_unbind_args(const char *str)
1857 /* :unbind context key */
1858 /* start and end pointers for context and key */
1859 const char *cs, *ce, *ks;
1860 char **tails;
1861 int c, len, pos, alloc;
1862 const struct binding *b;
1864 cs = str;
1865 ce = strchr(cs, ' ');
1866 if (ce == NULL) {
1867 expand_key_context(cs, "");
1868 return;
1871 /* context must be expandable */
1872 c = get_context(cs, ce - cs);
1873 if (c == -1) {
1874 /* context is ambiguous or invalid */
1875 return;
1878 ks = ce;
1879 while (*ks == ' ')
1880 ks++;
1882 /* expand key */
1883 len = strlen(ks);
1884 tails = NULL;
1885 alloc = 0;
1886 pos = 0;
1887 b = key_bindings[c];
1888 while (b) {
1889 if (strncmp(ks, b->key->name, len) == 0)
1890 tails = str_array_add(tails, &alloc, &pos, xstrdup(b->key->name + len));
1891 b = b->next;
1893 if (pos == 0)
1894 return;
1896 snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
1898 tails[pos] = NULL;
1899 tabexp.head = xstrdup(expbuf);
1900 tabexp.tails = tails;
1901 tabexp.nr_tails = pos;
1904 static void expand_factivate(const char *str)
1906 /* "name1 name2 name3", expand only name3 */
1907 struct filter_entry *e;
1908 int str_len, len, i, pos, alloc;
1909 const char *name;
1910 char **tails;
1912 str_len = strlen(str);
1913 i = str_len;
1914 while (i > 0) {
1915 if (str[i - 1] == ' ')
1916 break;
1917 i--;
1919 len = str_len - i;
1920 name = str + i;
1922 tails = NULL;
1923 alloc = 0;
1924 pos = 0;
1925 list_for_each_entry(e, &filters_head, node) {
1926 if (strncmp(name, e->name, len) == 0)
1927 tails = str_array_add(tails, &alloc, &pos, xstrdup(e->name + len));
1929 if (pos == 0)
1930 return;
1932 tails[pos] = NULL;
1933 tabexp.head = xstrdup(str);
1934 tabexp.tails = tails;
1935 tabexp.nr_tails = pos;
1938 static void expand_options(const char *str)
1940 struct cmus_opt *opt;
1941 int len;
1942 char **tails;
1944 /* tabexp is resetted */
1945 len = strlen(str);
1946 if (len > 1 && str[len - 1] == '=') {
1947 /* expand value */
1948 char *var = xstrndup(str, len - 1);
1950 list_for_each_entry(opt, &option_head, node) {
1951 if (strcmp(var, opt->name) == 0) {
1952 char buf[OPTION_MAX_SIZE];
1954 tails = xnew(char *, 1);
1956 buf[0] = 0;
1957 opt->get(opt->id, buf);
1958 tails[0] = xstrdup(buf);
1960 tails[1] = NULL;
1961 tabexp.head = xstrdup(str);
1962 tabexp.tails = tails;
1963 tabexp.nr_tails = 1;
1964 free(var);
1965 return;
1968 free(var);
1969 } else {
1970 /* expand variable */
1971 int pos;
1973 tails = xnew(char *, nr_options + 1);
1974 pos = 0;
1975 list_for_each_entry(opt, &option_head, node) {
1976 if (strncmp(str, opt->name, len) == 0)
1977 tails[pos++] = xstrdup(opt->name + len);
1979 if (pos > 0) {
1980 if (pos == 1) {
1981 /* only one variable matches, add '=' */
1982 char *tmp = xstrjoin(tails[0], "=");
1984 free(tails[0]);
1985 tails[0] = tmp;
1988 tails[pos] = NULL;
1989 tabexp.head = xstrdup(str);
1990 tabexp.tails = tails;
1991 tabexp.nr_tails = pos;
1992 } else {
1993 free(tails);
1998 static void expand_toptions(const char *str)
2000 struct cmus_opt *opt;
2001 int len, pos;
2002 char **tails;
2004 tails = xnew(char *, nr_options + 1);
2005 len = strlen(str);
2006 pos = 0;
2007 list_for_each_entry(opt, &option_head, node) {
2008 if (opt->toggle == NULL)
2009 continue;
2010 if (strncmp(str, opt->name, len) == 0)
2011 tails[pos++] = xstrdup(opt->name + len);
2013 if (pos > 0) {
2014 tails[pos] = NULL;
2015 tabexp.head = xstrdup(str);
2016 tabexp.tails = tails;
2017 tabexp.nr_tails = pos;
2018 } else {
2019 free(tails);
2023 static void load_themes(const char *dir, const char *str, int *allocp)
2025 DIR *d;
2026 struct dirent *dirent;
2027 int len = strlen(str);
2029 d = opendir(dir);
2030 if (d == NULL)
2031 return;
2033 while ((dirent = readdir(d)) != NULL) {
2034 const char *dot, *name = dirent->d_name;
2035 char filename[512];
2036 struct stat s;
2038 if (strncmp(name, str, len))
2039 continue;
2041 dot = strrchr(name, '.');
2042 if (dot == NULL || strcmp(dot, ".theme"))
2043 continue;
2045 if (dot - name < len) {
2046 /* str is "foo.th"
2047 * matches "foo.theme"
2048 * which also ends with ".theme"
2050 continue;
2053 snprintf(filename, sizeof(filename), "%s/%s", dir, name);
2054 if (stat(filename, &s) || !S_ISREG(s.st_mode))
2055 continue;
2057 tabexp.tails = str_array_add(tabexp.tails, allocp, &tabexp.nr_tails,
2058 xstrndup(name + len, dot - name - len));
2060 closedir(d);
2063 static void expand_colorscheme(const char *str)
2065 int alloc = 0;
2067 tabexp.nr_tails = 0;
2069 load_themes(cmus_config_dir, str, &alloc);
2070 load_themes(DATADIR "/cmus", str, &alloc);
2072 if (alloc) {
2073 qsort(tabexp.tails, tabexp.nr_tails, sizeof(char *), strptrcmp);
2075 tabexp.tails[tabexp.nr_tails] = NULL;
2076 tabexp.head = xstrdup(str);
2080 /* tab exp }}} */
2082 struct command {
2083 const char *name;
2084 void (*func)(char *arg);
2086 /* min/max number of arguments */
2087 int min_args;
2088 int max_args;
2090 void (*expand)(const char *str);
2093 /* sort by name */
2094 static struct command commands[] = {
2095 { "add", cmd_add, 1, 1, expand_add },
2096 { "bind", cmd_bind, 1, 1, expand_bind_args },
2097 { "browser-up", cmd_browser_up, 0, 0, NULL },
2098 { "cd", cmd_cd, 0, 1, expand_directories},
2099 { "clear", cmd_clear, 0, 1, NULL },
2100 { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme},
2101 { "echo", cmd_echo, 1,-1, NULL },
2102 { "factivate", cmd_factivate, 0, 1, expand_factivate },
2103 { "filter", cmd_filter, 0, 1, NULL },
2104 { "fset", cmd_fset, 1, 1, NULL },
2105 { "invert", cmd_invert, 0, 0, NULL },
2106 { "load", cmd_load, 1, 1, expand_load_save },
2107 { "mark", cmd_mark, 0, 1, NULL },
2108 { "player-next", cmd_p_next, 0, 0, NULL },
2109 { "player-pause", cmd_p_pause, 0, 0, NULL },
2110 { "player-play", cmd_p_play, 0, 1, expand_playable },
2111 { "player-prev", cmd_p_prev, 0, 0, NULL },
2112 { "player-stop", cmd_p_stop, 0, 0, NULL },
2113 { "quit", cmd_quit, 0, 0, NULL },
2114 { "refresh", cmd_refresh, 0, 0, NULL },
2115 { "run", cmd_run, 1,-1, NULL },
2116 { "save", cmd_save, 0, 1, expand_load_save },
2117 { "search-next", cmd_search_next,0, 0, NULL },
2118 { "search-prev", cmd_search_prev,0, 0, NULL },
2119 { "seek", cmd_seek, 1, 1, NULL },
2120 { "set", cmd_set, 1, 1, expand_options },
2121 { "showbind", cmd_showbind, 1, 1, expand_unbind_args},
2122 { "shuffle", cmd_reshuffle, 0, 0, NULL },
2123 { "source", cmd_source, 1, 1, expand_files },
2124 { "toggle", cmd_toggle, 1, 1, expand_toptions },
2125 { "unbind", cmd_unbind, 1, 1, expand_unbind_args},
2126 { "unmark", cmd_unmark, 0, 0, NULL },
2127 { "view", cmd_view, 1, 1, NULL },
2128 { "vol", cmd_vol, 1, 2, NULL },
2129 { "win-activate", cmd_win_activate,0, 0, NULL },
2130 { "win-add-l", cmd_win_add_l, 0, 0, NULL },
2131 { "win-add-p", cmd_win_add_p, 0, 0, NULL },
2132 { "win-add-Q", cmd_win_add_Q, 0, 0, NULL },
2133 { "win-add-q", cmd_win_add_q, 0, 0, NULL },
2134 { "win-bottom", cmd_win_bottom, 0, 0, NULL },
2135 { "win-down", cmd_win_down, 0, 0, NULL },
2136 { "win-mv-after", cmd_win_mv_after,0, 0, NULL },
2137 { "win-mv-before", cmd_win_mv_before,0, 0, NULL },
2138 { "win-next", cmd_win_next, 0, 0, NULL },
2139 { "win-page-down", cmd_win_pg_down,0, 0, NULL },
2140 { "win-page-up", cmd_win_pg_up, 0, 0, NULL },
2141 { "win-remove", cmd_win_remove, 0, 0, NULL },
2142 { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL },
2143 { "win-toggle", cmd_win_toggle, 0, 0, NULL },
2144 { "win-top", cmd_win_top, 0, 0, NULL },
2145 { "win-up", cmd_win_up, 0, 0, NULL },
2146 { "win-update", cmd_win_update, 0, 0, NULL },
2147 { NULL, NULL, 0, 0, 0 }
2150 /* fills tabexp struct */
2151 static void expand_commands(const char *str)
2153 int i, len, pos;
2154 char **tails;
2156 /* tabexp is resetted */
2157 tails = xnew(char *, sizeof(commands) / sizeof(struct command));
2158 len = strlen(str);
2159 pos = 0;
2160 for (i = 0; commands[i].name; i++) {
2161 if (strncmp(str, commands[i].name, len) == 0)
2162 tails[pos++] = xstrdup(commands[i].name + len);
2164 if (pos > 0) {
2165 if (pos == 1) {
2166 /* only one command matches, add ' ' */
2167 char *tmp = xstrjoin(tails[0], " ");
2169 free(tails[0]);
2170 tails[0] = tmp;
2172 tails[pos] = NULL;
2173 tabexp.head = xstrdup(str);
2174 tabexp.tails = tails;
2175 tabexp.nr_tails = pos;
2176 } else {
2177 free(tails);
2181 static const struct command *get_command(const char *str, int len)
2183 int i;
2185 for (i = 0; commands[i].name; i++) {
2186 if (strncmp(str, commands[i].name, len))
2187 continue;
2189 if (commands[i].name[len] == 0) {
2190 /* exact */
2191 return &commands[i];
2194 if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
2195 /* ambiguous */
2196 return NULL;
2198 return &commands[i];
2200 return NULL;
2203 /* fills tabexp struct */
2204 static void expand_command_line(const char *str)
2206 /* :command [arg]...
2208 * examples:
2210 * str expanded value (tabexp.head)
2211 * -------------------------------------
2212 * fs fset
2213 * b c bind common
2214 * se se (tabexp.tails = [ ek t ])
2216 /* command start/end, argument start */
2217 const char *cs, *ce, *as;
2218 const struct command *cmd;
2220 cs = str;
2221 ce = strchr(cs, ' ');
2222 if (ce == NULL) {
2223 /* expand command */
2224 expand_commands(cs);
2225 return;
2228 /* command must be expandable */
2229 cmd = get_command(cs, ce - cs);
2230 if (cmd == NULL) {
2231 /* command ambiguous or invalid */
2232 return;
2235 if (cmd->expand == NULL) {
2236 /* can't expand argument */
2237 return;
2240 as = ce;
2241 while (*as == ' ')
2242 as++;
2244 /* expand argument */
2245 cmd->expand(as);
2246 if (tabexp.head == NULL) {
2247 /* argument expansion failed */
2248 return;
2251 /* tabexp.head is now start of the argument string */
2252 snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
2253 free(tabexp.head);
2254 tabexp.head = xstrdup(expbuf);
2257 static void tab_expand(void)
2259 char *s1, *s2, *tmp;
2260 int pos;
2262 /* strip white space */
2263 pos = 0;
2264 while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
2265 pos++;
2267 /* string to expand */
2268 s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
2270 /* tail */
2271 s2 = xstrdup(cmdline.line + cmdline.bpos);
2273 tmp = tabexp_expand(s1, expand_command_line);
2274 if (tmp) {
2275 /* tmp.s2 */
2276 int l1, l2;
2278 l1 = strlen(tmp);
2279 l2 = strlen(s2);
2280 cmdline.blen = l1 + l2;
2281 if (cmdline.blen >= cmdline.size) {
2282 while (cmdline.blen >= cmdline.size)
2283 cmdline.size *= 2;
2284 cmdline.line = xrenew(char, cmdline.line, cmdline.size);
2286 sprintf(cmdline.line, "%s%s", tmp, s2);
2287 cmdline.bpos = l1;
2288 cmdline.cpos = u_strlen(tmp);
2289 cmdline.clen = u_strlen(cmdline.line);
2290 free(tmp);
2292 free(s1);
2293 free(s2);
2296 static void reset_tab_expansion(void)
2298 tabexp_reset();
2299 arg_expand_cmd = -1;
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 if (commands[i].name == NULL) {
2341 error_msg("unknown command\n");
2342 break;
2344 if (strncmp(cmd, commands[i].name, cmd_len) == 0) {
2345 const char *next = commands[i + 1].name;
2346 int exact = commands[i].name[cmd_len] == 0;
2348 if (!exact && next && strncmp(cmd, next, cmd_end - cmd_start) == 0) {
2349 error_msg("ambiguous command\n");
2350 break;
2352 if (commands[i].min_args > 0 && arg == NULL) {
2353 error_msg("not enough arguments\n");
2354 break;
2356 if (commands[i].max_args == 0 && arg) {
2357 error_msg("too many arguments\n");
2358 break;
2360 commands[i].func(arg);
2361 break;
2363 i++;
2365 free(arg);
2366 free(cmd);
2369 static void reset_history_search(void)
2371 history_reset_search(&cmd_history);
2372 free(history_search_text);
2373 history_search_text = NULL;
2376 static void backspace(void)
2378 if (cmdline.clen > 0) {
2379 cmdline_backspace();
2380 } else {
2381 input_mode = NORMAL_MODE;
2385 void command_mode_ch(uchar ch)
2387 switch (ch) {
2388 case 0x01: // ^A
2389 cmdline_move_home();
2390 break;
2391 case 0x02: // ^B
2392 cmdline_move_left();
2393 break;
2394 case 0x04: // ^D
2395 cmdline_delete_ch();
2396 break;
2397 case 0x05: // ^E
2398 cmdline_move_end();
2399 break;
2400 case 0x06: // ^F
2401 cmdline_move_right();
2402 break;
2403 case 0x03: // ^C
2404 case 0x07: // ^G
2405 case 0x1B: // ESC
2406 if (cmdline.blen) {
2407 history_add_line(&cmd_history, cmdline.line);
2408 cmdline_clear();
2410 input_mode = NORMAL_MODE;
2411 break;
2412 case 0x0A:
2413 if (cmdline.blen) {
2414 run_command(cmdline.line);
2415 history_add_line(&cmd_history, cmdline.line);
2416 cmdline_clear();
2418 input_mode = NORMAL_MODE;
2419 break;
2420 case 0x0B:
2421 cmdline_clear_end();
2422 break;
2423 case 0x09:
2424 /* tab expansion should not complain */
2425 display_errors = 0;
2427 tab_expand();
2428 break;
2429 case 0x15:
2430 cmdline_backspace_to_bol();
2431 break;
2432 case 127:
2433 backspace();
2434 break;
2435 default:
2436 cmdline_insert_ch(ch);
2438 reset_history_search();
2439 if (ch != 0x09)
2440 reset_tab_expansion();
2443 void command_mode_key(int key)
2445 reset_tab_expansion();
2446 switch (key) {
2447 case KEY_DC:
2448 cmdline_delete_ch();
2449 break;
2450 case KEY_BACKSPACE:
2451 backspace();
2452 break;
2453 case KEY_LEFT:
2454 cmdline_move_left();
2455 return;
2456 case KEY_RIGHT:
2457 cmdline_move_right();
2458 return;
2459 case KEY_HOME:
2460 cmdline_move_home();
2461 return;
2462 case KEY_END:
2463 cmdline_move_end();
2464 return;
2465 case KEY_UP:
2467 const char *s;
2469 if (history_search_text == NULL)
2470 history_search_text = xstrdup(cmdline.line);
2471 s = history_search_forward(&cmd_history, history_search_text);
2472 if (s)
2473 cmdline_set_text(s);
2475 return;
2476 case KEY_DOWN:
2477 if (history_search_text) {
2478 const char *s;
2480 s = history_search_backward(&cmd_history, history_search_text);
2481 if (s) {
2482 cmdline_set_text(s);
2483 } else {
2484 cmdline_set_text(history_search_text);
2487 return;
2488 default:
2489 d_print("key = %c (%d)\n", key, key);
2491 reset_history_search();
2494 void commands_init(void)
2496 cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
2497 history_load(&cmd_history, cmd_history_filename, 2000);
2500 void commands_exit(void)
2502 history_save(&cmd_history);
2503 free(cmd_history_filename);
2504 tabexp_reset();