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"
35 #include "play_queue.h"
43 #include "format_print.h"
49 #include "config/datadir.h"
54 #include <sys/types.h>
65 static struct history cmd_history
;
66 static char *cmd_history_filename
;
67 static char *history_search_text
= NULL
;
68 static int arg_expand_cmd
= -1;
69 static int prev_view
= -1;
71 static char *get_home_dir(const char *username
)
73 struct passwd
*passwd
;
76 return xstrdup(home_dir
);
77 passwd
= getpwnam(username
);
80 /* don't free passwd */
81 return xstrdup(passwd
->pw_dir
);
84 static char *expand_filename(const char *name
)
89 slash
= strchr(name
, '/');
91 char *username
, *home
;
93 if (slash
- name
- 1 > 0) {
95 username
= xstrndup(name
+ 1, slash
- name
- 1);
100 home
= get_home_dir(username
);
105 expanded
= xstrjoin(home
, slash
);
109 return xstrdup(name
);
113 return xstrdup(home_dir
);
117 home
= get_home_dir(name
+ 1);
120 return xstrdup(name
);
124 return xstrdup(name
);
130 void view_clear(int view
)
135 worker_remove_jobs(JOB_TYPE_LIB
);
137 editable_clear(&lib_editable
);
139 /* FIXME: make this optional? */
145 worker_remove_jobs(JOB_TYPE_PL
);
147 editable_clear(&pl_editable
);
151 worker_remove_jobs(JOB_TYPE_QUEUE
);
153 editable_clear(&pq_editable
);
157 info_msg(":clear only works in views 1-4");
161 void view_add(int view
, char *arg
, int prepend
)
166 tmp
= expand_filename(arg
);
167 ft
= cmus_detect_ft(tmp
, &name
);
168 if (ft
== FILE_TYPE_INVALID
) {
169 error_msg("adding '%s': %s", tmp
, strerror(errno
));
178 cmus_add(lib_add_track
, name
, ft
, JOB_TYPE_LIB
);
181 cmus_add(pl_add_track
, name
, ft
, JOB_TYPE_PL
);
185 cmus_add(play_queue_prepend
, name
, ft
, JOB_TYPE_QUEUE
);
187 cmus_add(play_queue_append
, name
, ft
, JOB_TYPE_QUEUE
);
191 info_msg(":add only works in views 1-4");
196 void view_load(int view
, char *arg
)
201 tmp
= expand_filename(arg
);
202 ft
= cmus_detect_ft(tmp
, &name
);
203 if (ft
== FILE_TYPE_INVALID
) {
204 error_msg("loading '%s': %s", tmp
, strerror(errno
));
210 if (ft
== FILE_TYPE_FILE
)
212 if (ft
!= FILE_TYPE_PL
) {
213 error_msg("loading '%s': not a playlist file", name
);
221 worker_remove_jobs(JOB_TYPE_LIB
);
223 editable_clear(&lib_editable
);
225 cmus_add(lib_add_track
, name
, FILE_TYPE_PL
, JOB_TYPE_LIB
);
230 worker_remove_jobs(JOB_TYPE_PL
);
232 editable_clear(&pl_editable
);
234 cmus_add(pl_add_track
, name
, FILE_TYPE_PL
, JOB_TYPE_PL
);
239 info_msg(":load only works in views 1-3");
244 static void do_save(for_each_ti_cb for_each_ti
, const char *arg
, char **filenamep
)
246 char *filename
= *filenamep
;
250 filename
= xstrdup(arg
);
251 *filenamep
= filename
;
255 if (cmus_save(for_each_ti
, filename
) == -1)
256 error_msg("saving '%s': %s", filename
, strerror(errno
));
260 void view_save(int view
, char *arg
)
265 tmp
= expand_filename(arg
);
266 arg
= path_absolute(tmp
);
273 if (worker_has_job(JOB_TYPE_LIB
))
275 do_save(lib_for_each
, arg
, &lib_filename
);
278 if (worker_has_job(JOB_TYPE_PL
))
280 do_save(pl_for_each
, arg
, &pl_filename
);
283 info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
288 error_msg("can't save when tracks are being added");
294 /* only returns the last flag which is enough for it's callers */
295 static int parse_flags(const char **strp
, const char *flags
)
297 const char *str
= *strp
;
312 if (str
[1] == '-' && (str
[2] == 0 || str
[2] == ' ')) {
318 if (str
[2] && str
[2] != ' ')
322 if (!strchr(flags
, flag
)) {
323 error_msg("invalid option -%c", flag
);
340 static int flag_to_view(int flag
)
346 return PLAYLIST_VIEW
;
355 static void cmd_add(char *arg
)
357 int flag
= parse_flags((const char **)&arg
, "lpqQ");
362 error_msg("not enough arguments\n");
365 view_add(flag_to_view(flag
), arg
, flag
== 'Q');
368 static void cmd_clear(char *arg
)
370 int flag
= parse_flags((const char **)&arg
, "lpq");
375 error_msg("too many arguments\n");
378 view_clear(flag_to_view(flag
));
381 static void cmd_load(char *arg
)
383 int flag
= parse_flags((const char **)&arg
, "lp");
388 error_msg("not enough arguments\n");
391 view_load(flag_to_view(flag
), arg
);
394 static void cmd_save(char *arg
)
396 int flag
= parse_flags((const char **)&arg
, "lp");
400 view_save(flag_to_view(flag
), arg
);
403 static void cmd_set(char *arg
)
408 for (i
= 0; arg
[i
]; i
++) {
416 option_set(arg
, value
);
417 help_win
->changed
= 1;
419 struct cmus_opt
*opt
;
420 char buf
[OPTION_MAX_SIZE
];
422 /* support "set <option>?" */
427 opt
= option_find(arg
);
429 opt
->get(opt
->id
, buf
);
430 info_msg("setting: '%s=%s'", arg
, buf
);
435 static void cmd_toggle(char *arg
)
437 struct cmus_opt
*opt
= option_find(arg
);
442 if (opt
->toggle
== NULL
) {
443 error_msg("%s is not toggle option", opt
->name
);
446 opt
->toggle(opt
->id
);
447 help_win
->changed
= 1;
450 static int get_number(char *str
, char **end
)
454 while (*str
>= '0' && *str
<= '9') {
462 static void cmd_seek(char *arg
)
465 int seek
= 0, sign
= 1, count
;
487 num
= get_number(arg
, &end
);
491 seek
= seek
* 60 + num
;
492 } while (++count
< 3);
499 switch (tolower(*arg
)) {
511 player_seek(seek
, relative
);
515 error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
518 static void cmd_factivate(char *arg
)
521 filters_activate_names(arg
);
525 static void cmd_filter(char *arg
)
528 filters_set_anonymous(arg
);
532 static void cmd_fset(char *arg
)
534 filters_set_filter(arg
);
537 static void cmd_invert(char *arg
)
542 editable_invert_marks(&lib_editable
);
545 editable_invert_marks(&pl_editable
);
548 editable_invert_marks(&pq_editable
);
551 info_msg(":invert only works in views 2-4");
556 static void cmd_mark(char *arg
)
561 editable_mark(&lib_editable
, arg
);
564 editable_mark(&pl_editable
, arg
);
567 editable_mark(&pq_editable
, arg
);
570 info_msg(":mark only works in views 2-4");
575 static void cmd_unmark(char *arg
)
580 editable_unmark(&lib_editable
);
583 editable_unmark(&pl_editable
);
586 editable_unmark(&pq_editable
);
589 info_msg(":unmark only works in views 2-4");
594 static void cmd_update_cache(char *arg
)
599 static void cmd_cd(char *arg
)
602 char *dir
, *absolute
;
604 dir
= expand_filename(arg
);
605 absolute
= path_absolute(dir
);
606 if (chdir(dir
) == -1) {
607 error_msg("could not cd to '%s': %s", dir
, strerror(errno
));
609 browser_chdir(absolute
);
614 if (chdir(home_dir
) == -1) {
615 error_msg("could not cd to '%s': %s", home_dir
, strerror(errno
));
617 browser_chdir(home_dir
);
622 static void cmd_bind(char *arg
)
624 int flag
= parse_flags((const char **)&arg
, "f");
633 key
= strchr(arg
, ' ');
640 func
= strchr(key
, ' ');
649 key_bind(arg
, key
, func
, flag
== 'f');
652 error_msg("expecting 3 arguments (context, key and function)\n");
655 static void cmd_unbind(char *arg
)
657 int flag
= parse_flags((const char **)&arg
, "f");
666 key
= strchr(arg
, ' ');
675 /* FIXME: remove spaces at end */
677 key_unbind(arg
, key
, flag
== 'f');
680 error_msg("expecting 2 arguments (context and key)\n");
683 static void cmd_showbind(char *arg
)
687 key
= strchr(arg
, ' ');
696 /* FIXME: remove spaces at end */
698 show_binding(arg
, key
);
701 error_msg("expecting 2 arguments (context and key)\n");
704 static void cmd_quit(char *arg
)
706 if (!worker_has_job(JOB_TYPE_ANY
) || yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]"))
710 static void cmd_reshuffle(char *arg
)
718 static void cmd_source(char *arg
)
720 char *filename
= expand_filename(arg
);
722 if (source_file(filename
) == -1)
723 error_msg("sourcing %s: %s", filename
, strerror(errno
));
727 static void cmd_colorscheme(char *arg
)
731 snprintf(filename
, sizeof(filename
), "%s/%s.theme", cmus_config_dir
, arg
);
732 if (source_file(filename
) == -1) {
733 snprintf(filename
, sizeof(filename
), DATADIR
"/cmus/%s.theme", arg
);
734 if (source_file(filename
) == -1)
735 error_msg("sourcing %s: %s", filename
, strerror(errno
));
740 * \" inside double-quotes becomes "
741 * \\ inside double-quotes becomes \
743 static char *parse_quoted(const char **strp
)
745 const char *str
= *strp
;
764 ret
= xnew(char, str
- start
);
774 if (c
!= '"' && c
!= '\\')
782 error_msg("`\"' expected");
786 static char *parse_escaped(const char **strp
)
788 const char *str
= *strp
;
796 if (c
== 0 || c
== ' ' || c
== '\'' || c
== '"')
808 ret
= xnew(char, str
- start
+ 1);
814 if (c
== 0 || c
== ' ' || c
== '\'' || c
== '"')
832 static char *parse_one(const char **strp
)
834 const char *str
= *strp
;
844 part
= parse_quoted(&str
);
847 } else if (c
== '\'') {
848 /* backslashes are normal chars inside single-quotes */
852 end
= strchr(str
, '\'');
855 part
= xstrndup(str
, end
- str
);
858 part
= parse_escaped(&str
);
864 char *tmp
= xstrjoin(ret
, part
);
872 error_msg("`'' expected");
878 static char **parse_cmd(const char *cmd
, int *args_idx
, int *ac
)
887 /* there can't be spaces at start of command
888 * and there is at least one argument */
889 if (cmd
[0] == '{' && cmd
[1] == '}' && (cmd
[2] == ' ' || cmd
[2] == 0)) {
890 /* {} is replaced with file arguments */
892 goto only_once_please
;
897 arg
= parse_one(&cmd
);
903 alloc
= alloc
? alloc
* 2 : 4;
904 av
= xrenew(char *, av
, alloc
+ 1);
915 error_msg("{} can be used only once");
923 static struct track_info
**sel_tis
;
924 static int sel_tis_alloc
;
925 static int sel_tis_nr
;
927 static int add_ti(void *data
, struct track_info
*ti
)
929 if (sel_tis_nr
== sel_tis_alloc
) {
930 sel_tis_alloc
= sel_tis_alloc
? sel_tis_alloc
* 2 : 8;
931 sel_tis
= xrenew(struct track_info
*, sel_tis
, sel_tis_alloc
);
934 sel_tis
[sel_tis_nr
++] = ti
;
938 static void cmd_run(char *arg
)
941 int ac
, argc
, i
, run
, files_idx
= -1;
943 if (cur_view
> QUEUE_VIEW
) {
944 info_msg("Command execution is supported only in views 1-4");
948 av
= parse_cmd(arg
, &files_idx
, &ac
);
953 /* collect selected files (struct track_info) */
961 __tree_for_each_sel(add_ti
, NULL
, 0);
964 __editable_for_each_sel(&lib_editable
, add_ti
, NULL
, 0);
967 __editable_for_each_sel(&pl_editable
, add_ti
, NULL
, 0);
970 __editable_for_each_sel(&pq_editable
, add_ti
, NULL
, 0);
975 if (sel_tis_nr
== 0) {
976 /* no files selected, do nothing */
980 sel_tis
[sel_tis_nr
] = NULL
;
983 argv
= xnew(char *, ac
+ sel_tis_nr
+ 1);
985 if (files_idx
== -1) {
986 /* add selected files after rest of the args */
987 for (i
= 0; i
< ac
; i
++)
988 argv
[argc
++] = av
[i
];
989 for (i
= 0; i
< sel_tis_nr
; i
++)
990 argv
[argc
++] = sel_tis
[i
]->filename
;
992 for (i
= 0; i
< files_idx
; i
++)
993 argv
[argc
++] = av
[i
];
994 for (i
= 0; i
< sel_tis_nr
; i
++)
995 argv
[argc
++] = sel_tis
[i
]->filename
;
996 for (i
= files_idx
; i
< ac
; i
++)
997 argv
[argc
++] = av
[i
];
1001 for (i
= 0; argv
[i
]; i
++)
1002 d_print("ARG: '%s'\n", argv
[i
]);
1005 if (confirm_run
&& (sel_tis_nr
> 1 || strcmp(argv
[0], "rm") == 0)) {
1006 if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg
, sel_tis_nr
)) {
1007 info_msg("Aborted");
1014 if (spawn(argv
, &status
)) {
1015 error_msg("executing %s: %s", argv
[0], strerror(errno
));
1017 if (WIFEXITED(status
)) {
1018 int rc
= WEXITSTATUS(status
);
1021 error_msg("%s returned %d", argv
[0], rc
);
1023 if (WIFSIGNALED(status
))
1024 error_msg("%s received signal %d", argv
[0], WTERMSIG(status
));
1029 /* this must be done before sel_tis are unreffed */
1033 /* remove non-existed files, update tags for changed files */
1034 cmus_update_tis(sel_tis
, sel_tis_nr
);
1036 /* we don't own sel_tis anymore! */
1043 for (i
= 0; sel_tis
[i
]; i
++)
1044 track_info_unref(sel_tis
[i
]);
1048 static int get_one_ti(void *data
, struct track_info
*ti
)
1050 struct track_info
**sel_ti
= data
;
1054 /* stop the for each loop, we need only the first selected track */
1058 static void cmd_echo(char *arg
)
1060 struct track_info
*sel_ti
;
1064 ptr
= strchr(ptr
, '{');
1073 info_msg("%s", arg
);
1077 if (cur_view
> QUEUE_VIEW
) {
1078 info_msg("echo with {} in its arguments is supported only in views 1-4");
1085 /* get only the first selected track */
1091 __tree_for_each_sel(get_one_ti
, &sel_ti
, 0);
1094 __editable_for_each_sel(&lib_editable
, get_one_ti
, &sel_ti
, 0);
1097 __editable_for_each_sel(&pl_editable
, get_one_ti
, &sel_ti
, 0);
1100 __editable_for_each_sel(&pq_editable
, get_one_ti
, &sel_ti
, 0);
1108 info_msg("%s%s%s", arg
, sel_ti
->filename
, ptr
);
1109 track_info_unref(sel_ti
);
1112 #define VF_RELATIVE 0x01
1113 #define VF_PERCENTAGE 0x02
1115 static int parse_vol_arg(const char *arg
, int *value
, unsigned int *flags
)
1118 int ch
, val
= 0, digits
= 0, sign
= 1;
1124 } else if (*arg
== '+') {
1131 if (ch
< '0' || ch
> '9')
1147 *value
= sign
* val
;
1154 static int calc_vol(int val
, int old
, int max_vol
, unsigned int flags
)
1156 if (flags
& VF_RELATIVE
) {
1157 if (flags
& VF_PERCENTAGE
)
1158 val
= scale_from_percentage(val
, max_vol
);
1160 } else if (flags
& VF_PERCENTAGE
) {
1161 val
= scale_from_percentage(val
, max_vol
);
1163 return clamp(val
, 0, max_vol
);
1167 * :vol value [value]
1169 * where value is [-+]?[0-9]+%?
1171 static void cmd_vol(char *arg
)
1173 char **values
= get_words(arg
);
1174 unsigned int lf
, rf
;
1177 if (values
[1] && values
[2])
1180 if (parse_vol_arg(values
[0], &l
, &lf
))
1185 if (values
[1] && parse_vol_arg(values
[1], &r
, &rf
))
1188 free_str_array(values
);
1191 l
= calc_vol(l
, soft_vol_l
, 100, lf
);
1192 r
= calc_vol(r
, soft_vol_r
, 100, rf
);
1193 player_set_soft_volume(l
, r
);
1195 mixer_read_volume();
1196 l
= calc_vol(l
, volume_l
, volume_max
, lf
);
1197 r
= calc_vol(r
, volume_r
, volume_max
, rf
);
1198 mixer_set_volume(l
, r
);
1200 update_statusline();
1203 free_str_array(values
);
1204 error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
1207 static void cmd_prev_view(char *arg
)
1210 if (prev_view
>= 0) {
1212 set_view(prev_view
);
1217 static void cmd_view(char *arg
)
1221 if (parse_enum(arg
, 1, NR_VIEWS
, view_names
, &view
) && (view
- 1) != cur_view
) {
1222 prev_view
= cur_view
;
1227 static void cmd_p_next(char *arg
)
1232 static void cmd_p_pause(char *arg
)
1237 static void cmd_p_play(char *arg
)
1240 cmus_play_file(arg
);
1246 static void cmd_p_prev(char *arg
)
1251 static void cmd_p_stop(char *arg
)
1256 static void cmd_search_next(char *arg
)
1259 if (!search_next(searchable
, search_str
, search_direction
))
1264 static void cmd_search_prev(char *arg
)
1267 if (!search_next(searchable
, search_str
, !search_direction
))
1272 static int sorted_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
1274 return editable_for_each_sel(&lib_editable
, cb
, data
, reverse
);
1277 static int pl_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
1279 return editable_for_each_sel(&pl_editable
, cb
, data
, reverse
);
1282 static int pq_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
1284 return editable_for_each_sel(&pq_editable
, cb
, data
, reverse
);
1287 static for_each_sel_ti_cb view_for_each_sel
[4] = {
1289 sorted_for_each_sel
,
1294 /* wrapper for void lib_add_track(struct track_info *) etc. */
1295 static int wrapper_cb(void *data
, struct track_info
*ti
)
1297 add_ti_cb add
= data
;
1303 static void add_from_browser(add_ti_cb add
, int job_type
)
1305 char *sel
= browser_get_sel();
1311 ft
= cmus_detect_ft(sel
, &ret
);
1312 if (ft
!= FILE_TYPE_INVALID
) {
1313 cmus_add(add
, ret
, ft
, job_type
);
1314 window_down(browser_win
, 1);
1321 static void cmd_win_add_l(char *arg
)
1323 if (cur_view
== TREE_VIEW
|| cur_view
== SORTED_VIEW
)
1326 if (cur_view
<= QUEUE_VIEW
) {
1328 view_for_each_sel
[cur_view
](wrapper_cb
, lib_add_track
, 0);
1330 } else if (cur_view
== BROWSER_VIEW
) {
1331 add_from_browser(lib_add_track
, JOB_TYPE_LIB
);
1335 static void cmd_win_add_p(char *arg
)
1337 /* could allow adding dups? */
1338 if (cur_view
== PLAYLIST_VIEW
)
1341 if (cur_view
<= QUEUE_VIEW
) {
1343 view_for_each_sel
[cur_view
](wrapper_cb
, pl_add_track
, 0);
1345 } else if (cur_view
== BROWSER_VIEW
) {
1346 add_from_browser(pl_add_track
, JOB_TYPE_PL
);
1350 static void cmd_win_add_Q(char *arg
)
1352 if (cur_view
== QUEUE_VIEW
)
1355 if (cur_view
<= QUEUE_VIEW
) {
1357 view_for_each_sel
[cur_view
](wrapper_cb
, play_queue_prepend
, 1);
1359 } else if (cur_view
== BROWSER_VIEW
) {
1360 add_from_browser(play_queue_prepend
, JOB_TYPE_QUEUE
);
1364 static void cmd_win_add_q(char *arg
)
1366 if (cur_view
== QUEUE_VIEW
)
1369 if (cur_view
<= QUEUE_VIEW
) {
1371 view_for_each_sel
[cur_view
](wrapper_cb
, play_queue_append
, 0);
1373 } else if (cur_view
== BROWSER_VIEW
) {
1374 add_from_browser(play_queue_append
, JOB_TYPE_QUEUE
);
1378 static void cmd_win_activate(char *arg
)
1380 struct track_info
*info
= NULL
;
1385 info
= tree_set_selected();
1388 info
= sorted_set_selected();
1391 info
= pl_set_selected();
1408 /* update lib/pl mode */
1414 player_play_file(info
);
1418 static void cmd_win_mv_after(char *arg
)
1425 editable_move_after(&lib_editable
);
1428 editable_move_after(&pl_editable
);
1431 editable_move_after(&pq_editable
);
1443 static void cmd_win_mv_before(char *arg
)
1450 editable_move_before(&lib_editable
);
1453 editable_move_before(&pl_editable
);
1456 editable_move_before(&pq_editable
);
1468 static void cmd_win_remove(char *arg
)
1476 editable_remove_sel(&lib_editable
);
1479 editable_remove_sel(&pl_editable
);
1482 editable_remove_sel(&pq_editable
);
1488 filters_delete_filter();
1497 static void cmd_win_sel_cur(char *arg
)
1505 sorted_sel_current();
1522 static void cmd_win_toggle(char *arg
)
1527 tree_toggle_expand_artist();
1532 editable_toggle_mark(&lib_editable
);
1537 editable_toggle_mark(&pl_editable
);
1542 editable_toggle_mark(&pq_editable
);
1548 filters_toggle_filter();
1556 static struct window
*current_win(void)
1562 return lib_editable
.win
;
1564 return pl_editable
.win
;
1566 return pq_editable
.win
;
1577 static void cmd_win_bottom(char *arg
)
1580 window_goto_bottom(current_win());
1584 static void cmd_win_down(char *arg
)
1587 window_down(current_win(), 1);
1591 static void cmd_win_next(char *arg
)
1593 if (cur_view
== TREE_VIEW
) {
1595 tree_toggle_active_window();
1600 static void cmd_win_pg_down(char *arg
)
1603 window_page_down(current_win());
1607 static void cmd_win_pg_up(char *arg
)
1610 window_page_up(current_win());
1614 static void cmd_win_top(char *arg
)
1617 window_goto_top(current_win());
1621 static void cmd_win_up(char *arg
)
1624 window_up(current_win(), 1);
1628 static void cmd_win_update(char *arg
)
1641 static void cmd_browser_up(char *arg
)
1646 static void cmd_refresh(char *arg
)
1648 clearok(curscr
, TRUE
);
1652 static int cmp_intp(const void *ap
, const void *bp
)
1659 static int *rand_array(int size
, int nmax
)
1661 int *r
= xnew(int, size
+ 1);
1665 if (count
> nmax
/ 2) {
1667 * Imagine that there are 1000 tracks in library and we want to
1668 * add 998 random tracks to queue. After we have added 997
1669 * random numbers to the array it would be quite hard to find a
1670 * random number that isn't already in the array (3/1000
1673 * So we invert the logic:
1675 * Find two (1000 - 998) random numbers in 0..999 range and put
1676 * them at end of the array. Sort the numbers and then fill
1677 * the array starting at index 0 with incrementing values that
1678 * are not in the set of random numbers.
1680 count
= nmax
- count
;
1681 offset
= size
- count
;
1684 for (i
= 0; i
< count
; ) {
1688 for (j
= 0; j
< i
; j
++) {
1689 if (r
[offset
+ j
] == v
)
1692 r
[offset
+ i
++] = v
;
1694 qsort(r
+ offset
, count
, sizeof(*r
), cmp_intp
);
1699 /* simplifies next loop */
1702 /* convert the indexes we don't want to those we want */
1716 static int count_albums(void)
1718 struct artist
*artist
;
1719 struct list_head
*item
;
1722 list_for_each_entry(artist
, &lib_artist_head
, node
) {
1723 list_for_each(item
, &artist
->album_head
)
1730 struct list_head node
;
1731 const struct album
*album
;
1734 static void cmd_lqueue(char *arg
)
1737 const struct list_head
*item
;
1738 const struct album
*album
;
1739 int count
= 1, nmax
, i
, pos
;
1745 if (str_to_int(arg
, &val
) || val
<= 0) {
1746 error_msg("argument must be positive integer");
1752 nmax
= count_albums();
1758 r
= rand_array(count
, nmax
);
1759 album
= to_album(to_artist(lib_artist_head
.next
)->album_head
.next
);
1761 for (i
= 0; i
< count
; i
++) {
1762 struct album_list
*a
;
1764 while (pos
< r
[i
]) {
1765 struct artist
*artist
= album
->artist
;
1766 if (album
->node
.next
== &artist
->album_head
) {
1767 artist
= to_artist(artist
->node
.next
);
1768 album
= to_album(artist
->album_head
.next
);
1770 album
= to_album(album
->node
.next
);
1774 a
= xnew(struct album_list
, 1);
1776 list_add_rand(&head
, &a
->node
, i
);
1782 struct list_head
*next
= item
->next
;
1783 struct album_list
*a
= container_of(item
, struct album_list
, node
);
1784 struct tree_track
*t
;
1786 list_for_each_entry(t
, &a
->album
->track_head
, node
)
1787 editable_add(&pq_editable
, simple_track_new(tree_track_info(t
)));
1790 } while (item
!= &head
);
1795 static void cmd_tqueue(char *arg
)
1798 struct list_head
*item
;
1799 int count
= 1, i
, pos
;
1805 if (str_to_int(arg
, &val
) || val
<= 0) {
1806 error_msg("argument must be positive integer");
1812 if (count
> lib_editable
.nr_tracks
)
1813 count
= lib_editable
.nr_tracks
;
1817 r
= rand_array(count
, lib_editable
.nr_tracks
);
1818 item
= lib_editable
.head
.next
;
1820 for (i
= 0; i
< count
; i
++) {
1821 struct simple_track
*t
;
1823 while (pos
< r
[i
]) {
1827 t
= simple_track_new(to_simple_track(item
)->info
);
1828 list_add_rand(&head
, &t
->node
, i
);
1834 struct list_head
*next
= item
->next
;
1835 struct simple_track
*t
= to_simple_track(item
);
1836 editable_add(&pq_editable
, t
);
1838 } while (item
!= &head
);
1845 * these functions fill tabexp struct, which is resetted beforehand
1848 /* buffer used for tab expansion */
1849 static char expbuf
[512];
1851 static int filter_directories(const char *name
, const struct stat
*s
)
1853 return S_ISDIR(s
->st_mode
);
1856 static int filter_any(const char *name
, const struct stat
*s
)
1861 static int filter_playable(const char *name
, const struct stat
*s
)
1863 return S_ISDIR(s
->st_mode
) || cmus_is_playable(name
);
1866 static int filter_playlist(const char *name
, const struct stat
*s
)
1868 return S_ISDIR(s
->st_mode
) || cmus_is_playlist(name
);
1871 static int filter_supported(const char *name
, const struct stat
*s
)
1873 return S_ISDIR(s
->st_mode
) || cmus_is_supported(name
);
1876 static void expand_files(const char *str
)
1878 expand_files_and_dirs(str
, filter_any
);
1881 static void expand_directories(const char *str
)
1883 expand_files_and_dirs(str
, filter_directories
);
1886 static void expand_playable(const char *str
)
1888 expand_files_and_dirs(str
, filter_playable
);
1891 static void expand_playlist(const char *str
)
1893 expand_files_and_dirs(str
, filter_playlist
);
1896 static void expand_supported(const char *str
)
1898 expand_files_and_dirs(str
, filter_supported
);
1901 static void expand_add(const char *str
)
1903 int flag
= parse_flags(&str
, "lpqQ");
1909 expand_supported(str
);
1911 if (tabexp
.head
&& flag
) {
1912 snprintf(expbuf
, sizeof(expbuf
), "-%c %s", flag
, tabexp
.head
);
1914 tabexp
.head
= xstrdup(expbuf
);
1918 static void expand_load_save(const char *str
)
1920 int flag
= parse_flags(&str
, "lp");
1926 expand_playlist(str
);
1928 if (tabexp
.head
&& flag
) {
1929 snprintf(expbuf
, sizeof(expbuf
), "-%c %s", flag
, tabexp
.head
);
1931 tabexp
.head
= xstrdup(expbuf
);
1935 static void expand_key_context(const char *str
, const char *force
)
1937 int pos
, i
, len
= strlen(str
);
1940 tails
= xnew(char *, NR_CTXS
+ 1);
1942 for (i
= 0; key_context_names
[i
]; i
++) {
1943 int cmp
= strncmp(str
, key_context_names
[i
], len
);
1948 tails
[pos
++] = xstrdup(key_context_names
[i
] + len
);
1956 char *tmp
= xstrjoin(tails
[0], " ");
1961 snprintf(expbuf
, sizeof(expbuf
), "%s%s", force
, str
);
1962 tabexp
.head
= xstrdup(expbuf
);
1963 tabexp
.tails
= tails
;
1966 static int get_context(const char *str
, int len
)
1968 int i
, c
= -1, count
= 0;
1970 for (i
= 0; key_context_names
[i
]; i
++) {
1971 if (strncmp(str
, key_context_names
[i
], len
) == 0) {
1972 if (key_context_names
[i
][len
] == 0) {
1985 static void expand_command_line(const char *str
);
1987 static void expand_bind_args(const char *str
)
1989 /* :bind context key function
1991 * possible values for str:
1996 * you need to know context before you can expand function
1998 /* start and end pointers for context, key and function */
1999 const char *cs
, *ce
, *ks
, *ke
, *fs
;
2001 int flag
= parse_flags((const char **)&str
, "f");
2002 const char *force
= "";
2013 ce
= strchr(cs
, ' ');
2015 expand_key_context(cs
, force
);
2019 /* context must be expandable */
2020 c
= get_context(cs
, ce
- cs
);
2022 /* context is ambiguous or invalid */
2029 ke
= strchr(ks
, ' ');
2032 int len
= strlen(ks
);
2035 for (i
= 0; key_table
[i
].name
; i
++) {
2036 int cmp
= strncmp(ks
, key_table
[i
].name
, len
);
2041 ptr_array_add(&array
, xstrdup(key_table
[i
].name
+ len
));
2047 if (array
.count
== 1) {
2048 char **ptrs
= array
.ptrs
;
2049 char *tmp
= xstrjoin(ptrs
[0], " ");
2054 snprintf(expbuf
, sizeof(expbuf
), "%s%s %s", force
, key_context_names
[c
], ks
);
2056 ptr_array_plug(&array
);
2057 tabexp
.head
= xstrdup(expbuf
);
2058 tabexp
.tails
= array
.ptrs
;
2062 /* key must be expandable */
2065 for (i
= 0; key_table
[i
].name
; i
++) {
2066 if (strncmp(ks
, key_table
[i
].name
, ke
- ks
) == 0) {
2067 if (key_table
[i
].name
[ke
- ks
] == 0) {
2078 /* key is ambiguous or invalid */
2089 /* expand com [arg...] */
2090 expand_command_line(fs
);
2091 if (tabexp
.head
== NULL
) {
2092 /* command expand failed */
2097 * tabexp.head is now "com"
2098 * tabexp.tails is [ mand1 mand2 ... ]
2100 * need to change tabexp.head to "context key com"
2103 snprintf(expbuf
, sizeof(expbuf
), "%s%s %s %s", force
, key_context_names
[c
],
2104 key_table
[k
].name
, tabexp
.head
);
2106 tabexp
.head
= xstrdup(expbuf
);
2109 static void expand_unbind_args(const char *str
)
2111 /* :unbind context key */
2112 /* start and end pointers for context and key */
2113 const char *cs
, *ce
, *ks
;
2114 const struct binding
*b
;
2119 ce
= strchr(cs
, ' ');
2121 expand_key_context(cs
, "");
2125 /* context must be expandable */
2126 c
= get_context(cs
, ce
- cs
);
2128 /* context is ambiguous or invalid */
2138 b
= key_bindings
[c
];
2140 if (!strncmp(ks
, b
->key
->name
, len
))
2141 ptr_array_add(&array
, xstrdup(b
->key
->name
+ len
));
2147 snprintf(expbuf
, sizeof(expbuf
), "%s %s", key_context_names
[c
], ks
);
2149 ptr_array_plug(&array
);
2150 tabexp
.head
= xstrdup(expbuf
);
2151 tabexp
.tails
= array
.ptrs
;
2154 static void expand_factivate(const char *str
)
2156 /* "name1 name2 name3", expand only name3 */
2157 struct filter_entry
*e
;
2160 int str_len
, len
, i
;
2162 str_len
= strlen(str
);
2165 if (str
[i
- 1] == ' ')
2172 list_for_each_entry(e
, &filters_head
, node
) {
2173 if (!strncmp(name
, e
->name
, len
))
2174 ptr_array_add(&array
, xstrdup(e
->name
+ len
));
2179 ptr_array_plug(&array
);
2180 tabexp
.head
= xstrdup(str
);
2181 tabexp
.tails
= array
.ptrs
;
2184 static void expand_options(const char *str
)
2186 struct cmus_opt
*opt
;
2190 /* tabexp is resetted */
2192 if (len
> 1 && str
[len
- 1] == '=') {
2194 char *var
= xstrndup(str
, len
- 1);
2196 list_for_each_entry(opt
, &option_head
, node
) {
2197 if (strcmp(var
, opt
->name
) == 0) {
2198 char buf
[OPTION_MAX_SIZE
];
2200 tails
= xnew(char *, 2);
2203 opt
->get(opt
->id
, buf
);
2204 tails
[0] = xstrdup(buf
);
2207 tabexp
.head
= xstrdup(str
);
2208 tabexp
.tails
= tails
;
2215 /* expand variable */
2218 tails
= xnew(char *, nr_options
+ 1);
2220 list_for_each_entry(opt
, &option_head
, node
) {
2221 if (strncmp(str
, opt
->name
, len
) == 0)
2222 tails
[pos
++] = xstrdup(opt
->name
+ len
);
2226 /* only one variable matches, add '=' */
2227 char *tmp
= xstrjoin(tails
[0], "=");
2234 tabexp
.head
= xstrdup(str
);
2235 tabexp
.tails
= tails
;
2242 static void expand_toptions(const char *str
)
2244 struct cmus_opt
*opt
;
2248 tails
= xnew(char *, nr_options
+ 1);
2251 list_for_each_entry(opt
, &option_head
, node
) {
2252 if (opt
->toggle
== NULL
)
2254 if (strncmp(str
, opt
->name
, len
) == 0)
2255 tails
[pos
++] = xstrdup(opt
->name
+ len
);
2259 tabexp
.head
= xstrdup(str
);
2260 tabexp
.tails
= tails
;
2266 static void load_themes(const char *dirname
, const char *str
, struct ptr_array
*array
)
2268 struct directory dir
;
2269 const char *name
, *dot
;
2270 int len
= strlen(str
);
2272 if (dir_open(&dir
, dirname
))
2275 while ((name
= dir_read(&dir
))) {
2276 if (!S_ISREG(dir
.st
.st_mode
))
2278 if (strncmp(name
, str
, len
))
2280 dot
= strrchr(name
, '.');
2281 if (dot
== NULL
|| strcmp(dot
, ".theme"))
2283 if (dot
- name
< len
)
2285 * matches "foo.theme"
2286 * which also ends with ".theme"
2289 ptr_array_add(array
, xstrndup(name
+ len
, dot
- name
- len
));
2294 static void expand_colorscheme(const char *str
)
2298 load_themes(cmus_config_dir
, str
, &array
);
2299 load_themes(DATADIR
"/cmus", str
, &array
);
2302 ptr_array_sort(&array
, strptrcmp
);
2304 ptr_array_plug(&array
);
2305 tabexp
.head
= xstrdup(str
);
2306 tabexp
.tails
= array
.ptrs
;
2313 struct command commands
[] = {
2314 { "add", cmd_add
, 1, 1, expand_add
, 0, 0 },
2315 { "bind", cmd_bind
, 1, 1, expand_bind_args
, 0, CMD_UNSAFE
},
2316 { "browser-up", cmd_browser_up
, 0, 0, NULL
, 0, 0 },
2317 { "cd", cmd_cd
, 0, 1, expand_directories
, 0, 0 },
2318 { "clear", cmd_clear
, 0, 1, NULL
, 0, 0 },
2319 { "colorscheme", cmd_colorscheme
,1, 1, expand_colorscheme
, 0, 0 },
2320 { "echo", cmd_echo
, 1,-1, NULL
, 0, 0 },
2321 { "factivate", cmd_factivate
, 0, 1, expand_factivate
, 0, 0 },
2322 { "filter", cmd_filter
, 0, 1, NULL
, 0, 0 },
2323 { "fset", cmd_fset
, 1, 1, NULL
, 0, 0 },
2324 { "invert", cmd_invert
, 0, 0, NULL
, 0, 0 },
2325 { "load", cmd_load
, 1, 1, expand_load_save
, 0, 0 },
2326 { "lqueue", cmd_lqueue
, 0, 1, NULL
, 0, 0 },
2327 { "mark", cmd_mark
, 0, 1, NULL
, 0, 0 },
2328 { "player-next", cmd_p_next
, 0, 0, NULL
, 0, 0 },
2329 { "player-pause", cmd_p_pause
, 0, 0, NULL
, 0, 0 },
2330 { "player-play", cmd_p_play
, 0, 1, expand_playable
, 0, 0 },
2331 { "player-prev", cmd_p_prev
, 0, 0, NULL
, 0, 0 },
2332 { "player-stop", cmd_p_stop
, 0, 0, NULL
, 0, 0 },
2333 { "prev-view", cmd_prev_view
, 0, 0, NULL
, 0, 0 },
2334 { "quit", cmd_quit
, 0, 0, NULL
, 0, 0 },
2335 { "refresh", cmd_refresh
, 0, 0, NULL
, 0, 0 },
2336 { "run", cmd_run
, 1,-1, NULL
, 0, CMD_UNSAFE
},
2337 { "save", cmd_save
, 0, 1, expand_load_save
, 0, CMD_UNSAFE
},
2338 { "search-next", cmd_search_next
,0, 0, NULL
, 0, 0 },
2339 { "search-prev", cmd_search_prev
,0, 0, NULL
, 0, 0 },
2340 { "seek", cmd_seek
, 1, 1, NULL
, 0, 0 },
2341 { "set", cmd_set
, 1, 1, expand_options
, 0, 0 },
2342 { "showbind", cmd_showbind
, 1, 1, expand_unbind_args
, 0, 0 },
2343 { "shuffle", cmd_reshuffle
, 0, 0, NULL
, 0, 0 },
2344 { "source", cmd_source
, 1, 1, expand_files
, 0, CMD_UNSAFE
},
2345 { "toggle", cmd_toggle
, 1, 1, expand_toptions
, 0, 0 },
2346 { "tqueue", cmd_tqueue
, 0, 1, NULL
, 0, 0 },
2347 { "unbind", cmd_unbind
, 1, 1, expand_unbind_args
, 0, 0 },
2348 { "unmark", cmd_unmark
, 0, 0, NULL
, 0, 0 },
2349 { "update-cache", cmd_update_cache
,0, 0, NULL
, 0, 0 },
2350 { "view", cmd_view
, 1, 1, NULL
, 0, 0 },
2351 { "vol", cmd_vol
, 1, 2, NULL
, 0, 0 },
2352 { "win-activate", cmd_win_activate
,0, 0, NULL
, 0, 0 },
2353 { "win-add-l", cmd_win_add_l
, 0, 0, NULL
, 0, 0 },
2354 { "win-add-p", cmd_win_add_p
, 0, 0, NULL
, 0, 0 },
2355 { "win-add-Q", cmd_win_add_Q
, 0, 0, NULL
, 0, 0 },
2356 { "win-add-q", cmd_win_add_q
, 0, 0, NULL
, 0, 0 },
2357 { "win-bottom", cmd_win_bottom
, 0, 0, NULL
, 0, 0 },
2358 { "win-down", cmd_win_down
, 0, 0, NULL
, 0, 0 },
2359 { "win-mv-after", cmd_win_mv_after
,0, 0, NULL
, 0, 0 },
2360 { "win-mv-before", cmd_win_mv_before
,0, 0, NULL
, 0, 0 },
2361 { "win-next", cmd_win_next
, 0, 0, NULL
, 0, 0 },
2362 { "win-page-down", cmd_win_pg_down
,0, 0, NULL
, 0, 0 },
2363 { "win-page-up", cmd_win_pg_up
, 0, 0, NULL
, 0, 0 },
2364 { "win-remove", cmd_win_remove
, 0, 0, NULL
, 0, CMD_UNSAFE
},
2365 { "win-sel-cur", cmd_win_sel_cur
,0, 0, NULL
, 0, 0 },
2366 { "win-toggle", cmd_win_toggle
, 0, 0, NULL
, 0, 0 },
2367 { "win-top", cmd_win_top
, 0, 0, NULL
, 0, 0 },
2368 { "win-up", cmd_win_up
, 0, 0, NULL
, 0, 0 },
2369 { "win-update", cmd_win_update
, 0, 0, NULL
, 0, 0 },
2370 { NULL
, NULL
, 0, 0, 0, 0, 0 }
2373 /* fills tabexp struct */
2374 static void expand_commands(const char *str
)
2379 /* tabexp is resetted */
2380 tails
= xnew(char *, sizeof(commands
) / sizeof(struct command
));
2383 for (i
= 0; commands
[i
].name
; i
++) {
2384 if (strncmp(str
, commands
[i
].name
, len
) == 0)
2385 tails
[pos
++] = xstrdup(commands
[i
].name
+ len
);
2389 /* only one command matches, add ' ' */
2390 char *tmp
= xstrjoin(tails
[0], " ");
2396 tabexp
.head
= xstrdup(str
);
2397 tabexp
.tails
= tails
;
2403 struct command
*get_command(const char *str
)
2409 for (len
= 0; str
[len
] && str
[len
] != ' '; len
++)
2412 for (i
= 0; commands
[i
].name
; i
++) {
2413 if (strncmp(str
, commands
[i
].name
, len
))
2416 if (commands
[i
].name
[len
] == 0) {
2418 return &commands
[i
];
2421 if (commands
[i
+ 1].name
&& strncmp(str
, commands
[i
+ 1].name
, len
) == 0) {
2425 return &commands
[i
];
2430 /* fills tabexp struct */
2431 static void expand_command_line(const char *str
)
2433 /* :command [arg]...
2437 * str expanded value (tabexp.head)
2438 * -------------------------------------
2441 * se se (tabexp.tails = [ ek t ])
2443 /* command start/end, argument start */
2444 const char *cs
, *ce
, *as
;
2445 const struct command
*cmd
;
2448 ce
= strchr(cs
, ' ');
2450 /* expand command */
2451 expand_commands(cs
);
2455 /* command must be expandable */
2456 cmd
= get_command(cs
);
2458 /* command ambiguous or invalid */
2462 if (cmd
->expand
== NULL
) {
2463 /* can't expand argument */
2471 /* expand argument */
2473 if (tabexp
.head
== NULL
) {
2474 /* argument expansion failed */
2478 /* tabexp.head is now start of the argument string */
2479 snprintf(expbuf
, sizeof(expbuf
), "%s %s", cmd
->name
, tabexp
.head
);
2481 tabexp
.head
= xstrdup(expbuf
);
2484 static void tab_expand(void)
2486 char *s1
, *s2
, *tmp
;
2489 /* strip white space */
2491 while (cmdline
.line
[pos
] == ' ' && pos
< cmdline
.bpos
)
2494 /* string to expand */
2495 s1
= xstrndup(cmdline
.line
+ pos
, cmdline
.bpos
- pos
);
2498 s2
= xstrdup(cmdline
.line
+ cmdline
.bpos
);
2500 tmp
= tabexp_expand(s1
, expand_command_line
);
2507 cmdline
.blen
= l1
+ l2
;
2508 if (cmdline
.blen
>= cmdline
.size
) {
2509 while (cmdline
.blen
>= cmdline
.size
)
2511 cmdline
.line
= xrenew(char, cmdline
.line
, cmdline
.size
);
2513 sprintf(cmdline
.line
, "%s%s", tmp
, s2
);
2515 cmdline
.cpos
= u_strlen(tmp
);
2516 cmdline
.clen
= u_strlen(cmdline
.line
);
2523 static void reset_tab_expansion(void)
2526 arg_expand_cmd
= -1;
2529 int parse_command(const char *buf
, char **cmdp
, char **argp
)
2531 int cmd_start
, cmd_end
, cmd_len
;
2532 int arg_start
, arg_end
;
2536 while (buf
[i
] && buf
[i
] == ' ')
2543 while (buf
[i
] && buf
[i
] != ' ')
2546 while (buf
[i
] && buf
[i
] == ' ')
2553 cmd_len
= cmd_end
- cmd_start
;
2557 *cmdp
= xstrndup(buf
+ cmd_start
, cmd_len
);
2558 if (arg_start
== arg_end
) {
2561 *argp
= xstrndup(buf
+ arg_start
, arg_end
- arg_start
);
2566 int run_only_safe_commands
;
2568 void run_parsed_command(char *cmd
, char *arg
)
2570 int cmd_len
= strlen(cmd
);
2574 const struct command
*c
= &commands
[i
];
2576 if (c
->name
== NULL
) {
2577 error_msg("unknown command\n");
2580 if (strncmp(cmd
, c
->name
, cmd_len
) == 0) {
2581 const char *next
= commands
[i
+ 1].name
;
2582 int exact
= c
->name
[cmd_len
] == 0;
2584 if (!exact
&& next
&& strncmp(cmd
, next
, cmd_len
) == 0) {
2585 error_msg("ambiguous command\n");
2588 if (c
->min_args
> 0 && arg
== NULL
) {
2589 error_msg("not enough arguments\n");
2592 if (c
->max_args
== 0 && arg
) {
2593 error_msg("too many arguments\n");
2596 if (run_only_safe_commands
&& c
->flags
& CMD_UNSAFE
) {
2597 d_print("trying to execute unsafe command over net\n");
2607 void run_command(const char *buf
)
2611 if (!parse_command(buf
, &cmd
, &arg
))
2614 run_parsed_command(cmd
, arg
);
2619 static void reset_history_search(void)
2621 history_reset_search(&cmd_history
);
2622 free(history_search_text
);
2623 history_search_text
= NULL
;
2626 static void backspace(void)
2628 if (cmdline
.clen
> 0) {
2629 cmdline_backspace();
2631 input_mode
= NORMAL_MODE
;
2635 void command_mode_ch(uchar ch
)
2639 cmdline_move_home();
2642 cmdline_move_left();
2645 cmdline_delete_ch();
2651 cmdline_move_right();
2657 history_add_line(&cmd_history
, cmdline
.line
);
2660 input_mode
= NORMAL_MODE
;
2664 run_command(cmdline
.line
);
2665 history_add_line(&cmd_history
, cmdline
.line
);
2668 input_mode
= NORMAL_MODE
;
2671 cmdline_clear_end();
2677 cmdline_backspace_to_bol();
2684 cmdline_insert_ch(ch
);
2686 reset_history_search();
2688 reset_tab_expansion();
2691 void command_mode_key(int key
)
2693 reset_tab_expansion();
2696 cmdline_delete_ch();
2702 cmdline_move_left();
2705 cmdline_move_right();
2708 cmdline_move_home();
2717 if (history_search_text
== NULL
)
2718 history_search_text
= xstrdup(cmdline
.line
);
2719 s
= history_search_forward(&cmd_history
, history_search_text
);
2721 cmdline_set_text(s
);
2725 if (history_search_text
) {
2728 s
= history_search_backward(&cmd_history
, history_search_text
);
2730 cmdline_set_text(s
);
2732 cmdline_set_text(history_search_text
);
2737 d_print("key = %c (%d)\n", key
, key
);
2739 reset_history_search();
2742 void commands_init(void)
2744 cmd_history_filename
= xstrjoin(cmus_config_dir
, "/command-history");
2745 history_load(&cmd_history
, cmd_history_filename
, 2000);
2748 void commands_exit(void)
2750 history_save(&cmd_history
);
2751 free(cmd_history_filename
);