#![warn(missing_docs)]
#[macro_use]
extern crate log;
#[global_allocator]
static GLOBAL_ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc;
use std::any::Any;
use std::borrow::Cow;
use std::fmt::Display;
use std::sync::Arc;
use crate::fuzzy_matcher::MatchIndices;
use ratatui::{
style::Style,
text::{Line, Span},
};
pub use crate::engine::fuzzy::FuzzyAlgorithm;
pub use crate::item::RankCriteria;
pub use crate::options::SkimOptions;
pub use crate::output::SkimOutput;
pub use crate::skim::*;
pub use crate::skim_item::SkimItem;
use crate::tui::Size;
pub use util::printf;
pub mod binds;
mod engine;
pub mod field;
pub mod fuzzy_matcher;
pub mod helper;
pub mod item;
pub mod matcher;
pub mod options;
mod output;
pub mod prelude;
pub mod reader;
mod skim;
mod skim_item;
pub mod spinlock;
pub mod theme;
pub mod tmux;
pub mod tui;
mod util;
#[cfg(feature = "cli")]
pub mod manpage;
#[cfg(feature = "cli")]
pub mod shell;
pub trait AsAny {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Any> AsAny for T {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Default, Debug, Clone)]
pub enum Matches {
#[default]
None,
CharIndices(MatchIndices),
CharRange(usize, usize),
ByteRange(usize, usize),
}
#[derive(Default, Clone)]
pub struct DisplayContext {
pub score: i32,
pub matches: Matches,
pub container_width: usize,
pub base_style: Style,
pub matched_syle: Style,
}
impl DisplayContext {
pub fn to_line(self, cow: Cow<str>) -> Line {
let text: String = cow.into_owned();
match &self.matches {
Matches::CharIndices(indices) => {
let mut res = Line::default();
let mut chars = text.chars();
let mut prev_index = 0;
for &index in indices {
let span_content = chars.by_ref().take(index - prev_index);
res.push_span(Span::styled(span_content.collect::<String>(), self.base_style));
let highlighted_char = chars.next().unwrap_or_default().to_string();
res.push_span(Span::styled(highlighted_char, self.base_style.patch(self.matched_syle)));
prev_index = index + 1;
}
res.push_span(Span::styled(chars.collect::<String>(), self.base_style));
res
}
#[allow(clippy::cast_possible_truncation)]
Matches::CharRange(start, end) => {
let mut chars = text.chars();
let mut res = Line::default();
res.push_span(Span::styled(
chars.by_ref().take(*start).collect::<String>(),
self.base_style,
));
let highlighted_text = chars.by_ref().take(*end - *start).collect::<String>();
res.push_span(Span::styled(highlighted_text, self.base_style.patch(self.matched_syle)));
res.push_span(Span::styled(chars.collect::<String>(), self.base_style));
res
}
Matches::ByteRange(start, end) => {
let mut bytes = text.bytes();
let mut res = Line::default();
res.push_span(Span::styled(
String::from_utf8(bytes.by_ref().take(*start).collect()).unwrap(),
self.base_style,
));
let highlighted_bytes = bytes.by_ref().take(*end - *start).collect();
let highlighted_text = String::from_utf8(highlighted_bytes).unwrap();
res.push_span(Span::styled(highlighted_text, self.base_style.patch(self.matched_syle)));
res.push_span(Span::styled(
String::from_utf8(bytes.collect()).unwrap(),
self.base_style,
));
res
}
Matches::None => Line::from(vec![Span::styled(text, self.base_style)]),
}
}
}
pub struct PreviewContext<'a> {
pub query: &'a str,
pub cmd_query: &'a str,
pub width: usize,
pub height: usize,
pub current_index: usize,
pub current_selection: &'a str,
pub selected_indices: &'a [usize],
pub selections: &'a [&'a str],
}
#[derive(Default, Copy, Clone, Debug)]
pub struct PreviewPosition {
pub h_scroll: Size,
pub h_offset: Size,
pub v_scroll: Size,
pub v_offset: Size,
}
pub enum ItemPreview {
Command(String),
Text(String),
AnsiText(String),
CommandWithPos(String, PreviewPosition),
TextWithPos(String, PreviewPosition),
AnsiWithPos(String, PreviewPosition),
Global,
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum), clap(rename_all = "snake_case"))]
pub enum CaseMatching {
Respect,
Ignore,
#[default]
Smart,
}
#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
pub enum Typos {
#[default]
Disabled,
Smart,
Fixed(usize),
}
impl From<usize> for Typos {
fn from(n: usize) -> Self {
match n {
0 => Typos::Disabled,
n => Typos::Fixed(n),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum MatchRange {
ByteRange(usize, usize),
Chars(MatchIndices),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Rank {
pub score: i32,
pub begin: i32,
pub end: i32,
pub length: i32,
pub index: i32,
pub path_name_offset: i32,
}
#[derive(Clone, Debug)]
pub struct MatchResult {
pub rank: Rank,
pub matched_range: MatchRange,
}
impl MatchResult {
#[must_use]
pub fn range_char_indices(&self, text: &str) -> MatchIndices {
match &self.matched_range {
&MatchRange::ByteRange(start, end) => {
let first = text[..start].chars().count();
let last = first + text[start..end].chars().count();
(first..last).collect()
}
MatchRange::Chars(vec) => vec.clone(),
}
}
}
pub trait MatchEngine: Sync + Send + Display {
fn match_item(&self, item: &dyn SkimItem) -> Option<MatchResult>;
}
pub trait MatchEngineFactory {
fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine>;
fn create_engine(&self, query: &str) -> Box<dyn MatchEngine> {
self.create_engine_with_case(query, CaseMatching::default())
}
}
pub trait Selector {
fn should_select(&self, index: usize, item: &dyn SkimItem) -> bool;
}
pub type SkimItemSender = kanal::Sender<Vec<Arc<dyn SkimItem>>>;
pub type SkimItemReceiver = kanal::Receiver<Vec<Arc<dyn SkimItem>>>;