Add Nano tool - user-friendly text editor
[tomato.git] / release / src / router / nano / src / browser.c
blobc43796b6d23eb3b237909d4b3d0337384307ec29
1 /* $Id: browser.c 4461 2009-12-09 17:09:37Z astyanax $ */
2 /**************************************************************************
3 * browser.c *
4 * *
5 * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 *
6 * Free Software Foundation, Inc. *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 3, or (at your option) *
10 * any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, but *
13 * WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
15 * General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the Free Software *
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
20 * 02110-1301, USA. *
21 * *
22 **************************************************************************/
24 #include "proto.h"
26 #include <stdio.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <errno.h>
31 #ifndef DISABLE_BROWSER
33 static char **filelist = NULL;
34 /* The list of files to display in the file browser. */
35 static size_t filelist_len = 0;
36 /* The number of files in the list. */
37 static int width = 0;
38 /* The number of files that we can display per line. */
39 static int longest = 0;
40 /* The number of columns in the longest filename in the list. */
41 static size_t selected = 0;
42 /* The currently selected filename in the list. This variable
43 * is zero-based. */
44 static bool search_last_file = FALSE;
45 /* Have we gone past the last file while searching? */
47 /* Our main file browser function. path is the tilde-expanded path we
48 * start browsing from. */
49 char *do_browser(char *path, DIR *dir)
51 char *retval = NULL;
52 int kbinput;
53 bool meta_key, func_key, old_const_update = ISSET(CONST_UPDATE);
54 bool abort = FALSE;
55 /* Whether we should abort the file browser. */
56 char *prev_dir = NULL;
57 /* The directory we were in, if any, before backing up via
58 * browsing to "..". */
59 char *ans = NULL;
60 /* The last answer the user typed at the statusbar prompt. */
61 size_t old_selected;
62 /* The selected file we had before the current selected file. */
63 const sc *s;
64 const subnfunc *f;
66 curs_set(0);
67 blank_statusbar();
68 #if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
69 currmenu = MBROWSER;
70 #endif
71 bottombars(MBROWSER);
72 wnoutrefresh(bottomwin);
74 UNSET(CONST_UPDATE);
76 ans = mallocstrcpy(NULL, "");
78 change_browser_directory:
79 /* We go here after we select a new directory. */
81 /* Start with no key pressed. */
82 kbinput = ERR;
84 path = mallocstrassn(path, get_full_path(path));
86 assert(path != NULL && path[strlen(path) - 1] == '/');
88 /* Get the file list, and set longest and width in the process. */
89 browser_init(path, dir);
91 assert(filelist != NULL);
93 /* Sort the file list. */
94 qsort(filelist, filelist_len, sizeof(char *), diralphasort);
96 /* If prev_dir isn't NULL, select the directory saved in it, and
97 * then blow it away. */
98 if (prev_dir != NULL) {
99 browser_select_filename(prev_dir);
101 free(prev_dir);
102 prev_dir = NULL;
103 /* Otherwise, select the first file or directory in the list. */
104 } else
105 selected = 0;
107 old_selected = (size_t)-1;
109 titlebar(path);
111 while (!abort) {
112 struct stat st;
113 int i;
114 size_t fileline = selected / width;
115 /* The line number the selected file is on. */
116 char *new_path;
117 /* The path we switch to at the "Go to Directory"
118 * prompt. */
120 /* Display the file list if we don't have a key, or if the
121 * selected file has changed, and set width in the process. */
122 if (kbinput == ERR || old_selected != selected)
123 browser_refresh();
125 old_selected = selected;
127 kbinput = get_kbinput(edit, &meta_key, &func_key);
129 #ifndef DISABLE_MOUSE
130 if (kbinput == KEY_MOUSE) {
132 int mouse_x, mouse_y;
134 /* We can click on the edit window to select a
135 * filename. */
136 if (get_mouseinput(&mouse_x, &mouse_y, TRUE) == 0 &&
137 wmouse_trafo(edit, &mouse_y, &mouse_x, FALSE)) {
138 /* longest is the width of each column. There
139 * are two spaces between each column. */
140 selected = (fileline / editwinrows) *
141 (editwinrows * width) + (mouse_y *
142 width) + (mouse_x / (longest + 2));
144 /* If they clicked beyond the end of a row,
145 * select the filename at the end of that
146 * row. */
147 if (mouse_x > width * (longest + 2))
148 selected--;
150 /* If we're off the screen, select the last
151 * filename. */
152 if (selected > filelist_len - 1)
153 selected = filelist_len - 1;
155 /* If we selected the same filename as last
156 * time, put back the Enter key so that it's
157 * read in. */
158 if (old_selected == selected)
159 unget_kbinput(sc_seq_or(DO_ENTER, 0), FALSE, FALSE);
162 #endif /* !DISABLE_MOUSE */
164 parse_browser_input(&kbinput, &meta_key, &func_key);
165 s = get_shortcut(MBROWSER, &kbinput, &meta_key, &func_key);
166 if (!s)
167 continue;
168 f = sctofunc((sc *) s);
169 if (!f)
170 break;
172 if (f->scfunc == TOTAL_REFRESH) {
173 total_redraw();
174 } else if (f->scfunc == DO_HELP_VOID) {
175 #ifndef DISABLE_HELP
176 do_browser_help();
177 curs_set(0);
178 #else
179 nano_disabled_msg();
180 #endif
181 /* Search for a filename. */
182 } else if (f->scfunc == DO_SEARCH) {
183 curs_set(1);
184 do_filesearch();
185 curs_set(0);
186 /* Search for another filename. */
187 } else if (f->scfunc == DO_RESEARCH) {
188 do_fileresearch();
189 } else if (f->scfunc == DO_PAGE_UP) {
190 if (selected >= (editwinrows + fileline % editwinrows) *
191 width)
192 selected -= (editwinrows + fileline % editwinrows) *
193 width;
194 else
195 selected = 0;
196 } else if (f->scfunc == DO_PAGE_DOWN) {
197 selected += (editwinrows - fileline % editwinrows) *
198 width;
199 if (selected > filelist_len - 1)
200 selected = filelist_len - 1;
201 } else if (f->scfunc == FIRST_FILE_MSG) {
202 if (meta_key)
203 selected = 0;
204 } else if (f->scfunc == LAST_FILE_MSG) {
205 if (meta_key)
206 selected = filelist_len - 1;
207 /* Go to a specific directory. */
208 } else if (f->scfunc == GOTO_DIR_MSG) {
209 curs_set(1);
211 i = do_prompt(TRUE,
212 #ifndef DISABLE_TABCOMP
213 FALSE,
214 #endif
215 MGOTODIR, ans,
216 &meta_key, &func_key,
217 #ifndef NANO_TINY
218 NULL,
219 #endif
220 browser_refresh, N_("Go To Directory"));
222 curs_set(0);
223 #if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
224 currmenu = MBROWSER;
225 #endif
226 bottombars(MBROWSER);
228 /* If the directory begins with a newline (i.e. an
229 * encoded null), treat it as though it's blank. */
230 if (i < 0 || *answer == '\n') {
231 /* We canceled. Indicate that on the statusbar, and
232 * blank out ans, since we're done with it. */
233 statusbar(_("Cancelled"));
234 ans = mallocstrcpy(ans, "");
235 continue;
236 } else if (i != 0) {
237 /* Put back the "Go to Directory" key and save
238 * answer in ans, so that the file list is displayed
239 * again, the prompt is displayed again, and what we
240 * typed before at the prompt is displayed again. */
241 unget_kbinput(sc_seq_or(DO_GOTOLINECOLUMN_VOID, 0), FALSE, FALSE);
242 ans = mallocstrcpy(ans, answer);
243 continue;
246 /* We have a directory. Blank out ans, since we're done
247 * with it. */
248 ans = mallocstrcpy(ans, "");
250 /* Convert newlines to nulls, just before we go to the
251 * directory. */
252 sunder(answer);
253 align(&answer);
255 new_path = real_dir_from_tilde(answer);
257 if (new_path[0] != '/') {
258 new_path = charealloc(new_path, strlen(path) +
259 strlen(answer) + 1);
260 sprintf(new_path, "%s%s", path, answer);
263 #ifndef DISABLE_OPERATINGDIR
264 if (check_operating_dir(new_path, FALSE)) {
265 statusbar(
266 _("Can't go outside of %s in restricted mode"),
267 operating_dir);
268 free(new_path);
269 continue;
271 #endif
273 dir = opendir(new_path);
274 if (dir == NULL) {
275 /* We can't open this directory for some reason.
276 * Complain. */
277 statusbar(_("Error reading %s: %s"), answer,
278 strerror(errno));
279 beep();
280 free(new_path);
281 continue;
284 /* Start over again with the new path value. */
285 free(path);
286 path = new_path;
287 goto change_browser_directory;
288 } else if (f->scfunc == DO_UP_VOID) {
289 if (selected >= width)
290 selected -= width;
291 } else if (f->scfunc == DO_LEFT) {
292 if (selected > 0)
293 selected--;
294 } else if (f->scfunc == DO_DOWN_VOID) {
295 if (selected + width <= filelist_len - 1)
296 selected += width;
297 } else if (f->scfunc == DO_RIGHT) {
298 if (selected < filelist_len - 1)
299 selected++;
300 } else if (f->scfunc == DO_ENTER) {
301 /* We can't move up from "/". */
302 if (strcmp(filelist[selected], "/..") == 0) {
303 statusbar(_("Can't move up a directory"));
304 beep();
305 continue;
308 #ifndef DISABLE_OPERATINGDIR
309 /* Note: The selected file can be outside the operating
310 * directory if it's ".." or if it's a symlink to a
311 * directory outside the operating directory. */
312 if (check_operating_dir(filelist[selected], FALSE)) {
313 statusbar(
314 _("Can't go outside of %s in restricted mode"),
315 operating_dir);
316 beep();
317 continue;
319 #endif
321 if (stat(filelist[selected], &st) == -1) {
322 /* We can't open this file for some reason.
323 * Complain. */
324 statusbar(_("Error reading %s: %s"),
325 filelist[selected], strerror(errno));
326 beep();
327 continue;
330 /* If we've successfully opened a file, we're done, so
331 * get out. */
332 if (!S_ISDIR(st.st_mode)) {
333 retval = mallocstrcpy(NULL, filelist[selected]);
334 abort = TRUE;
335 continue;
336 /* If we've successfully opened a directory, and it's
337 * "..", save the current directory in prev_dir, so that
338 * we can select it later. */
339 } else if (strcmp(tail(filelist[selected]), "..") == 0)
340 prev_dir = mallocstrcpy(NULL,
341 striponedir(filelist[selected]));
343 dir = opendir(filelist[selected]);
344 if (dir == NULL) {
345 /* We can't open this directory for some reason.
346 * Complain. */
347 statusbar(_("Error reading %s: %s"),
348 filelist[selected], strerror(errno));
349 beep();
350 continue;
353 path = mallocstrcpy(path, filelist[selected]);
355 /* Start over again with the new path value. */
356 goto change_browser_directory;
357 /* Abort the file browser. */
358 } else if (f->scfunc == DO_EXIT) {
359 abort = TRUE;
362 titlebar(NULL);
363 edit_refresh();
364 curs_set(1);
365 if (old_const_update)
366 SET(CONST_UPDATE);
368 free(path);
369 free(ans);
371 free_chararray(filelist, filelist_len);
372 filelist = NULL;
373 filelist_len = 0;
375 return retval;
378 /* The file browser front end. We check to see if inpath has a
379 * directory in it. If it does, we start do_browser() from there.
380 * Otherwise, we start do_browser() from the current directory. */
381 char *do_browse_from(const char *inpath)
383 struct stat st;
384 char *path;
385 /* This holds the tilde-expanded version of inpath. */
386 DIR *dir = NULL;
388 assert(inpath != NULL);
390 path = real_dir_from_tilde(inpath);
392 /* Perhaps path is a directory. If so, we'll pass it to
393 * do_browser(). Or perhaps path is a directory / a file. If so,
394 * we'll try stripping off the last path element and passing it to
395 * do_browser(). Or perhaps path doesn't have a directory portion
396 * at all. If so, we'll just pass the current directory to
397 * do_browser(). */
398 if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
399 path = mallocstrassn(path, striponedir(path));
401 if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
402 free(path);
404 path = charalloc(PATH_MAX + 1);
405 path = getcwd(path, PATH_MAX + 1);
407 if (path != NULL)
408 align(&path);
412 #ifndef DISABLE_OPERATINGDIR
413 /* If the resulting path isn't in the operating directory, use
414 * the operating directory instead. */
415 if (check_operating_dir(path, FALSE))
416 path = mallocstrcpy(path, operating_dir);
417 #endif
419 if (path != NULL)
420 dir = opendir(path);
422 /* If we can't open the path, get out. */
423 if (dir == NULL) {
424 if (path != NULL)
425 free(path);
426 beep();
427 return NULL;
430 return do_browser(path, dir);
433 /* Set filelist to the list of files contained in the directory path,
434 * set filelist_len to the number of files in that list, set longest to
435 * the width in columns of the longest filename in that list (between 15
436 * and COLS), and set width to the number of files that we can display
437 * per line. longest needs to be at least 15 columns in order to
438 * display ".. (parent dir)", as Pico does. Assume path exists and is a
439 * directory. */
440 void browser_init(const char *path, DIR *dir)
442 const struct dirent *nextdir;
443 size_t i = 0, path_len = strlen(path);
444 int col = 0;
445 /* The maximum number of columns that the filenames will take
446 * up. */
447 int line = 0;
448 /* The maximum number of lines that the filenames will take
449 * up. */
450 int filesperline = 0;
451 /* The number of files that we can display per line. */
453 assert(path != NULL && path[strlen(path) - 1] == '/' && dir != NULL);
455 /* Set longest to zero, just before we initialize it. */
456 longest = 0;
458 while ((nextdir = readdir(dir)) != NULL) {
459 size_t d_len;
461 /* Don't show the "." entry. */
462 if (strcmp(nextdir->d_name, ".") == 0)
463 continue;
465 d_len = strlenpt(nextdir->d_name);
466 if (d_len > longest)
467 longest = (d_len > COLS) ? COLS : d_len;
469 i++;
472 rewinddir(dir);
474 /* Put 10 columns' worth of blank space between columns of filenames
475 * in the list whenever possible, as Pico does. */
476 longest += 10;
478 if (filelist != NULL)
479 free_chararray(filelist, filelist_len);
481 filelist_len = i;
483 filelist = (char **)nmalloc(filelist_len * sizeof(char *));
485 i = 0;
487 while ((nextdir = readdir(dir)) != NULL && i < filelist_len) {
488 /* Don't show the "." entry. */
489 if (strcmp(nextdir->d_name, ".") == 0)
490 continue;
492 filelist[i] = charalloc(path_len + strlen(nextdir->d_name) + 1);
493 sprintf(filelist[i], "%s%s", path, nextdir->d_name);
495 i++;
498 /* Maybe the number of files in the directory changed between the
499 * first time we scanned and the second. i is the actual length of
500 * filelist, so record it. */
501 filelist_len = i;
503 closedir(dir);
505 /* Make sure longest is between 15 and COLS. */
506 if (longest < 15)
507 longest = 15;
508 if (longest > COLS)
509 longest = COLS;
511 /* Set width to zero, just before we initialize it. */
512 width = 0;
514 for (i = 0; i < filelist_len && line < editwinrows; i++) {
515 /* Calculate the number of columns one filename will take up. */
516 col += longest;
517 filesperline++;
519 /* Add some space between the columns. */
520 col += 2;
522 /* If the next entry isn't going to fit on the current line,
523 * move to the next line. */
524 if (col > COLS - longest) {
525 line++;
526 col = 0;
528 /* If width isn't initialized yet, and we've taken up more
529 * than one line, it means that width is equal to
530 * filesperline. */
531 if (width == 0)
532 width = filesperline;
536 /* If width isn't initialized yet, and we've taken up only one line,
537 * it means that width is equal to longest. */
538 if (width == 0)
539 width = longest;
542 /* Determine the shortcut key corresponding to the values of kbinput
543 * (the key itself), meta_key (whether the key is a meta sequence), and
544 * func_key (whether the key is a function key), if any. In the
545 * process, convert certain non-shortcut keys into their corresponding
546 * shortcut keys. */
547 void parse_browser_input(int *kbinput, bool *meta_key, bool *func_key)
549 get_shortcut(MBROWSER, kbinput, meta_key, func_key);
551 /* Pico compatibility. */
552 if (!*meta_key) {
553 switch (*kbinput) {
554 case ' ':
555 *kbinput = sc_seq_or(DO_PAGE_DOWN, 0);
556 break;
557 case '-':
558 *kbinput = sc_seq_or(DO_PAGE_UP, 0);
559 break;
560 case '?':
561 #ifndef DISABLE_HELP
562 *kbinput = sc_seq_or(DO_HELP_VOID, 0);
563 #endif
564 break;
565 /* Cancel equivalent to Exit here. */
566 case 'E':
567 case 'e':
568 *kbinput = sc_seq_or(DO_EXIT, 0);
569 break;
570 case 'G':
571 case 'g':
572 *kbinput = sc_seq_or(GOTO_DIR_MSG, 0);
573 break;
574 case 'S':
575 case 's':
576 *kbinput = sc_seq_or(DO_ENTER, 0);
577 break;
578 case 'W':
579 case 'w':
580 *kbinput = sc_seq_or(DO_SEARCH, 0);
581 break;
586 /* Set width to the number of files that we can display per line, if
587 * necessary, and display the list of files. */
588 void browser_refresh(void)
590 static int uimax_digits = -1;
591 size_t i;
592 int col = 0;
593 /* The maximum number of columns that the filenames will take
594 * up. */
595 int line = 0;
596 /* The maximum number of lines that the filenames will take
597 * up. */
598 char *foo;
599 /* The file information that we'll display. */
601 if (uimax_digits == -1)
602 uimax_digits = digits(UINT_MAX);
604 blank_edit();
606 wmove(edit, 0, 0);
608 i = width * editwinrows * ((selected / width) / editwinrows);
610 for (; i < filelist_len && line < editwinrows; i++) {
611 struct stat st;
612 const char *filetail = tail(filelist[i]);
613 /* The filename we display, minus the path. */
614 size_t filetaillen = strlenpt(filetail);
615 /* The length of the filename in columns. */
616 size_t foolen;
617 /* The length of the file information in columns. */
618 int foomaxlen = 7;
619 /* The maximum length of the file information in
620 * columns: seven for "--", "(dir)", or the file size,
621 * and 12 for "(parent dir)". */
622 bool dots = (COLS >= 15 && filetaillen >= longest -
623 foomaxlen - 1);
624 /* Do we put an ellipsis before the filename? Don't set
625 * this to TRUE if we have fewer than 15 columns (i.e.
626 * one column for padding, plus seven columns for a
627 * filename other than ".."). */
628 char *disp = display_string(filetail, dots ? filetaillen -
629 longest + foomaxlen + 4 : 0, longest, FALSE);
630 /* If we put an ellipsis before the filename, reserve
631 * one column for padding, plus seven columns for "--",
632 * "(dir)", or the file size, plus three columns for the
633 * ellipsis. */
635 /* Start highlighting the currently selected file or
636 * directory. */
637 if (i == selected)
638 wattron(edit, reverse_attr);
640 blank_line(edit, line, col, longest);
642 /* If dots is TRUE, we will display something like
643 * "...ename". */
644 if (dots)
645 mvwaddstr(edit, line, col, "...");
646 mvwaddstr(edit, line, dots ? col + 3 : col, disp);
648 free(disp);
650 col += longest;
652 /* Show information about the file. We don't want to report
653 * file sizes for links, so we use lstat(). */
654 if (lstat(filelist[i], &st) == -1 || S_ISLNK(st.st_mode)) {
655 /* If the file doesn't exist (i.e. it's been deleted while
656 * the file browser is open), or it's a symlink that doesn't
657 * point to a directory, display "--". */
658 if (stat(filelist[i], &st) == -1 || !S_ISDIR(st.st_mode))
659 foo = mallocstrcpy(NULL, "--");
660 /* If the file is a symlink that points to a directory,
661 * display it as a directory. */
662 else
663 /* TRANSLATORS: Try to keep this at most 7
664 * characters. */
665 foo = mallocstrcpy(NULL, _("(dir)"));
666 } else if (S_ISDIR(st.st_mode)) {
667 /* If the file is a directory, display it as such. */
668 if (strcmp(filetail, "..") == 0) {
669 /* TRANSLATORS: Try to keep this at most 12
670 * characters. */
671 foo = mallocstrcpy(NULL, _("(parent dir)"));
672 foomaxlen = 12;
673 } else
674 foo = mallocstrcpy(NULL, _("(dir)"));
675 } else {
676 unsigned long result = st.st_size;
677 char modifier;
679 foo = charalloc(uimax_digits + 4);
681 /* Bytes. */
682 if (st.st_size < (1 << 10))
683 modifier = ' ';
684 /* Kilobytes. */
685 else if (st.st_size < (1 << 20)) {
686 result >>= 10;
687 modifier = 'K';
688 /* Megabytes. */
689 } else if (st.st_size < (1 << 30)) {
690 result >>= 20;
691 modifier = 'M';
692 /* Gigabytes. */
693 } else {
694 result >>= 30;
695 modifier = 'G';
698 sprintf(foo, "%4lu %cB", result, modifier);
701 /* Make sure foo takes up no more than foomaxlen columns. */
702 foolen = strlenpt(foo);
703 if (foolen > foomaxlen) {
704 null_at(&foo, actual_x(foo, foomaxlen));
705 foolen = foomaxlen;
708 mvwaddstr(edit, line, col - foolen, foo);
710 /* Finish highlighting the currently selected file or
711 * directory. */
712 if (i == selected)
713 wattroff(edit, reverse_attr);
715 free(foo);
717 /* Add some space between the columns. */
718 col += 2;
720 /* If the next entry isn't going to fit on the current line,
721 * move to the next line. */
722 if (col > COLS - longest) {
723 line++;
724 col = 0;
727 wmove(edit, line, col);
730 wnoutrefresh(edit);
733 /* Look for needle. If we find it, set selected to its location. Note
734 * that needle must be an exact match for a file in the list. The
735 * return value specifies whether we found anything. */
736 bool browser_select_filename(const char *needle)
738 size_t currselected;
739 bool found = FALSE;
741 for (currselected = 0; currselected < filelist_len;
742 currselected++) {
743 if (strcmp(filelist[currselected], needle) == 0) {
744 found = TRUE;
745 break;
749 if (found)
750 selected = currselected;
752 return found;
755 /* Set up the system variables for a filename search. Return -1 if the
756 * search should be canceled (due to Cancel, a blank search string, or a
757 * failed regcomp()), return 0 on success, and return 1 on rerun calling
758 * program. */
759 int filesearch_init(void)
761 int i = 0;
762 char *buf;
763 bool meta_key, func_key;
764 const sc *s;
765 static char *backupstring = NULL;
766 /* The search string we'll be using. */
768 /* If backupstring doesn't exist, initialize it to "". */
769 if (backupstring == NULL)
770 backupstring = mallocstrcpy(NULL, "");
772 /* We display the search prompt below. If the user types a partial
773 * search string and then Replace or a toggle, we will return to
774 * do_search() or do_replace() and be called again. In that case,
775 * we should put the same search string back up. */
777 search_init_globals();
779 if (last_search[0] != '\0') {
780 char *disp = display_string(last_search, 0, COLS / 3, FALSE);
782 buf = charalloc(strlen(disp) + 7);
783 /* We use (COLS / 3) here because we need to see more on the
784 * line. */
785 sprintf(buf, " [%s%s]", disp,
786 (strlenpt(last_search) > COLS / 3) ? "..." : "");
787 free(disp);
788 } else
789 buf = mallocstrcpy(NULL, "");
791 /* This is now one simple call. It just does a lot. */
792 i = do_prompt(FALSE,
793 #ifndef DISABLE_TABCOMP
794 TRUE,
795 #endif
796 MWHEREISFILE, backupstring,
797 &meta_key, &func_key,
798 #ifndef NANO_TINY
799 &search_history,
800 #endif
801 browser_refresh, "%s%s%s%s%s%s", _("Search"),
802 #ifndef NANO_TINY
803 /* This string is just a modifier for the search prompt; no
804 * grammar is implied. */
805 ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") :
806 #endif
808 #ifdef HAVE_REGEX_H
809 /* This string is just a modifier for the search prompt; no
810 * grammar is implied. */
811 ISSET(USE_REGEXP) ? _(" [Regexp]") :
812 #endif
814 #ifndef NANO_TINY
815 /* This string is just a modifier for the search prompt; no
816 * grammar is implied. */
817 ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") :
818 #endif
819 "", "", buf);
821 /* Release buf now that we don't need it anymore. */
822 free(buf);
824 free(backupstring);
825 backupstring = NULL;
827 /* Cancel any search, or just return with no previous search. */
828 if (i == -1 || (i < 0 && *last_search == '\0') || (i == 0 &&
829 *answer == '\0')) {
830 statusbar(_("Cancelled"));
831 return -1;
832 } else {
833 s = get_shortcut(MBROWSER, &i, &meta_key, &func_key);
834 if (i == -2 || i == 0) {
835 #ifdef HAVE_REGEX_H
836 /* Use last_search if answer is an empty string, or
837 * answer if it isn't. */
838 if (ISSET(USE_REGEXP) && !regexp_init((i == -2) ?
839 last_search : answer))
840 return -1;
841 #endif
842 } else
843 #ifndef NANO_TINY
844 if (s && s->scfunc == CASE_SENS_MSG) {
845 TOGGLE(CASE_SENSITIVE);
846 backupstring = mallocstrcpy(backupstring, answer);
847 return 1;
848 } else if (s && s->scfunc == BACKWARDS_MSG) {
849 TOGGLE(BACKWARDS_SEARCH);
850 backupstring = mallocstrcpy(backupstring, answer);
851 return 1;
852 } else
853 #endif
854 #ifdef HAVE_REGEX_H
855 if (s && s->scfunc == REGEXP_MSG) {
856 TOGGLE(USE_REGEXP);
857 backupstring = mallocstrcpy(backupstring, answer);
858 return 1;
859 } else
860 #endif
861 return -1;
864 return 0;
867 /* Look for needle. If no_sameline is TRUE, skip over selected when
868 * looking for needle. begin is the location of the filename where we
869 * first started searching. The return value specifies whether we found
870 * anything. */
871 bool findnextfile(bool no_sameline, size_t begin, const char *needle)
873 size_t currselected = selected;
874 /* The location in the current file list of the match we
875 * find. */
876 const char *filetail = tail(filelist[currselected]);
877 /* The filename we display, minus the path. */
878 const char *rev_start = filetail, *found = NULL;
880 #ifndef NANO_TINY
881 if (ISSET(BACKWARDS_SEARCH))
882 rev_start += strlen(rev_start);
883 #endif
885 /* Look for needle in the current filename we're searching. */
886 while (TRUE) {
887 found = strstrwrapper(filetail, needle, rev_start);
889 /* We've found a potential match. If we're not allowed to find
890 * a match on the same filename we started on and this potential
891 * match is on that line, continue searching. */
892 if (found != NULL && (!no_sameline || currselected != begin))
893 break;
895 /* We've finished processing the filenames, so get out. */
896 if (search_last_file) {
897 not_found_msg(needle);
898 return FALSE;
901 /* Move to the previous or next filename in the list. If we've
902 * reached the start or end of the list, wrap around. */
903 #ifndef NANO_TINY
904 if (ISSET(BACKWARDS_SEARCH)) {
905 if (currselected > 0)
906 currselected--;
907 else {
908 currselected = filelist_len - 1;
909 statusbar(_("Search Wrapped"));
911 } else {
912 #endif
913 if (currselected < filelist_len - 1)
914 currselected++;
915 else {
916 currselected = 0;
917 statusbar(_("Search Wrapped"));
919 #ifndef NANO_TINY
921 #endif
923 /* We've reached the original starting file. */
924 if (currselected == begin)
925 search_last_file = TRUE;
927 filetail = tail(filelist[currselected]);
929 rev_start = filetail;
930 #ifndef NANO_TINY
931 if (ISSET(BACKWARDS_SEARCH))
932 rev_start += strlen(rev_start);
933 #endif
936 /* We've definitely found something. */
937 selected = currselected;
939 return TRUE;
942 /* Clear the flag indicating that a search reached the last file in the
943 * list. We need to do this just before a new search. */
944 void findnextfile_wrap_reset(void)
946 search_last_file = FALSE;
949 /* Abort the current filename search. Clean up by setting the current
950 * shortcut list to the browser shortcut list, displaying it, and
951 * decompiling the compiled regular expression we used in the last
952 * search, if any. */
953 void filesearch_abort(void)
955 currmenu = MBROWSER;
956 bottombars(MBROWSER);
957 #ifdef HAVE_REGEX_H
958 regexp_cleanup();
959 #endif
962 /* Search for a filename. */
963 void do_filesearch(void)
965 size_t begin = selected;
966 int i;
967 bool didfind;
969 i = filesearch_init();
970 if (i == -1) /* Cancel, blank search string, or regcomp()
971 * failed. */
972 filesearch_abort();
973 #if !defined(NANO_TINY) || defined(HAVE_REGEX_H)
974 else if (i == 1) /* Case Sensitive, Backwards, or Regexp search
975 * toggle. */
976 do_filesearch();
977 #endif
979 if (i != 0)
980 return;
982 /* If answer is now "", copy last_search into answer. */
983 if (*answer == '\0')
984 answer = mallocstrcpy(answer, last_search);
985 else
986 last_search = mallocstrcpy(last_search, answer);
988 #ifndef NANO_TINY
989 /* If answer is not "", add this search string to the search history
990 * list. */
991 if (answer[0] != '\0')
992 update_history(&search_history, answer);
993 #endif
995 findnextfile_wrap_reset();
996 didfind = findnextfile(FALSE, begin, answer);
998 /* Check to see if there's only one occurrence of the string and
999 * we're on it now. */
1000 if (selected == begin && didfind) {
1001 /* Do the search again, skipping over the current line. We
1002 * should only end up back at the same position if the string
1003 * isn't found again, in which case it's the only occurrence. */
1004 didfind = findnextfile(TRUE, begin, answer);
1005 if (selected == begin && !didfind)
1006 statusbar(_("This is the only occurrence"));
1009 filesearch_abort();
1012 /* Search for the last filename without prompting. */
1013 void do_fileresearch(void)
1015 size_t begin = selected;
1016 bool didfind;
1018 search_init_globals();
1020 if (last_search[0] != '\0') {
1021 #ifdef HAVE_REGEX_H
1022 /* Since answer is "", use last_search! */
1023 if (ISSET(USE_REGEXP) && !regexp_init(last_search))
1024 return;
1025 #endif
1027 findnextfile_wrap_reset();
1028 didfind = findnextfile(FALSE, begin, answer);
1030 /* Check to see if there's only one occurrence of the string and
1031 * we're on it now. */
1032 if (selected == begin && didfind) {
1033 /* Do the search again, skipping over the current line. We
1034 * should only end up back at the same position if the
1035 * string isn't found again, in which case it's the only
1036 * occurrence. */
1037 didfind = findnextfile(TRUE, begin, answer);
1038 if (selected == begin && !didfind)
1039 statusbar(_("This is the only occurrence"));
1041 } else
1042 statusbar(_("No current search pattern"));
1044 filesearch_abort();
1047 /* Select the first file in the list. */
1048 void do_first_file(void)
1050 selected = 0;
1053 /* Select the last file in the list. */
1054 void do_last_file(void)
1056 selected = filelist_len - 1;
1059 /* Strip one directory from the end of path, and return the stripped
1060 * path. The returned string is dynamically allocated, and should be
1061 * freed. */
1062 char *striponedir(const char *path)
1064 char *retval, *tmp;
1066 assert(path != NULL);
1068 retval = mallocstrcpy(NULL, path);
1070 tmp = strrchr(retval, '/');
1072 if (tmp != NULL)
1073 null_at(&retval, tmp - retval);
1075 return retval;
1078 #endif /* !DISABLE_BROWSER */