From 7375d30bf1b943c6adf1a6f986fe0b8a7b50524c Mon Sep 17 00:00:00 2001 From: LoricAndre <57358788+LoricAndre@users.noreply.github.com> Date: Sat, 11 Apr 2026 14:12:22 +0200 Subject: [PATCH] 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 --- man/man1/sk.1 | 21 +++----- shell/completion.bash | 2 +- shell/completion.fish | 4 +- shell/completion.nu | 6 +-- shell/completion.zsh | 2 +- src/options.rs | 19 ++++---- src/tui/app.rs | 5 ++ src/tui/input.rs | 29 +++++++---- src/tui/statusline.rs | 48 ++++++++++++------- tests/options.rs | 18 +++++++ .../options__opt_info_inline_custom@001.snap | 29 +++++++++++ .../options__opt_info_inline_custom@002.snap | 29 +++++++++++ .../options__opt_info_inline_right@001.snap | 29 +++++++++++ .../options__opt_info_inline_right@002.snap | 29 +++++++++++ ...ons__opt_info_inline_right_custom@001.snap | 29 +++++++++++ ...ons__opt_info_inline_right_custom@002.snap | 29 +++++++++++ 16 files changed, 266 insertions(+), 62 deletions(-) create mode 100644 tests/snapshots/options__opt_info_inline_custom@001.snap create mode 100644 tests/snapshots/options__opt_info_inline_custom@002.snap create mode 100644 tests/snapshots/options__opt_info_inline_right@001.snap create mode 100644 tests/snapshots/options__opt_info_inline_right@002.snap create mode 100644 tests/snapshots/options__opt_info_inline_right_custom@001.snap create mode 100644 tests/snapshots/options__opt_info_inline_right_custom@002.snap diff --git a/man/man1/sk.1 b/man/man1/sk.1 index 506ca886..64eeef14 100644 --- a/man/man1/sk.1 +++ b/man/man1/sk.1 @@ -377,21 +377,12 @@ Number of spaces that make up a tab \fB\-\-info\fR \fI\fR [default: default] Set matching result count display position - \- hidden: do not display info - \- inline: display info in the same row as the input - \- default: display info in a dedicated row above the input -.br - -.br -\fIPossible values:\fR -.RS 14 -.IP \(bu 2 -default -.IP \(bu 2 -inline -.IP \(bu 2 -hidden -.RE + \- hidden do not display info + \- inline[:SEP] display info in the same row as the input with an optional non\-default + 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 .TP \fB\-\-no\-info\fR Alias for \-\-info=hidden diff --git a/shell/completion.bash b/shell/completion.bash index 58f5b3eb..3a658d2a 100644 --- a/shell/completion.bash +++ b/shell/completion.bash @@ -154,7 +154,7 @@ _sk() { return 0 ;; --info) - COMPREPLY=($(compgen -W "default inline hidden" -- "${cur}")) + COMPREPLY=($(compgen -f "${cur}")) return 0 ;; --header) diff --git a/shell/completion.fish b/shell/completion.fish index 84014834..af41202c 100644 --- a/shell/completion.fish +++ b/shell/completion.fish @@ -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 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 info -d 'Set matching result count display position' -r -f -a "default\t'' -inline\t'' -hidden\t''" +complete -c sk -l info -d 'Set matching result count display position' -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 border -d 'Draw borders around the UI components' -r -f -a "force-off\t'ForceOff disables borders around popups too set with no_border' diff --git a/shell/completion.nu b/shell/completion.nu index 251a2e69..cd5cb8f4 100644 --- a/shell/completion.nu +++ b/shell/completion.nu @@ -20,10 +20,6 @@ module completions { [ "default" "reverse" "reverse-list" ] } - def "nu-complete sk info" [] { - [ "default" "inline" "hidden" ] - } - 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" ] } @@ -86,7 +82,7 @@ module completions { --ansi # Parse ANSI color codes in input strings --tabstop: string # Number of spaces that make up a tab --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 --inline-info # Alias for --info=inline --header: string # Set header, displayed next to the info diff --git a/shell/completion.zsh b/shell/completion.zsh index c2af7e91..1cde92a2 100644 --- a/shell/completion.zsh +++ b/shell/completion.zsh @@ -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' \ '--tabstop=[Number of spaces that make up a tab]:TABSTOP:_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-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" diff --git a/src/options.rs b/src/options.rs index 97c07240..0f10e435 100644 --- a/src/options.rs +++ b/src/options.rs @@ -501,18 +501,15 @@ pub struct SkimOptions { /// Set matching result count display position /// - /// - hidden: do not display info - /// - inline: display info in the same row as the input - /// - default: display info in a dedicated row above the input + /// - hidden do not display info + /// - inline[:SEP] display info in the same row as the input with an optional non-default + /// 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( feature = "cli", - arg( - long, - help_heading = "Display", - value_enum, - default_value = "default", - verbatim_doc_comment - ) + arg(long, help_heading = "Display", default_value = "default", verbatim_doc_comment) )] pub info: InfoDisplay, @@ -1215,7 +1212,7 @@ impl SkimOptions { self.scrollbar = String::new(); } if self.inline_info { - self.info = InfoDisplay::Inline; + self.info = InfoDisplay::Inline(crate::tui::statusline::DEFAULT_SEPARATOR.to_string()); } if self.no_info { self.info = InfoDisplay::Hidden; diff --git a/src/tui/app.rs b/src/tui/app.rs index ef7e30af..ff7a2ff1 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -174,6 +174,11 @@ impl Widget for &mut App { current_item_idx: self.item_list.current, hscroll_offset: i64::from(self.item_list.manual_hscroll), 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); diff --git a/src/tui/input.rs b/src/tui/input.rs index 6969c7e1..a84609d5 100644 --- a/src/tui/input.rs +++ b/src/tui/input.rs @@ -41,6 +41,8 @@ pub struct StatusInfo { pub hscroll_offset: i64, /// Start time for calculating spinner animation pub start: Option, + /// Inline prefix/separator (when the spinner is hidden) + pub inline_separator: String, } impl StatusInfo { @@ -86,16 +88,20 @@ impl StatusInfo { /// Get the inline separator character: spinner when active, '<' otherwise /// Used for Inline info display mode - pub fn inline_separator(&self) -> char { + pub fn inline_separator_or_spinner(&self) -> String { if self.show_spinner && let Some(start) = self.start { let spinner_elapsed_ms = start.elapsed().as_millis(); let index = ((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 { - '<' + self.inline_separator.clone() } } @@ -459,12 +465,12 @@ impl SkimWidget for Input { // Handle different info display modes match self.info_display { - InfoDisplay::Inline => { + InfoDisplay::Inline(_) | InfoDisplay::InlineRight(_) => { // Inline mode: render status on the same line as input // Format: prompt + value + " " + separator_char + " " + status + padding + right_status // separator_char is spinner when active, '<' otherwise 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 right_status = status.right_title(); @@ -472,7 +478,7 @@ impl SkimWidget for Input { // Format: " X " where X is separator (3 chars total) let prompt_width = display_width(&self.prompt); 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 right_status_width = display_width(&right_status); @@ -481,9 +487,16 @@ impl SkimWidget for Input { let available_width = u64::from(area.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::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)); Paragraph::new(line) diff --git a/src/tui/statusline.rs b/src/tui/statusline.rs index cb2a1a60..0191c4e2 100644 --- a/src/tui/statusline.rs +++ b/src/tui/statusline.rs @@ -1,7 +1,5 @@ -#[cfg(feature = "cli")] -use clap::ValueEnum; -#[cfg(feature = "cli")] -use clap::builder::PossibleValue; +/// Default inline info separator +pub const DEFAULT_SEPARATOR: &str = " < "; /// Display mode for the info/status line #[derive(Debug, Clone, Default, Eq, PartialEq)] @@ -10,24 +8,38 @@ pub enum InfoDisplay { #[default] Default, /// Display info inline with the input - Inline, + Inline(String), /// Hide the info display Hidden, + /// Inline and right-aligned + InlineRight(String), } -#[cfg(feature = "cli")] -impl ValueEnum for InfoDisplay { - fn value_variants<'a>() -> &'a [Self] { - use InfoDisplay::{Default, Hidden, Inline}; - &[Default, Inline, Hidden] - } - - fn to_possible_value(&self) -> Option { - use InfoDisplay::{Default, Hidden, Inline}; - match self { - Default => Some(PossibleValue::new("default")), - Inline => Some(PossibleValue::new("inline")), - Hidden => Some(PossibleValue::new("hidden")), +impl InfoDisplay { + pub(crate) fn separator(&self) -> Option { + if let InfoDisplay::Inline(s) = self { + Some(s.clone()) + } else if let InfoDisplay::InlineRight(s) = self { + Some(s.clone()) + } else { + None + } + } +} + +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`" + ), } } } diff --git a/tests/options.rs b/tests/options.rs index c6d72784..03d0faee 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -255,6 +255,24 @@ insta_test!(opt_info_inline, ["a", "b", "c"], &["--info", "inline"], { @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"], { @snap; @char 'a'; diff --git a/tests/snapshots/options__opt_info_inline_custom@001.snap b/tests/snapshots/options__opt_info_inline_custom@001.snap new file mode 100644 index 00000000..cdf00534 --- /dev/null +++ b/tests/snapshots/options__opt_info_inline_custom@001.snap @@ -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) diff --git a/tests/snapshots/options__opt_info_inline_custom@002.snap b/tests/snapshots/options__opt_info_inline_custom@002.snap new file mode 100644 index 00000000..1ae6f1b6 --- /dev/null +++ b/tests/snapshots/options__opt_info_inline_custom@002.snap @@ -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) diff --git a/tests/snapshots/options__opt_info_inline_right@001.snap b/tests/snapshots/options__opt_info_inline_right@001.snap new file mode 100644 index 00000000..9651c6a8 --- /dev/null +++ b/tests/snapshots/options__opt_info_inline_right@001.snap @@ -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) diff --git a/tests/snapshots/options__opt_info_inline_right@002.snap b/tests/snapshots/options__opt_info_inline_right@002.snap new file mode 100644 index 00000000..74a09d05 --- /dev/null +++ b/tests/snapshots/options__opt_info_inline_right@002.snap @@ -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) diff --git a/tests/snapshots/options__opt_info_inline_right_custom@001.snap b/tests/snapshots/options__opt_info_inline_right_custom@001.snap new file mode 100644 index 00000000..686596c6 --- /dev/null +++ b/tests/snapshots/options__opt_info_inline_right_custom@001.snap @@ -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) diff --git a/tests/snapshots/options__opt_info_inline_right_custom@002.snap b/tests/snapshots/options__opt_info_inline_right_custom@002.snap new file mode 100644 index 00000000..72bda9f5 --- /dev/null +++ b/tests/snapshots/options__opt_info_inline_right_custom@002.snap @@ -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)