mirror of
https://github.com/lotabout/skim.git
synced 2026-09-10 07:16:23 -04:00
1536 lines
58 KiB
Rust
1536 lines
58 KiB
Rust
//! Configuration options for skim.
|
|
//!
|
|
//! This module provides the `SkimOptions` struct and builder for configuring
|
|
//! all aspects of skim's behavior, including search, display, layout, and interaction settings.
|
|
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
use derive_builder::Builder;
|
|
#[cfg(feature = "image")]
|
|
use ratatui_image::picker::Picker;
|
|
use regex::Regex;
|
|
|
|
use crate::binds::KeyMap;
|
|
use crate::item::RankCriteria;
|
|
use crate::prelude::SkimItemReader;
|
|
use crate::reader::CommandCollector;
|
|
use crate::tui::actions::Action;
|
|
use crate::tui::options::{PreviewLayout, TuiLayout};
|
|
use crate::tui::statusline::{Info, InfoDisplay};
|
|
use crate::tui::{BorderType, PreviewCallback};
|
|
use crate::util::read_file_lines;
|
|
use crate::{CaseMatching, FuzzyAlgorithm, Selector, Typos};
|
|
|
|
#[cfg(feature = "cli")]
|
|
/// Custom value parser for delimiter that handles escape sequences
|
|
fn parse_delimiter_value(s: &str) -> Result<Regex, String> {
|
|
let unescaped = crate::util::unescape_delimiter(s);
|
|
Regex::new(&unescaped).map_err(|e| format!("Invalid regex delimiter: {e}"))
|
|
}
|
|
|
|
/// Custom value parser for border
|
|
///
|
|
/// Any undefined value falls back to [`BorderType::Plain`] (see the `FromStr` impl in `tui`)
|
|
/// instead of producing a parse error like the default `ValueEnum` parser would, while still
|
|
/// advertising the known variants in `--help` and shell completions by delegating
|
|
/// [`possible_values`](clap::builder::TypedValueParser::possible_values) to
|
|
/// [`BorderType`]'s [`ValueEnum`](clap::ValueEnum) members.
|
|
#[cfg(feature = "cli")]
|
|
#[derive(Clone)]
|
|
struct BorderValueParser;
|
|
|
|
#[cfg(feature = "cli")]
|
|
impl clap::builder::TypedValueParser for BorderValueParser {
|
|
type Value = BorderType;
|
|
|
|
fn parse_ref(
|
|
&self,
|
|
_cmd: &clap::Command,
|
|
_arg: Option<&clap::Arg>,
|
|
value: &std::ffi::OsStr,
|
|
) -> Result<Self::Value, clap::Error> {
|
|
// `FromStr for BorderType` is infallible: unknown values map to `BorderType::Plain`.
|
|
Ok(value.to_string_lossy().parse().unwrap_or(BorderType::Plain))
|
|
}
|
|
|
|
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
|
|
Some(Box::new(
|
|
<BorderType as clap::ValueEnum>::value_variants()
|
|
.iter()
|
|
.filter_map(clap::ValueEnum::to_possible_value),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "cli")]
|
|
/// Custom value parser for typo tolerance
|
|
///
|
|
/// - `"smart"` → `Typos::Smart` (adaptive: `pattern_length` / 4)
|
|
/// - `"disabled"` → `Typos::Disabled`
|
|
/// - `"N"` (N >= 0) → `Typos::Fixed(N)`
|
|
fn parse_typos(s: &str) -> Result<Typos, String> {
|
|
if s.eq_ignore_ascii_case("smart") {
|
|
Ok(Typos::Smart)
|
|
} else if s.eq_ignore_ascii_case("disabled") {
|
|
Ok(Typos::Disabled)
|
|
} else {
|
|
s.parse::<usize>()
|
|
.map(Typos::from)
|
|
.map_err(|_| format!("Invalid typos value '{s}': expected 'smart', 'disabled' or a non-negative integer"))
|
|
}
|
|
}
|
|
|
|
/// The options for `--scheme`
|
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
|
|
pub enum MatchScheme {
|
|
/// Default scheme, no modifications to the options
|
|
#[default]
|
|
Default,
|
|
/// Path scheme: will find the furthest match in the item and set `pathname` as the main
|
|
/// tiebreak
|
|
Path,
|
|
/// History scheme: will force `index` as the first tiebreak
|
|
History,
|
|
}
|
|
|
|
/// Image rendering protocols
|
|
#[cfg(feature = "image")]
|
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
|
|
pub enum ImageProtocol {
|
|
/// Default: automatically detect the available backend at startup
|
|
#[default]
|
|
Detect,
|
|
/// Force halfblocks if you want blurry previews but a faster startup or if the detection fails
|
|
Halfblocks,
|
|
}
|
|
|
|
/// sk - fuzzy finder in Rust
|
|
///
|
|
/// sk is a general purpose command-line fuzzy finder.
|
|
#[allow(missing_docs, clippy::struct_excessive_bools)] // derive_builder seems to have issues with doc comments ?
|
|
#[derive(Builder)]
|
|
#[builder(build_fn(name = "final_build"), setter(into, strip_option))]
|
|
#[builder(default)]
|
|
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
command(name = "sk", args_override_self = true, verbatim_doc_comment, version, about)
|
|
)]
|
|
#[derive(derive_more::Debug)]
|
|
pub struct SkimOptions {
|
|
// --- Search ---
|
|
/// Show results in reverse order
|
|
///
|
|
/// Often used in combination with --no-sort
|
|
#[cfg_attr(feature = "cli", 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
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Search"))]
|
|
pub min_query_length: Option<usize>,
|
|
|
|
/// Do not sort the results
|
|
///
|
|
/// Often used in combination with --tac
|
|
/// Example: `history | sk --tac --no-sort`
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Search"))]
|
|
pub no_sort: bool,
|
|
|
|
/// Comma-separated list of sort criteria to apply when the scores are tied.
|
|
///
|
|
/// * **score**: Score of the fuzzy match algorithm
|
|
///
|
|
/// - Each criterion could be negated, e.g. (-index)
|
|
/// - Each criterion should appear only once in the list
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
short,
|
|
long,
|
|
default_value = "score,begin,end",
|
|
value_enum,
|
|
value_delimiter = ',',
|
|
help_heading = "Search",
|
|
allow_hyphen_values = true,
|
|
verbatim_doc_comment
|
|
)
|
|
)]
|
|
pub tiebreak: Vec<RankCriteria>,
|
|
|
|
/// Fields to be matched
|
|
///
|
|
/// A field index expression can be a non-zero integer or a range expression (`[BEGIN]..[END]`).
|
|
/// `--nth` and `--with-nth` take a comma-separated list of field index expressions.
|
|
///
|
|
/// **Examples:**
|
|
/// 1 The 1st field
|
|
/// 2 The 2nd field
|
|
/// -1 The last field
|
|
/// -2 The 2nd to last field
|
|
/// 3..5 From the 3rd field to the 5th field
|
|
/// 2.. From the 2nd field to the last field
|
|
/// ..-3 From the 1st field to the 3rd to the last field
|
|
/// .. All the fields
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
short,
|
|
long,
|
|
default_value = "",
|
|
help_heading = "Search",
|
|
verbatim_doc_comment,
|
|
value_delimiter = ',',
|
|
allow_hyphen_values = true,
|
|
)
|
|
)]
|
|
pub nth: Vec<String>,
|
|
|
|
/// Fields to be transformed
|
|
///
|
|
/// See **nth** for the details
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_value = "", help_heading = "Search", value_delimiter = ',')
|
|
)]
|
|
pub with_nth: Vec<String>,
|
|
|
|
/// Fields to hide from display while keeping them searchable
|
|
///
|
|
/// Takes the same comma-separated field index expressions as **nth**. The listed
|
|
/// fields are removed from the displayed line but remain part of the text used for
|
|
/// matching, so a query can still match them. Characters in the hidden fields are
|
|
/// ignored for match highlighting and horizontal scrolling.
|
|
///
|
|
/// See **nth** for the field index expression syntax.
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long,
|
|
default_value = "",
|
|
help_heading = "Search",
|
|
verbatim_doc_comment,
|
|
value_delimiter = ',',
|
|
allow_hyphen_values = true,
|
|
)
|
|
)]
|
|
pub hide_nth: Vec<String>,
|
|
|
|
/// Delimiter between fields
|
|
///
|
|
/// In regex format, defaults to AWK-style. Escape sequences like \x00, \t, \n are supported.
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(short, long, default_value = r"[\t\n ]+", value_parser = parse_delimiter_value, help_heading = "Search")
|
|
)]
|
|
pub delimiter: Regex,
|
|
|
|
/// Run in exact mode
|
|
#[cfg_attr(feature = "cli", arg(short, long, help_heading = "Search"))]
|
|
pub exact: bool,
|
|
|
|
/// Start in regex mode instead of fuzzy-match
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Search"))]
|
|
pub regex: bool,
|
|
|
|
/// Fuzzy matching algorithm
|
|
///
|
|
/// - arinae (ari) Latest algorithm
|
|
///
|
|
/// - `skim_v2` Legacy skim algorithm
|
|
///
|
|
/// - clangd Used in clangd for keyword completion
|
|
///
|
|
/// - fzy Algorithm from fzy (<https://github.com/jhawthorn/fzy>)
|
|
///
|
|
/// - frizbee Algorithm used in the blink.cmp neovim plugin
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long = "algo",
|
|
value_enum,
|
|
default_value = "arinae",
|
|
help_heading = "Search",
|
|
verbatim_doc_comment
|
|
)
|
|
)]
|
|
pub algorithm: FuzzyAlgorithm,
|
|
|
|
/// Case sensitivity
|
|
///
|
|
/// Determines whether or not to ignore case while matching
|
|
/// Note: this is not used for the Frizbee matcher, which uses a penalty system to favor
|
|
/// case-sensitivity without enforcing it
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_value = "smart", value_enum, help_heading = "Search")
|
|
)]
|
|
pub case: CaseMatching,
|
|
|
|
/// Enable typo-tolerant matching
|
|
///
|
|
/// When passed without a value (`--typos`), uses adaptive formula (`pattern_length` / 4).
|
|
/// When passed with a value (e.g. `--typos=2`), uses that exact number as the
|
|
/// maximum allowed typos. `--typos=0` explicitly disables typo tolerance.
|
|
/// Applies to both fzy and frizbee matchers.
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_value = "disabled", default_missing_value = "smart", num_args = 0..=1, value_parser = parse_typos, overrides_with = "no_typos", help_heading = "Search")
|
|
)]
|
|
pub typos: Typos,
|
|
|
|
/// Disable typo-tolerant matching
|
|
#[cfg_attr(feature = "cli", arg(long, overrides_with = "typos", help_heading = "Search"))]
|
|
pub no_typos: bool,
|
|
|
|
/// Normalize unicode characters
|
|
///
|
|
/// When set, normalize accents and other unicode diacritics/others
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Search"))]
|
|
pub normalize: bool,
|
|
|
|
/// Enable split matching and set delimiter
|
|
///
|
|
/// Split matching runs the matcher in splits: `foo:bar` will match all items matching `foo`, then
|
|
/// `:`, then `bar` if the delimiter is present, or match normally if not.
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long,
|
|
default_missing_value = ":",
|
|
help_heading = "Search",
|
|
num_args=0..
|
|
)
|
|
)]
|
|
pub split_match: Option<char>,
|
|
|
|
/// Highlight the last match found, not the first one
|
|
/// This makes tiebreak more pertinent on path items where we want to prioritize a match on the
|
|
/// last parts
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Search"))]
|
|
pub last_match: bool,
|
|
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Search", default_value = "default"))]
|
|
scheme: Option<MatchScheme>,
|
|
|
|
// --- Interface ---
|
|
/// Comma-separated key, event, and action bindings
|
|
///
|
|
/// `--bind` takes comma-separated `<trigger>:<action>` expressions. A trigger can be a key, the
|
|
/// `double-click` mouse binding, a finder event (`change`, `start`, `load`, `result`, `focus`, `zero`, or
|
|
/// `one`), or an action name. Use the
|
|
/// `act-` prefix for action triggers; it is recommended to avoid ambiguity and required when the action
|
|
/// name is also a key, for example `act-up:last`. See the [KEYBINDS] section for details and its
|
|
/// [Default key bindings] subsection for the defaults.
|
|
///
|
|
/// **Example**: `sk --bind=ctrl-j:accept,load:last,act-up:down`
|
|
///
|
|
/// ## Multiple actions can be chained using + separator.
|
|
///
|
|
/// **Example**: `sk --bind 'ctrl-a:select-all+accept'`
|
|
///
|
|
/// # Special behaviors
|
|
///
|
|
/// With `execute(...)` and `reload(...)` action, you can execute arbitrary commands without leaving sk.
|
|
/// For example, you can turn sk into a simple file browser by binding enter key to less command like follows:
|
|
///
|
|
/// ```bash
|
|
/// sk --bind "enter:execute(less {})"
|
|
/// ```
|
|
///
|
|
/// Note: if no argument is supplied to reload, the default command is run.
|
|
///
|
|
/// You can use the same placeholder expressions as in --preview.
|
|
///
|
|
/// `sk` switches to the alternate screen when executing a command. However, if the command is
|
|
/// expected to complete quickly, and you are not interested in its output, you might want to use
|
|
/// execute-silent instead, which silently executes the command without the switching. Note that sk
|
|
/// will not be responsive until the command is complete. For asynchronous execution, start your
|
|
/// command as a background process (i.e. appending `&`).
|
|
///
|
|
/// With the `if-query-empty` and `if-query-not-empty` actions, you could specify the action to execute
|
|
/// depending on the query condition. For example:
|
|
///
|
|
/// `sk --bind 'ctrl-d:if-query-empty(abort)+delete-char'`
|
|
///
|
|
/// If the query is empty, skim will execute abort action, otherwise execute delete-char action. It
|
|
/// is equal to 'delete-char/eof'.
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(short, long, help_heading = "Interface", verbatim_doc_comment, default_value = "", num_args=0..)
|
|
)]
|
|
pub bind: Vec<String>,
|
|
|
|
/// Enable multiple selection
|
|
///
|
|
/// Uses Tab and S-Tab by default for selection
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(short, long, overrides_with = "no_multi", help_heading = "Interface")
|
|
)]
|
|
pub multi: bool,
|
|
|
|
/// Disable multiple selection
|
|
#[cfg_attr(feature = "cli", arg(long, overrides_with = "multi", help_heading = "Interface"))]
|
|
pub no_multi: bool,
|
|
|
|
/// Disable mouse
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub no_mouse: bool,
|
|
|
|
/// Command to invoke dynamically in interactive mode
|
|
///
|
|
/// Will be invoked using `sh -c` on Unix-like systems and `cmd /c` on Windows
|
|
#[cfg_attr(feature = "cli", arg(short, long, help_heading = "Interface"))]
|
|
pub cmd: Option<String>,
|
|
|
|
/// Start skim in interactive mode
|
|
///
|
|
/// In interactive mode, sk will run the command specified by `--cmd` option and display the
|
|
/// results.
|
|
#[cfg_attr(feature = "cli", arg(short, long, help_heading = "Interface"))]
|
|
pub interactive: bool,
|
|
|
|
/// Replace replstr with the selected item in commands
|
|
#[cfg_attr(feature = "cli", arg(short = 'I', default_value = "{}", help_heading = "Interface"))]
|
|
pub replstr: String,
|
|
|
|
/// Set color theme
|
|
///
|
|
/// Format: [BASE][,COLOR:ANSI[:ATTR1:ATTR2:..]]
|
|
/// See [THEME] section for details
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface", verbatim_doc_comment))]
|
|
pub color: Option<String>,
|
|
|
|
/// Highlight the entire current line, not just the text
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface", verbatim_doc_comment))]
|
|
pub highlight_line: bool,
|
|
|
|
/// Disable horizontal scroll
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub no_hscroll: bool,
|
|
|
|
/// Keep the right end of the line visible on overflow
|
|
///
|
|
/// Effective only when the query string is empty
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub keep_right: bool,
|
|
|
|
/// Show the matched pattern at the line start
|
|
///
|
|
/// Line will start with the start of the matched pattern. Effective only when the query
|
|
/// string is empty. Was designed to skip showing starts of paths of rg/grep results.
|
|
///
|
|
/// e.g. sk -i -c "rg {q} --color=always" --skip-to-pattern '[^/]*:' --ansi
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface", verbatim_doc_comment))]
|
|
pub skip_to_pattern: Option<String>,
|
|
|
|
/// Do not clear previous line if the command returns an empty result
|
|
///
|
|
/// Do not clear previous items if new command returns empty result. This might be useful to
|
|
/// reduce flickering when typing new commands and the half-complete commands are not valid.
|
|
///
|
|
/// This is not the default behavior because similar use cases for `grep` and `rg` have already been
|
|
/// optimized where empty query results actually mean "empty" and previous results should be
|
|
/// cleared.
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface", verbatim_doc_comment))]
|
|
pub no_clear_if_empty: bool,
|
|
|
|
/// Do not clear items on start
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub no_clear_start: bool,
|
|
|
|
/// Do not clear screen on exit
|
|
///
|
|
/// Do not clear finder interface on exit. If skim was started in full screen mode, it will not switch back to the
|
|
/// original screen, so you'll have to manually run tput rmcup to return. This option can be used to avoid
|
|
/// flickering of the screen when your application needs to start skim multiple times in order.
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub no_clear: bool,
|
|
|
|
/// Show error message if command fails
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub show_cmd_error: bool,
|
|
|
|
/// Cycle the results by wrapping around when scrolling
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub cycle: bool,
|
|
|
|
/// Disable matching entirely
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub disabled: bool,
|
|
|
|
/// Disable items based on this regex pattern
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Interface"))]
|
|
pub disable_pattern: Option<Regex>,
|
|
|
|
// --- Layout ---
|
|
/// Set layout
|
|
///
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, help_heading = "Layout", verbatim_doc_comment, default_value = "default")
|
|
)]
|
|
pub layout: TuiLayout,
|
|
|
|
/// Shorthand for reverse layout
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Layout", overrides_with = "layout"))]
|
|
pub reverse: bool,
|
|
|
|
/// Height of skim's window
|
|
///
|
|
/// Can either be a row count or a percentage
|
|
/// A negative row count will use `term height` - `value` as height
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_value = "100%", help_heading = "Layout", allow_hyphen_values = true)
|
|
)]
|
|
pub height: String,
|
|
|
|
/// Disable height (force full screen)
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Layout"))]
|
|
pub no_height: bool,
|
|
|
|
/// Minimum height of skim's window
|
|
///
|
|
/// Useful when the height is set as a percentage
|
|
/// Ignored when --height is not specified
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_value = "10", help_heading = "Layout", verbatim_doc_comment)
|
|
)]
|
|
pub min_height: String,
|
|
|
|
/// Screen margin
|
|
///
|
|
/// For each side, can be either a row count or a percentage of the terminal size
|
|
///
|
|
/// Format can be one of:
|
|
/// - TRBL
|
|
/// - TB,RL
|
|
/// - T,RL,B
|
|
/// - T,R,B,L
|
|
/// Example: 1,10%
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_value = "0", help_heading = "Layout", verbatim_doc_comment)
|
|
)]
|
|
pub margin: String,
|
|
|
|
/// Set prompt
|
|
#[cfg_attr(feature = "cli", arg(long, short, default_value = "> ", help_heading = "Layout"))]
|
|
pub prompt: String,
|
|
|
|
/// Set prompt in command mode
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "c> ", help_heading = "Layout"))]
|
|
pub cmd_prompt: String,
|
|
|
|
/// Set selected item icon
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long = "selector", alias = "pointer", default_value = ">", help_heading = "Layout")
|
|
)]
|
|
pub selector_icon: String,
|
|
|
|
/// Set multi-selected item icon
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long = "multi-selector",
|
|
alias = "marker",
|
|
default_value = ">",
|
|
help_heading = "Layout"
|
|
)
|
|
)]
|
|
pub multi_select_icon: String,
|
|
|
|
// --- Display ---
|
|
/// Parse ANSI color codes in input strings
|
|
///
|
|
/// When using skim as a library, this has no effect and ansi parsing should
|
|
/// be enabled by manually injecting a `cmd_collector` like so:
|
|
/// ```rust
|
|
/// use skim::prelude::*;
|
|
///
|
|
/// let _options = SkimOptionsBuilder::default()
|
|
/// .cmd("ls --color")
|
|
/// .cmd_collector(Rc::new(RefCell::new(SkimItemReader::new(
|
|
/// SkimItemReaderOption::default().ansi(true),
|
|
/// ))) as Rc<RefCell<dyn CommandCollector>>)
|
|
/// .build()
|
|
/// .unwrap();
|
|
/// ```
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display"))]
|
|
pub ansi: bool,
|
|
|
|
/// Number of spaces that make up a tab
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "8", help_heading = "Display"))]
|
|
pub tabstop: usize,
|
|
|
|
/// The characters used to display truncated lines
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, hide = true, allow_hyphen_values = true, default_value = "...")
|
|
)]
|
|
pub ellipsis: String,
|
|
|
|
/// Set matching result count display position
|
|
///
|
|
/// - 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
|
|
/// - left display all info left-aligned in a dedicated row above the input
|
|
/// - right display all info right-aligned in a dedicated row above the input
|
|
/// - inline-right[:SEP] display info right-aligned in the same row as the input with an optional
|
|
/// non-default separator
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, help_heading = "Display", default_value = "default", verbatim_doc_comment)
|
|
)]
|
|
pub info: Info,
|
|
|
|
/// Alias for --info=hidden
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display"))]
|
|
pub no_info: bool,
|
|
|
|
/// Alias for --info=inline
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display"))]
|
|
pub inline_info: bool,
|
|
|
|
/// Set header, displayed next to the info
|
|
///
|
|
/// The given string will be printed as the sticky header. The lines are displayed in the
|
|
/// given order from top to bottom regardless of --layout option, and are not affected by
|
|
/// --with-nth. ANSI color codes are processed even when --ansi is not set.
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display"))]
|
|
pub header: Option<String>,
|
|
|
|
/// Number of lines of the input treated as header
|
|
///
|
|
/// The first N lines of the input are treated as the sticky header. When `--with-nth` is set,
|
|
/// the lines are transformed just like the other lines that follow.
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "0", help_heading = "Display"))]
|
|
pub header_lines: usize,
|
|
|
|
/// Draw borders around the UI components
|
|
///
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, default_missing_value = "plain", help_heading = "Display", default_value = "none", num_args=0.., value_parser = BorderValueParser)
|
|
)]
|
|
pub border: BorderType,
|
|
|
|
/// Do not collapse adjacent borders into a shared row or column
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display"))]
|
|
pub border_no_collapse: bool,
|
|
|
|
/// Disables all borders, including in tmux/zellij popups
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display", overrides_with = "border"))]
|
|
pub no_border: bool,
|
|
|
|
/// Wrap items in the item list
|
|
#[cfg_attr(feature = "cli", arg(long = "wrap", help_heading = "Display"))]
|
|
pub wrap_items: bool,
|
|
|
|
/// Split item text into multiple display lines at the given separator character
|
|
/// defaults to `\n` if `read0` is set, and `\\n` if not (matching literal `\n` in text)
|
|
///
|
|
/// Each item's text will be split on the separator and each part will be
|
|
/// displayed as a separate line within that item's row.
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long = "multiline",
|
|
help_heading = "Display",
|
|
num_args = 0..=1
|
|
)
|
|
)]
|
|
pub multiline: Option<Option<String>>,
|
|
|
|
/// Set scrollbar style for the item list
|
|
///
|
|
/// The optional value is used as the indicator
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long,
|
|
help_heading = "Display",
|
|
value_name = "THUMB",
|
|
overrides_with = "no_scrollbar",
|
|
default_value = "▐",
|
|
verbatim_doc_comment
|
|
)
|
|
)]
|
|
pub scrollbar: String,
|
|
/// Disable the scrollbar in the item list
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Display"))]
|
|
pub no_scrollbar: bool,
|
|
|
|
// --- History ---
|
|
/// History file
|
|
///
|
|
/// Load search history from the specified file and update the file on completion.
|
|
///
|
|
/// When enabled, CTRL-N and CTRL-P are automatically remapped
|
|
/// to next-history and previous-history.
|
|
#[cfg_attr(feature = "cli", arg(long = "history", help_heading = "History"))]
|
|
pub history_file: Option<String>,
|
|
|
|
/// Maximum number of query history entries to keep
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "1000", help_heading = "History"))]
|
|
pub history_size: usize,
|
|
|
|
/// Command history file
|
|
///
|
|
/// Load command query history from the specified file and update the file on completion.
|
|
///
|
|
/// When enabled, CTRL-N and CTRL-P are automatically remapped
|
|
/// to next-history and previous-history.
|
|
#[cfg_attr(feature = "cli", arg(long = "cmd-history", help_heading = "History"))]
|
|
pub cmd_history_file: Option<String>,
|
|
|
|
/// Maximum number of query history entries to keep
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "1000", help_heading = "History"))]
|
|
pub cmd_history_size: usize,
|
|
|
|
// --- Preview ---
|
|
/// Preview command
|
|
///
|
|
/// Execute the given command with `sh -c` on linux and `cmd /c` on windows for the current line and display the result on the preview window.
|
|
/// `{}` in the command is the placeholder that is replaced to the single-quoted string of the current line.
|
|
/// To transform the replacement string, specify field index expressions between the braces (See FIELD INDEX EXPRESSION for the details).
|
|
///
|
|
/// **Examples**:
|
|
///
|
|
/// ```bash
|
|
/// sk --preview='head -$LINES {}'
|
|
/// ls -l | sk --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Preview", verbatim_doc_comment))]
|
|
pub preview: Option<String>,
|
|
|
|
/// Preview window layout
|
|
///
|
|
/// Format: [up|down|left|right][:SIZE][:hidden][:[no]wrap][:[no]pty][:+SCROLL[-OFFSET]]
|
|
///
|
|
/// Determine the layout of the preview window. If the argument ends with: hidden, the preview window will be hidden by
|
|
/// default until toggle-preview action is triggered. Long lines are truncated by default.
|
|
/// Line wrap can be enabled with `:wrap` flag.
|
|
/// For more interactive commands or previews that draw complex interfaces, the preview can use a PTY with the `:pty` flag.
|
|
///
|
|
/// Note: the preview will run in a PTY (interactive session) on Linux and when `wrap` is unset
|
|
///
|
|
/// SIZE can be either:
|
|
/// - `0`, which will hide the preview window
|
|
/// - A positive size (eg `20`)
|
|
/// - A percentage of the total size (eg `50%`)
|
|
/// - A negative size, which will set the size of everything but the preview to that value
|
|
///
|
|
/// +SCROLL[-OFFSET] determines the initial scroll offset of the preview window. SCROLL can be either a numeric integer
|
|
/// or a single-field index expression that refers to a numeric integer. The optional -OFFSET part is for adjusting the
|
|
/// base offset so that you can see the text above it. It should be given as a numeric integer (-INTEGER), or as a
|
|
/// denominator form (-/INTEGER) for specifying a fraction of the preview window height.
|
|
///
|
|
/// **Examples**:
|
|
/// ```bash
|
|
/// # Non-default scroll window positions and sizes
|
|
/// sk --preview="head {}" --preview-window=up:30%
|
|
/// sk --preview="file {}" --preview-window=down:2
|
|
///
|
|
/// # Initial scroll offset is set to the line number of each line of
|
|
/// # git grep output *minus* 5 lines (-5)
|
|
/// git grep --line-number '' |
|
|
/// sk --delimiter: --preview 'nl {1}' --preview-window +{2}-5
|
|
///
|
|
/// # Preview with bat, matching line in the middle of the window (-/2)
|
|
/// git grep --line-number '' |
|
|
/// sk --delimiter : \
|
|
/// --preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \
|
|
/// --preview-window +{2}-/2
|
|
/// ```
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(
|
|
long,
|
|
default_value = "right:50%",
|
|
help_heading = "Preview",
|
|
allow_hyphen_values = true
|
|
)
|
|
)]
|
|
pub preview_window: PreviewLayout,
|
|
|
|
/// Enable image preview
|
|
///
|
|
/// This will render the preview argument as an image instead of running it as a command.
|
|
///
|
|
/// If set to `detect` or if no value is passed, it will try to detect the available image backends at startup, which will add a small
|
|
/// delay before the first render.
|
|
/// If set to `halfblocks`, it will always use the `halfblocks` rendering method
|
|
///
|
|
/// Note: the backend detection **will not** work when piping data into skim, use
|
|
/// `SKIM_DEFAULT_COMMAND="find . -type f" sk --image` instead of `find . -type f | sk --image`
|
|
#[cfg(feature = "image")]
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, help_heading = "Preview", value_enum, default_missing_value = "detect", num_args=0..)
|
|
)]
|
|
pub image: Option<ImageProtocol>,
|
|
|
|
/// Terminal image protocol picker, queried after entering the alternate screen.
|
|
/// Built from `options.image` and an stdio detection if needed
|
|
#[cfg(feature = "image")]
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
#[builder(setter(skip))]
|
|
#[debug(skip)]
|
|
pub image_picker: Option<Picker>,
|
|
|
|
// --- Scripting ---
|
|
/// Initial query
|
|
#[cfg_attr(feature = "cli", arg(long, short, help_heading = "Scripting"))]
|
|
pub query: Option<String>,
|
|
|
|
/// Initial query in interactive mode
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub cmd_query: Option<String>,
|
|
|
|
/// Read input delimited by ASCII NUL(\\0) characters
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub read0: bool,
|
|
|
|
/// Print output delimited by ASCII NUL(\\0) characters
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub print0: bool,
|
|
|
|
/// Print the query as the first line
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub print_query: bool,
|
|
|
|
/// Print the command as the first line (after print-query)
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub print_cmd: bool,
|
|
|
|
/// 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,
|
|
|
|
/// Print the current (highlighted) item as the first line (after print-header)
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub print_current: bool,
|
|
|
|
/// Set the output format
|
|
/// If set, overrides all `print_` options
|
|
/// Will be expanded the same way as preview or commands
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub output_format: Option<String>,
|
|
|
|
/// Print the ANSI codes, making the output exactly match the input even when `--ansi` is on
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting", requires = "ansi"))]
|
|
pub no_strip_ansi: bool,
|
|
|
|
/// Do not enter the TUI if the query passed in `-q` matches only one item and return it
|
|
#[cfg_attr(feature = "cli", arg(long, short = '1', help_heading = "Scripting"))]
|
|
pub select_1: bool,
|
|
|
|
/// Do not enter the TUI if the query passed in `-q` does not match any item
|
|
#[cfg_attr(feature = "cli", arg(long, short = '0', help_heading = "Scripting"))]
|
|
pub exit_0: bool,
|
|
|
|
/// Synchronous search for multi-staged filtering
|
|
///
|
|
/// Synchronous search for multi-staged filtering. If specified,
|
|
/// `skim` will launch the TUI finder only after the input stream is complete.
|
|
/// e.g. `sk --multi | sk --sync`
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub sync: bool,
|
|
|
|
/// Pre-select the first n items in multi-selection mode
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "0", help_heading = "Scripting"))]
|
|
pub pre_select_n: usize,
|
|
|
|
/// Pre-select the matched items in multi-selection mode
|
|
///
|
|
/// Check the doc for the detailed syntax:
|
|
/// <https://docs.rs/regex/1.4.1/regex>/
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "", help_heading = "Scripting"))]
|
|
pub pre_select_pat: String,
|
|
|
|
/// Pre-select the items separated by newline character
|
|
///
|
|
/// Example: 'item1\nitem2'
|
|
#[cfg_attr(feature = "cli", arg(long, default_value = "", help_heading = "Scripting"))]
|
|
pub pre_select_items: String,
|
|
|
|
/// Pre-select the items read from this file
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub pre_select_file: Option<String>,
|
|
|
|
/// Query for filter mode
|
|
#[cfg_attr(feature = "cli", arg(long, short, help_heading = "Scripting"))]
|
|
pub filter: Option<String>,
|
|
|
|
/// Generate shell completion script
|
|
///
|
|
/// Generate completion script for the specified shell: bash, zsh, fish, etc.
|
|
/// The output can be directly sourced or saved to a file for automatic loading.
|
|
/// Examples: `source <(sk --shell bash)` (immediate use)
|
|
/// `sk --shell bash >> ~/.bash_completion` (persistent use)
|
|
///
|
|
/// Supported shells: bash, zsh, fish, powershell, elvish
|
|
#[cfg(feature = "cli")]
|
|
#[cfg_attr(
|
|
feature = "cli",
|
|
arg(long, value_name = "SHELL", help_heading = "Scripting", value_enum)
|
|
)]
|
|
pub shell: Option<crate::shell::Shell>,
|
|
|
|
/// Generate shell key bindings - only for bash, zsh and fish
|
|
///
|
|
/// Generate key bindings script after the shell completions
|
|
/// See the `shell` option for more details
|
|
#[cfg(feature = "cli")]
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting", requires = "shell"))]
|
|
pub shell_bindings: bool,
|
|
|
|
/// Generate man page and output it to stdout
|
|
#[cfg(feature = "cli")]
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub man: bool,
|
|
|
|
/// Run an IPC socket with optional name (defaults to `sk`)
|
|
///
|
|
/// The socket expects Actions in Ron format (similar to Rust code), see `./src/tui/event.rs` for all possible Actions
|
|
/// To write to it, see the `--remote` option or the man page
|
|
#[cfg(feature = "listen")]
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting", default_missing_value = "sk", num_args=0..))]
|
|
pub listen: Option<String>,
|
|
|
|
/// Send commands to an IPC socket with optional name (defaults to `sk`)
|
|
///
|
|
/// The commands are read from stdin, one per line, in the same format as the actions in the
|
|
/// bind flag. They can also be chained using `+` as a separator.
|
|
/// All other arguments will be ignored
|
|
#[cfg(feature = "listen")]
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting", default_missing_value = "sk", num_args=0..))]
|
|
pub remote: Option<String>,
|
|
|
|
/// Run in a tmux or zellij popup
|
|
///
|
|
/// Format: `sk --popup <center|top|bottom|left|right>[,SIZE[%]][,SIZE[%]]`
|
|
/// Note: this will try to detect a Zellij session, then a Tmux session
|
|
/// This means that in nested sessions, `skim` will prioritize Zellij over Tmux
|
|
#[cfg_attr(feature = "cli", arg(long, verbatim_doc_comment, help_heading = "Display", default_missing_value = "center,50%", num_args=0.., alias = "tmux"))]
|
|
pub popup: Option<String>,
|
|
|
|
/// Set the log level
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub log_level: Option<log::LevelFilter>,
|
|
|
|
/// Pipe log output to a file
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Scripting"))]
|
|
pub log_file: Option<String>,
|
|
|
|
/// Feature flags
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true, help_heading = "Scripting"))]
|
|
pub flags: Vec<FeatureFlag>,
|
|
|
|
// FZF compatibility args
|
|
#[cfg_attr(feature = "cli", arg(short = 'x', long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
extended: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
literal: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true, default_value = "10"))]
|
|
#[builder(setter(skip))]
|
|
hscroll_off: usize,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
filepath_word: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true, default_value = ""))]
|
|
#[builder(setter(skip))]
|
|
jump_labels: String,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
no_bold: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
phony: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
tail: Option<usize>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
style: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
no_color: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
padding: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
border_label: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
border_label_pos: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
wrap_sign: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
no_multi_line: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
raw: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
track: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
gap: Option<usize>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
gap_line: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true, default_value = "0"))]
|
|
#[builder(setter(skip))]
|
|
freeze_left: usize,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true, default_value = "0"))]
|
|
#[builder(setter(skip))]
|
|
freeze_right: usize,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true, default_value = "0"))]
|
|
#[builder(setter(skip))]
|
|
scroll_off: usize,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
gutter: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
gutter_raw: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
marker_multi_line: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
list_border: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
list_label: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
list_label_pos: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
no_input: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
info_command: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
separator: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
no_separator: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
ghost: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
input_border: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
input_label: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
input_label_pos: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
preview_label: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
preview_label_pos: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
header_first: bool,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
header_border: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
header_lines_border: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
footer: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
footer_border: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
footer_label: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
footer_label_pos: Option<String>,
|
|
#[cfg_attr(feature = "cli", arg(long, hide = true))]
|
|
#[builder(setter(skip))]
|
|
with_shell: Option<String>,
|
|
|
|
/// Deprecated, kept for compatibility purposes. See `accept()` bind instead.
|
|
#[cfg_attr(feature = "cli", arg(long, help_heading = "Deprecated", default_value = ""))]
|
|
expect: String,
|
|
|
|
/// Command collector for reading items from commands
|
|
#[cfg_attr(feature = "cli", clap(skip = Rc::new(RefCell::new(SkimItemReader::default())) as Rc<RefCell<dyn CommandCollector>>))]
|
|
#[builder(setter(into = false))]
|
|
#[debug(skip)]
|
|
pub cmd_collector: Rc<RefCell<dyn CommandCollector>>,
|
|
/// Query history entries loaded from history file
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
pub query_history: Vec<String>,
|
|
/// Command history entries loaded from cmd history file
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
pub cmd_history: Vec<String>,
|
|
/// Selector for pre-selecting items
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
#[builder(setter(into = false))]
|
|
#[debug(skip)]
|
|
pub selector: Option<Rc<dyn Selector>>,
|
|
/// Preview Callback
|
|
///
|
|
/// Used to define a function or closure for the preview window, instead of a shell command.
|
|
///
|
|
/// The function will take a `Vec<Arc<dyn SkimItem>>>` containing the currently selected items
|
|
/// and return a Vec<String> with the lines to display in UTF-8
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
#[debug(skip)]
|
|
pub preview_fn: Option<PreviewCallback>,
|
|
|
|
/// The internal (parsed) keymap
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
pub keymap: KeyMap,
|
|
|
|
/// Follow-up action bindings, keyed by the canonical action name.
|
|
///
|
|
/// Populated from `--bind` entries whose "key" is an action name rather than
|
|
/// a real key (e.g. `reload:first`). After an action runs, the chain bound to
|
|
/// its name is queued.
|
|
#[cfg_attr(feature = "cli", clap(skip))]
|
|
pub action_binds: std::collections::HashMap<String, Vec<Action>>,
|
|
}
|
|
|
|
impl Default for SkimOptions {
|
|
#[allow(clippy::too_many_lines)]
|
|
fn default() -> Self {
|
|
Self {
|
|
split_match: None,
|
|
no_strip_ansi: false,
|
|
wrap_items: false,
|
|
multiline: None,
|
|
#[cfg(feature = "listen")]
|
|
listen: None,
|
|
#[cfg(feature = "listen")]
|
|
remote: None,
|
|
print_header: false,
|
|
print_current: false,
|
|
disabled: false,
|
|
disable_pattern: None,
|
|
tac: Default::default(),
|
|
min_query_length: Default::default(),
|
|
no_sort: Default::default(),
|
|
tiebreak: vec![RankCriteria::Score, RankCriteria::Begin, RankCriteria::End],
|
|
nth: Default::default(),
|
|
with_nth: Default::default(),
|
|
hide_nth: Default::default(),
|
|
delimiter: Regex::new(r"[\t\n ]+").unwrap(),
|
|
exact: Default::default(),
|
|
regex: Default::default(),
|
|
algorithm: Default::default(),
|
|
case: Default::default(),
|
|
typos: Typos::Disabled,
|
|
no_typos: false,
|
|
normalize: false,
|
|
last_match: false,
|
|
bind: Default::default(),
|
|
multi: Default::default(),
|
|
no_multi: Default::default(),
|
|
no_mouse: Default::default(),
|
|
cmd: Default::default(),
|
|
interactive: Default::default(),
|
|
replstr: String::from("{}"),
|
|
color: Default::default(),
|
|
no_hscroll: Default::default(),
|
|
keep_right: Default::default(),
|
|
skip_to_pattern: Default::default(),
|
|
no_clear_if_empty: Default::default(),
|
|
no_clear_start: Default::default(),
|
|
no_clear: Default::default(),
|
|
show_cmd_error: Default::default(),
|
|
layout: TuiLayout::default(),
|
|
reverse: Default::default(),
|
|
height: String::from("100%"),
|
|
no_height: Default::default(),
|
|
min_height: String::from("10"),
|
|
margin: Default::default(),
|
|
prompt: String::from("> "),
|
|
cmd_prompt: String::from("c> "),
|
|
selector_icon: String::from(">"),
|
|
multi_select_icon: String::from(">"),
|
|
ansi: Default::default(),
|
|
tabstop: 8,
|
|
info: Default::default(),
|
|
no_info: Default::default(),
|
|
inline_info: Default::default(),
|
|
header: Default::default(),
|
|
header_lines: Default::default(),
|
|
history_file: Default::default(),
|
|
history_size: 1000,
|
|
cmd_history_file: Default::default(),
|
|
cmd_history_size: 1000,
|
|
preview: Default::default(),
|
|
preview_window: PreviewLayout::default(),
|
|
#[cfg(feature = "image")]
|
|
image: None,
|
|
#[cfg(feature = "image")]
|
|
image_picker: None,
|
|
query: Default::default(),
|
|
cmd_query: Default::default(),
|
|
read0: Default::default(),
|
|
print0: Default::default(),
|
|
print_query: Default::default(),
|
|
print_cmd: Default::default(),
|
|
print_score: Default::default(),
|
|
output_format: Default::default(),
|
|
select_1: Default::default(),
|
|
exit_0: Default::default(),
|
|
sync: Default::default(),
|
|
pre_select_n: Default::default(),
|
|
pre_select_pat: Default::default(),
|
|
pre_select_items: Default::default(),
|
|
pre_select_file: Default::default(),
|
|
filter: Default::default(),
|
|
popup: Default::default(),
|
|
log_file: Default::default(),
|
|
extended: Default::default(),
|
|
literal: Default::default(),
|
|
cycle: Default::default(),
|
|
hscroll_off: 10,
|
|
filepath_word: Default::default(),
|
|
jump_labels: String::from("abcdefghijklmnopqrstuvwxyz"),
|
|
border: Default::default(),
|
|
border_no_collapse: Default::default(),
|
|
no_bold: Default::default(),
|
|
phony: Default::default(),
|
|
scheme: Default::default(),
|
|
tail: Default::default(),
|
|
style: Default::default(),
|
|
no_color: Default::default(),
|
|
padding: Default::default(),
|
|
border_label: Default::default(),
|
|
border_label_pos: Default::default(),
|
|
highlight_line: Default::default(),
|
|
wrap_sign: Default::default(),
|
|
no_multi_line: Default::default(),
|
|
raw: Default::default(),
|
|
track: Default::default(),
|
|
gap: Default::default(),
|
|
gap_line: Default::default(),
|
|
freeze_left: Default::default(),
|
|
freeze_right: Default::default(),
|
|
scroll_off: Default::default(),
|
|
gutter: Default::default(),
|
|
gutter_raw: Default::default(),
|
|
marker_multi_line: Default::default(),
|
|
ellipsis: Default::default(),
|
|
scrollbar: Default::default(),
|
|
no_scrollbar: Default::default(),
|
|
list_border: Default::default(),
|
|
list_label: Default::default(),
|
|
list_label_pos: Default::default(),
|
|
no_input: Default::default(),
|
|
info_command: Default::default(),
|
|
separator: Default::default(),
|
|
no_separator: Default::default(),
|
|
ghost: Default::default(),
|
|
input_border: Default::default(),
|
|
input_label: Default::default(),
|
|
input_label_pos: Default::default(),
|
|
preview_label: Default::default(),
|
|
preview_label_pos: Default::default(),
|
|
header_first: Default::default(),
|
|
header_border: Default::default(),
|
|
header_lines_border: Default::default(),
|
|
footer: Default::default(),
|
|
footer_border: Default::default(),
|
|
footer_label: Default::default(),
|
|
footer_label_pos: Default::default(),
|
|
with_shell: Default::default(),
|
|
expect: Default::default(),
|
|
cmd_collector: Rc::new(RefCell::new(SkimItemReader::default())) as Rc<RefCell<dyn CommandCollector>>,
|
|
query_history: Default::default(),
|
|
cmd_history: Default::default(),
|
|
selector: Default::default(),
|
|
preview_fn: Default::default(),
|
|
keymap: Default::default(),
|
|
action_binds: Default::default(),
|
|
#[cfg(feature = "cli")]
|
|
shell: Default::default(),
|
|
#[cfg(feature = "cli")]
|
|
man: false,
|
|
#[cfg(feature = "cli")]
|
|
shell_bindings: false,
|
|
flags: Default::default(),
|
|
log_level: Default::default(),
|
|
no_border: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SkimOptionsBuilder {
|
|
/// Builds the `SkimOptions` from the builder
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error if any required fields are missing.
|
|
pub fn build(&mut self) -> Result<SkimOptions, SkimOptionsBuilderError> {
|
|
self.final_build().map(SkimOptions::build)
|
|
}
|
|
}
|
|
|
|
impl SkimOptions {
|
|
/// Finalizes the options by applying defaults and initializing components
|
|
#[must_use]
|
|
pub fn build(mut self) -> Self {
|
|
if self.no_height {
|
|
self.height = String::from("100%");
|
|
}
|
|
|
|
if let Some(None) = self.multiline {
|
|
if self.read0 {
|
|
self.multiline = Some(Some(String::from("\n")));
|
|
} else {
|
|
self.multiline = Some(Some(String::from("\\n")));
|
|
}
|
|
}
|
|
|
|
self.keymap = self.bind.iter().fold(KeyMap::default(), |mut res, part| {
|
|
res.add_keymaps_str(part);
|
|
res
|
|
});
|
|
|
|
// Bindings whose "key" is an action name (e.g. `reload:first`) become
|
|
// follow-up actions that run right after that action.
|
|
self.action_binds = self
|
|
.bind
|
|
.iter()
|
|
.flat_map(|part| crate::binds::parse_action_binds(crate::binds::split_top_level(part, ',').into_iter()))
|
|
.collect();
|
|
|
|
if self.reverse {
|
|
self.layout = TuiLayout::Reverse;
|
|
}
|
|
if self.history_file.is_some() || self.cmd_history_file.is_some() {
|
|
self.init_histories();
|
|
self.keymap.insert(
|
|
KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL),
|
|
vec![Action::PreviousHistory],
|
|
);
|
|
self.keymap.insert(
|
|
KeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL),
|
|
vec![Action::NextHistory],
|
|
);
|
|
}
|
|
if self.no_scrollbar {
|
|
self.scrollbar = String::new();
|
|
}
|
|
if self.inline_info {
|
|
self.info = Info {
|
|
display: InfoDisplay::Inline,
|
|
separator: Some(String::from(crate::tui::statusline::DEFAULT_SEPARATOR)),
|
|
};
|
|
}
|
|
if self.no_info {
|
|
self.info = Info {
|
|
display: InfoDisplay::Hidden,
|
|
separator: None,
|
|
};
|
|
}
|
|
if self.no_typos {
|
|
self.typos = Typos::Disabled;
|
|
}
|
|
if self.no_border {
|
|
self.border = BorderType::ForceOff;
|
|
}
|
|
|
|
if let Some(ref filter_query) = self.filter
|
|
&& self.query.is_none()
|
|
{
|
|
self.query = Some(filter_query.clone());
|
|
}
|
|
|
|
match self.scheme {
|
|
None | Some(MatchScheme::Default) => (),
|
|
Some(MatchScheme::Path) => {
|
|
self.last_match = true;
|
|
self.tiebreak.insert(0, RankCriteria::PathName);
|
|
self.tiebreak.insert(0, RankCriteria::Score);
|
|
}
|
|
Some(MatchScheme::History) => self.tiebreak.insert(0, RankCriteria::Index),
|
|
}
|
|
|
|
self
|
|
}
|
|
/// Initializes history from configured history files
|
|
pub fn init_histories(&mut self) {
|
|
if let Some(histfile) = &self.history_file {
|
|
self.query_history.extend(read_file_lines(histfile).unwrap_or_default());
|
|
}
|
|
|
|
if let Some(cmd_histfile) = &self.cmd_history_file {
|
|
self.cmd_history
|
|
.extend(read_file_lines(cmd_histfile).unwrap_or_default());
|
|
}
|
|
}
|
|
#[cfg(feature = "cli")]
|
|
/// Merges `SKIM_DEFAULT_OPTIONS` with the app's args
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error if argument parsing fails.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if the process was invoked with no arguments (which should never happen in practice).
|
|
pub fn from_env() -> Result<Self, clap::Error> {
|
|
use std::env;
|
|
|
|
let prog = env::args()
|
|
.next()
|
|
.expect("there should be at least one arg: the application name");
|
|
let options_file_content = env::var("SKIM_OPTIONS_FILE").ok().and_then(|f| std::fs::read(f).ok());
|
|
let default_options = env::var("SKIM_DEFAULT_OPTIONS").ok();
|
|
let default_command = env::var("SKIM_DEFAULT_COMMAND").ok();
|
|
|
|
Self::merge_args_and_parse(
|
|
prog,
|
|
options_file_content.as_deref(),
|
|
default_options.as_deref(),
|
|
env::args().skip(1),
|
|
default_command,
|
|
)
|
|
}
|
|
|
|
/// Build [`SkimOptions`] from explicitly provided sources, mirroring how
|
|
/// [`from_env`](Self::from_env) assembles them but without touching the
|
|
/// process environment — which makes it unit-testable and platform-agnostic.
|
|
///
|
|
/// Precedence (lowest to highest): `SKIM_OPTIONS_FILE` contents, then
|
|
/// `SKIM_DEFAULT_OPTIONS`, then the real CLI args. `default_command`
|
|
/// (`SKIM_DEFAULT_COMMAND`) only fills `cmd` when no `--cmd` was given,
|
|
/// falling back to [`crate::SKIM_DEFAULT_COMMAND`].
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns a [`clap::Error`] if argument parsing fails.
|
|
#[cfg(feature = "cli")]
|
|
pub(crate) fn merge_args_and_parse(
|
|
prog: String,
|
|
options_file_content: Option<&[u8]>,
|
|
default_options: Option<&str>,
|
|
cli_args: impl IntoIterator<Item = String>,
|
|
default_command: Option<String>,
|
|
) -> Result<Self, clap::Error> {
|
|
use clap::Parser;
|
|
|
|
let mut args = vec![prog];
|
|
|
|
if let Some(content) = options_file_content {
|
|
let mut in_comment = false;
|
|
let mut pending_comment = false;
|
|
let without_comments = content
|
|
.iter()
|
|
.filter_map(|b| match (in_comment, pending_comment, *b) {
|
|
(_, _, b'\n') => {
|
|
in_comment = false;
|
|
pending_comment = false;
|
|
Some(b' ')
|
|
}
|
|
(true, _, _) => {
|
|
pending_comment = false;
|
|
None
|
|
}
|
|
(_, true, b'#') => {
|
|
pending_comment = false;
|
|
Some(b'#')
|
|
}
|
|
(_, false, b'#') => {
|
|
pending_comment = true;
|
|
None
|
|
}
|
|
(false, true, _) => {
|
|
pending_comment = false;
|
|
in_comment = true;
|
|
None
|
|
}
|
|
(false, false, x) => Some(x),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let parsed = String::from_utf8_lossy(&without_comments);
|
|
args.extend(shlex::split(&parsed).unwrap_or_default());
|
|
}
|
|
|
|
args.extend(default_options.and_then(shlex::split).unwrap_or_default());
|
|
args.extend(cli_args);
|
|
|
|
Self::try_parse_from(args).map(|mut opts| {
|
|
if opts.cmd.is_none() {
|
|
opts.cmd = Some(default_command.unwrap_or_else(|| crate::SKIM_DEFAULT_COMMAND.to_string()));
|
|
}
|
|
opts
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Feature flags
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
|
|
pub enum FeatureFlag {
|
|
/// Disable preview PTY on Linux
|
|
NoPreviewPty,
|
|
/// Display the item's match score before its value in the item list (for matcher debugging)
|
|
ShowScore,
|
|
/// Display the item's index before its value in the item list
|
|
ShowIndex,
|
|
/// Limit the reader thread pool to a single thread
|
|
///
|
|
/// Forces the reader pipeline to run on exactly one thread, regardless of the number of
|
|
/// available CPU cores. Useful for debugging reader-side behaviour or for environments where
|
|
/// parallelism causes ordering issues.
|
|
SingleReader,
|
|
/// Limit the matcher thread pool to a single thread
|
|
///
|
|
/// Forces the matcher to run on exactly one thread, regardless of the number of available CPU
|
|
/// cores. Useful for reproducing deterministic match ordering or for debugging the matcher.
|
|
SingleMatcher,
|
|
}
|
|
|
|
#[allow(unused_macros)]
|
|
macro_rules! feature_flag {
|
|
($options:ident, $name:ident) => {
|
|
(std::env::var(stringify!(SKIM_FLAG_$name).replace(' ', "")).is_ok_and(|x| x.len() > 0)
|
|
|| $options.flags.contains(&crate::options::FeatureFlag::$name))
|
|
};
|
|
}
|
|
#[allow(unused_imports)]
|
|
pub(crate) use feature_flag;
|
|
|
|
#[cfg(test)]
|
|
#[path = "options_tests.rs"]
|
|
mod tests;
|