- f: file paths
- i: IPs
- u: URLs
+ - c: checksums (e.g. MD5, SHA1, ..)
- ...
- /: search mode
- for `normal' modes:
return;
}
+
+ sub prompt {
+ my ($self, %settings) = @_;
+
+ foreach (keys %settings) {
+ die if not exists $self->{prompt}{$_};
+ $self->{prompt}{$_} = $settings{$_};
+ }
+ return;
+ }
+
+
+ sub debug {
+ my ($self, $module, @args) = @_;
+
+ return if not $self->{debug};
+
+ state $fh; # only open the file once per run
+ if (not defined $fh) {
+ # Ignore errors if the directory doesn't exist.
+ if (not open $fh, '>', "$ENV{HOME}/.config/fcscs/log") {
+ $fh = undef; # a failed open still writes a value to $fh
+ return;
+ }
+ $fh->autoflush(1);
+ }
+
+ foreach (@args) {
+ $_ = $self->encode($_);
+ }
+ say $fh "$module: @args" or die $!;
+ return;
+ }
sub die {
my ($self, @args) = @_;
$self->deinit;
exit 1;
}
- sub debug {
- my ($self, $module, @args) = @_;
-
- return if not $self->{debug};
-
- state $fh; # only open the file once per run
- if (not defined $fh) {
- # Ignore errors if the directory doesn't exist.
- if (not open $fh, '>', "$ENV{HOME}/.config/fcscs/log") {
- $fh = undef; # a failed open still writes a value to $fh
- return;
- }
- $fh->autoflush(1);
- }
-
- foreach (@args) {
- $_ = $self->encode($_);
- }
- say $fh "$module: @args" or CORE::die $!;
- return;
- }
-
-
- sub prompt {
- my ($self, %settings) = @_;
-
- foreach (keys %settings) {
- CORE::die if not exists $self->{prompt}{$_};
- $self->{prompt}{$_} = $settings{$_};
- }
- return;
- }
# Wrapper for Curses.
sub width { return $Curses::COLS; }
# Necessary for GNU screen or it'll keep the window open until an
# external command has run.
- require File::Spec;
+ require File::Spec; # load here to speedup startup
my $devnull = File::Spec->devnull();
open STDIN, '<', $devnull or die $!;
open STDOUT, '>', $devnull or die $!;
$number = int($number / 10);
} elsif ($char eq "\n"
or $char eq $config->{setting}{alternative_return}) {
- if ($number == 0) { # number without selection matches last entry
- $number = 1;
- }
last;
# Selecting a new mode requires falling through into the main input
return { key => $char };
# All other mappings stay in the current mode.
} elsif (defined (my $m = $config->{mapping}{simple}{$char})) {
- $m->($char, $screen, $config, $input);
+ my $result = $m->($char, $screen, $config, $input);
+ last if defined $result->{select_match};
next;
} else {
$screen->draw_matches($config, $matches, \@remaining);
$screen->refresh;
}
+ # Number without selection matches last entry.
+ if ($number == 0) {
+ $number = 1;
+ }
- $screen->draw_matches($config, $matches, []); # remove matches
+ $screen->draw_matches($config, $matches, []); # clear matches
foreach (@{$matches}) {
return { match => $_ } if $_->{id} == $number;
} elsif ($char eq '$') { # select to end of line
extend_match_regex_right($line, $match, qr/.+/);
- # Allow mode changes if not overwritten by local mappings.
+ # Allow mode changes if not overwritten by above mappings.
} elsif (defined $config->{mapping}{mode}{$char}) {
$screen->draw_matches($config, [$match_old], []); # clear match
return { key => $char };
}
-sub mapping_paste {
- my ($key, $screen, $config, $input) = @_;
+sub mapping_state_helper {
+ my ($name, $flags, $key, $screen, $config, $input) = @_;
- $screen->debug('mapping_paste', 'started');
+ $screen->debug("mapping_$name", 'started');
- $config->{state}{handler} = $config->{handler}{paste};
+ $config->{state}{handler} = $config->{handler}{$name};
- $screen->prompt(flags => 'P'); # paste
+ $screen->prompt(flags => $flags);
$screen->draw_prompt($config);
$screen->refresh;
return {};
}
-sub mapping_yank {
- my ($key, $screen, $config, $input) = @_;
+sub mapping_state_now_helper {
+ my ($name, $key, $screen, $config, $input) = @_;
- $screen->debug('mapping_yank', 'started');
+ $screen->debug("mapping_${name}_now", 'started');
- $config->{state}{handler} = $config->{handler}{yank};
+ $config->{state}{handler} = $config->{handler}{$name};
- $screen->prompt(flags => 'Y'); # yank
- $screen->draw_prompt($config);
- $screen->refresh;
+ return {
+ select_match => 1,
+ };
+}
- return {};
+sub mapping_paste {
+ return mapping_state_helper('paste', 'P', @_);
+}
+sub mapping_paste_now {
+ return mapping_state_now_helper('paste', @_);
+}
+
+sub mapping_yank {
+ return mapping_state_helper('yank', 'Y', @_);
+}
+sub mapping_yank_now {
+ return mapping_state_now_helper('yank', @_);
}
=over 4
-=item B<path mode> select relative/absolute paths
+=item B<path mode> select relative/absolute paths
-=item B<url mode> select URLs
+=item B<url mode> select URLs
-=item B<ip mode> select IPv4 and IPv6 addresses
+=item B<ip mode> select IPv4 and IPv6 addresses
+
+=item B<checksum mode> select checksums (MD5, SHA1, SHA256, SHA512)
=back
=cut
-sub mapping_mode_path {
- my ($key, $screen, $config, $input) = @_;
+sub mapping_mode_helper {
+ my ($name, $select, $key, $screen, $config, $input) = @_;
- $screen->debug('mapping_mode_path', 'started');
+ $screen->debug("mapping_mode_$name", 'started');
- my @matches = get_regex_matches($input, $config->{regex}{path});
+ my @matches = get_regex_matches($input, $config->{regex}{$name});
return {
- select => 'path select',
+ select => $select,
matches => \@matches,
- handler => $config->{handler}{yank},
+ handler => $config->{handler}{$name},
};
}
+sub mapping_mode_path {
+ return mapping_mode_helper('path', 'path select', @_);
+}
sub mapping_mode_url {
- my ($key, $screen, $config, $input) = @_;
-
- $screen->debug('mapping_mode_url', 'started');
-
- my @matches = get_regex_matches($input, $config->{regex}{url});
- return {
- select => 'url select',
- matches => \@matches,
- handler => $config->{handler}{url},
- };
+ return mapping_mode_helper('url', 'url select', @_);
}
sub mapping_mode_ip {
my ($key, $screen, $config, $input) = @_;
handler => $config->{handler}{ip},
};
}
+sub mapping_mode_checksum {
+ return mapping_mode_helper('checksum', 'checksum select', @_);
+}
=head2 SEARCH MODE (AND EXTEND MODE)
$screen->debug('handler_yank', 'started');
- require File::Temp;
+ require File::Temp; # load here to speedup startup
# Use a temporary file to prevent leaking the yanked data to other users
# with the command line, e.g. ps aux or top.
$screen->debug('handler_paste', 'started');
- require Time::HiRes;
+ require Time::HiRes; # load here to speedup startup
my @cmd;
if ($config->{setting}{multiplexer} eq 'screen') {
=item B<i> select IPv4 and IPv6 addresses (C<\&mapping_mode_ip>)
+=item B<c> select checksums (e.g. MD5, SHA) (C<\&mapping_mode_checksum>)
+
=item B</> search for regex to get selection (C<\&mapping_mode_search>)
=item B<q> quit fcscs (C<\&mapping_quit>)
=item B<p> enable pasting (C<\&mapping_paste>)
+=item B<P> paste current selection (like C<\n> but paste) (C<\&mapping_paste_now>)
+
=item B<y> enable yanking (copying) (C<\&mapping_yank>)
+=item B<Y> yank current selection (like C<\n> but yank) (C<\&mapping_yank_now>)
+
=back
Note that yanking only uses the GNU screen or Tmux paste buffer by default. To
f => \&mapping_mode_path,
u => \&mapping_mode_url,
i => \&mapping_mode_ip,
+ c => \&mapping_mode_checksum,
'/' => \&mapping_mode_search,
q => \&mapping_quit,
);
my %mapping_simple = (
p => \&mapping_paste,
+ P => \&mapping_paste_now,
y => \&mapping_yank,
+ Y => \&mapping_yank_now,
);
=head2 ATTRIBUTES
=item B<initial_mode> start in this mode, must be a valid mode mapping (C<\&mapping_mode_url>)
-=item B<multiplexer> set multiplexer ("screen" or "tmux") if not autodetected (C<undef>)
+=item B<multiplexer> set multiplexer ("screen" or "tmux"), defaults to autodetection (C<undef>)
=item B<ignorecase> ignore case when searching (C<0>)
# enough.
ipv4 => qr!\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?)\b!,
ipv6 => qr!\b((?:[0-9a-fA-F]{1,4})?(?::+[0-9a-fA-F]{1,4})+(?:/\d{1,3})?)\b!,
+ # MD5, SHA1, SHA256, SHA512
+ checksum => qr!\b([0-9a-fA-F]{32}|[0-9a-fA-F]{40}|[0-9a-fA-F]{64}|[0-9a-fA-F]{128})\b!,
);
=head2 HANDLERS
=over
-=item B<yank> used to yank (copy) selection to paste buffer (C<\&handler_yank>)
+=item B<yank> used to yank (copy) selection to paste buffer (C<\&handler_yank>)
+
+=item B<paste> used to paste selection into window (C<\&handler_paste>)
-=item B<paste> used to paste selection into window (C<\&handler_paste>)
+=item B<path> used to handle paths (C<\&handler_yank>)
-=item B<url> used to open URLs (e.g. in a browser) (C<\&handler_url>)
+=item B<url> used to open URLs (e.g. in a browser) (C<\&handler_url>)
-=item B<ip> used to handle IPs (C<\&handler_yank>)
+=item B<ip> used to handle IPs (C<\&handler_yank>)
+
+=item B<checksum> used to handle checksums (C<\&handler_yank>)
=back
=cut
my %handler = (
- yank => \&handler_yank,
- paste => \&handler_paste,
- url => \&handler_url,
- ip => \&handler_yank,
+ yank => \&handler_yank,
+ paste => \&handler_paste,
+ path => \&handler_yank,
+ url => \&handler_url,
+ ip => \&handler_yank,
+ checksum => \&handler_yank,
);
my %state = (
mapping_mode_path()
mapping_mode_url()
mapping_mode_ip()
+ mapping_mode_checksum()
mapping_mode_search()
mapping_paste()
+ mapping_paste_now()
mapping_yank()
+ mapping_yank_now()
mapping_quit()
Used as mappings, see L</MAPPINGS> above.
run_command()
run_in_background()
-Helper functions when writing custom mappings, see the source for details.
+Helper functions when writing custom mappings, see the source and example for
+details.
Example:
}
return $result;
- }
+ };
# Also update initial mode to use our new "URL mode".
$config{setting}{initial_mode} = $config{mapping}{mode}{u};
});
};
-
-
=cut
# All variables and functions which are usable by ~/.fcscsrc.
sub mapping_mode_path { return main::mapping_mode_path(@_); }
sub mapping_mode_url { return main::mapping_mode_url(@_); }
sub mapping_mode_ip { return main::mapping_mode_ip(@_); }
+ sub mapping_mode_checksum { return main::mapping_mode_checksum(@_); }
sub mapping_mode_search { return main::mapping_mode_search(@_); }
sub mapping_paste { return main::mapping_paste(@_); }
+ sub mapping_paste_now { return main::mapping_paste_now(@_); }
sub mapping_yank { return main::mapping_yank(@_); }
+ sub mapping_yank_now { return main::mapping_yank_now(@_); }
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(@_); }
}
# 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 @stat = stat $path or $screen->die("Config '$decoded': $!");
my $mode = $stat[2];
if (($mode & Fcntl::S_IWGRP) or ($mode & Fcntl::S_IWOTH)) {
- die "Config '$decoded' must not be writable by other users.";
+ $screen->die("Config '$decoded' must not be writable by other users.");
}
my $result = do $path;