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
20 #include <command_mode.h>
21 #include <search_mode.h>
24 #include <ui_curses.h>
27 #include <tabexp_file.h>
34 #include <play_queue.h>
42 #include <format_print.h>
47 #include "config/datadir.h"
52 #include <sys/types.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
;
67 return xstrdup(home_dir
);
68 passwd
= getpwnam(username
);
71 /* don't free passwd */
72 return xstrdup(passwd
->pw_dir
);
75 static char *expand_filename(const char *name
)
80 slash
= strchr(name
, '/');
82 char *username
, *home
;
84 if (slash
- name
- 1 > 0) {
86 username
= xstrndup(name
+ 1, slash
- name
- 1);
91 home
= get_home_dir(username
);
96 expanded
= xstrjoin(home
, slash
);
100 return xstrdup(name
);
104 return xstrdup(home_dir
);
108 home
= get_home_dir(name
+ 1);
111 return xstrdup(name
);
115 return xstrdup(name
);
121 void view_clear(int view
)
126 worker_remove_jobs(JOB_TYPE_LIB
);
128 editable_clear(&lib_editable
);
130 /* FIXME: make this optional? */
136 worker_remove_jobs(JOB_TYPE_PL
);
138 editable_clear(&pl_editable
);
142 worker_remove_jobs(JOB_TYPE_QUEUE
);
144 editable_clear(&pq_editable
);
148 info_msg(":clear only works in views 1-4");
152 void view_add(int view
, char *arg
, int prepend
)
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
));
169 cmus_add(lib_add_track
, name
, ft
, JOB_TYPE_LIB
);
172 cmus_add(pl_add_track
, name
, ft
, JOB_TYPE_PL
);
176 cmus_add(play_queue_prepend
, name
, ft
, JOB_TYPE_QUEUE
);
178 cmus_add(play_queue_append
, name
, ft
, JOB_TYPE_QUEUE
);
182 info_msg(":add only works in views 1-4");
187 void view_load(int view
, char *arg
)
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
));
201 if (ft
== FILE_TYPE_FILE
)
203 if (ft
!= FILE_TYPE_PL
) {
204 error_msg("loading '%s': not a playlist file", name
);
212 worker_remove_jobs(JOB_TYPE_LIB
);
214 editable_clear(&lib_editable
);
216 cmus_add(lib_add_track
, name
, FILE_TYPE_PL
, JOB_TYPE_LIB
);
221 worker_remove_jobs(JOB_TYPE_PL
);
223 editable_clear(&pl_editable
);
225 cmus_add(pl_add_track
, name
, FILE_TYPE_PL
, JOB_TYPE_PL
);
230 info_msg(":load only works in views 1-3");
235 static void do_save(for_each_ti_cb for_each_ti
, const char *arg
, char **filenamep
)
237 char *filename
= *filenamep
;
241 filename
= xstrdup(arg
);
242 *filenamep
= filename
;
246 if (cmus_save(for_each_ti
, filename
) == -1)
247 error_msg("saving '%s': %s", filename
, strerror(errno
));
251 void view_save(int view
, char *arg
)
256 tmp
= expand_filename(arg
);
257 arg
= path_absolute(tmp
);
264 do_save(lib_for_each
, arg
, &lib_filename
);
267 do_save(pl_for_each
, arg
, &pl_filename
);
270 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
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
;
295 if (str
[1] == '-' && (str
[2] == 0 || str
[2] == ' ')) {
301 if (str
[2] && str
[2] != ' ')
305 if (!strchr(flags
, flag
)) {
306 error_msg("invalid option -%c", flag
);
323 static int flag_to_view(int flag
)
329 return PLAYLIST_VIEW
;
338 static void cmd_add(char *arg
)
340 int flag
= parse_flags((const char **)&arg
, "lpqQ");
345 error_msg("not enough arguments\n");
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");
358 error_msg("too many arguments\n");
361 view_clear(flag_to_view(flag
));
364 static void cmd_load(char *arg
)
366 int flag
= parse_flags((const char **)&arg
, "lp");
371 error_msg("not enough arguments\n");
374 view_load(flag_to_view(flag
), arg
);
377 static void cmd_save(char *arg
)
379 int flag
= parse_flags((const char **)&arg
, "lp");
383 view_save(flag_to_view(flag
), arg
);
386 static void cmd_set(char *arg
)
391 for (i
= 0; arg
[i
]; i
++) {
399 option_set(arg
, value
);
401 struct cmus_opt
*opt
;
402 char buf
[OPTION_MAX_SIZE
];
404 /* support "set <option>?" */
409 opt
= option_find(arg
);
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
);
424 if (opt
->toggle
== NULL
) {
425 error_msg("%s is not toggle option", opt
->name
);
428 opt
->toggle(opt
->id
);
431 static void cmd_seek(char *arg
)
436 /* Absolute or relative search */
437 seek_mode
= SEEK_SET
;
438 if (arg
[0] == '+' || arg
[0] == '-') {
439 seek_mode
= SEEK_CUR
;
442 seek
= (int) strtol(arg
, &endptr
, 10);
443 if (!seek
&& arg
== endptr
) {
444 error_msg("invalid seek value");
448 /* Expand M, H to seconds */
449 if (endptr
[0] && toupper(endptr
[0]) != 'S') {
450 endptr
[0] = toupper (endptr
[0]);
451 if (endptr
[0] == 'M') {
453 } else if (endptr
[0] == 'H') {
456 error_msg("invalid seek modifier");
461 player_seek(seek
, seek_mode
);
464 static void cmd_factivate(char *arg
)
467 filters_activate_names(arg
);
471 static void cmd_filter(char *arg
)
474 filters_set_anonymous(arg
);
478 static void cmd_fset(char *arg
)
480 filters_set_filter(arg
);
483 static void cmd_invert(char *arg
)
488 editable_invert_marks(&lib_editable
);
491 editable_invert_marks(&pl_editable
);
494 editable_invert_marks(&pq_editable
);
497 info_msg(":invert only works in views 2-4");
502 static void cmd_mark(char *arg
)
507 editable_mark(&lib_editable
, arg
);
510 editable_mark(&pl_editable
, arg
);
513 editable_mark(&pq_editable
, arg
);
516 info_msg(":mark only works in views 2-4");
521 static void cmd_unmark(char *arg
)
526 editable_unmark(&lib_editable
);
529 editable_unmark(&pl_editable
);
532 editable_unmark(&pq_editable
);
535 info_msg(":unmark only works in views 2-4");
540 static void cmd_cd(char *arg
)
543 char *dir
, *absolute
;
545 dir
= expand_filename(arg
);
546 absolute
= path_absolute(dir
);
547 if (chdir(dir
) == -1) {
548 error_msg("could not cd to '%s': %s", dir
, strerror(errno
));
550 browser_chdir(absolute
);
555 if (chdir(home_dir
) == -1) {
556 error_msg("could not cd to '%s': %s", home_dir
, strerror(errno
));
558 browser_chdir(home_dir
);
563 static void cmd_bind(char *arg
)
565 int flag
= parse_flags((const char **)&arg
, "f");
574 key
= strchr(arg
, ' ');
581 func
= strchr(key
, ' ');
590 key_bind(arg
, key
, func
, flag
== 'f');
593 error_msg("expecting 3 arguments (context, key and function)\n");
596 static void cmd_unbind(char *arg
)
598 int flag
= parse_flags((const char **)&arg
, "f");
607 key
= strchr(arg
, ' ');
616 /* FIXME: remove spaces at end */
618 key_unbind(arg
, key
, flag
== 'f');
621 error_msg("expecting 2 arguments (context and key)\n");
624 static void cmd_showbind(char *arg
)
628 key
= strchr(arg
, ' ');
637 /* FIXME: remove spaces at end */
639 show_binding(arg
, key
);
642 error_msg("expecting 2 arguments (context and key)\n");
645 static void cmd_quit(char *arg
)
650 static void cmd_reshuffle(char *arg
)
658 static void cmd_source(char *arg
)
660 char *filename
= expand_filename(arg
);
662 if (source_file(filename
) == -1)
663 error_msg("sourcing %s: %s", filename
, strerror(errno
));
667 static void cmd_colorscheme(char *arg
)
671 snprintf(filename
, sizeof(filename
), "%s/%s.theme", cmus_config_dir
, arg
);
672 if (source_file(filename
) == -1) {
673 snprintf(filename
, sizeof(filename
), DATADIR
"/cmus/%s.theme", arg
);
674 if (source_file(filename
) == -1)
675 error_msg("sourcing %s: %s", filename
, strerror(errno
));
680 * \" inside double-quotes becomes "
681 * \\ inside double-quotes becomes \
683 static char *parse_quoted(const char **strp
)
685 const char *str
= *strp
;
704 ret
= xnew(char, str
- start
);
714 if (c
!= '"' && c
!= '\\')
722 error_msg("`\"' expected");
726 static char *parse_escaped(const char **strp
)
728 const char *str
= *strp
;
736 if (c
== 0 || c
== ' ' || c
== '\'' || c
== '"')
748 ret
= xnew(char, str
- start
+ 1);
754 if (c
== 0 || c
== ' ' || c
== '\'' || c
== '"')
772 static char *parse_one(const char **strp
)
774 const char *str
= *strp
;
784 part
= parse_quoted(&str
);
787 } else if (c
== '\'') {
788 /* backslashes are normal chars inside single-quotes */
792 end
= strchr(str
, '\'');
795 part
= xstrndup(str
, end
- str
);
798 part
= parse_escaped(&str
);
804 char *tmp
= xstrjoin(ret
, part
);
812 error_msg("`'' expected");
818 static char **parse_cmd(const char *cmd
, int *args_idx
, int *ac
)
827 /* there can't be spaces at start of command
828 * and there is at least one argument */
829 if (cmd
[0] == '{' && cmd
[1] == '}' && (cmd
[2] == ' ' || cmd
[2] == 0)) {
830 /* {} is replaced with file arguments */
832 goto only_once_please
;
837 arg
= parse_one(&cmd
);
843 alloc
= alloc
? alloc
* 2 : 4;
844 av
= xrenew(char *, av
, alloc
+ 1);
855 error_msg("{} can be used only once");
863 static struct track_info
**sel_tis
;
864 static int sel_tis_alloc
;
865 static int sel_tis_nr
;
867 static int add_ti(void *data
, struct track_info
*ti
)
869 if (sel_tis_nr
== sel_tis_alloc
) {
870 sel_tis_alloc
= sel_tis_alloc
? sel_tis_alloc
* 2 : 8;
871 sel_tis
= xrenew(struct track_info
*, sel_tis
, sel_tis_alloc
);
874 sel_tis
[sel_tis_nr
++] = ti
;
878 static void cmd_run(char *arg
)
881 int ac
, argc
, i
, run
, files_idx
= -1;
883 if (cur_view
> QUEUE_VIEW
) {
884 info_msg("Command execution is supported only in views 1-4");
888 av
= parse_cmd(arg
, &files_idx
, &ac
);
893 /* collect selected files (struct track_info) */
901 __tree_for_each_sel(add_ti
, NULL
, 0);
904 __editable_for_each_sel(&lib_editable
, add_ti
, NULL
, 0);
907 __editable_for_each_sel(&pl_editable
, add_ti
, NULL
, 0);
910 __editable_for_each_sel(&pq_editable
, add_ti
, NULL
, 0);
915 if (sel_tis_nr
== 0) {
916 /* no files selected, do nothing */
920 sel_tis
[sel_tis_nr
] = NULL
;
923 argv
= xnew(char *, ac
+ sel_tis_nr
+ 1);
925 if (files_idx
== -1) {
926 /* add selected files after rest of the args */
927 for (i
= 0; i
< ac
; i
++)
928 argv
[argc
++] = av
[i
];
929 for (i
= 0; i
< sel_tis_nr
; i
++)
930 argv
[argc
++] = sel_tis
[i
]->filename
;
932 for (i
= 0; i
< files_idx
; i
++)
933 argv
[argc
++] = av
[i
];
934 for (i
= 0; i
< sel_tis_nr
; i
++)
935 argv
[argc
++] = sel_tis
[i
]->filename
;
936 for (i
= files_idx
; i
< ac
; i
++)
937 argv
[argc
++] = av
[i
];
943 for (i
= 0; argv
[i
]; i
++)
944 d_print("ARG: '%s'\n", argv
[i
]);
947 if (confirm_run
&& (sel_tis_nr
> 1 || strcmp(argv
[0], "rm") == 0)) {
948 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg
, sel_tis_nr
)) {
956 if (spawn(argv
, &status
)) {
957 error_msg("executing %s: %s", argv
[0], strerror(errno
));
959 if (WIFEXITED(status
)) {
960 int rc
= WEXITSTATUS(status
);
963 error_msg("%s returned %d", argv
[0], rc
);
965 if (WIFSIGNALED(status
))
966 error_msg("%s received signal %d", argv
[0], WTERMSIG(status
));
971 /* this must be done before sel_tis are unreffed */
972 free_str_array(argv
);
974 /* remove non-existed files, update tags for changed files */
975 cmus_update_tis(sel_tis
, sel_tis_nr
);
977 /* we don't own sel_tis anymore! */
982 free_str_array(argv
);
983 for (i
= 0; sel_tis
[i
]; i
++)
984 track_info_unref(sel_tis
[i
]);
988 static int get_one_ti(void *data
, struct track_info
*ti
)
990 struct track_info
**sel_ti
= data
;
994 /* stop the for each loop, we need only the first selected track */
998 static void cmd_echo(char *arg
)
1000 struct track_info
*sel_ti
;
1004 ptr
= strchr(ptr
, '{');
1013 info_msg("%s", arg
);
1017 if (cur_view
> QUEUE_VIEW
) {
1018 info_msg("echo with {} in its arguments is supported only in views 1-4");
1025 /* get only the first selected track */
1031 __tree_for_each_sel(get_one_ti
, &sel_ti
, 0);
1034 __editable_for_each_sel(&lib_editable
, get_one_ti
, &sel_ti
, 0);
1037 __editable_for_each_sel(&pl_editable
, get_one_ti
, &sel_ti
, 0);
1040 __editable_for_each_sel(&pq_editable
, get_one_ti
, &sel_ti
, 0);
1048 info_msg("%s%s%s", arg
, sel_ti
->filename
, ptr
);
1049 track_info_unref(sel_ti
);
1052 #define VF_RELATIVE 0x01
1053 #define VF_PERCENTAGE 0x02
1055 static int parse_vol_arg(const char *arg
, int *value
, unsigned int *flags
)
1058 int ch
, val
= 0, digits
= 0, sign
= 1;
1064 } else if (*arg
== '+') {
1071 if (ch
< '0' || ch
> '9')
1087 *value
= sign
* val
;
1094 static int calc_vol(int val
, int old
, unsigned int flags
)
1096 if (flags
& VF_RELATIVE
) {
1097 if (flags
& VF_PERCENTAGE
)
1098 val
= scale_from_percentage(val
, volume_max
);
1100 } else if (flags
& VF_PERCENTAGE
) {
1101 val
= scale_from_percentage(val
, volume_max
);
1103 return clamp(val
, 0, volume_max
);
1107 * :vol value [value]
1109 * where value is [-+]?[0-9]+%?
1111 static void cmd_vol(char *arg
)
1113 char **values
= get_words(arg
);
1114 unsigned int lf
, rf
;
1117 if (values
[1] && values
[2])
1120 if (parse_vol_arg(values
[0], &l
, &lf
))
1125 if (values
[1] && parse_vol_arg(values
[1], &r
, &rf
))
1128 free_str_array(values
);
1130 player_get_volume(&ol
, &or);
1131 l
= calc_vol(l
, ol
, lf
);
1132 r
= calc_vol(r
, or, rf
);
1133 player_set_volume(l
, r
);
1136 free_str_array(values
);
1137 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1140 static void cmd_view(char *arg
)
1144 if (parse_enum(arg
, 1, NR_VIEWS
, view_names
, &view
))
1148 static void cmd_p_next(char *arg
)
1153 static void cmd_p_pause(char *arg
)
1158 static void cmd_p_play(char *arg
)
1161 player_play_file(arg
);
1167 static void cmd_p_prev(char *arg
)
1172 static void cmd_p_stop(char *arg
)
1177 static void cmd_search_next(char *arg
)
1180 if (!search_next(searchable
, search_str
, search_direction
))
1185 static void cmd_search_prev(char *arg
)
1188 if (!search_next(searchable
, search_str
, !search_direction
))
1193 static int sorted_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
1195 return editable_for_each_sel(&lib_editable
, cb
, data
, reverse
);
1198 static int pl_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
1200 return editable_for_each_sel(&pl_editable
, cb
, data
, reverse
);
1203 static int pq_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
1205 return editable_for_each_sel(&pq_editable
, cb
, data
, reverse
);
1208 static for_each_sel_ti_cb view_for_each_sel
[4] = {
1210 sorted_for_each_sel
,
1215 /* wrapper for void lib_add_track(struct track_info *) etc. */
1216 static int wrapper_cb(void *data
, struct track_info
*ti
)
1218 add_ti_cb add
= data
;
1224 static void add_from_browser(add_ti_cb add
, int job_type
)
1226 char *sel
= browser_get_sel();
1232 ft
= cmus_detect_ft(sel
, &ret
);
1233 if (ft
!= FILE_TYPE_INVALID
) {
1234 cmus_add(add
, ret
, ft
, job_type
);
1235 window_down(browser_win
, 1);
1242 static void cmd_win_add_l(char *arg
)
1244 if (cur_view
== TREE_VIEW
|| cur_view
== SORTED_VIEW
)
1247 if (cur_view
<= QUEUE_VIEW
) {
1249 view_for_each_sel
[cur_view
](wrapper_cb
, lib_add_track
, 0);
1251 } else if (cur_view
== BROWSER_VIEW
) {
1252 add_from_browser(lib_add_track
, JOB_TYPE_LIB
);
1256 static void cmd_win_add_p(char *arg
)
1258 /* could allow adding dups? */
1259 if (cur_view
== PLAYLIST_VIEW
)
1262 if (cur_view
<= QUEUE_VIEW
) {
1264 view_for_each_sel
[cur_view
](wrapper_cb
, pl_add_track
, 0);
1266 } else if (cur_view
== BROWSER_VIEW
) {
1267 add_from_browser(pl_add_track
, JOB_TYPE_PL
);
1271 static void cmd_win_add_Q(char *arg
)
1273 if (cur_view
== QUEUE_VIEW
)
1276 if (cur_view
<= QUEUE_VIEW
) {
1278 view_for_each_sel
[cur_view
](wrapper_cb
, play_queue_prepend
, 1);
1280 } else if (cur_view
== BROWSER_VIEW
) {
1281 add_from_browser(play_queue_prepend
, JOB_TYPE_QUEUE
);
1285 static void cmd_win_add_q(char *arg
)
1287 if (cur_view
== QUEUE_VIEW
)
1290 if (cur_view
<= QUEUE_VIEW
) {
1292 view_for_each_sel
[cur_view
](wrapper_cb
, play_queue_append
, 0);
1294 } else if (cur_view
== BROWSER_VIEW
) {
1295 add_from_browser(play_queue_append
, JOB_TYPE_QUEUE
);
1299 static void cmd_win_activate(char *arg
)
1301 struct track_info
*info
= NULL
;
1306 info
= tree_set_selected();
1309 info
= sorted_set_selected();
1312 info
= pl_set_selected();
1326 /* update lib/pl mode */
1332 player_play_file(info
->filename
);
1333 track_info_unref(info
);
1337 static void cmd_win_mv_after(char *arg
)
1344 editable_move_after(&lib_editable
);
1347 editable_move_after(&pl_editable
);
1350 editable_move_after(&pq_editable
);
1360 static void cmd_win_mv_before(char *arg
)
1367 editable_move_before(&lib_editable
);
1370 editable_move_before(&pl_editable
);
1373 editable_move_before(&pq_editable
);
1383 static void cmd_win_remove(char *arg
)
1391 editable_remove_sel(&lib_editable
);
1394 editable_remove_sel(&pl_editable
);
1397 editable_remove_sel(&pq_editable
);
1403 filters_delete_filter();
1409 static void cmd_win_sel_cur(char *arg
)
1417 sorted_sel_current();
1432 static void cmd_win_toggle(char *arg
)
1437 tree_toggle_expand_artist();
1440 editable_toggle_mark(&lib_editable
);
1443 editable_toggle_mark(&pl_editable
);
1446 editable_toggle_mark(&pq_editable
);
1451 filters_toggle_filter();
1457 static struct window
*current_win(void)
1463 return lib_editable
.win
;
1465 return pl_editable
.win
;
1467 return pq_editable
.win
;
1476 static void cmd_win_bottom(char *arg
)
1479 window_goto_bottom(current_win());
1483 static void cmd_win_down(char *arg
)
1486 window_down(current_win(), 1);
1490 static void cmd_win_next(char *arg
)
1492 if (cur_view
== TREE_VIEW
) {
1494 tree_toggle_active_window();
1499 static void cmd_win_pg_down(char *arg
)
1502 window_page_down(current_win());
1506 static void cmd_win_pg_up(char *arg
)
1509 window_page_up(current_win());
1513 static void cmd_win_top(char *arg
)
1516 window_goto_top(current_win());
1520 static void cmd_win_up(char *arg
)
1523 window_up(current_win(), 1);
1527 static void cmd_win_update(char *arg
)
1540 static void cmd_browser_up(char *arg
)
1545 static void cmd_refresh(char *arg
)
1547 clearok(curscr
, TRUE
);
1553 * these functions fill tabexp struct, which is resetted beforehand
1556 /* buffer used for tab expansion */
1557 static char expbuf
[512];
1559 static int filter_directories(const char *name
, const struct stat
*s
)
1561 return S_ISDIR(s
->st_mode
);
1564 static int filter_any(const char *name
, const struct stat
*s
)
1569 static int filter_playable(const char *name
, const struct stat
*s
)
1571 return S_ISDIR(s
->st_mode
) || cmus_is_playable(name
);
1574 static int filter_playlist(const char *name
, const struct stat
*s
)
1576 return S_ISDIR(s
->st_mode
) || cmus_is_playlist(name
);
1579 static int filter_supported(const char *name
, const struct stat
*s
)
1581 return S_ISDIR(s
->st_mode
) || cmus_is_supported(name
);
1584 static void expand_files(const char *str
)
1586 expand_files_and_dirs(str
, filter_any
);
1589 static void expand_directories(const char *str
)
1591 expand_files_and_dirs(str
, filter_directories
);
1594 static void expand_playable(const char *str
)
1596 expand_files_and_dirs(str
, filter_playable
);
1599 static void expand_playlist(const char *str
)
1601 expand_files_and_dirs(str
, filter_playlist
);
1604 static void expand_supported(const char *str
)
1606 expand_files_and_dirs(str
, filter_supported
);
1609 static void expand_add(const char *str
)
1611 int flag
= parse_flags(&str
, "lpqQ");
1617 expand_supported(str
);
1619 if (tabexp
.head
&& flag
) {
1620 snprintf(expbuf
, sizeof(expbuf
), "-%c %s", flag
, tabexp
.head
);
1622 tabexp
.head
= xstrdup(expbuf
);
1626 static void expand_load_save(const char *str
)
1628 int flag
= parse_flags(&str
, "lp");
1634 expand_playlist(str
);
1636 if (tabexp
.head
&& flag
) {
1637 snprintf(expbuf
, sizeof(expbuf
), "-%c %s", flag
, tabexp
.head
);
1639 tabexp
.head
= xstrdup(expbuf
);
1643 static void expand_key_context(const char *str
, const char *force
)
1645 int pos
, i
, len
= strlen(str
);
1648 tails
= xnew(char *, NR_CTXS
+ 1);
1650 for (i
= 0; key_context_names
[i
]; i
++) {
1651 int cmp
= strncmp(str
, key_context_names
[i
], len
);
1656 tails
[pos
++] = xstrdup(key_context_names
[i
] + len
);
1664 char *tmp
= xstrjoin(tails
[0], " ");
1669 snprintf(expbuf
, sizeof(expbuf
), "%s%s", force
, str
);
1670 tabexp
.head
= xstrdup(expbuf
);
1671 tabexp
.tails
= tails
;
1672 tabexp
.nr_tails
= pos
;
1675 static int get_context(const char *str
, int len
)
1677 int i
, c
= -1, count
= 0;
1679 for (i
= 0; key_context_names
[i
]; i
++) {
1680 if (strncmp(str
, key_context_names
[i
], len
) == 0) {
1681 if (key_context_names
[i
][len
] == 0) {
1694 static void expand_command_line(const char *str
);
1696 static void expand_bind_args(const char *str
)
1698 /* :bind context key function
1700 * possible values for str:
1705 * you need to know context before you can expand function
1707 /* start and end pointers for context, key and function */
1708 const char *cs
, *ce
, *ks
, *ke
, *fs
;
1710 int i
, c
, k
, len
, pos
, alloc
, count
;
1711 int flag
= parse_flags((const char **)&str
, "f");
1712 const char *force
= "";
1723 ce
= strchr(cs
, ' ');
1725 expand_key_context(cs
, force
);
1729 /* context must be expandable */
1730 c
= get_context(cs
, ce
- cs
);
1732 /* context is ambiguous or invalid */
1739 ke
= strchr(ks
, ' ');
1746 for (i
= 0; key_table
[i
].name
; i
++) {
1747 int cmp
= strncmp(ks
, key_table
[i
].name
, len
);
1752 tails
= str_array_add(tails
, &alloc
, &pos
, xstrdup(key_table
[i
].name
+ len
));
1759 tmp
= xstrjoin(tails
[0], " ");
1764 snprintf(expbuf
, sizeof(expbuf
), "%s%s %s", force
, key_context_names
[c
], ks
);
1767 tabexp
.head
= xstrdup(expbuf
);
1768 tabexp
.tails
= tails
;
1769 tabexp
.nr_tails
= pos
;
1773 /* key must be expandable */
1776 for (i
= 0; key_table
[i
].name
; i
++) {
1777 if (strncmp(ks
, key_table
[i
].name
, ke
- ks
) == 0) {
1778 if (key_table
[i
].name
[ke
- ks
] == 0) {
1789 /* key is ambiguous or invalid */
1800 /* expand com [arg...] */
1801 expand_command_line(fs
);
1802 if (tabexp
.head
== NULL
) {
1803 /* command expand failed */
1808 * tabexp.head is now "com"
1809 * tabexp.tails is [ mand1 mand2 ... ]
1811 * need to change tabexp.head to "context key com"
1814 snprintf(expbuf
, sizeof(expbuf
), "%s%s %s %s", force
, key_context_names
[c
],
1815 key_table
[k
].name
, tabexp
.head
);
1817 tabexp
.head
= xstrdup(expbuf
);
1820 static void expand_unbind_args(const char *str
)
1822 /* :unbind context key */
1823 /* start and end pointers for context and key */
1824 const char *cs
, *ce
, *ks
;
1826 int c
, len
, pos
, alloc
;
1827 const struct binding
*b
;
1830 ce
= strchr(cs
, ' ');
1832 expand_key_context(cs
, "");
1836 /* context must be expandable */
1837 c
= get_context(cs
, ce
- cs
);
1839 /* context is ambiguous or invalid */
1852 b
= key_bindings
[c
];
1854 if (strncmp(ks
, b
->key
->name
, len
) == 0)
1855 tails
= str_array_add(tails
, &alloc
, &pos
, xstrdup(b
->key
->name
+ len
));
1861 snprintf(expbuf
, sizeof(expbuf
), "%s %s", key_context_names
[c
], ks
);
1864 tabexp
.head
= xstrdup(expbuf
);
1865 tabexp
.tails
= tails
;
1866 tabexp
.nr_tails
= pos
;
1869 static void expand_factivate(const char *str
)
1871 /* "name1 name2 name3", expand only name3 */
1872 struct filter_entry
*e
;
1873 int str_len
, len
, i
, pos
, alloc
;
1877 str_len
= strlen(str
);
1880 if (str
[i
- 1] == ' ')
1890 list_for_each_entry(e
, &filters_head
, node
) {
1891 if (strncmp(name
, e
->name
, len
) == 0)
1892 tails
= str_array_add(tails
, &alloc
, &pos
, xstrdup(e
->name
+ len
));
1898 tabexp
.head
= xstrdup(str
);
1899 tabexp
.tails
= tails
;
1900 tabexp
.nr_tails
= pos
;
1903 static void expand_options(const char *str
)
1905 struct cmus_opt
*opt
;
1909 /* tabexp is resetted */
1911 if (len
> 1 && str
[len
- 1] == '=') {
1913 char *var
= xstrndup(str
, len
- 1);
1915 list_for_each_entry(opt
, &option_head
, node
) {
1916 if (strcmp(var
, opt
->name
) == 0) {
1917 char buf
[OPTION_MAX_SIZE
];
1919 tails
= xnew(char *, 1);
1922 opt
->get(opt
->id
, buf
);
1923 tails
[0] = xstrdup(buf
);
1926 tabexp
.head
= xstrdup(str
);
1927 tabexp
.tails
= tails
;
1928 tabexp
.nr_tails
= 1;
1935 /* expand variable */
1938 tails
= xnew(char *, nr_options
+ 1);
1940 list_for_each_entry(opt
, &option_head
, node
) {
1941 if (strncmp(str
, opt
->name
, len
) == 0)
1942 tails
[pos
++] = xstrdup(opt
->name
+ len
);
1946 /* only one variable matches, add '=' */
1947 char *tmp
= xstrjoin(tails
[0], "=");
1954 tabexp
.head
= xstrdup(str
);
1955 tabexp
.tails
= tails
;
1956 tabexp
.nr_tails
= pos
;
1963 static void expand_toptions(const char *str
)
1965 struct cmus_opt
*opt
;
1969 tails
= xnew(char *, nr_options
+ 1);
1972 list_for_each_entry(opt
, &option_head
, node
) {
1973 if (opt
->toggle
== NULL
)
1975 if (strncmp(str
, opt
->name
, len
) == 0)
1976 tails
[pos
++] = xstrdup(opt
->name
+ len
);
1980 tabexp
.head
= xstrdup(str
);
1981 tabexp
.tails
= tails
;
1982 tabexp
.nr_tails
= pos
;
1988 static void load_themes(const char *dir
, const char *str
, int *allocp
)
1991 struct dirent
*dirent
;
1992 int len
= strlen(str
);
1998 while ((dirent
= readdir(d
)) != NULL
) {
1999 const char *dot
, *name
= dirent
->d_name
;
2003 if (strncmp(name
, str
, len
))
2006 dot
= strrchr(name
, '.');
2007 if (dot
== NULL
|| strcmp(dot
, ".theme"))
2010 if (dot
- name
< len
) {
2012 * matches "foo.theme"
2013 * which also ends with ".theme"
2018 snprintf(filename
, sizeof(filename
), "%s/%s", dir
, name
);
2019 if (stat(filename
, &s
) || !S_ISREG(s
.st_mode
))
2022 tabexp
.tails
= str_array_add(tabexp
.tails
, allocp
, &tabexp
.nr_tails
,
2023 xstrndup(name
+ len
, dot
- name
- len
));
2028 static void expand_colorscheme(const char *str
)
2032 tabexp
.nr_tails
= 0;
2034 load_themes(cmus_config_dir
, str
, &alloc
);
2035 load_themes(DATADIR
"/cmus", str
, &alloc
);
2038 qsort(tabexp
.tails
, tabexp
.nr_tails
, sizeof(char *), strptrcmp
);
2040 tabexp
.tails
[tabexp
.nr_tails
] = NULL
;
2041 tabexp
.head
= xstrdup(str
);
2049 void (*func
)(char *arg
);
2051 /* min/max number of arguments */
2055 void (*expand
)(const char *str
);
2059 static struct command commands
[] = {
2060 { "add", cmd_add
, 1, 1, expand_add
},
2061 { "bind", cmd_bind
, 1, 1, expand_bind_args
},
2062 { "browser-up", cmd_browser_up
, 0, 0, NULL
},
2063 { "cd", cmd_cd
, 0, 1, expand_directories
},
2064 { "clear", cmd_clear
, 0, 1, NULL
},
2065 { "colorscheme", cmd_colorscheme
,1, 1, expand_colorscheme
},
2066 { "echo", cmd_echo
, 1,-1, NULL
},
2067 { "factivate", cmd_factivate
, 0, 1, expand_factivate
},
2068 { "filter", cmd_filter
, 0, 1, NULL
},
2069 { "fset", cmd_fset
, 1, 1, NULL
},
2070 { "invert", cmd_invert
, 0, 0, NULL
},
2071 { "load", cmd_load
, 1, 1, expand_load_save
},
2072 { "mark", cmd_mark
, 0, 1, NULL
},
2073 { "player-next", cmd_p_next
, 0, 0, NULL
},
2074 { "player-pause", cmd_p_pause
, 0, 0, NULL
},
2075 { "player-play", cmd_p_play
, 0, 1, expand_playable
},
2076 { "player-prev", cmd_p_prev
, 0, 0, NULL
},
2077 { "player-stop", cmd_p_stop
, 0, 0, NULL
},
2078 { "quit", cmd_quit
, 0, 0, NULL
},
2079 { "refresh", cmd_refresh
, 0, 0, NULL
},
2080 { "run", cmd_run
, 1,-1, NULL
},
2081 { "save", cmd_save
, 0, 1, expand_load_save
},
2082 { "search-next", cmd_search_next
,0, 0, NULL
},
2083 { "search-prev", cmd_search_prev
,0, 0, NULL
},
2084 { "seek", cmd_seek
, 1, 1, NULL
},
2085 { "set", cmd_set
, 1, 1, expand_options
},
2086 { "showbind", cmd_showbind
, 1, 1, expand_unbind_args
},
2087 { "shuffle", cmd_reshuffle
, 0, 0, NULL
},
2088 { "source", cmd_source
, 1, 1, expand_files
},
2089 { "toggle", cmd_toggle
, 1, 1, expand_toptions
},
2090 { "unbind", cmd_unbind
, 1, 1, expand_unbind_args
},
2091 { "unmark", cmd_unmark
, 0, 0, NULL
},
2092 { "view", cmd_view
, 1, 1, NULL
},
2093 { "vol", cmd_vol
, 1, 2, NULL
},
2094 { "win-activate", cmd_win_activate
,0, 0, NULL
},
2095 { "win-add-l", cmd_win_add_l
, 0, 0, NULL
},
2096 { "win-add-p", cmd_win_add_p
, 0, 0, NULL
},
2097 { "win-add-Q", cmd_win_add_Q
, 0, 0, NULL
},
2098 { "win-add-q", cmd_win_add_q
, 0, 0, NULL
},
2099 { "win-bottom", cmd_win_bottom
, 0, 0, NULL
},
2100 { "win-down", cmd_win_down
, 0, 0, NULL
},
2101 { "win-mv-after", cmd_win_mv_after
,0, 0, NULL
},
2102 { "win-mv-before", cmd_win_mv_before
,0, 0, NULL
},
2103 { "win-next", cmd_win_next
, 0, 0, NULL
},
2104 { "win-page-down", cmd_win_pg_down
,0, 0, NULL
},
2105 { "win-page-up", cmd_win_pg_up
, 0, 0, NULL
},
2106 { "win-remove", cmd_win_remove
, 0, 0, NULL
},
2107 { "win-sel-cur", cmd_win_sel_cur
,0, 0, NULL
},
2108 { "win-toggle", cmd_win_toggle
, 0, 0, NULL
},
2109 { "win-top", cmd_win_top
, 0, 0, NULL
},
2110 { "win-up", cmd_win_up
, 0, 0, NULL
},
2111 { "win-update", cmd_win_update
, 0, 0, NULL
},
2112 { NULL
, NULL
, 0, 0, 0 }
2115 /* fills tabexp struct */
2116 static void expand_commands(const char *str
)
2121 /* tabexp is resetted */
2122 tails
= xnew(char *, sizeof(commands
) / sizeof(struct command
));
2125 for (i
= 0; commands
[i
].name
; i
++) {
2126 if (strncmp(str
, commands
[i
].name
, len
) == 0)
2127 tails
[pos
++] = xstrdup(commands
[i
].name
+ len
);
2131 /* only one command matches, add ' ' */
2132 char *tmp
= xstrjoin(tails
[0], " ");
2138 tabexp
.head
= xstrdup(str
);
2139 tabexp
.tails
= tails
;
2140 tabexp
.nr_tails
= pos
;
2146 static const struct command
*get_command(const char *str
, int len
)
2150 for (i
= 0; commands
[i
].name
; i
++) {
2151 if (strncmp(str
, commands
[i
].name
, len
))
2154 if (commands
[i
].name
[len
] == 0) {
2156 return &commands
[i
];
2159 if (commands
[i
+ 1].name
&& strncmp(str
, commands
[i
+ 1].name
, len
) == 0) {
2163 return &commands
[i
];
2168 /* fills tabexp struct */
2169 static void expand_command_line(const char *str
)
2171 /* :command [arg]...
2175 * str expanded value (tabexp.head)
2176 * -------------------------------------
2179 * se se (tabexp.tails = [ ek t ])
2181 /* command start/end, argument start */
2182 const char *cs
, *ce
, *as
;
2183 const struct command
*cmd
;
2186 ce
= strchr(cs
, ' ');
2188 /* expand command */
2189 expand_commands(cs
);
2193 /* command must be expandable */
2194 cmd
= get_command(cs
, ce
- cs
);
2196 /* command ambiguous or invalid */
2200 if (cmd
->expand
== NULL
) {
2201 /* can't expand argument */
2209 /* expand argument */
2211 if (tabexp
.head
== NULL
) {
2212 /* argument expansion failed */
2216 /* tabexp.head is now start of the argument string */
2217 snprintf(expbuf
, sizeof(expbuf
), "%s %s", cmd
->name
, tabexp
.head
);
2219 tabexp
.head
= xstrdup(expbuf
);
2222 static void tab_expand(void)
2224 char *s1
, *s2
, *tmp
;
2227 /* strip white space */
2229 while (cmdline
.line
[pos
] == ' ' && pos
< cmdline
.bpos
)
2232 /* string to expand */
2233 s1
= xstrndup(cmdline
.line
+ pos
, cmdline
.bpos
- pos
);
2236 s2
= xstrdup(cmdline
.line
+ cmdline
.bpos
);
2238 tmp
= tabexp_expand(s1
, expand_command_line
);
2245 cmdline
.blen
= l1
+ l2
;
2246 if (cmdline
.blen
>= cmdline
.size
) {
2247 while (cmdline
.blen
>= cmdline
.size
)
2249 cmdline
.line
= xrenew(char, cmdline
.line
, cmdline
.size
);
2251 sprintf(cmdline
.line
, "%s%s", tmp
, s2
);
2253 cmdline
.cpos
= u_strlen(tmp
);
2254 cmdline
.clen
= u_strlen(cmdline
.line
);
2261 static void reset_tab_expansion(void)
2264 arg_expand_cmd
= -1;
2267 /* FIXME: parse all arguments */
2268 void run_command(const char *buf
)
2271 int cmd_start
, cmd_end
, cmd_len
;
2272 int arg_start
, arg_end
;
2276 while (buf
[i
] && buf
[i
] == ' ')
2283 while (buf
[i
] && buf
[i
] != ' ')
2286 while (buf
[i
] && buf
[i
] == ' ')
2293 cmd_len
= cmd_end
- cmd_start
;
2297 cmd
= xstrndup(buf
+ cmd_start
, cmd_len
);
2298 if (arg_start
== arg_end
) {
2301 arg
= xstrndup(buf
+ arg_start
, arg_end
- arg_start
);
2305 if (commands
[i
].name
== NULL
) {
2306 error_msg("unknown command\n");
2309 if (strncmp(cmd
, commands
[i
].name
, cmd_len
) == 0) {
2310 const char *next
= commands
[i
+ 1].name
;
2311 int exact
= commands
[i
].name
[cmd_len
] == 0;
2313 if (!exact
&& next
&& strncmp(cmd
, next
, cmd_end
- cmd_start
) == 0) {
2314 error_msg("ambiguous command\n");
2317 if (commands
[i
].min_args
> 0 && arg
== NULL
) {
2318 error_msg("not enough arguments\n");
2321 if (commands
[i
].max_args
== 0 && arg
) {
2322 error_msg("too many arguments\n");
2325 commands
[i
].func(arg
);
2334 static void reset_history_search(void)
2336 history_reset_search(&cmd_history
);
2337 free(history_search_text
);
2338 history_search_text
= NULL
;
2341 static void backspace(void)
2343 if (cmdline
.clen
> 0) {
2344 cmdline_backspace();
2346 input_mode
= NORMAL_MODE
;
2350 void command_mode_ch(uchar ch
)
2354 cmdline_move_home();
2357 cmdline_move_left();
2360 cmdline_delete_ch();
2366 cmdline_move_right();
2372 history_add_line(&cmd_history
, cmdline
.line
);
2375 input_mode
= NORMAL_MODE
;
2379 run_command(cmdline
.line
);
2380 history_add_line(&cmd_history
, cmdline
.line
);
2383 input_mode
= NORMAL_MODE
;
2386 cmdline_clear_end();
2389 /* tab expansion should not complain */
2395 cmdline_backspace_to_bol();
2401 cmdline_insert_ch(ch
);
2403 reset_history_search();
2405 reset_tab_expansion();
2408 void command_mode_key(int key
)
2410 reset_tab_expansion();
2413 cmdline_delete_ch();
2419 cmdline_move_left();
2422 cmdline_move_right();
2425 cmdline_move_home();
2434 if (history_search_text
== NULL
)
2435 history_search_text
= xstrdup(cmdline
.line
);
2436 s
= history_search_forward(&cmd_history
, history_search_text
);
2438 cmdline_set_text(s
);
2442 if (history_search_text
) {
2445 s
= history_search_backward(&cmd_history
, history_search_text
);
2447 cmdline_set_text(s
);
2449 cmdline_set_text(history_search_text
);
2454 d_print("key = %c (%d)\n", key
, key
);
2456 reset_history_search();
2459 void commands_init(void)
2461 cmd_history_filename
= xstrjoin(cmus_config_dir
, "/command-history");
2462 history_load(&cmd_history
, cmd_history_filename
, 2000);
2465 void commands_exit(void)
2467 history_save(&cmd_history
);
2468 free(cmd_history_filename
);