#!/usr/bin/env perl use strict; use warnings; use Cwd qw(cwd); use POSIX qw(mkfifo); # use POSIX qw(:signal_h); use File::Spec; use File::Basename; use Term::ANSIColor; my $parent = ''; if ($ENV{'NNN_FIFO'}) { ($parent = $ENV{'NNN_FIFO'}) =~ s/^[^.]*\.//; } my $tmpdir = '/tmp'; my $cwd = cwd(); $ENV{'PWD'} = $cwd; $ENV{'FIFOPID'} = "$tmpdir/nnn-preview-joe-fifopid.$parent"; $ENV{'FIFOPATH'} = "$tmpdir/nnn-preview-joe-fifo.$parent"; $ENV{'PREVIEWPID'} = "$tmpdir/nnn-preview-joe-previewpid.$parent"; $ENV{'CURSEL'} = "$tmpdir/nnn-preview-joe-selection.$parent"; $ENV{'POSOFFSET'} = "$tmpdir/nnn-preview-joe-posoffset"; $ENV{'NNN_PAGER'} ||= 'less'; $ENV{'NNN_BATSTYLE'} ||= 'numbers'; $ENV{'NNN_BATTHEME'} ||= 'ansi'; sub image_preview { print "IMAGE PREVIEW\n"; return; } sub generate_preview { print "GENERAL PREVIEW\n"; return; } sub fifo_pager { my ($cmd, @args) = @_; my $reader_pid; my $writer_pid; my $pager; $pager = $ENV{'NNN_PAGER'} // 'less'; mkfifo($ENV{'FIFOPATH'}, 0700) or return; $reader_pid = fork(); die $! unless defined $reader_pid; if ($reader_pid == 0) { open(STDIN, '<', $ENV{'FIFOPATH'}) or die $!; exec($pager) or die $!; } open(my $pid_fh, '>', $ENV{'PREVIEWPID'}) or die $!; print $pid_fh $reader_pid; close($pid_fh); $writer_pid = fork(); die $! unless defined $writer_pid; if ($writer_pid == 0) { open(STDOUT, '>', $ENV{'FIFOPATH'}) or die $!; if ($cmd eq 'pager') { my $cols = `tput cols`; chomp $cols; exec("bat", "--terminal-width=$cols", "--decorations=always", "--color=always", "--paging=never", "--style=$ENV{'NNN_BATSTYLE'}", "--theme=$ENV{'NNN_BATTHEME'}", @args ) or die $!; } elsif ($cmd eq 'print_bin_info') { print "-------- ", color('bold red'), "Binary file", color('reset'), " --------\n"; exec('mediainfo', @args); } else { exec($cmd, @args) or die $!; } } waitpid($writer_pid, 0); unlink $ENV{'FIFOPATH'} or warn $!; return; } sub handle_ext { my ($file, $ext, $type) = @_; if ($ext eq 'epub') { generate_preview($file, 'epub'); } elsif ($ext eq 'pdf') { generate_preview($file, 'pdf'); } elsif ( $ext eq 'gz' || $ext eq 'bz2' ) { fifo_pager('tar', '-tvf', $file); } elsif ($ext eq 'md') { fifo_pager('glow', '-s', 'dark', $file); } elsif ( $ext eq 'htm' || $ext eq 'html' || $ext eq 'xhtml' ) { fifo_pager('w3m', $file); } elsif ( $ext eq '7z' || $ext eq 'a' || $ext eq 'ace' || $ext eq 'alz' || $ext eq 'arc' || $ext eq 'arj' || $ext eq 'bz' || $ext eq 'cab' || $ext eq 'cpio' || $ext eq 'deb' || $ext eq 'jar' || $ext eq 'lha' || $ext eq 'lz' || $ext eq 'lzh' || $ext eq 'lzma' || $ext eq 'lzo' || $ext eq 'rar' || $ext eq 'rpm' || $ext eq 'rz' || $ext eq 't7z' || $ext eq 'tar' || $ext eq 'tbz' || $ext eq 'tbz2' || $ext eq 'tgz' || $ext eq 'tlz' || $ext eq 'txz' || $ext eq 'tZ' || $ext eq 'tzo' || $ext eq 'war' || $ext eq 'xpi' || $ext eq 'xz' || $ext eq 'xz' || $ext eq 'zst' || $ext eq 'Z' ) { fifo_pager('atool', '-l', $file); } elsif ($ext eq 'json') { fifo_pager('jq', '--color-output', '.', $file); } elsif ($type eq 'bin') { fifo_pager('print_bin_info', $file); } else { fifo_pager('pager', $file); } return; } sub handle_mime { my ($file, $mimetype, $ext, $type) = @_; if ($mimetype eq 'image/jpeg') { image_preview($file); } elsif ($mimetype eq 'image/gif') { generate_preview($file, 'gif'); } elsif ($mimetype eq 'image/vnd.djvu') { generate_preview($file, 'djvu'); } elsif ($mimetype =~ /^image\//) { generate_preview($file, 'image'); } elsif ($mimetype =~ /^video\//) { generate_preview($file, 'video'); } elsif ($mimetype =~ /^audio\//) { generate_preview($file, 'audio'); } elsif ( $mimetype =~ /^application\/font/ || $mimetype =~ /^application\/.*opentype/ || $mimetype =~ /^font\// ) { generate_preview($file, 'font'); } elsif ( $mimetype =~ m{/.+office.+} || $mimetype =~ m{/.+document.+} || $mimetype =~ m{/.+msword} || $mimetype =~ m{/.+ms-excel} ) { generate_preview($file, 'office'); } elsif ($mimetype eq 'application/zip') { fifo_pager('unzip', '-l', $file); } elsif ($mimetype eq 'text/troff') { fifo_pager('man', '-Pcat', '-l', $file); } else { handle_ext($file, $ext, $type); } return; } sub preview_file { my ($file) = @_; my ($ext) = $file =~ /\.([^.]*)$/; my $encoding; my $mimetype; my $lines; my $cols; system('clear'); $encoding = `file -bL --mime-encoding -- $file`; $mimetype = `file -bL --mime-type -- $file`; $ext = lc($ext) if length($ext) > 0; $lines = `tput lines`; $cols = `tput cols`; if (-d $file) { chdir $file or return; fifo_pager( 'eza', '-T', '--group-directories-first', '--colour=always', '-L', '3' ); chdir '..'; } elsif ($encoding =~ /\)/) { my ($suffix) = $encoding =~ /\)(.*)/; if (defined $suffix && $suffix eq 'binary') { handle_mime($file, $mimetype, $ext, 'bin'); } else { handle_mime($file, $mimetype, $ext); } } return; } sub preview_fifo { while (1) { open(my $fh, '<', $ENV{'NNN_FIFO'}) or die $!; while (my $selection = <$fh>) { chomp $selection; if ($selection) { pidkill($ENV{'PREVIEWPID'}); last if $selection eq 'close'; preview_file($selection); open(my $cursel_fh, '>', $ENV{'CURSEL'}) or die $!; print $cursel_fh $selection; close($cursel_fh); } } close $fh; } return; } sub pidkill { my ($pidfile) = @_; my $pid; my $ret; if (-e $pidfile) { $pid = `cat $pidfile 2>/dev/null` or return 1; $ret = system("kill $pid >/dev/null 2>&1"); system("wait $pid 2>/dev/null"); return $ret; } return 1; } sub winch_handler { my $filepath; system('clear'); pidkill($ENV{'PREVIEWPID'}); open(my $fh, '<', $ENV{'CURSEL'}) or die $!; $filepath = <$fh>; chomp $filepath if $filepath; close($fh); preview_file($filepath) if $filepath; return; } sub start_preview { my ($file) = @_; my $pid; $ENV{'NNN_TERMINAL'} = $ENV{'NNN_TERMINAL'} // 'xterm'; $ENV{'PREVIEW_MODE'} = 1; $pid = fork(); die $! unless defined $pid; if ($pid == 0) { exec($ENV{'NNN_TERMINAL'}, '-e', $0, $file) or die $!; } return; } sub toggle_preview { my ($file) = @_; my $nnn_ppipe; $ENV{'PWD'} = cwd(); $ENV{'PATH'} = $ENV{'PATH'}; $ENV{'NNN_FIFO'} = $ENV{'NNN_FIFO'}; $nnn_ppipe = $ENV{'NNN_PPIPE'} // ''; if (pidkill($ENV{'FIFOPID'})) { if ($nnn_ppipe && -p $nnn_ppipe) { open(my $fh, '>', $nnn_ppipe) or warn $!; print $fh "0"; close($fh); } pidkill($ENV{'PREVIEWPID'}); } else { if ($nnn_ppipe && -p $nnn_ppipe) { open(my $fh, '>', $nnn_ppipe) or warn $!; print $fh "1"; close($fh); } start_preview($file); } return; } sub joe_preview { my $file; my $pid; my $cwd; $cwd = cwd(); $SIG{PIPE} = 'IGNORE'; $file = $ARGV[0]; if (($ENV{'PREVIEW_MODE'} || 0) == 1) { preview_file($cwd . '/' . $file); $pid = fork(); die $! unless defined $pid; if ($pid == 0) { preview_fifo(); return; } open(my $pidfh, '>', $ENV{'FIFOPID'}) or die $!; print $pidfh "$pid\n"; close($pidfh); open(my $curselfh, '>', $ENV{'CURSEL'}) or die $!; print $curselfh "$cwd/$file\n"; close($curselfh); $SIG{WINCH} = sub { winch_handler(); }; for my $signal (qw(INT HUP EXIT)) { $SIG{$signal} = sub { unlink( $ENV{'PREVIEWPID'}, $ENV{'CURSEL'}, $ENV{'FIFOPID'}, $ENV{'POSOFFSET'} ); exit 0; }; } while (kill 0, $pid) { waitpid($pid, 0); } return; } else { # $pid = fork(); # die $! unless defined $pid; # if ($pid == 0) { toggle_preview($file); # return; # } } return; } joe_preview(); __END__