2020-10-19 21:05:11 +02:00
|
|
|
#!/usr/bin/perl
|
2020-10-21 12:58:36 +02:00
|
|
|
################################################################################
|
|
|
|
# GADGETOPROMPT
|
2022-10-22 11:11:19 +02:00
|
|
|
# A really useless bash prompt generator that most likely only works on Linux
|
|
|
|
# and requires Perl.
|
2020-10-21 12:58:36 +02:00
|
|
|
################################################################################
|
|
|
|
|
|
|
|
# Have a look at README.md for some documentation
|
|
|
|
# Licensed under the WTFPL version 2, which should be in the LICENSE file.
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
use utf8;
|
|
|
|
use open ':std', ':encoding(UTF-8)';
|
2022-10-22 18:31:35 +02:00
|
|
|
use POSIX qw(strftime :termios_h);
|
2022-10-22 11:10:54 +02:00
|
|
|
use Cwd qw(abs_path getcwd);
|
2022-10-29 15:03:52 +02:00
|
|
|
use Data::Dumper;
|
2020-10-19 21:05:11 +02:00
|
|
|
|
2020-10-20 16:12:26 +02:00
|
|
|
|
2020-10-21 09:45:17 +02:00
|
|
|
# DEFAULT CONFIGURATION ====================================================={{{
|
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
our %CONFIG = (
|
2020-10-21 10:00:13 +02:00
|
|
|
# CONFIGURATION
|
|
|
|
# - Issue warnings about configuration files
|
|
|
|
cfg_warn_files => 1 ,
|
|
|
|
# - Allow overrides from environment
|
|
|
|
cfg_from_env => 0 ,
|
2020-10-21 12:39:23 +02:00
|
|
|
# - System theme dirs
|
2020-10-21 12:58:36 +02:00
|
|
|
cfg_sys_themes => [ '/usr/share/gprompt/themes' ] ,
|
2020-10-21 12:39:23 +02:00
|
|
|
# - User theme dirs
|
2020-10-21 12:58:36 +02:00
|
|
|
cfg_user_themes => [ '.local/share/gprompt/themes' , '.gprompt-themes' ] ,
|
2022-10-19 19:19:39 +02:00
|
|
|
# - Use tput sgr0 for resets
|
|
|
|
cfg_sgr0_reset => 0 ,
|
2020-10-20 16:32:56 +02:00
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
# LAYOUT
|
2020-10-19 23:42:19 +02:00
|
|
|
# - Theme and local overrides
|
2020-10-21 12:39:23 +02:00
|
|
|
layout_theme => '' ,
|
2020-10-19 23:42:19 +02:00
|
|
|
layout_theme_overrides => {} ,
|
2020-10-19 21:41:05 +02:00
|
|
|
# - Section generators for the left side of the top bar
|
2020-10-21 12:39:23 +02:00
|
|
|
layout_left => [ ] ,
|
2020-10-19 21:41:05 +02:00
|
|
|
# - Section generator for the central part of the top bar (undef if unused)
|
2020-10-21 12:39:23 +02:00
|
|
|
layout_middle => '' ,
|
2020-10-19 21:41:05 +02:00
|
|
|
# - Section generators for the right side of the top bar
|
2020-10-21 12:39:23 +02:00
|
|
|
layout_right => [ ] ,
|
2020-10-19 21:41:05 +02:00
|
|
|
# - Section generators for the input bar
|
2020-10-21 12:39:23 +02:00
|
|
|
layout_input => [ qw( userhost cwd ) ] ,
|
2020-10-20 12:12:23 +02:00
|
|
|
# - Always generate input line?
|
2020-10-19 21:05:11 +02:00
|
|
|
layout_input_always => 0 ,
|
2022-10-22 18:31:35 +02:00
|
|
|
# - Add an empty line before the prompt? 0=no, 1=always, 2=not at the top
|
|
|
|
# of the terminal, 3=only if the previous command didn't finish with \n
|
|
|
|
layout_empty_line => 3 ,
|
2020-10-19 21:05:11 +02:00
|
|
|
|
2020-10-20 17:35:59 +02:00
|
|
|
# TERMINAL TITLE
|
|
|
|
# - Set title from the prompt? 0=no, 1=normal, 2=minimized, 3=both
|
|
|
|
term_set_title => 1 ,
|
|
|
|
# - Generators to use
|
2020-10-21 12:39:23 +02:00
|
|
|
term_generators => [ qw( userhost cwd ) ] ,
|
2020-10-20 17:35:59 +02:00
|
|
|
# - Separator
|
2020-10-21 12:39:23 +02:00
|
|
|
term_separator => ':' ,
|
2020-10-20 17:35:59 +02:00
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
# CURRENT WORKING DIRECTORY
|
|
|
|
# - Max width as a percentage of the terminal's width
|
2020-10-20 16:12:26 +02:00
|
|
|
cwd_max_width => 50 ,
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
# USER@HOST
|
|
|
|
# - Display username? 0=no, 1=yes
|
|
|
|
uh_username => 1 ,
|
|
|
|
# - Display hostname? 0=no, 1=always, 2=remote only
|
2020-10-21 12:39:23 +02:00
|
|
|
uh_hostname => 1 ,
|
2020-10-20 12:12:23 +02:00
|
|
|
# - Display symbol for remote hosts?
|
2020-10-21 12:39:23 +02:00
|
|
|
uh_remote => 0 ,
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
# DATE/TIME
|
|
|
|
# - Display date?
|
|
|
|
dt_show_date => 0 ,
|
|
|
|
# - Display time?
|
|
|
|
dt_show_time => 1 ,
|
|
|
|
# - Date format
|
|
|
|
dt_date_fmt => '%Y-%m-%d' ,
|
|
|
|
# - Time format
|
|
|
|
dt_time_fmt => '%H:%M' ,
|
|
|
|
|
|
|
|
# PREVIOUS COMMAND STATE
|
|
|
|
# - Display OK/failed symbol?
|
|
|
|
pcmd_show_symbol => 1 ,
|
|
|
|
# - Display status code? 0=no, 1=always, 2=on failure
|
2020-10-20 14:40:20 +02:00
|
|
|
pcmd_show_code => 2 ,
|
2020-10-19 21:05:11 +02:00
|
|
|
# - Pad status code display? 0 = no, -1 = left aligned, 1 = right aligned
|
2020-10-21 14:40:11 +02:00
|
|
|
pcmd_pad_code => 0 ,
|
2020-10-19 21:05:11 +02:00
|
|
|
# Success/failure colors for 0=nothing, 1=symbol, 2=code, 3=both
|
|
|
|
pcmd_colors => 1 ,
|
|
|
|
|
2022-10-22 15:56:58 +02:00
|
|
|
# JOBS
|
|
|
|
# - Always display?
|
|
|
|
jobs_always => 0 ,
|
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
# LOAD AVERAGE
|
|
|
|
# - Minimal load average before the section is displayed
|
2020-10-21 14:52:07 +02:00
|
|
|
load_min => 0 ,
|
2020-10-20 12:12:23 +02:00
|
|
|
|
|
|
|
# GIT
|
|
|
|
# - Branches for which the prompt should emit a strong warning
|
|
|
|
git_branch_danger => [ 'main' , 'master' ] ,
|
|
|
|
# - Branches for which the prompt should emit a weak warning
|
|
|
|
git_branch_warn => [ 'dev' , 'develop' ] ,
|
|
|
|
# - Warning mode for detached heads (0=none, 1=weak, 2=strong)
|
|
|
|
git_detached_warning => 2 ,
|
|
|
|
# - Show git status?
|
|
|
|
git_show_status => 1 ,
|
|
|
|
# - Show git stash count?
|
|
|
|
git_show_stash => 1 ,
|
2022-10-22 12:05:31 +02:00
|
|
|
|
|
|
|
# PYTHON
|
|
|
|
# - Display Python version (0=no, 1=if venv is set, 2=always)
|
|
|
|
pyenv_py_version => 1 ,
|
2020-10-19 21:05:11 +02:00
|
|
|
);
|
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
# Default theme -------------------------------------------------------------{{{
|
2020-10-20 13:51:43 +02:00
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
sub default_theme
|
2020-10-20 16:12:26 +02:00
|
|
|
{
|
2020-10-21 12:39:23 +02:00
|
|
|
return {
|
|
|
|
# Padding character
|
|
|
|
'padding' => ' ',
|
|
|
|
|
|
|
|
# Extra colors for transition strings
|
|
|
|
'transition' => [ 7 ] ,
|
|
|
|
# Left side of top line
|
2020-10-23 12:50:03 +02:00
|
|
|
'bg_left' => -2,
|
|
|
|
'fg_left' => -2,
|
2020-10-21 12:39:23 +02:00
|
|
|
'left_prefix' => '' ,
|
|
|
|
'left_separator' => ' ' ,
|
|
|
|
'left_suffix' => '\f2 | ' ,
|
|
|
|
# Middle of top line
|
2020-10-23 12:50:03 +02:00
|
|
|
'bg_middle' => -2,
|
|
|
|
'fg_middle' => -2,
|
2020-10-21 12:39:23 +02:00
|
|
|
'middle_prefix' => ' ',
|
|
|
|
'middle_separator' => ' ',
|
|
|
|
'middle_suffix' => ' ',
|
|
|
|
# Right side of top line
|
2020-10-23 12:50:03 +02:00
|
|
|
'bg_right' => -2,
|
|
|
|
'fg_right' => -2,
|
2020-10-21 12:39:23 +02:00
|
|
|
'right_prefix' => '\f2 | ' ,
|
|
|
|
'right_separator' => ' ' ,
|
|
|
|
'right_suffix' => '' ,
|
|
|
|
# Input line
|
2020-10-23 12:50:03 +02:00
|
|
|
'bg_input' => -2,
|
|
|
|
'fg_input' => -2,
|
2020-10-21 12:39:23 +02:00
|
|
|
'input_prefix' => '' ,
|
|
|
|
'input_separator' => '\f2:' ,
|
|
|
|
'input_suffix' => '\f2 ; ' ,
|
|
|
|
# Secondary prompt
|
2020-10-23 12:50:03 +02:00
|
|
|
'bg_ps2' => -2,
|
2020-10-21 12:39:23 +02:00
|
|
|
ps2_suffix => ' : ' ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
2022-10-22 18:31:35 +02:00
|
|
|
# Text appended to a line without EOL when layout_empty_line is 3
|
|
|
|
'noeol_text' => '<NO EOL>' ,
|
|
|
|
# Colors and style for the above text
|
|
|
|
'noeol_fg' => 1 ,
|
|
|
|
'noeol_bg' => -1 ,
|
|
|
|
'noeol_style' => 'b' ,
|
|
|
|
|
2020-10-20 16:12:26 +02:00
|
|
|
# Current working directory - Truncation string
|
2020-10-21 12:39:23 +02:00
|
|
|
cwd_trunc => '...' ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Current working directory - Foreground / background colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'cwd_fg_color' => 12 ,
|
|
|
|
'cwd_bg_color' => -1 ,
|
2020-10-23 21:06:35 +02:00
|
|
|
# Current working directory - Colors when directory is missing
|
|
|
|
'cwd_missing_fg_color' => 1 ,
|
|
|
|
'cwd_missing_bg_color' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
|
|
|
# User@host - Remote host symbol
|
2020-10-21 12:39:23 +02:00
|
|
|
'uh_remote_symbol' => '(r)',
|
2020-10-20 16:12:26 +02:00
|
|
|
# User@host - User - Foreground and background colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'uh_user_fg' => 10 ,
|
|
|
|
'uh_user_bg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# User@host - Root - Foreground and background colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'uh_root_fg' => 9 ,
|
|
|
|
'uh_root_bg' => -1 ,
|
2022-10-19 21:53:12 +02:00
|
|
|
# User@host - Hostname and remote host color
|
|
|
|
'uh_host_fg' => 10 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
|
|
|
# Date/time - Colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'dt_time_fg' => -1 ,
|
|
|
|
'dt_date_fg' => -1 ,
|
|
|
|
'dt_bg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
|
|
|
# Previous command state - Symbols
|
2020-10-21 14:40:11 +02:00
|
|
|
'pcmd_ok_sym' => 'O',
|
|
|
|
'pcmd_err_sym' => 'X',
|
2020-10-20 16:12:26 +02:00
|
|
|
# Previous command state - OK text / background color
|
2020-10-21 12:39:23 +02:00
|
|
|
'pcmd_ok_fg' => -1 ,
|
|
|
|
'pcmd_ok_bg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Previous command state - Error text / background color
|
2020-10-21 12:39:23 +02:00
|
|
|
'pcmd_err_fg' => -1 ,
|
|
|
|
'pcmd_err_bg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Previous command state - Other text foreground
|
2020-10-21 12:39:23 +02:00
|
|
|
'pcmd_text_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
2022-10-22 15:56:58 +02:00
|
|
|
# Job count - Prefix and suffix text
|
|
|
|
'jobs_prefix' => '&' ,
|
|
|
|
'jobs_suffix' => '',
|
|
|
|
# Job count - Background color
|
|
|
|
'jobs_bg' => -1 ,
|
|
|
|
# Job count - Style and foreground color for the job count
|
|
|
|
'jobs_count_style' => 'b' ,
|
|
|
|
'jobs_count_fg' => -1 ,
|
|
|
|
# Job count - Style and foreground color for the prefix
|
|
|
|
'jobs_prefix_style' => '' ,
|
|
|
|
'jobs_prefix_fg' => -1 ,
|
|
|
|
# Job count - Style and foreground color for the suffix
|
|
|
|
'jobs_suffix_style' => '' ,
|
|
|
|
'jobs_suffix_fg' => -1 ,
|
|
|
|
|
2020-10-20 16:12:26 +02:00
|
|
|
# Load average - Symbol or text
|
2020-10-21 12:39:23 +02:00
|
|
|
'load_title' => 'ld',
|
2020-10-20 16:12:26 +02:00
|
|
|
# Load average - Low load colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'load_low_fg' => -1,
|
|
|
|
'load_low_bg' => -1,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Load average - Medium load colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'load_med_fg' => -1 ,
|
|
|
|
'load_med_bg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Load average - High load colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'load_high_fg' => -1 ,
|
|
|
|
'load_high_bg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
# Git - Branch symbol
|
|
|
|
'git_branch_symbol' => 'B ',
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Branch colors - No warning
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_branch_ok_bg' => -1 ,
|
|
|
|
'git_branch_ok_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Branch colors - Weak warning
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_branch_warn_bg' => -1 ,
|
|
|
|
'git_branch_warn_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Branch colors - Strong warning
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_branch_danger_bg' => -1 ,
|
|
|
|
'git_branch_danger_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Repo state colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_repstate_bg' => -1 ,
|
|
|
|
'git_repstate_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Padding character for status sections
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_status_pad' => '' ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Untracked symbol and colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_untracked_symbol' => 'U ',
|
|
|
|
'git_untracked_bg' => -1 ,
|
|
|
|
'git_untracked_normal_fg' => -1 ,
|
|
|
|
'git_untracked_add_fg' => -1 ,
|
|
|
|
'git_untracked_mod_fg' => -1 ,
|
|
|
|
'git_untracked_del_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Indexed symbol and colors
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_indexed_symbol' => 'I ',
|
|
|
|
'git_indexed_bg' => -1 ,
|
|
|
|
'git_indexed_normal_fg' => -1 ,
|
|
|
|
'git_indexed_add_fg' => -1 ,
|
|
|
|
'git_indexed_mod_fg' => -1 ,
|
|
|
|
'git_indexed_del_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git - Add/modify/delete symbols
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_add_symbol' => '+' ,
|
|
|
|
'git_mod_symbol' => '~',
|
|
|
|
'git_del_symbol' => '-',
|
2020-10-20 16:12:26 +02:00
|
|
|
# Git stash symbol and color
|
2020-10-21 12:39:23 +02:00
|
|
|
'git_stash_symbol' => 'S ',
|
|
|
|
'git_stash_bg' => -1 ,
|
|
|
|
'git_stash_fg' => -1 ,
|
2020-10-20 16:12:26 +02:00
|
|
|
|
|
|
|
# Python virtual environment section colors
|
2022-10-22 11:18:31 +02:00
|
|
|
'pyenv_text' => 'PY:',
|
2022-10-22 12:05:31 +02:00
|
|
|
'pyenv_sep' => '/',
|
2020-10-21 12:39:23 +02:00
|
|
|
'pyenv_fg' => -1,
|
|
|
|
'pyenv_bg' => -1,
|
2020-10-20 16:12:26 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
# }}}
|
2020-10-21 09:45:17 +02:00
|
|
|
# }}}
|
|
|
|
# MAIN PROGRAM =============================================================={{{
|
2020-10-19 21:05:11 +02:00
|
|
|
|
2020-10-23 21:06:35 +02:00
|
|
|
our $HASCWD;
|
2020-10-21 09:45:17 +02:00
|
|
|
our $COLUMNS;
|
2022-10-19 19:19:39 +02:00
|
|
|
our $RESET;
|
2022-10-22 13:32:09 +02:00
|
|
|
our %INPUT = ();
|
2020-10-19 21:05:11 +02:00
|
|
|
our %TCCACHE = ();
|
2020-10-20 17:12:16 +02:00
|
|
|
our %TLEN = ();
|
|
|
|
our %SCACHE = ();
|
2020-10-21 12:39:23 +02:00
|
|
|
our %THEME = ();
|
2020-10-23 12:50:03 +02:00
|
|
|
our %STYLES = (
|
|
|
|
none => '' ,
|
|
|
|
b => 'bold' ,
|
2022-10-22 10:08:46 +02:00
|
|
|
d => 'dim' ,
|
2020-10-23 12:50:03 +02:00
|
|
|
u => 'smul' ,
|
|
|
|
i => 'sitm' ,
|
|
|
|
bu => 'bold,smul' ,
|
|
|
|
iu => 'sitm,smul' ,
|
|
|
|
);
|
2020-10-21 12:39:23 +02:00
|
|
|
|
|
|
|
# Terminal commands ---------------------------------------------------------{{{
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
sub tput_sequence
|
|
|
|
{
|
|
|
|
my $args = shift;
|
|
|
|
return $TCCACHE{ $args } if exists $TCCACHE{ $args };
|
|
|
|
open( my $fh , "tput $args|" );
|
|
|
|
my $value = <$fh>;
|
|
|
|
close( $fh );
|
2022-10-30 09:58:25 +01:00
|
|
|
$value =~ s/\033/\\033/g;
|
2020-10-19 21:05:11 +02:00
|
|
|
return ( $TCCACHE{ $args } = "\\[$value\\]" );
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_color
|
|
|
|
{
|
|
|
|
my ( $type , $index ) = @_;
|
|
|
|
return tput_sequence( "seta$type $index" );
|
|
|
|
}
|
|
|
|
|
2022-10-29 10:03:07 +02:00
|
|
|
sub flush_term_and_read_pos($)
|
|
|
|
{
|
|
|
|
my $ttyIn = shift;
|
|
|
|
my ($input, $col, $line) = ("", "", "");
|
|
|
|
my @pending = ();
|
|
|
|
my $state = 0;
|
|
|
|
while (sysread $ttyIn, $input, 1) {
|
|
|
|
if ($state == 0) {
|
|
|
|
if ($input eq "\033") {
|
|
|
|
$state = 1;
|
|
|
|
} else {
|
|
|
|
push @pending, $input;
|
|
|
|
}
|
|
|
|
} elsif ($state == 1) {
|
|
|
|
$state = 2 if $input eq '[';
|
|
|
|
} elsif ($state == 2) {
|
|
|
|
if ($input eq ';') {
|
|
|
|
$state = 3;
|
|
|
|
} else {
|
|
|
|
$line .= $input;
|
|
|
|
}
|
|
|
|
} elsif ($state == 3) {
|
|
|
|
last if $input eq 'R';
|
|
|
|
$col .= $input;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $col, $line, @pending;
|
|
|
|
}
|
|
|
|
|
2022-10-30 09:58:00 +01:00
|
|
|
sub term_open
|
2022-10-22 18:31:35 +02:00
|
|
|
{
|
|
|
|
open(my $ttyIn, '<:bytes' , '/dev/tty');
|
|
|
|
open(my $ttyOut, '>:bytes' , '/dev/tty');
|
|
|
|
my $term = POSIX::Termios->new;
|
2022-10-30 09:58:00 +01:00
|
|
|
return [$ttyIn, $ttyOut, $term];
|
|
|
|
}
|
|
|
|
|
|
|
|
sub term_close
|
|
|
|
{
|
|
|
|
my ($ttyIn, $ttyOut, $term) = @{ $_[0] };
|
|
|
|
close $ttyOut;
|
|
|
|
close $ttyIn;
|
|
|
|
@{ $_[0] } = ();
|
|
|
|
}
|
|
|
|
|
|
|
|
sub term_set_raw
|
|
|
|
{
|
|
|
|
my ($ttyIn, $ttyOut, $term) = @{ $_[0] };
|
2022-10-22 18:31:35 +02:00
|
|
|
my $ttyInFd = fileno $ttyIn;
|
|
|
|
$term->getattr($ttyInFd);
|
2022-10-30 09:58:00 +01:00
|
|
|
my $oTerm = $term->getlflag;
|
2022-10-22 18:31:35 +02:00
|
|
|
$term->setlflag($oTerm & ~( ECHO | ECHOK | ICANON ));
|
|
|
|
$term->setcc(VTIME, 1);
|
|
|
|
$term->setattr($ttyInFd, TCSANOW);
|
2022-10-30 09:58:00 +01:00
|
|
|
return $oTerm
|
|
|
|
}
|
2022-10-22 18:31:35 +02:00
|
|
|
|
2022-10-30 09:58:00 +01:00
|
|
|
sub term_set_cooked
|
|
|
|
{
|
|
|
|
my ($ttyIn, $ttyOut, $term) = @{ $_[0] };
|
|
|
|
my $oTerm = $_[1];
|
2022-10-22 18:31:35 +02:00
|
|
|
$term->setlflag($oTerm);
|
|
|
|
$term->setcc(VTIME, 0);
|
2022-10-30 09:58:00 +01:00
|
|
|
$term->setattr(fileno( $ttyIn ), TCSANOW);
|
|
|
|
}
|
2022-10-22 18:31:35 +02:00
|
|
|
|
2022-10-30 09:58:00 +01:00
|
|
|
# Fake input using TIOCSTI (0x5412)
|
|
|
|
sub term_feed
|
|
|
|
{
|
|
|
|
my ($ttyIn, $ttyOut, $term) = @{ $_[0] };
|
|
|
|
shift;
|
|
|
|
foreach my $pByte (@_) {
|
2022-10-30 10:02:20 +01:00
|
|
|
ioctl $ttyIn, 0x5412, $pByte;
|
2022-10-30 09:58:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_cursor_pos
|
|
|
|
{
|
|
|
|
my $term = term_open;
|
|
|
|
my $oTerm = term_set_raw($term);
|
|
|
|
|
|
|
|
# Read position then restore input
|
|
|
|
syswrite $term->[1], "\033[6n", 4;
|
|
|
|
my ($col, $line, @pending) = flush_term_and_read_pos $term->[0];
|
|
|
|
term_feed $term, @pending;
|
|
|
|
|
|
|
|
term_set_cooked($term, $oTerm);
|
|
|
|
term_close($term);
|
2022-10-22 18:31:35 +02:00
|
|
|
|
|
|
|
return $line, $col;
|
|
|
|
}
|
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
#}}}
|
|
|
|
# Theming support -----------------------------------------------------------{{{
|
|
|
|
|
|
|
|
sub thref($) { bless {r=>$_[0]}, 'ThemeRef'; }
|
|
|
|
|
2020-10-23 12:50:03 +02:00
|
|
|
sub TERM_DEFAULT() { -2 }
|
|
|
|
sub SECTION_DEFAULT() { -1 }
|
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
sub load_theme
|
|
|
|
{
|
|
|
|
my $theme = $CONFIG{layout_theme};
|
|
|
|
return default_theme unless $theme;
|
|
|
|
|
|
|
|
my $warn = $CONFIG{cfg_warn_files};
|
|
|
|
my @tdirs = (
|
|
|
|
( map { $ENV{HOME} . '/' . $_ } @{$CONFIG{cfg_user_themes}} ) ,
|
|
|
|
@{$CONFIG{cfg_sys_themes}}
|
|
|
|
);
|
|
|
|
foreach my $dir ( @tdirs ) {
|
|
|
|
my $path = "$dir/${theme}.pm";
|
|
|
|
next unless -f $path;
|
|
|
|
my $data = do $path;
|
|
|
|
if ( $@ ) {
|
|
|
|
warn "could not parse `$path': $@" if $warn;
|
|
|
|
} elsif ( !defined $data ) {
|
|
|
|
warn "could not do `$path': $!\n" if $warn;
|
|
|
|
} elsif ( !$data ) {
|
|
|
|
warn "could not run `$path'\n" if $warn;
|
|
|
|
} elsif ( ref( $data ) ne 'HASH' ) {
|
|
|
|
warn "`$path' does not contain a hash\n" if $warn;
|
|
|
|
} else {
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return default_theme;
|
|
|
|
}
|
|
|
|
|
2020-10-20 13:51:43 +02:00
|
|
|
sub theme_resolve
|
|
|
|
{
|
2020-10-21 12:39:23 +02:00
|
|
|
my ( $key , $stack ) = @_;
|
|
|
|
|
|
|
|
$stack = {} unless defined $stack;
|
|
|
|
if ( exists $stack->{ $key } ) {
|
|
|
|
warn "inifinite loop in theme ($key)\n";
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
$stack->{ $key } = 1;
|
|
|
|
|
|
|
|
my $value = $THEME{ $key };
|
2020-10-20 13:51:43 +02:00
|
|
|
if ( ref( $value ) eq 'ThemeRef' ) {
|
2020-10-21 12:39:23 +02:00
|
|
|
$THEME{ $key } = theme_resolve( $value->{r} );
|
|
|
|
$value = $THEME{ $key };
|
2020-10-20 13:51:43 +02:00
|
|
|
}
|
2020-10-21 12:39:23 +02:00
|
|
|
|
2020-10-20 13:51:43 +02:00
|
|
|
return $value;
|
|
|
|
}
|
2020-10-21 12:39:23 +02:00
|
|
|
|
2020-10-19 23:42:19 +02:00
|
|
|
sub themed($)
|
|
|
|
{
|
|
|
|
my $k = shift;
|
2020-10-21 12:39:23 +02:00
|
|
|
unless ( %THEME ) {
|
2022-10-22 11:23:31 +02:00
|
|
|
%THEME = (
|
|
|
|
%{ &default_theme } ,
|
|
|
|
%{ &load_theme } ,
|
|
|
|
%{ $CONFIG{layout_theme_overrides} }
|
|
|
|
);
|
2020-10-21 12:39:23 +02:00
|
|
|
my @to_resolve = grep { ref( $THEME{$_} ) eq 'ThemeRef' } keys %THEME;
|
2020-10-20 13:51:43 +02:00
|
|
|
foreach my $k ( @to_resolve ) {
|
2020-10-21 12:39:23 +02:00
|
|
|
theme_resolve( $k );
|
2020-10-20 13:51:43 +02:00
|
|
|
}
|
2020-10-19 23:42:19 +02:00
|
|
|
}
|
2020-10-21 12:39:23 +02:00
|
|
|
return $THEME{ $k };
|
2020-10-19 23:42:19 +02:00
|
|
|
}
|
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
#}}}
|
|
|
|
# Rendering utilities -------------------------------------------------------{{{
|
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
sub get_section_length
|
|
|
|
{
|
|
|
|
my $section = shift;
|
|
|
|
my $len = 0;
|
|
|
|
foreach my $item ( @{ $section->{content} } ) {
|
|
|
|
next if ref $item;
|
|
|
|
$len += length $item;
|
|
|
|
}
|
|
|
|
return $len;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_length
|
|
|
|
{
|
|
|
|
my $len = 0;
|
|
|
|
foreach my $section ( @_ ) {
|
|
|
|
$len += get_section_length( $section );
|
|
|
|
}
|
|
|
|
return $len;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub gen_transition
|
|
|
|
{
|
|
|
|
my $transition = shift;
|
2020-10-19 23:42:19 +02:00
|
|
|
my @colors = ( @_ , @{ themed 'transition' } );
|
2020-10-19 21:05:11 +02:00
|
|
|
my $state = 0;
|
|
|
|
my $pc;
|
2020-10-23 13:20:07 +02:00
|
|
|
my $out = [ {style=>'none'} ];
|
2020-10-19 21:05:11 +02:00
|
|
|
foreach my $char ( split // , $transition ) {
|
|
|
|
if ( $state == 1 ) {
|
|
|
|
if ( $char eq 'f' || $char eq 'b' ) {
|
|
|
|
$pc = $char;
|
|
|
|
$state = 2;
|
|
|
|
} else {
|
|
|
|
$state = 0;
|
|
|
|
push @$out , "\\$char";
|
|
|
|
}
|
|
|
|
} elsif ( $state == 2 ) {
|
|
|
|
$char = '0' unless $char =~ /^\d$/;
|
|
|
|
push @$out , { $pc . 'g' => $colors[ int($char) ] };
|
|
|
|
$state = 0;
|
|
|
|
} elsif ( $char eq '\\' ) {
|
|
|
|
$state = 1;
|
|
|
|
} else {
|
|
|
|
push @$out , $char;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return { content => $out };
|
|
|
|
}
|
|
|
|
|
|
|
|
sub compute_trans_lengths
|
|
|
|
{
|
|
|
|
my %out = ();
|
|
|
|
foreach my $side ( qw( left right input ) ) {
|
|
|
|
foreach my $type ( qw( prefix separator suffix ) ) {
|
|
|
|
my $k = $side . '_' . $type;
|
|
|
|
$out{ $k } = get_section_length( gen_transition(
|
2020-10-19 23:42:19 +02:00
|
|
|
themed $k , 1 , 2 ) );
|
2020-10-19 21:05:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return %out;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub gen_prompt_section
|
|
|
|
{
|
2022-10-29 14:15:24 +02:00
|
|
|
my ( $state , $section ) = @_;
|
|
|
|
unless ( exists $state->{ $section } ) {
|
|
|
|
no strict 'refs';
|
|
|
|
my $sFunc = 'readstate_' . $section;
|
|
|
|
$state->{ $section } = &$sFunc;
|
|
|
|
}
|
2020-10-20 17:12:16 +02:00
|
|
|
unless ( exists $SCACHE{ $section } ) {
|
|
|
|
no strict 'refs';
|
2022-10-29 14:15:24 +02:00
|
|
|
my $rFunc = 'render_' . $section;
|
|
|
|
$SCACHE{ $section } = [ &$rFunc( $state->{ $section } ) ];
|
2020-10-20 17:12:16 +02:00
|
|
|
}
|
|
|
|
return @{ $SCACHE{ $section } };
|
2020-10-19 21:05:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub gen_prompt_sections
|
|
|
|
{
|
2022-10-29 14:15:24 +02:00
|
|
|
my ( $state , $reverse , @input ) = @_;
|
2020-10-20 12:12:23 +02:00
|
|
|
@input = reverse @input if $reverse;
|
2020-10-19 21:05:11 +02:00
|
|
|
my @output = ( );
|
2020-10-20 12:12:23 +02:00
|
|
|
foreach my $section ( @input ) {
|
2022-10-29 14:15:24 +02:00
|
|
|
my @section = gen_prompt_section( $state , $section );
|
2020-10-20 12:12:23 +02:00
|
|
|
@section = reverse @section if $reverse;
|
|
|
|
@output = ( @output , @section );
|
2020-10-19 21:05:11 +02:00
|
|
|
}
|
|
|
|
return @output;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub adapt_to_width
|
|
|
|
{
|
|
|
|
my ( $length , $side , @input ) = @_;
|
|
|
|
my $minTransLen = $TLEN{ $side . '_prefix' } + $TLEN{ $side . '_suffix' };
|
|
|
|
my @output = ( );
|
|
|
|
foreach my $section ( @input ) {
|
|
|
|
my $slen = get_section_length( $section );
|
|
|
|
my $rlen = $minTransLen
|
|
|
|
+ scalar( @output ) * $TLEN{ $side . '_separator' };
|
|
|
|
last if $$length + $slen + $rlen > $COLUMNS;
|
|
|
|
push @output , $section;
|
|
|
|
$$length += $slen;
|
|
|
|
}
|
|
|
|
if ( @output ) {
|
|
|
|
$$length += $minTransLen
|
|
|
|
+ ( scalar( @output ) - 1 ) * $TLEN{ $side . '_separator' };
|
|
|
|
}
|
|
|
|
return @output;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add_transitions
|
|
|
|
{
|
|
|
|
my $name = shift;
|
|
|
|
my $cBefore = shift;
|
|
|
|
my $cAfter = shift;
|
|
|
|
|
2020-10-19 23:42:19 +02:00
|
|
|
my $prefix = themed( $name . '_prefix' );
|
|
|
|
my $separator = themed( $name . '_separator' );
|
|
|
|
my $suffix = themed( $name . '_suffix' );
|
2020-10-20 13:51:43 +02:00
|
|
|
my $bgDefault = themed( 'bg_' . $name );
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
my $prevBg = undef;
|
|
|
|
my $curBg = $cBefore;
|
|
|
|
my @out = ( );
|
|
|
|
foreach my $section ( @_ ) {
|
|
|
|
$prevBg = $curBg;
|
|
|
|
$curBg = ( exists $section->{bg} ) ? $section->{bg} : -1;
|
2020-10-20 13:51:43 +02:00
|
|
|
$curBg = $bgDefault if $curBg < 0;
|
2020-10-19 21:05:11 +02:00
|
|
|
my $trans = scalar(@out) ? $separator : $prefix;
|
|
|
|
@out = ( @out ,
|
|
|
|
gen_transition( $trans , $prevBg , $curBg ) ,
|
|
|
|
$section ,
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
@out = ( @out , gen_transition( $suffix , $curBg , $cAfter ) ) if @out;
|
|
|
|
return @out;
|
|
|
|
}
|
|
|
|
|
2020-10-23 12:50:03 +02:00
|
|
|
sub apply_style
|
|
|
|
{
|
|
|
|
my ( $bg , $fg , $style ) = @_;
|
2022-10-19 19:19:39 +02:00
|
|
|
my $out = $RESET;
|
2020-10-23 12:50:03 +02:00
|
|
|
$out .= set_color( 'b' , $bg ) unless $bg == -2;
|
|
|
|
$out .= set_color( 'f' , $fg ) unless $fg == -2;
|
|
|
|
if ( $style ne 'none' ) {
|
|
|
|
foreach my $ctrl ( split /,/, $STYLES{ $style } ) {
|
|
|
|
$out .= tput_sequence( $ctrl );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
sub render
|
|
|
|
{
|
|
|
|
my $name = shift;
|
|
|
|
|
|
|
|
my $out = '';
|
|
|
|
my $mustSetFg = undef;
|
|
|
|
my $mustSetBg = undef;
|
2020-10-23 12:50:03 +02:00
|
|
|
my $mustSetStyle = undef;
|
2020-10-20 13:51:43 +02:00
|
|
|
my $bgDefault = themed( 'bg_' . $name );
|
|
|
|
my $fgDefault = themed( 'fg_' . $name );
|
2020-10-23 12:50:03 +02:00
|
|
|
my ( $fg , $bg , $style ) = ( -2 , -2 , 'none' );
|
2020-10-19 21:05:11 +02:00
|
|
|
foreach my $section ( @_ ) {
|
|
|
|
$mustSetBg = $section->{bg} if exists $section->{bg};
|
|
|
|
foreach my $part ( @{ $section->{content} } ) {
|
|
|
|
if ( ref $part ) {
|
|
|
|
$mustSetBg = $part->{bg} if exists $part->{bg};
|
|
|
|
$mustSetFg = $part->{fg} if exists $part->{fg};
|
2020-10-23 12:50:03 +02:00
|
|
|
$mustSetStyle = $part->{style} if exists $part->{style};
|
2020-10-19 21:05:11 +02:00
|
|
|
} else {
|
2020-10-23 12:50:03 +02:00
|
|
|
# Check background color changes
|
2020-10-19 21:05:11 +02:00
|
|
|
if ( defined $mustSetBg ) {
|
2020-10-23 12:50:03 +02:00
|
|
|
$mustSetBg = $bgDefault if $mustSetBg == -1;
|
|
|
|
$mustSetBg = undef if $mustSetBg == $bg;
|
2020-10-19 21:05:11 +02:00
|
|
|
}
|
2020-10-23 12:50:03 +02:00
|
|
|
# Check foreground color changes
|
2020-10-19 21:05:11 +02:00
|
|
|
if ( defined $mustSetFg ) {
|
2020-10-23 12:50:03 +02:00
|
|
|
$mustSetFg = $fgDefault if $mustSetFg == -1;
|
|
|
|
$mustSetFg = undef if $mustSetFg == $fg;
|
|
|
|
}
|
|
|
|
# Check style changes
|
|
|
|
if ( defined( $mustSetStyle ) && ( $mustSetStyle eq $style
|
|
|
|
|| !exists( $STYLES{ $style } ) ) ) {
|
|
|
|
$mustSetStyle = undef;
|
|
|
|
}
|
|
|
|
# Change style and colors if necessary
|
|
|
|
if ( defined( $mustSetBg ) || defined( $mustSetFg )
|
|
|
|
|| defined( $mustSetStyle ) ) {
|
|
|
|
if ( defined $mustSetBg ) {
|
|
|
|
$bg = $mustSetBg;
|
|
|
|
$mustSetBg = undef;
|
|
|
|
}
|
|
|
|
if ( defined $mustSetFg ) {
|
|
|
|
$fg = $mustSetFg;
|
|
|
|
$mustSetFg = undef;
|
|
|
|
}
|
|
|
|
if ( defined $mustSetStyle ) {
|
|
|
|
$style = $mustSetStyle;
|
|
|
|
$mustSetStyle = undef;
|
|
|
|
}
|
|
|
|
$out .= apply_style( $bg , $fg , $style );
|
2020-10-19 21:05:11 +02:00
|
|
|
}
|
2020-10-19 21:18:01 +02:00
|
|
|
$part =~ s/\\/\\\\/g;
|
|
|
|
$part =~ s/"/\\\"/g;
|
2020-10-19 21:05:11 +02:00
|
|
|
$out .= $part;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
2020-10-21 12:39:23 +02:00
|
|
|
#}}}
|
|
|
|
# Prompt parts --------------------------------------------------------------{{{
|
|
|
|
|
2022-10-22 18:31:35 +02:00
|
|
|
sub gen_empty_line
|
|
|
|
{
|
|
|
|
my $lel = $CONFIG{layout_empty_line};
|
|
|
|
my $nl;
|
|
|
|
my $out = "";
|
|
|
|
if ($lel > 1) {
|
|
|
|
my ($line, $col) = get_cursor_pos;
|
|
|
|
$nl = ( $lel == 2 && $line != 1 ) || ( $lel == 3 && $col != 1 );
|
|
|
|
if ( $lel == 3 && $col != 1 ) {
|
|
|
|
$out .= render('input', {
|
|
|
|
content => [
|
|
|
|
{
|
|
|
|
style => themed 'noeol_style' ,
|
|
|
|
fg => themed 'noeol_fg' ,
|
|
|
|
bg => themed 'noeol_bg' ,
|
|
|
|
},
|
|
|
|
( themed 'noeol_text' )
|
|
|
|
]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$nl = $lel
|
|
|
|
}
|
|
|
|
$out .= "\\n" if $nl;
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
2020-10-19 21:05:11 +02:00
|
|
|
sub gen_top_line
|
|
|
|
{
|
2022-10-29 14:15:24 +02:00
|
|
|
my $state = shift;
|
2020-10-19 21:41:05 +02:00
|
|
|
my @left = @{ $CONFIG{layout_left} };
|
|
|
|
my @right = @{ $CONFIG{layout_right} };
|
|
|
|
my $midGen = $CONFIG{layout_middle};
|
2020-10-21 12:39:23 +02:00
|
|
|
return "" unless ( @left || @right || $midGen );
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
# Generate content
|
2020-10-23 21:06:35 +02:00
|
|
|
my @middle = ( );
|
2020-10-19 23:42:19 +02:00
|
|
|
my $mc = themed 'bg_middle';
|
2022-10-29 14:15:24 +02:00
|
|
|
@left = gen_prompt_sections( $state , 0 , @left );
|
2020-10-19 21:41:05 +02:00
|
|
|
if ( defined $midGen ) {
|
2022-10-29 14:15:24 +02:00
|
|
|
@middle = ( gen_prompt_section( $state , $midGen ) );
|
2020-10-19 21:05:11 +02:00
|
|
|
if ( @middle ) {
|
2020-10-23 21:06:35 +02:00
|
|
|
@middle = (
|
|
|
|
add_transitions( 'middle' , themed( 'bg_left' ) ,
|
|
|
|
themed( 'bg_right' ) , @middle )
|
2020-10-19 21:44:48 +02:00
|
|
|
);
|
2020-10-19 21:05:11 +02:00
|
|
|
foreach my $entry ( @middle ) {
|
|
|
|
delete $entry->{bg};
|
|
|
|
}
|
2020-10-23 21:06:35 +02:00
|
|
|
unshift @middle , { bg => themed('bg_middle') };
|
2020-10-19 21:05:11 +02:00
|
|
|
}
|
|
|
|
}
|
2022-10-29 14:15:24 +02:00
|
|
|
@right = gen_prompt_sections( $state , 1 , @right );
|
2020-10-19 21:05:11 +02:00
|
|
|
|
|
|
|
# Adapt to width
|
2020-10-23 21:06:35 +02:00
|