package Term::ReadLine::Zoid::FileBrowse; use strict; use base 'Term::ReadLine::Zoid'; our $VERSION = 0.01; our %_keymap = ( # maybe inherit from insert ? these could be remapped up => 'select_previous', down => 'select_next', right => 'select_next_col', left => 'select_previous_col', page_up => 'page_up', page_down => 'page_down', return => 'accept_line', ctrl_C => 'return_empty_string', escape => 'switch_mode', '/' => 'fb_mini_buffer', ' ' => 'toggle_mark', '.' => 'toggle_hide_hidden', _default => 'self_insert', _on_switch => 'fb_switch', ); sub keymap { my $self = shift; for ( ['hide_hidden_files', 1], ['fb_prompt', "\e[1;37m -- \%s -- \e[0m"], ) { $$self{config}{$$_[0]} = $$_[1] unless defined $$self{config}{$$_[0]} } return \%_keymap; } # fb_item == item currently selected (pointed at) # fb_marks == marked _numbers_ in current dir # fb_marked == marked _items_ in other dirs # fb_items == items in current dir # fb_dir == current dir sub fb_switch { my $self = shift; @$self{qw/fb_item fb_marks fb_marked fb_items/} = (0, [], [], []); $self->fb_switch_dir('.'); } sub fb_switch_dir { # FIXME FIXME "marked" admisnistrationis contains bugs my ($self, $dir) = @_; $dir ||= $$self{fb_dir}; my $pwd; if ($dir eq '.') { $dir = $ENV{PWD}; $pwd++; } else { $dir =~ s#^\./#$ENV{PWD}#; $dir =~ s#/\.(/|$)|//+#/#g; $dir =~ s#(^|/)[^/]*/\.\.(/|$)##g; $dir =~ s#/?$#/#; } opendir DIR, $dir or return $self->bell; my (@marks, @marked); for ($self->marked()) { if (m#(.*/)(.*)#) { if ($1 eq $dir) { push @marks, $2 } else { push @marked, $_ } } elsif ($pwd) { push @marks, $_ } else { push @marked, $_ } } $$self{fb_items} = [ '../', map {-d "$dir/$_" ? $_.'/' : $_} $$self{config}{hide_hidden_files} ? (sort grep {$_ !~ /^\./ } readdir DIR) : (sort grep {$_ !~ /^\.\.?$/} readdir DIR) ]; close DIR; $$self{fb_marked} = \@marked; $$self{fb_marks} = []; if (@marks) { my $i = 0; for my $item (@{$$self{fb_items}}) { push @{$$self{fb_marks}}, $i if grep {$_ eq $item} @marks; $i++; } } @$self{qw/fb_item fb_dir/} = (0, $dir); } sub draw { # Render Fu my $self = shift; my @pos = (1, 1); my @lines = map " $_", @{$$self{fb_items}}; for (@{$$self{fb_marks}}) { $lines[$_] =~ s/^ /*/ } $lines[ $$self{fb_item} ] =~ s/^(.) /$1>/; @lines = $self->col_format( @lines ); $$self{fb_rows} = scalar @lines; $pos[1] += ($$self{fb_item} % $$self{fb_rows}); # assuming +1 offset due to fb_prompt unshift @lines, sprintf $$self{config}{fb_prompt}, $$self{fb_dir}; $self->print(\@lines, \@pos); } sub toggle_hide_hidden { $_[0]{config}{hide_hidden_files} = $_[0]{config}{hide_hidden_files} ? 0 : 1 ; $_[0]->fb_switch_dir(); } sub self_insert { my ($self, $key) = @_; return $self->bell unless $key =~ /^\d+$/; #$$self{fb_item} .= $key; } sub accept_line { my $self = shift; my $dir = $$self{fb_dir}.'/'.$$self{fb_items}[ $$self{fb_item} ]; return $self->fb_switch_dir($dir) if -d $dir; push @{$$self{fb_marks}}, $$self{fb_item}; my @words = map {s/'/\\'/g; '\''.$_.'\''} $self->marked(); $self->substring(join(' ', @words), $$self{pos}); $self->switch_mode(); } sub fb_mini_buffer { my $self = shift; } sub select_next { $_[0]{fb_item}++ if $_[0]{fb_item} < $#{$_[0]{fb_items}} } sub page_up { my $self = shift; my (undef, $higth) = $self->TermSize(); my $vpos = $$self{fb_item} % $$self{fb_rows}; if ($vpos > $higth) { $$self{fb_item} -= $higth } else { $$self{fb_item} -= $vpos } } sub page_down { my $self = shift; my (undef, $higth) = $self->TermSize(); my $rvpos = $$self{fb_rows} - ($$self{fb_item} % $$self{fb_rows}); if ($rvpos > $higth) { $$self{fb_item} += $higth } else { $$self{fb_item} += $rvpos } $$self{fb_item} = $#{$$self{fb_items}} if $$self{fb_item} > $#{$$self{fb_items}}; } sub select_previous { $_[0]{fb_item}-- if $_[0]{fb_item} > 0 } sub select_next_col { $_[0]{fb_item} += $_[0]{fb_rows} unless $_[0]{fb_rows} > $#{$_[0]{fb_items}} - $_[0]{fb_item}; } sub select_previous_col { $_[0]{fb_item} -= $_[0]{fb_rows} unless $_[0]{fb_rows} > $_[0]{fb_item}; } sub toggle_mark { # FIXME should be toggle my $self = shift; my $l = scalar @{$$self{fb_marks}}; @{$$self{fb_marks}} = grep {$_ != $$self{fb_item}} @{$$self{fb_marks}}; push @{$$self{fb_marks}}, $$self{fb_item} if $l == scalar @{$$self{fb_marks}}; $self->select_next(); } sub marked { my $self = shift; my $dir = $$self{fb_dir}; $dir =~ s#^\Q$ENV{PWD}\E/?##; $dir =~ s#/?$#/# if length $dir; return @{$$self{fb_marked}}, map $dir.$_, @{$$self{fb_items}}[ @{$$self{fb_marks}} ]; } 1; __END__ =head1 NAME Term::ReadLine::Zoid::FileBrowse - a readline file browser mode =head1 SYNOPSIS This class is used as a mode under L, see there for usage details. =head1 DESCRIPTION This module provides a "file browse" mode that lets you interactively select files and navigate your file-system. =head1 KEY MAPPING =over 4 =item up (I) =item down (I) =item right (I) =item left (I) =item page_up (I) =item page_down (I) =item return (I) =item ^C (I) =item escape (I) =item / (I) =item space (I) =item . (I) =back =head1 TODO make fb_prompt config compatible with PS1 stuff and keep $ENV{CLICOLOR} in mind =head1 AUTHOR Jaap Karssenberg (Pardus) Epardus@cpan.orgE Copyright (c) 2004 Jaap G Karssenberg. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L =cut