mirror of
https://github.com/lotabout/skim.git
synced 2026-09-10 07:16:23 -04:00
feat: add min query length option (#806)
* feat: add min query length option * chore: generate completions & manpage * chore: fmt --------- Co-authored-by: LoricAndre <loric.andre@pm.me> Co-authored-by: Skim bot <skim-bot@skim-rs.github.io>
This commit is contained in:
parent
e66813518d
commit
71b82d0f58
28
AGENTS.md
Normal file
28
AGENTS.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Skim Agent Guidelines
|
||||
|
||||
## Build/Test/Lint Commands
|
||||
- Build: `cargo build [--release]`
|
||||
- Run: `cargo run [--release]`
|
||||
- Test (all): `cargo test`
|
||||
- Test (single): `cargo test test_name` or `cargo test -- test_name`
|
||||
- E2E tests: `cargo test -p e2e`
|
||||
- Lint: `cargo clippy`
|
||||
- Format: `cargo fmt` (check only: `cargo fmt --check`)
|
||||
|
||||
## Code Style
|
||||
- Format with 120 char line width (defined in .rustfmt.toml)
|
||||
- Use standard Rust naming conventions (snake_case for functions/variables, CamelCase for types)
|
||||
- Organize imports by standard library, external crates, then internal modules
|
||||
- Prefer Option/Result types for error handling over panicking
|
||||
- Use proper error propagation with `?` operator
|
||||
- Document public API with rustdoc comments
|
||||
- Use meaningful type annotations, especially for public functions
|
||||
- Follow the existing structure for new modules (see src/engine/ or src/model/)
|
||||
- Implement relevant traits (SkimItem, etc.) for new types when needed
|
||||
|
||||
## Project Structure
|
||||
- Core functionality in `skim/src/`
|
||||
- UI toolkit in `skim-tuikit/`
|
||||
- Common utilities in `skim-common/`
|
||||
- End-to-end tests in `e2e/`
|
||||
- Task automation in `xtask/`
|
||||
|
|
@ -49,6 +49,29 @@ fn opt_with_nth_preview() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opt_min_query_length() -> Result<()> {
|
||||
let (tmux, _) = setup("line1\nline2\nline3", &["--min-query-length", "3"])?;
|
||||
|
||||
// With empty query, no results should be shown
|
||||
let lines = tmux.capture()?;
|
||||
assert!(!lines.iter().any(|s| s.contains("line")));
|
||||
|
||||
// Type 'li' (2 chars), still no results should be shown
|
||||
tmux.send_keys(&[Key('l'), Key('i')])?;
|
||||
tmux.until(|l| l[0].starts_with("> li"))?;
|
||||
let lines = tmux.capture()?;
|
||||
assert!(!lines.iter().any(|s| s.contains("line")));
|
||||
|
||||
// Type 'n' (3rd char), now results should appear
|
||||
tmux.send_keys(&[Key('n')])?;
|
||||
tmux.until(|l| l[0].starts_with("> lin"))?;
|
||||
let lines = tmux.capture()?;
|
||||
assert!(lines.iter().any(|s| s.contains("line")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opt_with_nth_1() -> Result<()> {
|
||||
let (tmux, _) = setup("f1,f2,f3,f4", &["--delimiter", ",", "--with-nth", "1"])?;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ fn setup_tmux_mock(tmux: &TmuxController) -> Result<String> {
|
|||
let dir = &tmux.tempdir;
|
||||
let path = dir.path().join("tmux");
|
||||
let mock_bin = Path::new(&path);
|
||||
let mut writer = File::create_new(mock_bin)?;
|
||||
let mut writer = File::create(mock_bin)?;
|
||||
let outfile = dir.path().join("tmux-mock-cmd");
|
||||
writer.write_fmt(format_args!(
|
||||
"#!/bin/sh
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
.SH NAME
|
||||
sk \- sk \- fuzzy finder in Rust
|
||||
.SH SYNOPSIS
|
||||
\fBsk\fR [\fB\-\-tac\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\-\-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\-\-ansi\fR] [\fB\-\-tabstop\fR] [\fB\-\-info\fR] [\fB\-\-no\-info\fR] [\fB\-\-inline\-info\fR] [\fB\-\-header\fR] [\fB\-\-header\-lines\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\-\-expect\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\-\-tmux\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\-\-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\-\-ansi\fR] [\fB\-\-tabstop\fR] [\fB\-\-info\fR] [\fB\-\-no\-info\fR] [\fB\-\-inline\-info\fR] [\fB\-\-header\fR] [\fB\-\-header\-lines\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\-\-expect\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\-\-tmux\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR]
|
||||
.SH DESCRIPTION
|
||||
sk \- fuzzy finder in Rust
|
||||
.PP
|
||||
|
|
@ -71,6 +71,11 @@ Show results in reverse order
|
|||
|
||||
Often used in combination with \-\-no\-sort
|
||||
.TP
|
||||
\fB\-\-min\-query\-length\fR=\fIMIN_QUERY_LENGTH\fR
|
||||
Minimum query length to start showing results
|
||||
|
||||
Only show results when the query is at least this many characters long
|
||||
.TP
|
||||
\fB\-\-no\-sort\fR
|
||||
Do not sort the results
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,16 @@ _sk() {
|
|||
|
||||
case "${cmd}" in
|
||||
sk)
|
||||
opts="-t -n -d -e -b -m -c -i -I -p -q -1 -0 -f -x -h -V --tac --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 --layout --reverse --height --no-height --min-height --margin --prompt --cmd-prompt --ansi --tabstop --info --no-info --inline-info --header --header-lines --history --history-size --cmd-history --cmd-history-size --preview --preview-window --query --cmd-query --expect --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 --tmux --extended --literal --cycle --hscroll-off --filepath-word --jump-labels --border --no-bold --pointer --marker --phony --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 --layout --reverse --height --no-height --min-height --margin --prompt --cmd-prompt --ansi --tabstop --info --no-info --inline-info --header --header-lines --history --history-size --cmd-history --cmd-history-size --preview --preview-window --query --cmd-query --expect --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 --tmux --extended --literal --cycle --hscroll-off --filepath-word --jump-labels --border --no-bold --pointer --marker --phony --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
--min-query-length)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--tiebreak)
|
||||
COMPREPLY=($(compgen -W "score -score begin -begin end -end length -length index -index" -- "${cur}"))
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
complete -c sk -l min-query-length -d 'Minimum query length to start showing results' -r
|
||||
complete -c sk -s t -l tiebreak -d 'Comma-separated list of sort criteria to apply when the scores are tied' -r -f -a "score\t''
|
||||
-score\t''
|
||||
begin\t''
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ _sk() {
|
|||
|
||||
local context curcontext="$curcontext" state line
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--min-query-length=[Minimum query length to start showing results]:MIN_QUERY_LENGTH:_default' \
|
||||
'*-t+[Comma-separated list of sort criteria to apply when the scores are tied]:TIEBREAK:(score -score begin -begin end -end length -length index -index)' \
|
||||
'*--tiebreak=[Comma-separated list of sort criteria to apply when the scores are tied]:TIEBREAK:(score -score begin -begin end -end length -length index -index)' \
|
||||
'*-n+[Fields to be matched]:NTH:_default' \
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ pub struct Model {
|
|||
no_clear_if_empty: bool,
|
||||
theme: Arc<ColorTheme>,
|
||||
|
||||
// Minimum query length to show results
|
||||
min_query_length: Option<usize>,
|
||||
|
||||
// timer thread for scheduled events
|
||||
timer: Timer,
|
||||
hb_timer_guard: Option<TimerGuard>,
|
||||
|
|
@ -177,6 +180,7 @@ impl Model {
|
|||
info: InfoDisplay::Default,
|
||||
no_clear_if_empty: false,
|
||||
theme,
|
||||
min_query_length: options.min_query_length,
|
||||
timer: Timer::new(),
|
||||
hb_timer_guard: None,
|
||||
|
||||
|
|
@ -288,6 +292,15 @@ impl Model {
|
|||
}
|
||||
|
||||
fn act_heart_beat(&mut self, env: &mut ModelEnv) {
|
||||
// Check if query meets minimum length requirement
|
||||
if let Some(min_length) = self.min_query_length {
|
||||
if env.query.chars().count() < min_length {
|
||||
// Clear selection if query is too short
|
||||
self.selection.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// save the processed items
|
||||
let matcher_stopped = self
|
||||
.matcher_control
|
||||
|
|
@ -406,6 +419,19 @@ impl Model {
|
|||
if let Some(ctrl) = self.matcher_control.take() {
|
||||
ctrl.kill();
|
||||
}
|
||||
|
||||
// Check if query meets minimum length requirement
|
||||
if let Some(min_length) = self.min_query_length {
|
||||
if env.query.chars().count() < min_length {
|
||||
// Clear selection if query is too short
|
||||
self.selection.clear();
|
||||
// Don't restart matcher if query is too short
|
||||
self.item_pool.reset();
|
||||
self.num_options = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
env.clear_selection = ClearStrategy::Clear;
|
||||
self.item_pool.reset();
|
||||
self.num_options = 0;
|
||||
|
|
@ -737,6 +763,14 @@ impl Model {
|
|||
// send heart beat (so that heartbeat/refresh is triggered)
|
||||
let _ = self.tx.send((Key::Null, Event::EvHeartBeat));
|
||||
|
||||
// Check if query meets minimum length requirement
|
||||
if let Some(min_length) = self.min_query_length {
|
||||
if query.chars().count() < min_length {
|
||||
// Don't run matcher if query is too short
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let matcher = if self.use_regex {
|
||||
&self.regex_matcher
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,12 @@ pub struct SkimOptions {
|
|||
#[arg(long, help_heading = "Search")]
|
||||
pub tac: bool,
|
||||
|
||||
/// Minimum query length to start showing results
|
||||
///
|
||||
/// Only show results when the query is at least this many characters long
|
||||
#[arg(long, help_heading = "Search")]
|
||||
pub min_query_length: Option<usize>,
|
||||
|
||||
/// Do not sort the results
|
||||
///
|
||||
/// *Often used in combination with `--tac`*
|
||||
|
|
|
|||
Loading…
Reference in a new issue