Separated state from rendering code

* Each generator now consists in two functions, readstate_ and
    render_. The first computes a state, the second renders it.
This commit is contained in:
Emmanuel BENOîT 2022-10-29 14:15:24 +02:00
parent 799438be80
commit 2a1cf31d4d

View file

@ -515,23 +515,27 @@ sub compute_trans_lengths
sub gen_prompt_section sub gen_prompt_section
{ {
my $section = shift; my ( $state , $section ) = @_;
unless ( exists $state->{ $section } ) {
no strict 'refs';
my $sFunc = 'readstate_' . $section;
$state->{ $section } = &$sFunc;
}
unless ( exists $SCACHE{ $section } ) { unless ( exists $SCACHE{ $section } ) {
no strict 'refs'; no strict 'refs';
my $func = 'render_' . $section; my $rFunc = 'render_' . $section;
$SCACHE{ $section } = [ &$func ]; $SCACHE{ $section } = [ &$rFunc( $state->{ $section } ) ];
} }
return @{ $SCACHE{ $section } }; return @{ $SCACHE{ $section } };
} }
sub gen_prompt_sections sub gen_prompt_sections
{ {
my $reverse = shift; my ( $state , $reverse , @input ) = @_;
my @input = @_;
@input = reverse @input if $reverse; @input = reverse @input if $reverse;
my @output = ( ); my @output = ( );
foreach my $section ( @input ) { foreach my $section ( @input ) {
my @section = gen_prompt_section( $section ); my @section = gen_prompt_section( $state , $section );
@section = reverse @section if $reverse; @section = reverse @section if $reverse;
@output = ( @output , @section ); @output = ( @output , @section );
} }
@ -693,6 +697,7 @@ sub gen_empty_line
sub gen_top_line sub gen_top_line
{ {
my $state = shift;
my @left = @{ $CONFIG{layout_left} }; my @left = @{ $CONFIG{layout_left} };
my @right = @{ $CONFIG{layout_right} }; my @right = @{ $CONFIG{layout_right} };
my $midGen = $CONFIG{layout_middle}; my $midGen = $CONFIG{layout_middle};
@ -701,9 +706,9 @@ sub gen_top_line
# Generate content # Generate content
my @middle = ( ); my @middle = ( );
my $mc = themed 'bg_middle'; my $mc = themed 'bg_middle';
@left = gen_prompt_sections( 0 , @left ); @left = gen_prompt_sections( $state , 0 , @left );
if ( defined $midGen ) { if ( defined $midGen ) {
@middle = ( gen_prompt_section( $midGen ) ); @middle = ( gen_prompt_section( $state , $midGen ) );
if ( @middle ) { if ( @middle ) {
@middle = ( @middle = (
add_transitions( 'middle' , themed( 'bg_left' ) , add_transitions( 'middle' , themed( 'bg_left' ) ,
@ -715,7 +720,7 @@ sub gen_top_line
unshift @middle , { bg => themed('bg_middle') }; unshift @middle , { bg => themed('bg_middle') };
} }
} }
@right = gen_prompt_sections( 1 , @right ); @right = gen_prompt_sections( $state , 1 , @right );
# Adapt to width # Adapt to width
my $len = get_length( ( @middle ) ); my $len = get_length( ( @middle ) );
@ -739,11 +744,12 @@ sub gen_top_line
sub gen_input_line sub gen_input_line
{ {
my $state = shift;
my @input = @{ $CONFIG{layout_input} }; my @input = @{ $CONFIG{layout_input} };
return "" unless @input || $CONFIG{layout_input_always}; return "" unless @input || $CONFIG{layout_input_always};
my $len = 0; my $len = 0;
@input = adapt_to_width( \$len , 'input' , @input = adapt_to_width( \$len , 'input' ,
gen_prompt_sections( 0 , @input ) ); gen_prompt_sections( $state , 0 , @input ) );
push @input , {content=>['']} unless @input; push @input , {content=>['']} unless @input;
return ( $len , return ( $len ,
render( 'input' , add_transitions( 'input' , 0 , 0 , @input ) ) . $RESET render( 'input' , add_transitions( 'input' , 0 , 0 , @input ) ) . $RESET
@ -766,9 +772,10 @@ sub gen_ps2
sub gen_term_title sub gen_term_title
{ {
my $state = shift;
my @parts = @{ $CONFIG{term_generators} }; my @parts = @{ $CONFIG{term_generators} };
return '' unless @parts && $CONFIG{term_set_title}; return '' unless @parts && $CONFIG{term_set_title};
@parts = gen_prompt_sections( 0 , @parts ); @parts = gen_prompt_sections( $state , 0 , @parts );
my @str_parts = (); my @str_parts = ();
foreach my $part ( @parts ) { foreach my $part ( @parts ) {
my $cur = ''; my $cur = '';
@ -896,11 +903,12 @@ sub main
$RESET = $CONFIG{cfg_sgr0_reset} ? tput_sequence( 'sgr0' ) : "\033[0m"; $RESET = $CONFIG{cfg_sgr0_reset} ? tput_sequence( 'sgr0' ) : "\033[0m";
$RESET = '\\[' . $RESET . '\\]'; $RESET = '\\[' . $RESET . '\\]';
%TLEN = compute_trans_lengths; %TLEN = compute_trans_lengths;
my $pg = gen_term_title; my $state = {};
my $pg = gen_term_title( $state );
my $ps1 = $pg; my $ps1 = $pg;
$ps1 .= gen_empty_line; $ps1 .= gen_empty_line( $state );
$ps1 .= gen_top_line; $ps1 .= gen_top_line( $state );
my ( $ill , $ilt ) = gen_input_line; my ( $ill , $ilt ) = gen_input_line( $state );
$ps1 .= $ilt; $ps1 .= $ilt;
my $ps2 = $pg . gen_ps2( $ill ); my $ps2 = $pg . gen_ps2( $ill );
print "export PS1=\"$ps1\" PS2=\"$ps2\"\n"; print "export PS1=\"$ps1\" PS2=\"$ps2\"\n";
@ -913,9 +921,16 @@ main;
# Date/time -----------------------------------------------------------------{{{ # Date/time -----------------------------------------------------------------{{{
sub render_datetime sub readstate_datetime
{ {
my @cur_time = localtime time; my @cur_time = localtime time;
return { t => [@cur_time] };
}
sub render_datetime
{
my $state = shift;
my @cur_time = @{ $state->{t} };
my @out = ( ); my @out = ( );
if ( $CONFIG{dt_show_date} ) { if ( $CONFIG{dt_show_date} ) {
push @out , {fg=>themed 'dt_date_fg'}; push @out , {fg=>themed 'dt_date_fg'};
@ -932,12 +947,24 @@ sub render_datetime
#}}} #}}}
# Current working directory -------------------------------------------------{{{ # Current working directory -------------------------------------------------{{{
sub readstate_cwd
{
my $state = { exists=> $HASCWD };
$state->{home} = $ENV{HOME} if exists $ENV{HOME};
if ($HASCWD) {
$state->{cwd} = getcwd;
} elsif (exists $ENV{PWD}) {
$state->{cwd} = $ENV{PWD};
}
return $state;
}
sub render_cwd sub render_cwd
{ {
my $state = shift;
my @out = ( ); my @out = ( );
my $cwd = getcwd;
my @cols; my @cols;
unless ( $HASCWD ) { unless ( $state->{exists} ) {
@cols = map { themed $_ } qw( @cols = map { themed $_ } qw(
cwd_missing_bg_color cwd_missing_fg_color ); cwd_missing_bg_color cwd_missing_fg_color );
push @out , { push @out , {
@ -950,18 +977,20 @@ sub render_cwd
'(no cwd)' '(no cwd)'
] , ] ,
}; };
return @out unless exists $ENV{PWD}; return @out unless exists $state->{cwd};
$cwd = $ENV{PWD};
} else { } else {
@cols = map { themed $_ } qw( cwd_bg_color cwd_fg_color ); @cols = map { themed $_ } qw( cwd_bg_color cwd_fg_color );
$cwd = getcwd;
} }
my $cwd = $state->{cwd};
( my $dir = $cwd ) =~ s!^.*/!!; ( my $dir = $cwd ) =~ s!^.*/!!;
if (exists $state->{home}) {
my $home = $state->{home};
( $dir = $cwd ) =~ s!^\Q$home\E(\z|/.*)$!~$1!;
}
my $max_len = int( $COLUMNS * $CONFIG{cwd_max_width} / 100 ); my $max_len = int( $COLUMNS * $CONFIG{cwd_max_width} / 100 );
$max_len = length( $dir ) if length( $dir ) > $max_len; $max_len = length( $dir ) if length( $dir ) > $max_len;
( $dir = $cwd ) =~ s!^$ENV{HOME}(\z|/.*)$!~$1!;
my $offset = length( $dir ) - $max_len; my $offset = length( $dir ) - $max_len;
if ( $offset > 0 ) { if ( $offset > 0 ) {
$dir = substr $dir , $offset , $max_len; $dir = substr $dir , $offset , $max_len;
@ -979,34 +1008,42 @@ sub render_cwd
# }}} # }}}
# User/Host -----------------------------------------------------------------{{{ # User/Host -----------------------------------------------------------------{{{
sub readstate_userhost
{
my $is_remote = 0;
foreach my $ev ( qw( SSH_CLIENT SSH2_CLIENT SSH_TTY ) ) {
if ( exists($ENV{$ev}) && $ENV{$ev} ne '' ) {
$is_remote = 1;
last;
}
}
use Sys::Hostname;
return {
rmt => $is_remote ,
user => ( getpwuid( $< ) || '(?)' ) ,
host => hostname
}
}
sub render_userhost sub render_userhost
{ {
use Sys::Hostname; my $state = shift;
my ( $un , $hn , $rm ) = map { my ( $un , $hn , $rm ) = map {
$CONFIG{"uh_$_"} $CONFIG{"uh_$_"}
} qw( username hostname remote ); } qw( username hostname remote );
return () unless $un || $hn || $rm; return () unless $un || $hn || $rm;
my $is_remote = 0;
if ( $hn == 2 || $CONFIG{uh_remote} ) {
foreach my $ev ( qw( SSH_CLIENT SSH2_CLIENT SSH_TTY ) ) {
if ( exists($ENV{$ev}) && $ENV{$ev} ne '' ) {
$is_remote = 1;
last;
}
}
}
my @out = (); my @out = ();
if ( $un ) { if ( $un ) {
push @out , ( getpwuid( $< ) || '(?)' ); push @out , $state->{user};
} }
if ( $hn == 1 || ( $hn == 2 && $is_remote ) ) { if ( $hn == 1 || ( $hn == 2 && $state->{rmt} ) ) {
push @out , { fg => themed 'uh_host_fg', style => 'd' }; push @out , { fg => themed 'uh_host_fg', style => 'd' };
push @out , '@' if @out; push @out , '@' if @out;
push @out , hostname; push @out , $state->{host};
} }
if ( $rm && $is_remote ) { if ( $rm && $state->{rmt} ) {
push @out , {style => 'b'}; push @out , {style => 'b'};
push @out , ( themed 'uh_remote_symbol' ); push @out , ( themed 'uh_remote_symbol' );
} }
@ -1024,13 +1061,19 @@ sub render_userhost
# }}} # }}}
# Previous command state ----------------------------------------------------{{{ # Previous command state ----------------------------------------------------{{{
sub readstate_prevcmd
{
return { rc => $INPUT{rc} };
}
sub render_prevcmd sub render_prevcmd
{ {
my $state = shift;
my ( $ss , $sc , $pc , $cl ) = map { my ( $ss , $sc , $pc , $cl ) = map {
$CONFIG{ "pcmd_$_" } $CONFIG{ "pcmd_$_" }
} qw( show_symbol show_code pad_code colors ); } qw( show_symbol show_code pad_code colors );
return () unless exists $INPUT{rc}; return () unless exists $state->{rc};
my $status = $INPUT{rc}; my $status = $state->{rc};
$sc = ( $sc == 1 || ( $sc == 2 && $status ) ); $sc = ( $sc == 1 || ( $sc == 2 && $status ) );
return () unless $sc || $ss; return () unless $sc || $ss;
@ -1068,7 +1111,7 @@ sub render_prevcmd
# }}} # }}}
# Load average --------------------------------------------------------------{{{ # Load average --------------------------------------------------------------{{{
sub render_load sub readstate_load
{ {
my $ncpu; my $ncpu;
if ( open( my $fh , '</proc/cpuinfo' ) ) { if ( open( my $fh , '</proc/cpuinfo' ) ) {
@ -1086,6 +1129,13 @@ sub render_load
close $fh; close $fh;
$load =~ s/ .*$//; $load =~ s/ .*$//;
$load = int( $load * 100 / $ncpu ); $load = int( $load * 100 / $ncpu );
return $load;
}
sub render_load
{
my $load = shift;
return () if $load < $CONFIG{load_min}; return () if $load < $CONFIG{load_min};
my $cat; my $cat;
@ -1110,6 +1160,11 @@ sub render_load
# }}} # }}}
# Jobs ----------------------------------------------------------------------{{{ # Jobs ----------------------------------------------------------------------{{{
sub readstate_jobs
{
return ( exists $INPUT{jobs} ) ? $INPUT{jobs} : -1;
}
sub _render_jobs_part sub _render_jobs_part
{ {
my ($text, $themeName) = @_; my ($text, $themeName) = @_;
@ -1125,9 +1180,8 @@ sub _render_jobs_part
sub render_jobs sub render_jobs
{ {
return () unless exists $INPUT{jobs}; my $jobs = shift;
my $jobs = $INPUT{jobs}; return () if ( $jobs == 0 && !$CONFIG{jobs_always} ) || $jobs < 0;
return () if $jobs == 0 && !$CONFIG{jobs_always};
my @output = (); my @output = ();
my $section = themed 'jobs_prefix'; my $section = themed 'jobs_prefix';
@ -1145,75 +1199,50 @@ sub render_jobs
# }}} # }}}
# Git repository information ------------------------------------------------{{{ # Git repository information ------------------------------------------------{{{
sub _render_git_branch sub _readstate_git_branch
{ {
# Get branch and associated warning level
chop( my $branch = `git symbolic-ref -q HEAD` ); chop( my $branch = `git symbolic-ref -q HEAD` );
my $detached = ( $? != 0 ); my $detached = ( $? != 0 );
my $branch_warning;
if ( $detached ) { if ( $detached ) {
chop( $branch = `git rev-parse --short -q HEAD` ); chop( $branch = `git rev-parse --short -q HEAD` );
$branch = "($branch)"; $branch = "($branch)";
$branch_warning = $CONFIG{git_detached_warning};
} else { } else {
$branch =~ s!^refs/heads/!!; $branch =~ s!^refs/heads/!!;
my %branch_tab = (
( map { $_ => 1 } @{ $CONFIG{git_branch_warn} } ) ,
( map { $_ => 2 } @{ $CONFIG{git_branch_danger} } ) ,
);
#use Data::Dumper; print STDERR Dumper( \%branch_tab );
$branch_warning = exists( $branch_tab{ $branch } )
? $branch_tab{ $branch } : 0;
} }
$branch_warning = qw(ok warn danger)[ $branch_warning ]; return { d => $detached, id => $branch };
return {
bg => themed( 'git_branch_' . $branch_warning . '_bg' ) ,
content => [
{fg => themed( 'git_branch_' . $branch_warning . '_fg' )} ,
themed( 'git_branch_symbol' ) ,
{style=>'b'},
$branch,
{style=>'none'},
]
};
} }
sub _render_git_repstate sub _readstate_git_repstate
{ {
return () unless open( my $fh , my $state = shift;
if ( open( my $fh ,
'git rev-parse --git-dir --is-inside-git-dir ' 'git rev-parse --git-dir --is-inside-git-dir '
. '--is-bare-repository 2>/dev/null|' ); . '--is-bare-repository 2>/dev/null|' ) ) {
chop( my $gd = <$fh> ); chop( my $gd = <$fh> );
chop( my $igd = <$fh> ); chop( my $igd = <$fh> );
chop( my $bare = <$fh> ); chop( my $bare = <$fh> );
close $fh;
my $str = undef; my $str = undef;
if ( $bare eq 'true' ) { if ( $bare eq 'true' ) {
$str = 'bare'; $str = 'bare';
} elsif ( $igd eq 'true' ) { } elsif ( $igd eq 'true' ) {
$str = 'in git dir'; $str = 'in git dir';
} else { } else {
if ( -f "$gd/MERGE_HEAD" ) { if ( -f "$gd/MERGE_HEAD" ) {
$str = 'merge'; $str = 'merge';
} elsif ( -d "$gd/rebase-apply" || -d "$gd/rebase-merge" ) { } elsif ( -d "$gd/rebase-apply" || -d "$gd/rebase-merge" ) {
$str = 'rebase'; $str = 'rebase';
} elsif ( -f "$gd/CHERRY_PICK_HEAD" ) { } elsif ( -f "$gd/CHERRY_PICK_HEAD" ) {
$str = 'cherry-pick'; $str = 'cherry-pick';
}
} }
$state->{rs} = $str if $str;
} }
return () unless defined $str;
return {
bg => themed 'git_repstate_bg' ,
content => [
{fg=>themed 'git_repstate_fg'},
$str
]
};
} }
sub _render_git_status sub _readstate_git_status
{ {
# Read status information
my %parts = ( my %parts = (
'\?\?' => 0 , '\?\?' => 0 ,
'.M' => 1 , '.M' => 1 ,
@ -1234,6 +1263,86 @@ sub _render_git_status
} }
close $fh; close $fh;
} }
return [ @counters ];
}
sub readstate_git
{
return undef unless $HASCWD;
system( 'git rev-parse --is-inside-work-tree >/dev/null 2>&1' );
return undef if $? != 0;
my $state = {};
# Branch information
$state->{br} = _readstate_git_branch;
# Repository state
_readstate_git_repstate( $state );
# Status
$state->{status} = _readstate_git_status if $CONFIG{git_show_status};
# Stash information
if ($CONFIG{git_show_stash}
&& open( my $fh , 'git stash list 2>/dev/null|' )) {
my @lines = grep { $_ =~ /^stash/ } <$fh>;
close( $fh );
my $nl = scalar( @lines );
$state->{stash} = $nl if $nl;
}
return $state;
}
sub _render_git_branch
{
my $state = shift;
# Get branch and associated warning level
my $branch = $state->{id};
my $detached = $state->{d};
my $branch_warning;
if ( $detached ) {
$branch_warning = $CONFIG{git_detached_warning};
} else {
my %branch_tab = (
( map { $_ => 1 } @{ $CONFIG{git_branch_warn} } ) ,
( map { $_ => 2 } @{ $CONFIG{git_branch_danger} } ) ,
);
$branch_warning = exists( $branch_tab{ $branch } )
? $branch_tab{ $branch } : 0;
}
$branch_warning = qw(ok warn danger)[ $branch_warning ];
return {
bg => themed( 'git_branch_' . $branch_warning . '_bg' ) ,
content => [
{fg => themed( 'git_branch_' . $branch_warning . '_fg' )} ,
themed( 'git_branch_symbol' ) ,
{style=>'b'},
$branch,
{style=>'none'},
]
};
}
sub _render_git_repstate
{
my $state = shift;
return () unless exists $state->{rs};
return {
bg => themed 'git_repstate_bg' ,
content => [
{fg=>themed 'git_repstate_fg'},
$state->{rs}
]
};
}
sub _render_git_status
{
my $state = shift;
return () unless exists $state->{status};
my @counters = @{ $state->{status} };
# Generate status sections # Generate status sections
my @sec_names = ( 'untracked' , 'indexed' ); my @sec_names = ( 'untracked' , 'indexed' );
@ -1278,19 +1387,15 @@ sub _render_git_status
sub _render_git_stash sub _render_git_stash
{ {
return () unless open( my $fh , 'git stash list 2>/dev/null|' ); my $state = shift;
my @lines = grep { $_ =~ /^stash/ } <$fh>; return () unless exists $state->{stash};
close( $fh );
my $nl = scalar( @lines );
return () unless $nl;
return { return {
bg => themed('git_stash_bg') , bg => themed('git_stash_bg') ,
content => [ content => [
{fg=>themed('git_stash_fg')} , {fg=>themed('git_stash_fg')} ,
themed('git_stash_symbol') , themed('git_stash_symbol') ,
{style=>'b'}, {style=>'b'},
$nl , $state->{stash} ,
{style=>'none'}, {style=>'none'},
] ]
}; };
@ -1298,32 +1403,47 @@ sub _render_git_stash
sub render_git sub render_git
{ {
my @out = ( ); my $state = shift;
return @out unless $HASCWD; return () unless defined $state;
system( 'git rev-parse --is-inside-work-tree >/dev/null 2>&1' ); my @out = ( _render_git_branch( $state->{br} ) );
return @out if $? != 0; @out = ( @out , _render_git_repstate( $state ) );
@out = ( @out , _render_git_branch , _render_git_repstate ); @out = ( @out , _render_git_status( $state ) );
@out = ( @out , _render_git_status ) if $CONFIG{git_show_status}; @out = ( @out , _render_git_stash( $state ) );
@out = ( @out , _render_git_stash ) if $CONFIG{git_show_stash};
return @out; return @out;
} }
# }}} # }}}
# Python virtual environment ------------------------------------------------{{{ # Python virtual environment ------------------------------------------------{{{
sub readstate_pyenv
{
my $state = {};
if ( exists $ENV{VIRTUAL_ENV} ) {
$state->{env} = $ENV{VIRTUAL_ENV};
} elsif (exists $ENV{CONDA_VIRTUAL_ENV}) {
$state->{env} = $ENV{CONDA_VIRTUAL_ENV};
}
$state->{env} =~ s!.*/!! if exists $state->{env};
my $vd = $CONFIG{pyenv_py_version};
if ( $vd == 2 || ( $vd == 1 && exists $state->{env} ) ) {
my $cmd = join('||',
(map { "python$_ --version 2>/dev/null" } ('', 3, 2))
);
chop( my $pyver = `$cmd` );
$state->{ver} = (split /\s+/, $pyver, 2)[1];
}
return $state;
}
sub render_pyenv sub render_pyenv
{ {
my $state = shift;
my $vd = $CONFIG{pyenv_py_version}; my $vd = $CONFIG{pyenv_py_version};
my $env; my $env = exists( $state->{env} ) ? $state->{env} : '';
if ( exists $ENV{VIRTUAL_ENV} ) { my $ver = exists( $state->{ver} ) ? $state->{ver} : '';
$env = $ENV{VIRTUAL_ENV}; return unless $env || ( $vd == 2 && $ver );
} elsif (exists $ENV{CONDA_VIRTUAL_ENV}) {
$env = $ENV{CONDA_VIRTUAL_ENV};
} else {
$env = '';
}
$env =~ s!.*/!!;
return if !$env && $vd < 2;
my @output = ( my @output = (
{ fg=> themed 'pyenv_fg', style => 'd' }, { fg=> themed 'pyenv_fg', style => 'd' },
(themed 'pyenv_text') (themed 'pyenv_text')
@ -1331,12 +1451,7 @@ sub render_pyenv
@output = (@output, { style => 'b' }, $env ) if $env; @output = (@output, { style => 'b' }, $env ) if $env;
@output = (@output, { style => 'd' }, (themed 'pyenv_sep')) if $env && $vd; @output = (@output, { style => 'd' }, (themed 'pyenv_sep')) if $env && $vd;
if ($vd == 2 || ( $vd == 1 && $env )) { if ($vd == 2 || ( $vd == 1 && $env )) {
my $cmd = join('||', @output = (@output, {style => 'none' }, $ver);
(map { "python$_ --version 2>/dev/null" } ('', 3, 2))
);
chop( my $pyver = `$cmd` );
$pyver = (split /\s+/, $pyver, 2)[1];
@output = (@output, {style => 'none' }, $pyver);
} }
return { return {
bg => themed 'pyenv_bg' , bg => themed 'pyenv_bg' ,