feat: add more info variants (closes #1042) (#1048)

* feat: add more info variants (closes #1042)

* chore: generate completions & manpage

* docs: update info docs

---------

Co-authored-by: Skim bot <skim-bot@skim-rs.github.io>
This commit is contained in:
LoricAndre 2026-04-11 14:12:22 +02:00 committed by GitHub
parent 4997e2d253
commit 7375d30bf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 266 additions and 62 deletions

View file

@ -377,21 +377,12 @@ Number of spaces that make up a tab
\fB\-\-info\fR \fI<INFO>\fR [default: default] \fB\-\-info\fR \fI<INFO>\fR [default: default]
Set matching result count display position Set matching result count display position
\- hidden: do not display info \- hidden do not display info
\- inline: display info in the same row as the input \- inline[:SEP] display info in the same row as the input with an optional non\-default
\- default: display info in a dedicated row above the input separator
.br \- default display info in a dedicated row above the input
\- inline\-right[:SEP] display info in the same row as the input with an optional
.br non\-default separator
\fIPossible values:\fR
.RS 14
.IP \(bu 2
default
.IP \(bu 2
inline
.IP \(bu 2
hidden
.RE
.TP .TP
\fB\-\-no\-info\fR \fB\-\-no\-info\fR
Alias for \-\-info=hidden Alias for \-\-info=hidden

View file

@ -154,7 +154,7 @@ _sk() {
return 0 return 0
;; ;;
--info) --info)
COMPREPLY=($(compgen -W "default inline hidden" -- "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--header) --header)

View file

@ -44,9 +44,7 @@ complete -c sk -l selector -d 'Set selected item icon' -r
complete -c sk -l multi-selector -d 'Set selected item icon' -r complete -c sk -l multi-selector -d 'Set selected item icon' -r
complete -c sk -l tabstop -d 'Number of spaces that make up a tab' -r complete -c sk -l tabstop -d 'Number of spaces that make up a tab' -r
complete -c sk -l ellipsis -d 'The characters used to display truncated lines' -r complete -c sk -l ellipsis -d 'The characters used to display truncated lines' -r
complete -c sk -l info -d 'Set matching result count display position' -r -f -a "default\t'' complete -c sk -l info -d 'Set matching result count display position' -r
inline\t''
hidden\t''"
complete -c sk -l header -d 'Set header, displayed next to the info' -r complete -c sk -l header -d 'Set header, displayed next to the info' -r
complete -c sk -l header-lines -d 'Number of lines of the input treated as header' -r complete -c sk -l header-lines -d 'Number of lines of the input treated as header' -r
complete -c sk -l border -d 'Draw borders around the UI components' -r -f -a "force-off\t'ForceOff disables borders around popups too set with no_border' complete -c sk -l border -d 'Draw borders around the UI components' -r -f -a "force-off\t'ForceOff disables borders around popups too set with no_border'

View file

@ -20,10 +20,6 @@ module completions {
[ "default" "reverse" "reverse-list" ] [ "default" "reverse" "reverse-list" ]
} }
def "nu-complete sk info" [] {
[ "default" "inline" "hidden" ]
}
def "nu-complete sk border" [] { def "nu-complete sk border" [] {
[ "force-off" "none" "plain" "rounded" "double" "thick" "light-double-dashed" "heavy-double-dashed" "light-triple-dashed" "heavy-triple-dashed" "light-quadruple-dashed" "heavy-quadruple-dashed" "quadrant-inside" "quadrant-outside" ] [ "force-off" "none" "plain" "rounded" "double" "thick" "light-double-dashed" "heavy-double-dashed" "light-triple-dashed" "heavy-triple-dashed" "light-quadruple-dashed" "heavy-quadruple-dashed" "quadrant-inside" "quadrant-outside" ]
} }
@ -86,7 +82,7 @@ module completions {
--ansi # Parse ANSI color codes in input strings --ansi # Parse ANSI color codes in input strings
--tabstop: string # Number of spaces that make up a tab --tabstop: string # Number of spaces that make up a tab
--ellipsis: string # The characters used to display truncated lines --ellipsis: string # The characters used to display truncated lines
--info: string@"nu-complete sk info" # Set matching result count display position --info: string # Set matching result count display position
--no-info # Alias for --info=hidden --no-info # Alias for --info=hidden
--inline-info # Alias for --info=inline --inline-info # Alias for --info=inline
--header: string # Set header, displayed next to the info --header: string # Set header, displayed next to the info

View file

@ -56,7 +56,7 @@ reverse-list\:"Display from the top of the screen, prompt at the bottom"))' \
'--multi-selector=[Set selected item icon]:MULTI_SELECT_ICON:_default' \ '--multi-selector=[Set selected item icon]:MULTI_SELECT_ICON:_default' \
'--tabstop=[Number of spaces that make up a tab]:TABSTOP:_default' \ '--tabstop=[Number of spaces that make up a tab]:TABSTOP:_default' \
'--ellipsis=[The characters used to display truncated lines]:ELLIPSIS:_default' \ '--ellipsis=[The characters used to display truncated lines]:ELLIPSIS:_default' \
'--info=[Set matching result count display position]:INFO:(default inline hidden)' \ '--info=[Set matching result count display position]:INFO:_default' \
'--header=[Set header, displayed next to the info]:HEADER:_default' \ '--header=[Set header, displayed next to the info]:HEADER:_default' \
'--header-lines=[Number of lines of the input treated as header]:HEADER_LINES:_default' \ '--header-lines=[Number of lines of the input treated as header]:HEADER_LINES:_default' \
'--border=[Draw borders around the UI components]::BORDER:((force-off\:"ForceOff disables borders around popups too set with no_border" '--border=[Draw borders around the UI components]::BORDER:((force-off\:"ForceOff disables borders around popups too set with no_border"

View file

@ -501,18 +501,15 @@ pub struct SkimOptions {
/// Set matching result count display position /// Set matching result count display position
/// ///
/// - hidden: do not display info /// - hidden do not display info
/// - inline: display info in the same row as the input /// - inline[:SEP] display info in the same row as the input with an optional non-default
/// - default: display info in a dedicated row above the input /// separator
/// - default display info in a dedicated row above the input
/// - inline-right[:SEP] display info in the same row as the input with an optional
/// non-default separator
#[cfg_attr( #[cfg_attr(
feature = "cli", feature = "cli",
arg( arg(long, help_heading = "Display", default_value = "default", verbatim_doc_comment)
long,
help_heading = "Display",
value_enum,
default_value = "default",
verbatim_doc_comment
)
)] )]
pub info: InfoDisplay, pub info: InfoDisplay,
@ -1215,7 +1212,7 @@ impl SkimOptions {
self.scrollbar = String::new(); self.scrollbar = String::new();
} }
if self.inline_info { if self.inline_info {
self.info = InfoDisplay::Inline; self.info = InfoDisplay::Inline(crate::tui::statusline::DEFAULT_SEPARATOR.to_string());
} }
if self.no_info { if self.no_info {
self.info = InfoDisplay::Hidden; self.info = InfoDisplay::Hidden;

View file

@ -174,6 +174,11 @@ impl Widget for &mut App {
current_item_idx: self.item_list.current, current_item_idx: self.item_list.current,
hscroll_offset: i64::from(self.item_list.manual_hscroll), hscroll_offset: i64::from(self.item_list.manual_hscroll),
start: Some(self.spinner_start), start: Some(self.spinner_start),
inline_separator: self
.options
.info
.separator()
.unwrap_or(super::statusline::DEFAULT_SEPARATOR.to_string()),
}) })
}; };
res |= self.input.render(self.layout.input_area, buf); res |= self.input.render(self.layout.input_area, buf);

View file

@ -41,6 +41,8 @@ pub struct StatusInfo {
pub hscroll_offset: i64, pub hscroll_offset: i64,
/// Start time for calculating spinner animation /// Start time for calculating spinner animation
pub start: Option<Instant>, pub start: Option<Instant>,
/// Inline prefix/separator (when the spinner is hidden)
pub inline_separator: String,
} }
impl StatusInfo { impl StatusInfo {
@ -86,16 +88,20 @@ impl StatusInfo {
/// Get the inline separator character: spinner when active, '<' otherwise /// Get the inline separator character: spinner when active, '<' otherwise
/// Used for Inline info display mode /// Used for Inline info display mode
pub fn inline_separator(&self) -> char { pub fn inline_separator_or_spinner(&self) -> String {
if self.show_spinner if self.show_spinner
&& let Some(start) = self.start && let Some(start) = self.start
{ {
let spinner_elapsed_ms = start.elapsed().as_millis(); let spinner_elapsed_ms = start.elapsed().as_millis();
let index = let index =
((spinner_elapsed_ms / u128::from(SPINNER_DURATION)) % (SPINNERS_UNICODE.len() as u128)) as usize; ((spinner_elapsed_ms / u128::from(SPINNER_DURATION)) % (SPINNERS_UNICODE.len() as u128)) as usize;
SPINNERS_UNICODE[index] format!(
"{}{}",
SPINNERS_UNICODE[index],
" ".repeat(display_width(&self.inline_separator).try_into().unwrap())
)
} else { } else {
'<' self.inline_separator.clone()
} }
} }
@ -459,12 +465,12 @@ impl SkimWidget for Input {
// Handle different info display modes // Handle different info display modes
match self.info_display { match self.info_display {
InfoDisplay::Inline => { InfoDisplay::Inline(_) | InfoDisplay::InlineRight(_) => {
// Inline mode: render status on the same line as input // Inline mode: render status on the same line as input
// Format: prompt + value + " " + separator_char + " " + status + padding + right_status // Format: prompt + value + " " + separator_char + " " + status + padding + right_status
// separator_char is spinner when active, '<' otherwise // separator_char is spinner when active, '<' otherwise
if let Some(ref status) = self.status_info { if let Some(ref status) = self.status_info {
let separator = status.inline_separator(); let separator = status.inline_separator_or_spinner();
let inline_status = status.inline_status(); let inline_status = status.inline_status();
let right_status = status.right_title(); let right_status = status.right_title();
@ -472,7 +478,7 @@ impl SkimWidget for Input {
// Format: " X " where X is separator (3 chars total) // Format: " X " where X is separator (3 chars total)
let prompt_width = display_width(&self.prompt); let prompt_width = display_width(&self.prompt);
let value_width = display_width(&self.value); let value_width = display_width(&self.value);
let separator_width = 4; // " X " (2xspace + separator + space) let separator_width = display_width(&separator); // " X " (2xspace + separator + space)
let inline_status_width = display_width(&inline_status); let inline_status_width = display_width(&inline_status);
let right_status_width = display_width(&right_status); let right_status_width = display_width(&right_status);
@ -481,9 +487,16 @@ impl SkimWidget for Input {
let available_width = u64::from(area.width); let available_width = u64::from(area.width);
let padding_width = available_width.saturating_sub(used_width); let padding_width = available_width.saturating_sub(used_width);
line.push_span(Span::styled(format!(" {separator} "), self.theme.info)); if let InfoDisplay::InlineRight(_) = self.info_display {
line.push_span(Span::raw(" ".repeat(usize::try_from(padding_width).unwrap() - 2)));
}
line.push_span(Span::styled(separator, self.theme.info));
line.push_span(Span::styled(inline_status, self.theme.info)); line.push_span(Span::styled(inline_status, self.theme.info));
line.push_span(Span::raw(" ".repeat(padding_width.try_into().unwrap()))); if let InfoDisplay::Inline(_) = self.info_display {
line.push_span(Span::raw(" ".repeat(padding_width.try_into().unwrap())));
} else {
line.push_span(Span::raw(" ".repeat(2)));
}
line.push_span(Span::styled(right_status, self.theme.info)); line.push_span(Span::styled(right_status, self.theme.info));
Paragraph::new(line) Paragraph::new(line)

View file

@ -1,7 +1,5 @@
#[cfg(feature = "cli")] /// Default inline info separator
use clap::ValueEnum; pub const DEFAULT_SEPARATOR: &str = " < ";
#[cfg(feature = "cli")]
use clap::builder::PossibleValue;
/// Display mode for the info/status line /// Display mode for the info/status line
#[derive(Debug, Clone, Default, Eq, PartialEq)] #[derive(Debug, Clone, Default, Eq, PartialEq)]
@ -10,24 +8,38 @@ pub enum InfoDisplay {
#[default] #[default]
Default, Default,
/// Display info inline with the input /// Display info inline with the input
Inline, Inline(String),
/// Hide the info display /// Hide the info display
Hidden, Hidden,
/// Inline and right-aligned
InlineRight(String),
} }
#[cfg(feature = "cli")] impl InfoDisplay {
impl ValueEnum for InfoDisplay { pub(crate) fn separator(&self) -> Option<String> {
fn value_variants<'a>() -> &'a [Self] { if let InfoDisplay::Inline(s) = self {
use InfoDisplay::{Default, Hidden, Inline}; Some(s.clone())
&[Default, Inline, Hidden] } else if let InfoDisplay::InlineRight(s) = self {
} Some(s.clone())
} else {
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> { None
use InfoDisplay::{Default, Hidden, Inline}; }
match self { }
Default => Some(PossibleValue::new("default")), }
Inline => Some(PossibleValue::new("inline")),
Hidden => Some(PossibleValue::new("hidden")), impl From<&str> for InfoDisplay {
fn from(s: &str) -> Self {
use InfoDisplay::{Default, Hidden, Inline, InlineRight};
let (variant, separator) = s.split_once(':').unwrap_or((s, DEFAULT_SEPARATOR));
match variant {
"default" => Default,
"inline" => Inline(separator.to_string()),
"inline-right" => InlineRight(separator.to_string()),
"hidden" => Hidden,
x => panic!(
"Failed to parse {x} as an InfoDisplay. Possible options are `default`, `inline[:separator]`, `inline-right:[separator]` or `hidden`"
),
} }
} }
} }

View file

@ -255,6 +255,24 @@ insta_test!(opt_info_inline, ["a", "b", "c"], &["--info", "inline"], {
@snap; @snap;
}); });
insta_test!(opt_info_inline_right, ["a", "b", "c"], &["--info", "inline-right"], {
@snap;
@char 'a';
@snap;
});
insta_test!(opt_info_inline_custom, ["a", "b", "c"], &["--info", "inline:SEP"], {
@snap;
@char 'a';
@snap;
});
insta_test!(opt_info_inline_right_custom, ["a", "b", "c"], &["--info", "inline-right:SEP"], {
@snap;
@char 'a';
@snap;
});
insta_test!(opt_inline_info, ["a", "b", "c"], &["--inline-info"], { insta_test!(opt_inline_info, ["a", "b", "c"], &["--inline-info"], {
@snap; @snap;
@char 'a'; @char 'a';

View file

@ -0,0 +1,29 @@
---
source: tests/options.rs
description: "input: items [\"a\", \"b\", \"c\"]\noptions: --info inline:SEP"
---
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" c "
" b "
"> a "
"> SEP3/3 0/0"
cursor: (25, 3)

View file

@ -0,0 +1,29 @@
---
source: tests/options.rs
description: "input: items [\"a\", \"b\", \"c\"]\noptions: --info inline:SEP\nafter:\n @char 'a'"
---
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"> a "
"> aSEP1/3 0/0"
cursor: (25, 4)

View file

@ -0,0 +1,29 @@
---
source: tests/options.rs
description: "input: items [\"a\", \"b\", \"c\"]\noptions: --info inline-right"
---
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" c "
" b "
"> a "
"> < 3/3 0/0"
cursor: (25, 3)

View file

@ -0,0 +1,29 @@
---
source: tests/options.rs
description: "input: items [\"a\", \"b\", \"c\"]\noptions: --info inline-right\nafter:\n @char 'a'"
---
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"> a "
"> a < 1/3 0/0"
cursor: (25, 4)

View file

@ -0,0 +1,29 @@
---
source: tests/options.rs
description: "input: items [\"a\", \"b\", \"c\"]\noptions: --info inline-right:SEP"
---
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" c "
" b "
"> a "
"> SEP3/3 0/0"
cursor: (25, 3)

View file

@ -0,0 +1,29 @@
---
source: tests/options.rs
description: "input: items [\"a\", \"b\", \"c\"]\noptions: --info inline-right:SEP\nafter:\n @char 'a'"
---
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"> a "
"> a SEP1/3 0/0"
cursor: (25, 4)