]> mj.ucw.cz Git - vs.git/commitdiff
Split view, searching, hiding of chords, help screen, lots of minor
authorMartin Mares <mj@ucw.cz>
Tue, 25 May 2004 10:02:30 +0000 (10:02 +0000)
committerMartin Mares <mj@ucw.cz>
Tue, 25 May 2004 10:02:30 +0000 (10:02 +0000)
improvements.

TODO [new file with mode: 0644]
vs.pl

diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..d425edf
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+- search for song text (grep)
+- sorting of file list (use locale;)
+- auto-transpose :)
+- chord hints window
diff --git a/vs.pl b/vs.pl
index 6fd0db632c448d143c1f913b967093d4e75ac2d3..34502e8c2d9a39d7a54792571c0d76480e354fac 100755 (executable)
--- a/vs.pl
+++ b/vs.pl
@@ -1,11 +1,51 @@
 #!/usr/bin/perl
-# The Virtual Songbook 0.0
-# (c) 2003 Martin Mares <mj@ucw.cz>
+# The Virtual Songbook
+# (c) 2003--2004 Martin Mares <mj@ucw.cz>
 
 use Curses;
 use strict;
 use warnings;
 
+### Help ###
+
+my @help_message = (
+       "",
+       "   The Virtual Songbook 0.9\n",
+       "   (c) 2003--2004 Martin Mares <mj\@ucw.cz>",
+       "",
+       "Control keys:",
+       "",
+       "   ?            display this help text",
+       "   q            quit the program",
+       "   <arrows>     scroll text in the current window",
+       "   [ ]          enlarge/shrink the current window",
+       "   f            display the file list window, focus it if already displayed",
+       "   s            display the split view window, focus it if already displayed",
+       "   m            focus the main window",
+       "   F S          hide the file list / split view window",
+       "   Ctrl-L       redraw the screen",
+       "",
+       "Main window keys:",
+       "",
+       "   + - 0        transpose chords up/down/reset",
+       "   =            a synonym for +",
+       "   c            toggle display of chords",
+       "",
+       "File list keys:",
+       "",
+       "   <up> <down>  select next/previous item",
+       "   <left>       go one directory up",
+       "   <enter>      go to the selected directory",
+       "   <right>      like <enter>",
+       "   j k h l      synonyms of the arrow keys (like in vi) [works in any window]",
+       "   Ctrl-R       reload the list",
+       "   r            toggle resolving of song/directory names",
+       "   /            incremental search (/=next, \\=previous)",
+       "",
+);
+
+my $help_status = "The Virtual Songbook 0.9";
+
 ### Interface with Curses ###
 
 my $W;
@@ -25,7 +65,7 @@ sub cleanup_terminal() {
        endwin;
 }
 
-my ($attr_normal, $attr_status, $attr_hilite, $attr_chord);
+my ($attr_normal, $attr_status, $attr_hilite, $attr_chord, $attr_search);
 
 sub setup_attrs() {
        if ($color_mode) {
@@ -33,9 +73,12 @@ sub setup_attrs() {
                $attr_status = COLOR_PAIR(1) | A_BOLD;
                init_pair(2, COLOR_YELLOW, COLOR_BLACK);
                $attr_chord = COLOR_PAIR(2);
+               init_pair(3, COLOR_YELLOW, COLOR_GREEN);
+               $attr_search = COLOR_PAIR(3) | A_BOLD;
        } else {
                $attr_status = A_BOLD;
                $attr_chord = A_BOLD;
+               $attr_search = A_BOLD;
        }
        $attr_normal = A_NORMAL;
        $attr_hilite = A_BOLD;
@@ -44,25 +87,17 @@ sub setup_attrs() {
 my $try_full_names = 1;
 my $auto_enter = 1;
 my $file_window_width = 20;
+my $split_window_height = 10;
 
 my ($term_w, $term_h);
 my @window_list = ();
-my $status_window = new VS::Window::Status;
-my $main_window = new VS::Window::Main;
 my $file_window = new VS::Window::File;
+my $main_window = new VS::Window::Main;
+my $split_window = new VS::Window::Main;
+my $status_window = new VS::Window::Status;
+$split_window->{"visible"} = 0;
 $file_window->reload;
-my $focused_window_i = 2;
-my $focused_window = $file_window;
-
-sub focus_next() {
-       $focused_window->{"focused"} = 0;
-       do {
-               $focused_window_i++;
-               if ($focused_window_i > $#window_list) { $focused_window_i=0; }
-               $focused_window = $window_list[$focused_window_i];
-       } while (!$focused_window->{"focusable"} || !$focused_window->{"visible"});
-       $focused_window->{"focused"} = 1;
-}
+my $focused_window;
 
 sub recalc_windows() {
        my $w = COLS;
@@ -72,55 +107,67 @@ sub recalc_windows() {
        $status_window->place(0, 0, 1, $w);
        if ($file_window->{"visible"}) {
                my $fww = $file_window_width;
-               $main_window->place(1, 0, $h-1, $w-$fww-1);
-               $W->attrset($focused_window == $file_window ? $attr_hilite : $attr_normal);
-               $W->vline(2, $w-$fww-1, ACS_VLINE, $h-3);
-               $W->hline(1, $w-$fww, ACS_HLINE, $fww);
-               $W->hline($h-1, $w-$fww, ACS_HLINE, $fww);
-               $W->addch(1, $w-$fww-1, ACS_ULCORNER);
-               $W->addch($h-1, $w-$fww-1, ACS_LLCORNER);
+               $w -= $fww;
+               $W->attrset($file_window->{"focused"} ? $attr_hilite : $attr_normal);
+               $W->vline(2, $w-1, ACS_VLINE, $h-3);
+               $W->hline(1, $w, ACS_HLINE, $fww);
+               $W->hline($h-1, $w, ACS_HLINE, $fww);
+               $W->addch(1, $w-1, ACS_ULCORNER);
+               $W->addch($h-1, $w-1, ACS_LLCORNER);
                $W->attrset($attr_normal);
-               $file_window->place(2, $w-$fww, $h-3, $fww);
-       } else {
-               $main_window->place(1, 0, $h-1, $w);
-               $file_window->place(0, 0, 0, 0);
-       }
+               $file_window->place(2, $w, $h-3, $fww);
+               $w--;
+       } else { $file_window->place(0, 0, 0, 0); }
+       if ($split_window->{"visible"}) {
+               my $swh = $split_window_height;
+               $h -= $swh;
+               $W->attrset($split_window->{"focused"} ? $attr_hilite : $attr_normal);
+               $W->hline($h-1, 0, ($split_window->{"focused"} ? "=" : ACS_HLINE), $w);
+               $W->attrset($attr_normal);
+               $split_window->place($h, 0, $swh, $w);
+               $h--;
+       } else { $split_window->place(0, 0, 0, 0); }
+       $main_window->place(1, 0, $h-1, $w);
 }
 
-sub toggle_window($) {
-       my $win = shift;
-       if ($win->{"visible"} = !$win->{"visible"}) {
-               while ($focused_window != $win) { focus_next; }
-       } else {
-               if ($focused_window == $win) { focus_next; }
-       }
-       recalc_windows;
+sub focus_next() {
+       my @wins = ( $main_window, $split_window, $file_window );       # [0] is always focusable
+       my $i = 0;
+       while ($i <= $#wins && $wins[$i] != $focused_window) { $i++; }
+       $i++;
+       while ($i <= $#wins && !$wins[$i]->{"visible"}) { $i++; }
+       if ($i > $#wins) { $i=0; }
+       $wins[$i]->focus;
 }
 
 init_terminal;
 setup_attrs;
-recalc_windows;
+$file_window->focus;   # calls recalc_windows
+$main_window->view_help;
 
 for(;;) {
-       $W->move($term_h-1, $term_w-1);
+       $focused_window->show_cursor;
        $W->refresh;
        my $key = $W->getch;
-       if ($key eq "\033" || $key eq "q") {
+       if ($focused_window->key($key)) {
+               # key handled by the window
+       } elsif ($key eq "\033" || $key eq "q") {
                cleanup_terminal;
                exit 0;
        } elsif ($key eq "f") {
-               toggle_window($file_window);
-       } elsif ($key eq "\t") {
-               focus_next;
-               recalc_windows;
+               $file_window->toggle(1);
+       } elsif ($key eq "F") {
+               $file_window->toggle(0);
+       } elsif ($key eq "s") {
+               $split_window->toggle(1);
+       } elsif ($key eq "S") {
+               $split_window->toggle(0);
+       } elsif ($key eq "m") {
+               $main_window->toggle(1);
+       } elsif ($key eq "?") {
+               $main_window->view_help;
        } elsif ($key eq "\014") {
                $curscr->clearok(1);
-       } elsif ($key eq "<" && $file_window_width < $term_w-1) {
-               $file_window_width++;
-               recalc_windows;
-       } elsif ($key eq ">" && $file_window_width > 1) {
-               $file_window_width--;
-               recalc_windows;
        } elsif ($key eq "j") {
                $file_window->key(KEY_DOWN);
        } elsif ($key eq "k") {
@@ -129,8 +176,8 @@ for(;;) {
                $file_window->key(KEY_LEFT);
        } elsif ($key eq "l") {
                $file_window->key(KEY_RIGHT);
-       } else {
-               $focused_window->key($key);
+       } elsif ($key eq "\t") {
+               focus_next;
        }
 }
 
@@ -161,10 +208,10 @@ BEGIN {
        "G#" => 8,
                "Ab" => 8,
        "A" => 9,
-               "Bb" => 9,
        "A#" => 10,
                "Hb" => 10,
                "B" => 10,
+               "Bb" => 10,     # common error
        "H" => 11,
                "B#" => 11,
                "Cb" => 11,
@@ -216,7 +263,6 @@ package VS::Window;
 sub new($) {
        my $w = {
                "visible" => 1,
-               "focusable" => 1,
                "focused" => 0,
                "x" => -1,
                "y" => -1,
@@ -244,34 +290,107 @@ sub place($$$$$) {
        }
 }
 
-sub key($) { }
+sub focus($) {
+       my $w = shift;
+       $focused_window->{"focused"} = 0 if defined $focused_window;
+       if (!$w->{"visible"}) { $w=$main_window; }      # Main is always visible
+       $focused_window = $w;
+       $w->{"focused"} = 1;
+       ::recalc_windows;
+}
+
+sub toggle($;$) {
+       my $w = shift;
+       my $vis = shift;
+       if ($vis) {
+               if (!$w->{"visible"} || !$w->{"focused"}) {
+                       $w->{"visible"} = 1;
+                       $w->focus;
+               }
+       } else {
+               if ($w->{"visible"}) {
+                       $w->{"visible"} = 0;
+                       $main_window->focus;
+               }
+       }
+}
+
+sub key($) { return 0; }
 sub redraw($) { }
 
+sub reset_cursor($) {
+       $W->move($term_h-1, $term_w-1);
+}
+
+sub set_cursor($$$) {
+       my ($w,$x,$y) = @_;
+       if ($x < 0) {
+               $x = 0;
+       } elsif ($x >= $w->{"h"}) {
+               $x = $w->{"h"} - 1;
+       }
+       if ($y < 0) {
+               $y = 0;
+       } elsif ($y >= $w->{"w"}) {
+               $y = $w->{"w"} - 1;
+       }
+       $W->move($w->{"x"} + $x, $w->{"y"} + $y);
+}
+
+sub show_cursor($) {
+       my ($w) = @_;
+       $w->reset_cursor;
+}
+
 package VS::Window::Main;
 use Curses;
 BEGIN { our @ISA = qw(VS::Window); }
 
 sub new($) {
        my $w = new VS::Window;
+       bless $w;
+       $w->empty;
+       return $w;
+}
+
+sub empty($) {
+       my $w = shift;
        $w->{"file"} = "";
        $w->{"attrs"} = {};
-       $w->{"lines"} = ["", "", "   The Virtual Songbook 0.0\n", "   (c) 2003 Martin Mares <mj\@ucw.cz>"];
-       $w->{"chords"} = [0,0,0,0];
-       $w->{"n"} = 4;
+       $w->{"lines"} = [];
+       $w->{"text_lines"} = [];
+       $w->{"chords"} = undef;
+       $w->{"visible_lines"} = [];
+       $w->{"n"} = 0;
        $w->{"top"} = 0;
-       $w->{"chords_analysed"} = 0;
        $w->{"current_xpose"} = 0;
        $w->{"requested_xpose"} = 0;
-       return bless $w;
+       $w->{"chords_visible"} = 1;
+}
+
+sub update_text($) {
+       my $w = shift;
+       my $l = $w->{($w->{"chords_visible"} ? "lines" : "text_lines")};
+       $w->{"visible_lines"} = $l;
+       $w->{"n"} = @$l;
+}
+
+sub view_help($) {
+       my $w = shift;
+       $w->empty;
+       $w->{"text_lines"} = $w->{"lines"} = \@help_message;
+       $w->update_text;
+       $status_window->tell($help_status);
+       $w->update_other;
+       $w->sync;
 }
 
 sub view($$$) {
        my ($w,$f,$x) = @_;
        if ($w->{"file"} ne $f) {
+               $w->empty;
                $w->{"file"} = $f;
                $w->{"xfile"} = $x;
-               $w->{"current_xpose"} = 0;
-               $w->{"chords_analysed"} = 0;
                $f =~ s@^./@@;
                $x =~ s@^./@@;
                if (open X, $f) {
@@ -284,20 +403,20 @@ sub view($$$) {
                                }
                        }
                        my @lines = ();
-                       my @chords = ();
+                       my @text_lines = ();
                        while (<X>) {
                                chomp;
-                               if (s/^! //) { push @chords, 1; } else { push @chords, 0; }
                                push @lines, $_;
+                               push @text_lines, $_ unless /^!/;
                        }
                        close X;
                        $w->{"attrs"} = \%attrs;
                        $w->{"lines"} = \@lines;
-                       $w->{"chords"} = \@chords;
-                       $w->{"chordtable"} = [];
+                       $w->{"text_lines"} = \@text_lines;
                        $w->{"top"} = 0;
-                       $w->{"n"} = scalar @lines;
-                       $w->redraw;
+                       $w->update_text;
+                       $w->update_other;
+                       $w->sync;
                        if (defined $attrs{"Name"}) {
                                $x = $attrs{"Name"};
                                $x = $attrs{"Author"} . ": $x" if defined $attrs{"Author"};
@@ -311,17 +430,15 @@ sub view($$$) {
 
 sub transpose($) {
        my $w = shift @_;
-       if (!$w->{"chords_analysed"}) {
-               for (my $i=0; $i<$w->{"n"}; $i++) {
-                       if ($w->{"chords"}->[$i]) {
-                               $w->{"chordtable"}->[$i] = VS::Chord::parse_line($w->{"lines"}->[$i]);
-                       }
+       if (!defined $w->{"chords"}) {
+               $w->{"chords"} = [];
+               for (my $i=0; $i<@{$w->{"lines"}}; $i++) {
+                       push @{$w->{"chords"}}, ($w->{"lines"}->[$i] =~/^!(.*)/ ? VS::Chord::parse_line($1) : undef);
                }
-               $w->{"chords_analysed"} = 1;
        }
-       for (my $i=0; $i<$w->{"n"}; $i++) {
-               if ($w->{"chords"}->[$i]) {
-                       $w->{"lines"}->[$i] = VS::Chord::synthesize_line($w->{"chordtable"}->[$i], $w->{"requested_xpose"});
+       for (my $i=0; $i<@{$w->{"lines"}}; $i++) {
+               if (defined $w->{"chords"}->[$i]) {
+                       $w->{"lines"}->[$i] = "!" . VS::Chord::synthesize_line($w->{"chords"}->[$i], $w->{"requested_xpose"});
                }
        }
        $w->{"current_xpose"} = $w->{"requested_xpose"};
@@ -330,8 +447,8 @@ sub transpose($) {
 sub redraw_line($$) {
        my ($w,$i) = @_;
        my $win = $w->{"win"};
-       my $l = $w->{"lines"}->[$i];
-       if ($w->{"chords"}->[$i]) { $win->attrset($attr_chord); }
+       my $l = $w->{"visible_lines"}->[$i] || "";
+       if ($l =~ s/^!//) { $win->attrset($attr_chord); }
        if (length $l < $w->{"w"}) {
                $win->addstr($i-$w->{"top"}, 0, $l);
                $win->clrtoeol;
@@ -343,7 +460,6 @@ sub redraw_line($$) {
 
 sub redraw($) {
        my $w = shift @_;
-       $w->transpose if $w->{"current_xpose"} != $w->{"requested_xpose"};
        my $win = $w->{"win"};
        my $top = $w->{"top"};
        my $cnt = $w->{"n"} - $w->{"top"};
@@ -356,6 +472,31 @@ sub redraw($) {
        $win->noutrefresh;
 }
 
+sub other_split($) {
+       my $w = shift @_;
+       return ($w == $main_window) ? $split_window : $main_window;
+}
+
+sub update_other($) {
+       my $w = shift @_;
+       my $other = $w->other_split;
+       foreach $a ("attrs", "lines", "text_lines", "chords", "n", "top", "current_xpose", "requested_xpose") {
+               $other->{$a} = $w->{$a} if defined $w->{$a};
+       }
+       $other->update_text;
+}
+
+sub sync($) {
+       my $w = shift @_;
+       my $other = $w->other_split;
+       if ($w->{"current_xpose"} != $w->{"requested_xpose"}) {
+               $w->transpose;
+               $w->update_other;
+       }
+       $w->redraw;
+       $other->redraw if $other->{"visible"};
+}
+
 sub go($$) {
        my ($w,$delta) = @_;
        my $win = $w->{"win"};
@@ -394,16 +535,28 @@ sub key($$) {
        elsif ($key eq KEY_NPAGE) { $w->go($w->{"h"}-1); }
        elsif ($key eq KEY_HOME) { $w->go(-1000000000); }
        elsif ($key eq KEY_END) { $w->go(1000000000); }
-       elsif ($key eq "+" || $key eq "=") { $w->{"requested_xpose"} = ($w->{"requested_xpose"}+1)%12; $status_window->redraw; $w->redraw; }
-       elsif ($key eq "-") { $w->{"requested_xpose"} = ($w->{"requested_xpose"}+11)%12; $status_window->redraw; $w->redraw; }
-       elsif ($key eq "0") { $w->{"requested_xpose"} = 0; $status_window->redraw; $w->redraw; }
-       else { $status_window->tell("Unknown key <$key>"); }
+       elsif ($key eq "+" || $key eq "=") { $w->{"requested_xpose"} = ($w->{"requested_xpose"}+1)%12; $w->sync; $status_window->redraw; }
+       elsif ($key eq "-") { $w->{"requested_xpose"} = ($w->{"requested_xpose"}+11)%12; $w->sync; $status_window->redraw; }
+       elsif ($key eq "0") { $w->{"requested_xpose"} = 0; $w->sync; $status_window->redraw; }
+       elsif ($key eq "c") { $w->{"chords_visible"} = !$w->{"chords_visible"}; $w->update_text; $w->redraw; }
+       elsif ($w == $split_window) {
+               if ($key eq "]" && $split_window_height > 1) {
+                       $split_window_height--;
+                       ::recalc_windows;
+               } elsif ($key eq "[" && $split_window_height < $term_h-2) {
+                       $split_window_height++;
+                       ::recalc_windows;
+               } else { return 0; }
+       } else { return 0; }
+       return 1;
 }
 
 package VS::Window::File;
 use Curses;
 BEGIN { our @ISA = qw(VS::Window); }
 
+our %name_cache = ();
+
 sub new($) {
        my $w = new VS::Window;
        $w->{"dir"} = "./";
@@ -411,6 +564,43 @@ sub new($) {
        return bless $w;
 }
 
+sub lookup_full_name($) {
+       my $f = shift;
+       return $name_cache{$f} if exists $name_cache{$f};
+       return undef if (!$try_full_names || ($f =~ /\/$/ && $try_full_names < 2));
+       my $full;
+       if ($f =~ /\/$/) {
+               my @sub = `cd $f && ls`;
+               while (!defined($full) && @sub) {
+                       my $z = shift @sub;
+                       chomp $z;
+                       if (-f "$f/$z" && open(X, "$f/$z")) {
+                               while (<X>) {
+                                       chomp;
+                                       /^$/ && last;
+                                       if (/^Author:\s*(.*)/) {
+                                               $full = $1;
+                                               last;
+                                       }
+                               }
+                               close X;
+                       }
+               }
+       } elsif (open(X, $f)) {
+               while (<X>) {
+                       chomp;
+                       /^$/ && last;
+                       if (/^Name:\s*(.*)/) {
+                               $full = $1;
+                               last;
+                       }
+               }
+               close X;
+       }
+       $name_cache{$f} = $full;
+       return $full;
+}
+
 sub reload($) {
        my $w = shift;
        my $p = $w->{"dir"};
@@ -422,26 +612,18 @@ sub reload($) {
                chomp $x;
                if (-f "$p/$x") {
                        push @fn, $x;
-                       my $fullname = $x;
-                       if ($try_full_names && open(X, "$p/$x")) {
-                               while (<X>) {
-                                       chomp;
-                                       /^$/ && last;
-                                       if (/^Name:\s*(.*)/) {
-                                               $fullname = $1;
-                                               last;
-                                       }
-                               }
-                               close X;
-                       }
-                       push @full, $fullname;
-               } elsif (-d "$p/$x") { push @fn, "$x/"; push @full, "$x/"; }
+                       push @full, (lookup_full_name("$p/$x") || $x);
+               } elsif (-d "$p/$x") {
+                       push @fn, "$x/";
+                       push @full, (lookup_full_name("$p/$x/") || $x) . "/";
+               }
        }
        $w->{"flist"} = \@fn;
        $w->{"list"} = \@full;
        $w->{"n"} = scalar @fn;
        $w->{"i"} = 0;
        $w->{"1st"} = 0;
+       $w->{"search"} = undef;
 }
 
 sub redraw_line($$) {
@@ -449,8 +631,14 @@ sub redraw_line($$) {
        my $line = $i - $w->{"1st"};
        if ($line < 0 || $line >= $w->{"h"}) { return; }
        my $win = $w->{"win"};
-       my $item = ($i < $w->{"n"}) ? substr($w->{"list"}->[$i], 0, $w->{"w"}) : "";
-       if ($i == $w->{"i"}) { $win->bkgdset($attr_status); }
+       my $item = ($i < $w->{"n"}) ? $w->{"list"}->[$i] : "";
+       if ($i == $w->{"i"}) {
+               if (defined $w->{"search"}) {
+                       $win->bkgdset($attr_search);
+                       substr($item, 0, length $w->{"search"}) = $w->{"search"};
+               } else { $win->bkgdset($attr_status); }
+       }
+       $item = substr($item, 0, $w->{"w"});
        $win->addstr($line, 0, $item);
        $win->clrtoeol if length $item < $w->{"w"};
        $win->bkgdset($attr_normal);
@@ -469,11 +657,9 @@ sub redraw($) {
        $win->noutrefresh;
 }
 
-sub go($$) {
-       my ($w,$delta) = @_;
-       my $i = $w->{"i"};
-       my $oldi = $i;
-       $i += $delta;
+sub goto($$) {
+       my ($w,$i) = @_;
+       my $oldi = $w->{"i"};
        if ($i < 0) { $i = 0; }
        if ($i >= $w->{"n"}) { $i = $w->{"n"}-1; }
        $w->{"i"} = $i;
@@ -503,6 +689,30 @@ sub go($$) {
        if ($auto_enter && $i < $w->{"n"} && $w->{"flist"}->[$i] !~ /\/$/) { $w->select; }
 }
 
+sub go($$) {
+       my ($w,$delta) = @_;
+       $w->{"search"} = undef;
+       $w->goto($w->{"i"} + $delta);
+}
+
+sub search_next($$) {
+       my ($w,$i,$skip) = @_;
+       $i = ($i + $w->{"n"}) % $w->{"n"};
+       my $start = $i;
+       my $p = lc $w->{"search"};
+       do {
+               my $s = lc substr($w->{"list"}->[$i], 0, length $p);
+               if ($s eq $p) {
+                       $w->goto($i);
+                       return;
+               }
+               $i += $skip;
+               if ($i < 0) { $i = $w->{"n"}-1; }
+               elsif ($i >= $w->{"n"}) { $i=0; }
+       } while ($i != $start);
+       $w->goto($i);
+}
+
 sub select($) {
        my ($w) = @_;
        if ($w->{"i"} < $w->{"n"}) {
@@ -541,13 +751,60 @@ sub key($$) {
        elsif ($key eq KEY_NPAGE) { $w->go($w->{"h"}-1); }
        elsif ($key eq KEY_HOME) { $w->go(-1000000000); }
        elsif ($key eq KEY_END) { $w->go(1000000000); }
-       elsif ($key eq "\n" || $key eq "\r" || $key eq KEY_RIGHT) { $w->select; }
+       elsif ($key eq KEY_RIGHT || $key eq "\r" || $key eq "\n") { $w->{"search"}=undef; $w->goto($w->{"i"}); $w->select; }
        elsif ($key eq KEY_LEFT) {
                if ($w->{"list"}->[0] eq "<parent>") {
                        $w->{"i"} = 0;
                        $w->select;
                }
+       } elsif ($key eq "\x12") {
+               $w->reload;
+               $w->redraw;
+       } elsif ($key eq "[" && $file_window_width < $term_w-1) {
+               $file_window_width++;
+               ::recalc_windows;
+       } elsif ($key eq "]" && $file_window_width > 1) {
+               $file_window_width--;
+               ::recalc_windows;
+       } elsif (defined $w->{"search"}) {
+               if ($key eq "\033") {
+                       $w->go(0);
+               } elsif ($key eq "/") {
+                       $w->search_next($w->{"i"}+1, 1);
+               } elsif ($key eq "\\") {
+                       $w->search_next($w->{"i"}-1, -1);
+               } elsif ($key eq "?") {
+                       return 0;
+               } elsif ($key eq KEY_BACKSPACE) {
+                       if (length $w->{"search"}) {
+                               $w->{"search"} =~ s/.$//;
+                               $w->goto($w->{"i"});
+                       } else { $w->go(0); }
+               } elsif (($key ge ' ' && $key lt "\x7f" || $key ge "\xa0" && $key le "\xff") && length $key == 1) {
+                       $w->{"search"} .= $key;
+                       $w->search_next($w->{"i"}, 1);
+               } else { return 0; }
+       } else {
+               if ($key eq "r") {
+                       $try_full_names = ($try_full_names+1) % 3;
+                       %name_cache = ();
+                       $w->reload;
+                       $w->redraw;
+               } elsif ($key eq "/") {
+                       $w->{"search"} = "";
+                       $w->goto($w->{"i"});
+               } elsif ($key eq "\n" || $key eq "\r") {
+                       $w->select;
+               } else { return 0; }
        }
+       return 1;
+}
+
+sub show_cursor($) {
+       my ($w) = @_;
+       if (defined $w->{"search"}) {
+               $w->set_cursor($w->{"i"} - $w->{"1st"}, length $w->{"search"});
+       } else { $w->reset_cursor; }
 }
 
 package VS::Window::Status;
@@ -555,7 +812,6 @@ BEGIN { our @ISA = qw(VS::Window); }
 
 sub new($) {
        my $w = new VS::Window;
-       $w->{"focusable"} = 0;
        $w->{"msg"} = "";
        return bless $w;
 }