quit fcscs (C<\&mapping_quit>) =back The following simple mappings are available by default: =over =item Benable pasting (C<\&mapping_paste>) =item B
enable yanking (copying) (C<\&mapping_yank>) =back All (single-byte) keys except numbers, backspace and return can be mapped. Unknown mappings are ignored when pressing keys. To remove a default mapping, delete it from the mapping hash. Example: # Map 'p' to select paths, 'P' to enable pasting. $config{mapping}{mode}{p} = \&mapping_mode_path; $config{mapping}{simple}{P} = \&mapping_paste; # Disable 'f' mapping. delete $config{mapping}{mode}{f}; =cut my %mapping_mode = ( f => \&mapping_mode_path, u => \&mapping_mode_url, '/' => \&mapping_mode_search, q => \&mapping_quit, ); my %mapping_simple = ( p => \&mapping_paste, y => \&mapping_yank, ); =head2 ATTRIBUTES Attributes are used to style the output. They must be Curses attributes. Defaults in parentheses (foreground, background, attribute). =over =item B attribute for match numbers (red, default, bold) =item B attribute for matches (yellow, default, normal) =item B attribute for prompt name (standout) =item B attribute for prompt flags (standout) =back Example: # Draw prompt flags in bold red with default background color. $config{attribute}{prompt_flags} = Curses::A_BOLD | color_pair(Curses::COLOR_RED, -1); =cut my %attribute = ( match_id => $screen->color_pair(Curses::COLOR_RED, -1) | Curses::A_BOLD, match_string => $screen->color_pair(Curses::COLOR_YELLOW, -1), prompt_name => Curses::A_STANDOUT, prompt_flags => Curses::A_STANDOUT, ); =head2 SETTINGS Defaults in parentheses. =over =item B enable debug mode (redirect stderr when enabling) (C<0>) =item B start in this mode, must be a valid mode mapping (C<\&mapping_mode_url>) =item B set multiplexer ("screen" or "tmux") if not autodetected (C ) =item B ignore case when searching (C<0>) =item B ignore case unless one uppercase character is searched (C<1>) =item B sleep x us before running paste command (C<100_000>) =item B GNU Screen's msgwait variable, used when yanking (C<5>) =item B browser command as array reference (C<['x-www-browser']>) =back Example: # Select paths on startup instead of URLs. $config{setting}{initial_mode} = \&mapping_mode_path; =cut my %setting = ( # options debug => 0, initial_mode => \&mapping_mode_url, multiplexer => undef, ignorecase => 0, smartcase => 1, paste_sleep => 100_000, screen_msgwait => 5, # commands browser => ['x-www-browser'], ); =head2 REGEXPS =over =item B used by C<\&mapping_mode_url()> =item B used by C<\&mapping_mode_path()> =back Example: # Select all non-whitespace characters when searching for paths. $config{regex}{path} = qr{(\S+)}; =cut my %regex = ( # Taken from urlview's default configuration file, thanks. url => qr{((?:(?:(?:http|https|ftp|gopher)|mailto):(?://)?[^ <>"\t]*|(?:www|ftp)[0-9]?\.[-a-z0-9.]+)[^ .,;\t\n\r<">\):]?[^, <>"\t]*[^ .,;\t\n\r<">\):])}, path => qr{(~?[a-zA-Z0-9_./-]*/[a-zA-Z0-9_./-]+)}, ); =head2 HANDLERS Handlers are used to perform actions on the selected string. The following handlers are available, defaults in parentheses. =over =item B used to yank (copy) selection to paste buffer (C<\&handler_yank>) =item B used to paste selection into window (C<\&handler_paste>) =item B used to open URLs (e.g. in a browser) (C<\&handler_url>) =back Example: # Download YouTube videos with a custom wrapper, handle all other URLs # with the default URL handler. $config{handler}{url} = sub { my ($screen, $config, $match) = @_; if ($match->{value} =~ m{^https://www.youtube.com/}) { return run_in_background($config, sub { run_command($config, ['youtube-dl-wrapper', $match->{value}]); }); } handler_url(@_); }; =cut my %handler = ( yank => \&handler_yank, paste => \&handler_paste, url => \&handler_url, ); my %state = ( initial => 1, # used by select_match() for 'initial_mode' handler => undef, ); # CONFIGURATION "API" =head2 FUNCTIONS The following functions are available: color_pair($fg, $bg) Create a new Curses attribute with the given fore- and background color. mapping_mode_path() mapping_mode_url() mapping_mode_search() mapping_paste() mapping_yank() mapping_quit() Used as mappings, see L above. handler_yank() handler_paste() handler_url() Used as handler to yank, paste selection or open URL in browser. debug() get_regex_matches() select_match() run_command() run_in_background() Helper functions when writing custom mappings, see the source for details. Example: TODO =cut # All variables and functions which are usable by ~/.fcscsrc. package Fcscs { our $screen; # "private" our %config; sub color_pair { return $screen->color_pair(@_); } sub mapping_mode_path { return main::mapping_mode_path(@_); } sub mapping_mode_url { return main::mapping_mode_url(@_); } sub mapping_mode_search { return main::mapping_mode_search(@_); } sub mapping_paste { return main::mapping_paste(@_); } sub mapping_yank { return main::mapping_yank(@_); } sub mapping_quit { return main::mapping_quit(@_); } sub handler_yank { return main::handler_yank(@_); } sub handler_paste { return main::handler_paste(@_); } sub handler_url { return main::handler_url(@_); } sub debug { return main::debug(@_); } sub get_regex_matches { return main::get_regex_matches(@_); } sub select_match { return main::select_match(@_); } sub run_command { return main::run_command(@_); } sub run_in_background { return main::run_in_background(@_); } } $Fcscs::screen = $screen; # LOAD USER CONFIG # Alias %config and %Fcscs::config. %config is less to type. our %config; local *config = \%Fcscs::config; $config{mapping}{mode} = \%mapping_mode; $config{mapping}{simple} = \%mapping_simple; $config{attribute} = \%attribute; $config{setting} = \%setting; $config{regex} = \%regex; $config{handler} = \%handler; $config{state} = \%state; package Fcscs { my @configs = ("$ENV{HOME}/.fcscsrc", "$ENV{HOME}/.config/fcscs/fcscsrc"); foreach my $path (@configs) { my $decoded = $screen->decode($path); # Load configuration file. Checks have a race condition if the home # directory is writable by an attacker (but then the user is screwed # anyway). next unless -e $path; if (not -O $path) { $screen->die("Config '$decoded' not owned by current user!"); } # Make sure the file is not writable by other users. Doesn't handle # ACLs and see comment above about race conditions. my @stat = stat $path or die $!; my $mode = $stat[2]; if (($mode & Fcntl::S_IWGRP) or ($mode & Fcntl::S_IWOTH)) { die "Config '$decoded' must not be writable by other users."; } my $result = do $path; if (not $result) { $screen->die("Failed to parse '$decoded': $@") if $@; $screen->die("Failed to do '$decoded': $!") unless defined $result; $screen->die("Failed to run '$decoded'."); } last; # success, don't load more files } } $screen->{debug} = $config{setting}{debug}; # MAIN eval { # Auto-detect current multiplexer. if (not defined $config{setting}{multiplexer}) { if (defined $ENV{STY} and defined $ENV{TMUX}) { die 'Found both $STY and $TMUX, set $config{setting}{multiplexer}.'; } elsif (defined $ENV{STY}) { $config{setting}{multiplexer} = 'screen'; } elsif (defined $ENV{TMUX}) { $config{setting}{multiplexer} = 'tmux'; } else { die 'No multiplexer found.'; } } my $binmode = $encoding; # GNU screen stores the screen dump for unknown reasons as ISO-8859-1 # instead of the currently active encoding. if ($config{setting}{multiplexer} eq 'screen') { $binmode = 'ISO-8859-1'; } my @input_lines; open my $fh, '<', $ARGV[0] or die $!; binmode $fh, ":encoding($binmode)" or die $!; while (<$fh>) { chomp; push @input_lines, $_; } close $fh or die $!; my $input = prepare_input($screen, \@input_lines); # Display original screen content. my $y = 0; foreach (@{$input->{lines}}) { $screen->draw_simple($y++, 0, undef, $_); } $screen->refresh; my $mapping = $config{setting}{initial_mode}; my $key; while (1) { if (not defined $mapping) { $key = $screen->getch unless defined $key; debug \%config, 'input', "got key '$key'"; $mapping = $config{mapping}{mode}{$key}; $mapping = $config{mapping}{simple}{$key} unless defined $mapping; if (not defined $mapping) { # ignore unknown mappings $key = undef; next; } } debug \%config, 'input', 'running mapping'; my $result = $mapping->($key, $screen, \%config, $input); $mapping = undef; RESULT: if (defined $result->{quit}) { debug \%config, 'input', 'quitting'; last; } if (defined $result->{key}) { $key = $result->{key}; # lookup another mapping debug \%config, 'input', "processing new key: '$key'"; next; } if (defined $result->{select}) { debug \%config, 'input', 'selecting match'; my $tmp = $result; $result = select_match($result->{select}, $screen, \%config, $input, $result->{matches}); $result->{handler} = $tmp->{handler}; goto RESULT; # reprocess special entries in result } if (defined $result->{match}) { if (not defined $result->{match}->{value}) { $result->{match}->{value} = $result->{match}->{string}; } debug \%config, 'input', 'running handler'; # Choose handler with falling priority. my @handlers = ( $config{state}{handler}, # set by user $result->{match}->{handler}, # set by match $result->{handler}, # set by mapping $config{handler}{yank}, # fallback ); foreach my $handler (@handlers) { next unless defined $handler; $handler->($screen, \%config, $result->{match}); last; } last; } $key = undef; # get next key from user } }; if ($@) { $screen->die("$@"); } $screen->deinit; __END__ =head1 EXIT STATUS =over 4 =item B<0> Success. =item B<1> An error occurred. =item B<2> Invalid arguments/options. =back =head1 AUTHOR Simon Ruderich E simon@ruderich.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2013-2016 by Simon Ruderich This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see E http://www.gnu.org/licenses/E . =head1 SEE ALSO L , L , L =cut