feat: add print-header flag (and readd print-score) (closes #470)

This commit is contained in:
Loric ANDRE 2026-01-16 23:16:48 +01:00
parent 7c9b7194e0
commit f608ef9d24
13 changed files with 121 additions and 20 deletions

View file

@ -4,7 +4,7 @@
.SH NAME
sk \- Fuzzy Finder in rust!
.SH SYNOPSIS
\fBsk\fR [\fB\-\-tac\fR] [\fB\-\-min\-query\-length\fR] [\fB\-\-no\-sort\fR] [\fB\-t\fR|\fB\-\-tiebreak\fR] [\fB\-n\fR|\fB\-\-nth\fR] [\fB\-\-with\-nth\fR] [\fB\-d\fR|\fB\-\-delimiter\fR] [\fB\-e\fR|\fB\-\-exact\fR] [\fB\-\-regex\fR] [\fB\-\-algo\fR] [\fB\-\-case\fR] [\fB\-b\fR|\fB\-\-bind\fR] [\fB\-m\fR|\fB\-\-multi\fR] [\fB\-\-no\-multi\fR] [\fB\-\-no\-mouse\fR] [\fB\-c\fR|\fB\-\-cmd\fR] [\fB\-i\fR|\fB\-\-interactive\fR] [\fB\-I \fR] [\fB\-\-color\fR] [\fB\-\-no\-hscroll\fR] [\fB\-\-keep\-right\fR] [\fB\-\-skip\-to\-pattern\fR] [\fB\-\-no\-clear\-if\-empty\fR] [\fB\-\-no\-clear\-start\fR] [\fB\-\-no\-clear\fR] [\fB\-\-show\-cmd\-error\fR] [\fB\-\-cycle\fR] [\fB\-\-disabled\fR] [\fB\-\-layout\fR] [\fB\-\-reverse\fR] [\fB\-\-height\fR] [\fB\-\-no\-height\fR] [\fB\-\-min\-height\fR] [\fB\-\-margin\fR] [\fB\-p\fR|\fB\-\-prompt\fR] [\fB\-\-cmd\-prompt\fR] [\fB\-\-selector\fR] [\fB\-\-multi\-selector\fR] [\fB\-\-ansi\fR] [\fB\-\-tabstop\fR] [\fB\-\-info\fR] [\fB\-\-no\-info\fR] [\fB\-\-inline\-info\fR] [\fB\-\-header\fR] [\fB\-\-header\-lines\fR] [\fB\-\-border\fR] [\fB\-\-history\fR] [\fB\-\-history\-size\fR] [\fB\-\-cmd\-history\fR] [\fB\-\-cmd\-history\-size\fR] [\fB\-\-preview\fR] [\fB\-\-preview\-window\fR] [\fB\-q\fR|\fB\-\-query\fR] [\fB\-\-cmd\-query\fR] [\fB\-\-read0\fR] [\fB\-\-print0\fR] [\fB\-\-print\-query\fR] [\fB\-\-print\-cmd\fR] [\fB\-\-print\-score\fR] [\fB\-1\fR|\fB\-\-select\-1\fR] [\fB\-0\fR|\fB\-\-exit\-0\fR] [\fB\-\-sync\fR] [\fB\-\-pre\-select\-n\fR] [\fB\-\-pre\-select\-pat\fR] [\fB\-\-pre\-select\-items\fR] [\fB\-\-pre\-select\-file\fR] [\fB\-f\fR|\fB\-\-filter\fR] [\fB\-\-shell\fR] [\fB\-\-shell\-bindings\fR] [\fB\-\-man\fR] [\fB\-\-listen\fR] [\fB\-\-tmux\fR] [\fB\-\-log\-file\fR] [\fB\-\-expect\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR]
\fBsk\fR [\fB\-\-tac\fR] [\fB\-\-min\-query\-length\fR] [\fB\-\-no\-sort\fR] [\fB\-t\fR|\fB\-\-tiebreak\fR] [\fB\-n\fR|\fB\-\-nth\fR] [\fB\-\-with\-nth\fR] [\fB\-d\fR|\fB\-\-delimiter\fR] [\fB\-e\fR|\fB\-\-exact\fR] [\fB\-\-regex\fR] [\fB\-\-algo\fR] [\fB\-\-case\fR] [\fB\-b\fR|\fB\-\-bind\fR] [\fB\-m\fR|\fB\-\-multi\fR] [\fB\-\-no\-multi\fR] [\fB\-\-no\-mouse\fR] [\fB\-c\fR|\fB\-\-cmd\fR] [\fB\-i\fR|\fB\-\-interactive\fR] [\fB\-I \fR] [\fB\-\-color\fR] [\fB\-\-no\-hscroll\fR] [\fB\-\-keep\-right\fR] [\fB\-\-skip\-to\-pattern\fR] [\fB\-\-no\-clear\-if\-empty\fR] [\fB\-\-no\-clear\-start\fR] [\fB\-\-no\-clear\fR] [\fB\-\-show\-cmd\-error\fR] [\fB\-\-cycle\fR] [\fB\-\-disabled\fR] [\fB\-\-layout\fR] [\fB\-\-reverse\fR] [\fB\-\-height\fR] [\fB\-\-no\-height\fR] [\fB\-\-min\-height\fR] [\fB\-\-margin\fR] [\fB\-p\fR|\fB\-\-prompt\fR] [\fB\-\-cmd\-prompt\fR] [\fB\-\-selector\fR] [\fB\-\-multi\-selector\fR] [\fB\-\-ansi\fR] [\fB\-\-tabstop\fR] [\fB\-\-info\fR] [\fB\-\-no\-info\fR] [\fB\-\-inline\-info\fR] [\fB\-\-header\fR] [\fB\-\-header\-lines\fR] [\fB\-\-border\fR] [\fB\-\-history\fR] [\fB\-\-history\-size\fR] [\fB\-\-cmd\-history\fR] [\fB\-\-cmd\-history\-size\fR] [\fB\-\-preview\fR] [\fB\-\-preview\-window\fR] [\fB\-q\fR|\fB\-\-query\fR] [\fB\-\-cmd\-query\fR] [\fB\-\-read0\fR] [\fB\-\-print0\fR] [\fB\-\-print\-query\fR] [\fB\-\-print\-cmd\fR] [\fB\-\-print\-score\fR] [\fB\-\-print\-header\fR] [\fB\-1\fR|\fB\-\-select\-1\fR] [\fB\-0\fR|\fB\-\-exit\-0\fR] [\fB\-\-sync\fR] [\fB\-\-pre\-select\-n\fR] [\fB\-\-pre\-select\-pat\fR] [\fB\-\-pre\-select\-items\fR] [\fB\-\-pre\-select\-file\fR] [\fB\-f\fR|\fB\-\-filter\fR] [\fB\-\-shell\fR] [\fB\-\-shell\-bindings\fR] [\fB\-\-man\fR] [\fB\-\-listen\fR] [\fB\-\-tmux\fR] [\fB\-\-log\-file\fR] [\fB\-\-expect\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR]
.SH DESCRIPTION
sk \- fuzzy finder in Rust
.PP
@ -459,29 +459,49 @@ Replace replstr with the selected item in commands
Set color theme
Format: [BASE][,COLOR:ANSI[:ATTR1:ATTR2:..]]
Example:
Available themes:
* molokai: molokai 256color
* light: light 256color
* 16: dark base16 theme
* bw: black & white theme
* none: base color scheme
* dark | default: dark 256color, default value
Available color names:
* normal (or "" empty string): normal text
* matched (or hl): matched text
* current (or fg+): current line foreground
* bg+: current line background (special case, always sets background)
* current_match (or hl+): matched text in current line
* query: query text
* spinner: spinner character
* info: info text (match count)
* prompt: prompt text
* cursor (or pointer): cursor/pointer
* selected (or marker): selected item marker
* header: header text
* border: border lines
Adding `\-fg`, `_fg`, `\-bg`, `_bg`, `\-underline`, `_underline` sets the corresponding part of
@ -763,7 +783,10 @@ Print the query as the first line
Print the command as the first line (after print\-query)
.TP
\fB\-\-print\-score\fR
Print the command as the first line (after print\-cmd)
Print the score after each item
.TP
\fB\-\-print\-header\fR
Print the header as the first line (after print\-score)
.TP
\fB\-1\fR, \fB\-\-select\-1\fR
Automatically select the match if there is only one

View file

@ -23,7 +23,7 @@ _sk() {
case "${cmd}" in
sk)
opts="-t -n -d -e -b -m -c -i -I -p -q -1 -0 -f -x -h -V --tac --min-query-length --no-sort --tiebreak --nth --with-nth --delimiter --exact --regex --algo --case --bind --multi --no-multi --no-mouse --cmd --interactive --color --no-hscroll --keep-right --skip-to-pattern --no-clear-if-empty --no-clear-start --no-clear --show-cmd-error --cycle --disabled --layout --reverse --height --no-height --min-height --margin --prompt --cmd-prompt --selector --multi-selector --ansi --tabstop --info --no-info --inline-info --header --header-lines --border --history --history-size --cmd-history --cmd-history-size --preview --preview-window --query --cmd-query --read0 --print0 --print-query --print-cmd --print-score --select-1 --exit-0 --sync --pre-select-n --pre-select-pat --pre-select-items --pre-select-file --filter --shell --shell-bindings --man --listen --tmux --log-file --extended --literal --hscroll-off --filepath-word --jump-labels --no-bold --pointer --marker --phony --expect --help --version"
opts="-t -n -d -e -b -m -c -i -I -p -q -1 -0 -f -x -h -V --tac --min-query-length --no-sort --tiebreak --nth --with-nth --delimiter --exact --regex --algo --case --bind --multi --no-multi --no-mouse --cmd --interactive --color --no-hscroll --keep-right --skip-to-pattern --no-clear-if-empty --no-clear-start --no-clear --show-cmd-error --cycle --disabled --layout --reverse --height --no-height --min-height --margin --prompt --cmd-prompt --selector --multi-selector --ansi --tabstop --info --no-info --inline-info --header --header-lines --border --history --history-size --cmd-history --cmd-history-size --preview --preview-window --query --cmd-query --read0 --print0 --print-query --print-cmd --print-score --print-header --select-1 --exit-0 --sync --pre-select-n --pre-select-pat --pre-select-items --pre-select-file --filter --shell --shell-bindings --man --listen --tmux --log-file --extended --literal --hscroll-off --filepath-word --jump-labels --no-bold --pointer --marker --phony --expect --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0

View file

@ -90,7 +90,8 @@ complete -c sk -l read0 -d 'Read input delimited by ASCII NUL(\\0) characters'
complete -c sk -l print0 -d 'Print output delimited by ASCII NUL(\\0) characters'
complete -c sk -l print-query -d 'Print the query as the first line'
complete -c sk -l print-cmd -d 'Print the command as the first line (after print-query)'
complete -c sk -l print-score -d 'Print the command as the first line (after print-cmd)'
complete -c sk -l print-score -d 'Print the score after each item'
complete -c sk -l print-header -d 'Print the header as the first line (after print-score)'
complete -c sk -s 1 -l select-1 -d 'Automatically select the match if there is only one'
complete -c sk -s 0 -l exit-0 -d 'Automatically exit when no match is left'
complete -c sk -l sync -d 'Synchronous search for multi-staged filtering'

View file

@ -84,7 +84,8 @@ module completions {
--print0 # Print output delimited by ASCII NUL(\0) characters
--print-query # Print the query as the first line
--print-cmd # Print the command as the first line (after print-query)
--print-score # Print the command as the first line (after print-cmd)
--print-score # Print the score after each item
--print-header # Print the header as the first line (after print-score)
--select-1(-1) # Automatically select the match if there is only one
--exit-0(-0) # Automatically exit when no match is left
--sync # Synchronous search for multi-staged filtering

View file

@ -107,7 +107,8 @@ zsh\:"Zsh"))' \
'--print0[Print output delimited by ASCII NUL(\\0) characters]' \
'--print-query[Print the query as the first line]' \
'--print-cmd[Print the command as the first line (after print-query)]' \
'--print-score[Print the command as the first line (after print-cmd)]' \
'--print-score[Print the score after each item]' \
'--print-header[Print the header as the first line (after print-score)]' \
'-1[Automatically select the match if there is only one]' \
'--select-1[Automatically select the match if there is only one]' \
'-0[Automatically exit when no match is left]' \

View file

@ -144,6 +144,8 @@ fn sk_main(mut opts: SkimOptions) -> Result<i32> {
filter: opts.filter.clone(),
print_query: opts.print_query,
print_cmd: opts.print_cmd,
print_score: opts.print_score,
print_header: opts.print_header,
output_ending: String::from(if opts.print0 { "\0" } else { "\n" }),
};
@ -182,12 +184,19 @@ fn sk_main(mut opts: SkimOptions) -> Result<i32> {
print!("{}{}", result.cmd, bin_options.output_ending);
}
if bin_options.print_header {
print!("{}{}", result.header, bin_options.output_ending);
}
if let Event::Action(Action::Accept(Some(accept_key))) = result.final_event {
print!("{}{}", accept_key, bin_options.output_ending);
}
for item in &result.selected_items {
print!("{}{}", item.output(), bin_options.output_ending);
if bin_options.print_score {
print!("{}{}", item.rank[0], bin_options.output_ending);
}
}
std::io::stdout().flush()?;
@ -241,6 +250,8 @@ pub struct BinOptions {
output_ending: String,
print_query: bool,
print_cmd: bool,
print_score: bool,
print_header: bool,
}
/// Runs skim in filter mode, matching items against a fixed query without interactive UI

View file

@ -525,6 +525,7 @@ impl Skim {
query: app.input.to_string(),
is_abort,
selected_items: app.results(),
header: app.header.header,
})
}
}

View file

@ -900,10 +900,14 @@ pub struct SkimOptions {
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
pub print_cmd: bool,
/// Print the command as the first line (after print-cmd)
/// Print the score after each item
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
pub print_score: bool,
/// Print the header as the first line (after print-score)
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
pub print_header: bool,
/// Automatically select the match if there is only one
#[cfg_attr(feature = "cli", arg(long, short = '1', help_heading = "Scripting"))]
pub select_1: bool,
@ -1082,6 +1086,7 @@ impl Default for SkimOptions {
fn default() -> Self {
Self {
listen: None,
print_header: false,
disabled: false,
tac: Default::default(),
min_query_length: Default::default(),

View file

@ -1,4 +1,4 @@
use crate::SkimItem;
use crate::item::MatchedItem;
use crate::tui::Event;
use std::sync::Arc;
@ -23,5 +23,8 @@ pub struct SkimOutput {
pub cmd: String,
/// The selected items.
pub selected_items: Vec<Arc<dyn SkimItem>>,
pub selected_items: Vec<Arc<MatchedItem>>,
/// The header
pub header: String,
}

View file

@ -25,6 +25,7 @@ use which::which;
use crate::{
SkimItem, SkimOptions, SkimOutput,
item::MatchedItem,
tui::{Event, event::Action},
};
@ -266,10 +267,31 @@ pub fn run_with(opts: &SkimOptions) -> Option<SkimOutput> {
""
};
let mut output_lines: Vec<Arc<dyn SkimItem>> = vec![];
for line in stdout {
let header = if opts.print_header && status.success() {
stdout.next().expect("Not enough lines to unpack in downstream result")
} else {
""
}
.to_string();
let mut output_lines: Vec<Arc<MatchedItem>> = vec![];
while let Some(line) = stdout.next() {
debug!("Adding output line: {line}");
output_lines.push(Arc::new(SkimTmuxOutput { line: line.to_string() }));
let mut item = MatchedItem {
item: Arc::new(SkimTmuxOutput { line: line.to_string() }),
rank: [0; 5],
matched_range: None,
};
if opts.print_score {
item.rank = [
stdout.next().unwrap_or_default().parse().unwrap_or_default(),
0,
0,
0,
0,
];
}
output_lines.push(Arc::new(item));
}
let is_abort = !status.success();
@ -289,6 +311,7 @@ pub fn run_with(opts: &SkimOptions) -> Option<SkimOutput> {
query: query_str.to_string(),
cmd: command_str.to_string(),
selected_items: output_lines,
header,
};
Some(skim_output)
}

View file

@ -1059,18 +1059,15 @@ impl<'a> App<'a> {
}
/// Returns the selected items as results
pub fn results(&self) -> Vec<Arc<dyn SkimItem>> {
pub fn results(&self) -> Vec<Arc<MatchedItem>> {
if self.options.multi && !self.item_list.selection.is_empty() {
self.item_list
.selection
.iter()
.map(|item| {
debug!("res index: {}", item.get_index());
item.item.clone()
})
.map(|item| Arc::new(item.clone()))
.collect()
} else if let Some(sel) = self.item_list.selected() {
vec![sel]
} else if let Some(sel) = self.item_list.items.get(self.item_list.current) {
vec![Arc::new(sel.clone())]
} else {
vec![]
}

View file

@ -17,7 +17,8 @@ use unicode_width::UnicodeWidthChar;
/// Header widget for displaying static text above the item list
#[derive(Clone)]
pub struct Header {
header: String,
/// The header string with expanded tabstop, as will be displayed on screen
pub header: String,
theme: Arc<ColorTheme>,
}

View file

@ -303,6 +303,40 @@ sk_test!(opt_print_cmd_and_query, "10\\n20\\n30", &["--cmd-query", "cmd", "--pri
@output[2] trim().eq("20");
});
sk_test!(opt_print_header, "x", &["--header", "foo", "--print-header"], {
@capture[0] starts_with(">");
@keys Enter;
@output[0] trim().eq("foo");
@output[1] trim().eq("x");
});
sk_test!(opt_print_score, "x\\nyx\\nyz", &["--print-score"], {
@capture[0] starts_with(">");
@keys Key('x');
@capture[0] starts_with("> x");
@capture[1] trim().starts_with("2/3");
@keys Enter;
@output[0] trim().eq("x");
@output[1] trim().eq("-31");
});
sk_test!(opt_print_score_multi, "x\\nyx\\nyz", &["--print-score", "-m"], {
@capture[0] starts_with(">");
@keys Key('x');
@capture[0] starts_with("> x");
@capture[1] trim().starts_with("2/3");
@keys BTab;
@capture[2] trim().eq(">x");
@capture[3] trim().eq("> yx");
@keys BTab;
@capture[3] trim().eq(">>yx");
@keys Enter;
@output[0] trim().eq("x");
@output[1] trim().eq("-31");
@output[2] trim().eq("yx");
@output[3] trim().eq("-15");
});
sk_test!(opt_hscroll_begin, &format!("b{}", &["a"; 1000].join("")), &["-q", "b"], {
@capture[2] ends_with("..");
});