1 #include "snd.h"
2 #include "sndlib-strings.h"
3 #include "clm-strings.h"
4
5
6 static char *current_match = NULL;
7
8 /* TAB completion requires knowing what is currently defined, which requires scrounging
9 * around in the symbol tables
10 */
11
12 #if HAVE_SCHEME
13
14 typedef struct {
15 const char *text;
16 int matches, len;
17 } match_info;
18
compare_names(const char * symbol_name,void * data)19 static bool compare_names(const char *symbol_name, void *data)
20 {
21 match_info *m = (match_info *)data;
22
23 if (strncmp(m->text, symbol_name, m->len) == 0)
24 {
25 m->matches++;
26 add_possible_completion(symbol_name);
27 if (!current_match)
28 current_match = mus_strdup(symbol_name);
29 else
30 {
31 int j, curlen;
32 curlen = mus_strlen(current_match);
33 for (j = 0; j < curlen; j++)
34 if (current_match[j] != symbol_name[j])
35 {
36 current_match[j] = '\0';
37 break;
38 }
39 }
40 }
41 return(false);
42 }
43
completions(const char * text)44 static int completions(const char *text)
45 {
46 match_info *m;
47 int matches;
48
49 m = (match_info *)calloc(1, sizeof(match_info));
50 m->text = text;
51 m->len = strlen(text);
52 m->matches = 0;
53 s7_for_each_symbol_name(s7, compare_names, (void *)m);
54 matches = m->matches;
55 free(m);
56 return(matches);
57 }
58
59 #endif
60
61
62 #if HAVE_RUBY
snd_rb_methods(void)63 static Xen snd_rb_methods(void)
64 {
65 /* returns all the functions we defined */
66 Xen argv[1];
67 argv[0] = Xen_true;
68 return(rb_class_private_instance_methods(1, argv, rb_mKernel));
69 /* rb_ary_new here -- should we free? */
70 }
71
72
completions(const char * text)73 static int completions(const char *text)
74 {
75 Xen tab;
76 int i, n, len, matches = 0;
77
78 tab = snd_rb_methods();
79 n = Xen_vector_length(tab);
80 len = strlen(text);
81
82 for (i = 0; i < n; ++i)
83 {
84 char *sym;
85 Xen handle;
86
87 handle = Xen_vector_ref(tab, i);
88 sym = Xen_object_to_C_string(handle);
89
90 if (strncmp(text, sym, len) == 0)
91 {
92 matches++;
93 add_possible_completion(sym);
94 if (!current_match)
95 current_match = mus_strdup(sym);
96 else
97 {
98 int j, curlen;
99 curlen = mus_strlen(current_match);
100 for (j = 0; j < curlen; j++)
101 if (current_match[j] != sym[j])
102 {
103 current_match[j] = '\0';
104 break;
105 }
106 }
107 }
108 }
109 return(matches);
110 }
111 #endif
112
113
114 #if HAVE_FORTH
completions(const char * text)115 static int completions(const char *text)
116 {
117 Xen tab = fth_find_in_wordlist(text);
118 int i, matches = Xen_vector_length(tab);
119
120 for (i = 0; i < matches; i++)
121 {
122 char *sym = Xen_string_to_C_string(Xen_vector_ref(tab, i));
123 add_possible_completion(sym);
124 if (!current_match)
125 current_match = mus_strdup(sym);
126 else
127 {
128 int j, curlen;
129 curlen = mus_strlen(current_match);
130 for (j = 0; j < curlen; j++)
131 if (current_match[j] != sym[j])
132 {
133 current_match[j] = '\0';
134 break;
135 }
136 }
137 }
138 return(matches);
139 }
140 #endif
141
142
143 #if (!HAVE_EXTENSION_LANGUAGE)
completions(const char * text)144 static int completions(const char *text) {return(0);}
145 #endif
146
147
148 #if HAVE_FORTH
is_separator(char c)149 static bool is_separator(char c)
150 {
151 /* only space is separator */
152 return(!(isgraph((int)c)));
153 }
154 #else
is_separator(char c)155 static bool is_separator(char c)
156 {
157 return((!(isalpha((int)c))) &&
158 (!(isdigit((int)c))) &&
159 #if HAVE_RUBY
160 (c != '?') &&
161 (c != '!') &&
162 (c != '_') &&
163 #endif
164 #if HAVE_SCHEME
165 (c != '-') &&
166 (c != '_') &&
167 (c != '>') &&
168 (c != '?') &&
169 (c != '!') &&
170 (c != '=') &&
171 (c != '<') &&
172 (c != '*') &&
173 (c != '+') &&
174 (c != '%') &&
175 (c != ':') &&
176 (c != '/') &&
177 #endif
178 (c != '$'));
179 }
180 #endif
181
182
direct_completions(const char * str)183 char *direct_completions(const char *str)
184 {
185 int matches = 0;
186 current_match = NULL;
187 clear_possible_completions();
188 set_completion_matches(0);
189 set_save_completions(true);
190 matches = completions(str);
191 set_completion_matches(matches);
192 set_save_completions(false);
193 return(current_match);
194 }
195
196
expression_completer(widget_t w,const char * original_text,void * data)197 char *expression_completer(widget_t w, const char *original_text, void *data)
198 {
199 int len, beg, matches = 0;
200 /* first back up to some delimiter to get the current expression */
201
202 current_match = NULL;
203 set_completion_matches(0);
204
205 if ((original_text) && (*original_text))
206 {
207 const char *text;
208 int i;
209
210 len = strlen(original_text);
211 for (i = len - 1; i >= 0; i--)
212 if (is_separator(original_text[i]))
213 break;
214 beg = i + 1;
215
216 if (beg == len)
217 {
218 /* returning original = no-op response to <tab> which seems useless;
219 * so, if it's a function that we recognize and that function has its own completer, call it?
220 * result null -> return original (current behavior), else append result as selection to current;
221 * this way, I think it's easy to clear the suggested completion (cursor at end, so backspace deletes all).
222 * or scan back through full text (assuming listener or history available) and find match?
223 */
224 return(mus_strdup(original_text));
225 }
226
227 if (beg > 0)
228 text = (const char *)(original_text + beg);
229 else text = original_text;
230 matches = completions(text);
231 }
232 else return(mus_strdup(original_text));
233
234 set_completion_matches(matches);
235 if ((current_match) &&
236 (*current_match))
237 {
238 if (beg == 0)
239 return(current_match);
240 else
241 {
242 char *text;
243 len = mus_strlen(current_match) + beg + 2;
244 text = (char *)calloc(len, sizeof(char));
245 memcpy(text, original_text, beg);
246 strcat(text, current_match);
247 free(current_match);
248 return(text);
249 }
250 }
251 return(mus_strdup(original_text));
252 }
253
254
255
256 /* -------- saved choices -------- */
257
258 #define BEST_COMPLETIONS 64
259 static char *best_completions[BEST_COMPLETIONS];
260
preload_best_completions(void)261 void preload_best_completions(void)
262 {
263 /* set up the array with some reasonable first choices */
264 int n = 0;
265 best_completions[n++] = mus_strdup(S_open_sound);
266 best_completions[n++] = mus_strdup(S_channels);
267 best_completions[n++] = mus_strdup(S_close_sound);
268 best_completions[n++] = mus_strdup(S_cursor);
269 best_completions[n++] = mus_strdup(S_env_channel);
270 best_completions[n++] = mus_strdup(S_file_name);
271 best_completions[n++] = mus_strdup(S_framples);
272 best_completions[n++] = mus_strdup(S_map_channel);
273 best_completions[n++] = mus_strdup(S_maxamp);
274 best_completions[n++] = mus_strdup(S_play);
275 best_completions[n++] = mus_strdup(S_save_sound);
276 best_completions[n++] = mus_strdup(S_scale_channel);
277 best_completions[n++] = mus_strdup(S_srate);
278 best_completions[n++] = mus_strdup("with-sound");
279 best_completions[n++] = mus_strdup(S_outa);
280 best_completions[n++] = mus_strdup("*output*");
281 best_completions[n++] = mus_strdup("lambda");
282 best_completions[n++] = mus_strdup("define");
283 }
284
285
save_completion_choice(const char * selection)286 void save_completion_choice(const char *selection)
287 {
288 int i;
289 char *cur, *old = NULL;
290
291 cur = mus_strdup(selection);
292 for (i = 0; i < BEST_COMPLETIONS; i++)
293 {
294 old = best_completions[i];
295 best_completions[i] = cur;
296 if ((!old) ||
297 (mus_strcmp(old, cur)))
298 break;
299 cur = old;
300 }
301 if (old) free(old);
302 }
303
304
find_best_completion(char ** choices,int num_choices)305 int find_best_completion(char **choices, int num_choices)
306 {
307 int i, k;
308 for (i = 0; (i < BEST_COMPLETIONS) && (best_completions[i]); i++)
309 for (k = 0; k < num_choices; k++)
310 if (mus_strcmp(choices[k], best_completions[i]))
311 return(k + 1); /* row numbering is 1-based */
312 return(1);
313 }
314
315
316
317
318
319 /* ---------------- EXPRESSION/FILENAME COMPLETIONS ---------------- */
320
321 typedef char *(*completer_func)(widget_t w, const char *text, void *data);
322 static completer_func *completer_funcs = NULL;
323 typedef void (*multicompleter_func)(widget_t w, void *data);
324 static multicompleter_func *multicompleter_funcs = NULL;
325 static void **completer_data = NULL;
326 static int completer_funcs_size = 0;
327 static int completer_funcs_end = 0;
328
329
add_completer_func(char * (* func)(widget_t w,const char * text,void * context),void * data)330 int add_completer_func(char *(*func)(widget_t w, const char *text, void *context), void *data)
331 {
332 if (completer_funcs_size == completer_funcs_end)
333 {
334 completer_funcs_size += 8;
335 if (!completer_funcs)
336 {
337 completer_funcs = (completer_func *)calloc(completer_funcs_size, sizeof(completer_func));
338 multicompleter_funcs = (multicompleter_func *)calloc(completer_funcs_size, sizeof(multicompleter_func));
339 completer_data = (void **)calloc(completer_funcs_size, sizeof(void *));
340 }
341 else
342 {
343 int i;
344 completer_funcs = (completer_func *)realloc(completer_funcs, completer_funcs_size * sizeof(completer_func));
345 multicompleter_funcs = (multicompleter_func *)realloc(multicompleter_funcs, completer_funcs_size * sizeof(multicompleter_func));
346 completer_data = (void **)realloc(completer_data, completer_funcs_size * sizeof(void *));
347 for (i = completer_funcs_end; i < completer_funcs_size; i++) completer_data[i] = NULL;
348 }
349 }
350 completer_funcs[completer_funcs_end] = func;
351 completer_data[completer_funcs_end] = data;
352 completer_funcs_end++;
353 return(completer_funcs_end - 1);
354 }
355
356
add_completer_func_with_multicompleter(char * (* func)(widget_t w,const char * text,void * context),void * data,void (* multi_func)(widget_t w,void * data))357 int add_completer_func_with_multicompleter(char *(*func)(widget_t w, const char *text, void *context), void *data, void (*multi_func)(widget_t w, void *data))
358 {
359 int row;
360 row = add_completer_func(func, data);
361 multicompleter_funcs[row] = multi_func;
362 return(row);
363 }
364
365
366 static int completion_matches = 0;
367
get_completion_matches(void)368 int get_completion_matches(void) {return(completion_matches);}
set_completion_matches(int matches)369 void set_completion_matches(int matches) {completion_matches = matches;}
370
371 static bool save_completions = 0;
372 static char **possible_completions = NULL;
373 static int possible_completions_size = 0;
374 static int possible_completions_ctr = 0;
375
set_save_completions(bool save)376 void set_save_completions(bool save) {save_completions = save;}
377
378
add_possible_completion(const char * text)379 void add_possible_completion(const char *text)
380 {
381 if (save_completions)
382 {
383 if (possible_completions_size == possible_completions_ctr)
384 {
385 possible_completions_size += 16;
386 if (!possible_completions)
387 possible_completions = (char **)calloc(possible_completions_size, sizeof(char *));
388 else
389 {
390 int i;
391 possible_completions = (char **)realloc(possible_completions, possible_completions_size * sizeof(char *));
392 for (i = possible_completions_ctr; i < possible_completions_size; i++) possible_completions[i] = NULL;
393 }
394 }
395 if (possible_completions[possible_completions_ctr]) free(possible_completions[possible_completions_ctr]);
396 possible_completions[possible_completions_ctr] = mus_strdup(text);
397 possible_completions_ctr++;
398 }
399 }
400
401
get_possible_completions_size(void)402 int get_possible_completions_size(void)
403 {
404 return(possible_completions_ctr);
405 }
406
407
get_possible_completions(void)408 char **get_possible_completions(void)
409 {
410 return(possible_completions);
411 }
412
413
handle_completions(widget_t w,int completer)414 void handle_completions(widget_t w, int completer)
415 {
416 if ((completer >= 0) &&
417 (completer < completer_funcs_end) &&
418 (multicompleter_funcs[completer]))
419 (*multicompleter_funcs[completer])(w, completer_data[completer]);
420 }
421
422
complete_text(widget_t w,const char * text,int func)423 char *complete_text(widget_t w, const char *text, int func)
424 {
425 /* given text, call proc table entry func, return new text (not text!) */
426 completion_matches = -1; /* i.e. no completer */
427 possible_completions_ctr = 0;
428 if ((func >= 0) &&
429 (func < completer_funcs_end))
430 return((*completer_funcs[func])(w, text, completer_data[func]));
431 else return(mus_strdup(text));
432 }
433
434
clear_possible_completions(void)435 void clear_possible_completions(void)
436 {
437 int i;
438 for (i = 0; i < possible_completions_size; i++)
439 if (possible_completions[i])
440 {
441 free(possible_completions[i]);
442 possible_completions[i] = NULL;
443 }
444 possible_completions_ctr = 0;
445 }
446
447
448 static list_completer_info *srate_info = NULL;
449
init_srate_list(void)450 static void init_srate_list(void)
451 {
452 if (!srate_info)
453 {
454 int loc = 0;
455 srate_info = (list_completer_info *)calloc(1, sizeof(list_completer_info));
456 srate_info->exact_match = true;
457 srate_info->values_size = 16;
458 srate_info->values = (char **)calloc(srate_info->values_size, sizeof(char *));
459 srate_info->values[loc++] = mus_strdup("44100");
460 srate_info->values[loc++] = mus_strdup("22050");
461 srate_info->values[loc++] = mus_strdup("8000");
462 srate_info->values[loc++] = mus_strdup("48000");
463 srate_info->num_values = loc;
464 }
465 }
466
467
add_srate_to_completion_list(int srate)468 void add_srate_to_completion_list(int srate)
469 {
470 char *str;
471 int i;
472
473 init_srate_list();
474 str = (char *)malloc(16 * sizeof(char));
475 snprintf(str, 16, "%d", srate);
476
477 for (i = 0; i < srate_info->num_values; i++)
478 if (mus_strcmp(srate_info->values[i], str))
479 {
480 free(str);
481 return;
482 }
483
484 if (srate_info->num_values >= srate_info->values_size)
485 {
486 srate_info->values_size += 16;
487 srate_info->values = (char **)realloc(srate_info->values, srate_info->values_size * sizeof(char *));
488 for (i = srate_info->num_values; i < srate_info->values_size; i++) srate_info->values[i] = NULL;
489 }
490
491 srate_info->values[srate_info->num_values++] = str;
492 }
493
494
srate_completer(widget_t w,const char * text,void * data)495 char *srate_completer(widget_t w, const char *text, void * data)
496 {
497 init_srate_list();
498 return(list_completer(w, text, (void *)srate_info));
499 }
500
501
502 #ifndef _MSC_VER
503 #include <dirent.h>
504 #endif
505
506 enum {ANY_FILE_TYPE, SOUND_FILE_TYPE};
507
508
filename_completer_1(widget_t w,const char * text,int file_type)509 static char *filename_completer_1(widget_t w, const char *text, int file_type)
510 {
511 /* assume text is a partial filename */
512 /* get directory name, opendir, read files checking for match */
513 /* return name of same form as original (i.e. don't change user's directory indication) */
514 /* if directory, add "/" -- is_directory(name) static in snd-xfile.c */
515
516 char *full_name = NULL, *dir_name = NULL, *file_name = NULL, *current_match = NULL;
517 int i, j, k, len, curlen, matches = 0;
518 DIR *dpos;
519
520 if (mus_strlen(text) == 0) return(NULL);
521 full_name = mus_expand_filename(text);
522 len = mus_strlen(full_name);
523 for (i = len - 1; i > 0; i--)
524 if (full_name[i] == '/')
525 break;
526
527 dir_name = (char *)calloc(i + 1, sizeof(char));
528 memcpy(dir_name, full_name, i);
529
530 file_name = (char *)calloc(len - i + 2, sizeof(char));
531 for (j = 0, k = i + 1; k < len; j++, k++)
532 file_name[j] = full_name[k];
533
534 if (full_name)
535 {
536 free(full_name);
537 full_name = NULL;
538 }
539
540 len = mus_strlen(file_name);
541 if ((dpos = opendir(dir_name)))
542 {
543 struct dirent *dirp;
544 while ((dirp = readdir(dpos)))
545 if ((dirp->d_name[0] != '.') &&
546 (strncmp(dirp->d_name, file_name, len) == 0)) /* match dirp->d_name against rest of text */
547 {
548 if ((file_type == ANY_FILE_TYPE) ||
549 (is_sound_file(dirp->d_name)))
550 {
551 matches++;
552 add_possible_completion(dirp->d_name);
553 if (!current_match)
554 current_match = mus_strdup(dirp->d_name);
555 else
556 {
557 curlen = strlen(current_match);
558 for (j = 0; j < curlen; j++)
559 if (current_match[j] != dirp->d_name[j])
560 {
561 current_match[j] = '\0';
562 break;
563 }
564 }
565 }
566 }
567
568 if (closedir(dpos) != 0)
569 snd_error("closedir %s failed (%s)!", dir_name, snd_io_strerror());
570 }
571
572 if (dir_name) free(dir_name);
573 if (file_name) free(file_name);
574
575 set_completion_matches(matches);
576
577 if ((current_match) &&
578 (*current_match))
579 {
580 /* attach matched portion to user's indication of dir */
581 len = mus_strlen(text);
582 for (i = len - 1; i >= 0; i--)
583 if (text[i] == '/')
584 break;
585 if (i < 0) return(current_match);
586 curlen = strlen(current_match) + len + 3;
587 file_name = (char *)calloc(curlen, sizeof(char));
588 memcpy(file_name, text, i + 1);
589 strcat(file_name, current_match);
590 if (is_directory(file_name))
591 strcat(file_name, "/");
592 free(current_match);
593 return(file_name);
594 }
595 return(mus_strdup(text));
596 }
597
598
filename_completer(widget_t w,const char * text,void * data)599 char *filename_completer(widget_t w, const char *text, void *data)
600 {
601 return(filename_completer_1(w, text, ANY_FILE_TYPE));
602 }
603
604
sound_filename_completer(widget_t w,const char * text,void * data)605 char *sound_filename_completer(widget_t w, const char *text, void *data)
606 {
607 return(filename_completer_1(w, text, SOUND_FILE_TYPE));
608 }
609
610
find_indentation(char * str,int loc)611 static int find_indentation(char *str, int loc)
612 {
613 int line_beg = 0, open_paren = -1, parens, i;
614 parens = 0;
615 for (i = loc - 1; i >= 0; i--)
616 {
617 if (str[i] == ')') parens--;
618 if (str[i] == '(') parens++;
619 if (parens == 1)
620 {
621 open_paren = i;
622 break;
623 }
624 }
625 if (open_paren == -1) return(1);
626 if (open_paren == 0) return(3);
627 for (i = open_paren - 1; i > 0; i--)
628 if (str[i] == '\n')
629 {
630 line_beg = i;
631 break;
632 }
633 if (line_beg == 0) return(1);
634 return(open_paren - line_beg + 2);
635 }
636
637
complete_listener_text(char * old_text,int end,bool * try_completion,char ** to_file_text)638 char *complete_listener_text(char *old_text, int end, bool *try_completion, char **to_file_text)
639 {
640 int len, i, k, spaces, text_pos = 0, cr_pos = 0;
641 char *new_text = NULL, *file_text = NULL;
642
643 len = strlen(old_text);
644 for (i = len - 1; i > 0; i--)
645 {
646 if (old_text[i] == '\n')
647 {
648 /* tab as indentation */
649 /* look at previous line to decide */
650 spaces = find_indentation(old_text, i);
651 if (spaces > 0)
652 {
653 file_text = (char *)calloc(spaces + 1, sizeof(char));
654 for (k = 0; k < spaces; k++)
655 file_text[k] = ' ';
656 file_text[spaces] = 0;
657 append_listener_text(end, file_text);
658 free(file_text);
659 file_text = NULL;
660 }
661 (*try_completion) = false;
662 return(NULL);
663 }
664
665 if (old_text[i] == ';')
666 {
667 /* this isn't quite right, but how much effort should we put in it? */
668 spaces = 20;
669 for (k = i - 1; k > 0; k--)
670 if (old_text[k] == '\n')
671 {
672 cr_pos = k;
673 break;
674 }
675 else
676 if ((!(isspace((int)(old_text[k])))) &&
677 (text_pos == 0))
678 text_pos = k;
679 if (text_pos > 0)
680 text_pos -= cr_pos;
681 if (cr_pos == 0) spaces--;
682 if (text_pos < spaces)
683 {
684 file_text = (char *)calloc(spaces + 2, sizeof(char));
685 for (k = text_pos + 1; k < spaces; k++)
686 file_text[k - text_pos - 1] = ' ';
687 file_text[spaces] = ';';
688 file_text[spaces + 1] = 0;
689 append_listener_text(end - 1, file_text);
690 free(file_text);
691 file_text = NULL;
692 }
693 (*try_completion) = false;
694 return(NULL);
695 }
696
697 if (old_text[i] == '\"')
698 {
699 char *new_file;
700 file_text = mus_strdup((char *)(old_text + i + 1));
701 new_file = filename_completer(NULL_WIDGET, file_text, NULL);
702 len = mus_strlen(new_file);
703 if (len > 0)
704 {
705 len += i + 2;
706 new_text = (char *)calloc(len, sizeof(char));
707 memcpy(new_text, old_text, i + 1);
708 strcat(new_text, new_file);
709 free(new_file);
710 }
711 break;
712 }
713 if (isspace((int)(old_text[i]))) break;
714 }
715
716 if (!new_text) new_text = expression_completer(NULL_WIDGET, old_text, NULL);
717 (*try_completion) = true;
718 (*to_file_text) = file_text;
719 return(new_text);
720 }
721
722
list_completer(widget_t w,const char * text,void * data)723 char *list_completer(widget_t w, const char *text, void *data)
724 {
725 list_completer_info *info = (list_completer_info *)data;
726 int i, j = 0, len, matches = 0, current_match = -1;
727 char *trimmed_text;
728
729 set_completion_matches(0);
730 /* check for null text */
731 len = mus_strlen(text);
732 if (len == 0) return(mus_strdup(text));
733
734 /* strip away leading and trailing white space */
735 trimmed_text = (char *)calloc(len + 1, sizeof(char));
736 for (i = 0; i < len; i++)
737 if (!(isspace((int)text[i])))
738 trimmed_text[j++] = text[i];
739
740 if (j == 0)
741 {
742 free(trimmed_text);
743 return(mus_strdup(text));
744 }
745
746 /* check for match(es) against values */
747 if (info->exact_match)
748 {
749 for (i = 0; i < info->num_values; i++)
750 if ((info->values[i]) &&
751 (strncmp(info->values[i], trimmed_text, len) == 0))
752 {
753 matches++;
754 current_match = i;
755 }
756 }
757 else
758 {
759 for (i = 0; i < info->num_values; i++)
760 if ((info->values[i]) &&
761 (STRNCMP(info->values[i], trimmed_text, len) == 0))
762 {
763 matches++;
764 current_match = i;
765 }
766 }
767 free(trimmed_text);
768 if (matches != 1)
769 return(mus_strdup(text));
770 set_completion_matches(1);
771
772 return(mus_strdup(info->values[current_match]));
773 }
774