1#[macro_use]
8extern crate lazy_static;
9#[macro_use]
10extern crate log;
11
12use std::any::Any;
13use std::fmt::Display;
14use std::sync::mpsc::channel;
15use std::sync::Arc;
16use std::thread;
17
18use crossbeam::channel::{Receiver, Sender};
19use tuikit::prelude::{Event as TermEvent, *};
20
21pub use crate::ansi::AnsiString;
22pub use crate::engine::fuzzy::FuzzyAlgorithm;
23use crate::event::{EventReceiver, EventSender};
24use crate::model::Model;
25pub use crate::options::SkimOptions;
26pub use crate::output::SkimOutput;
27use crate::reader::Reader;
28
29mod ansi;
30pub mod context;
31mod engine;
32mod event;
33pub mod field;
34mod global;
35mod header;
36pub mod helper;
37mod input;
38pub mod item;
39mod matcher;
40mod model;
41pub mod options;
42mod orderedvec;
43mod output;
44pub mod prelude;
45mod previewer;
46mod query;
47pub mod reader;
48mod selection;
49mod spinlock;
50mod theme;
51pub mod tmux;
52pub mod util;
53
54pub trait AsAny {
56 fn as_any(&self) -> &dyn Any;
57 fn as_any_mut(&mut self) -> &mut dyn Any;
58}
59
60impl<T: Any> AsAny for T {
61 fn as_any(&self) -> &dyn Any {
62 self
63 }
64
65 fn as_any_mut(&mut self) -> &mut dyn Any {
66 self
67 }
68}
69
70pub trait SkimItem: AsAny + Send + Sync + 'static {
111 fn text(&self) -> &str;
113
114 fn display(&self, context: DisplayContext) -> AnsiString {
116 AnsiString::from(context)
117 }
118
119 fn preview(&self, _context: PreviewContext) -> ItemPreview {
122 ItemPreview::Global
123 }
124
125 fn output(&self) -> String {
130 self.text().to_string()
131 }
132
133 fn get_matching_ranges(&self) -> Option<&[(usize, usize)]> {
136 None
137 }
138}
139
140impl<T: AsRef<str> + Send + Sync + 'static> SkimItem for T {
144 fn text(&self) -> &str {
145 self.as_ref()
146 }
147}
148
149pub enum Matches<'a> {
152 None,
153 CharIndices(&'a [usize]),
154 CharRange(usize, usize),
155 ByteRange(usize, usize),
156}
157
158pub struct DisplayContext<'a> {
159 pub text: String,
160 pub score: i32,
161 pub matches: Matches<'a>,
162 pub container_width: usize,
163 pub highlight_attr: Attr,
164}
165
166impl<'a> From<DisplayContext<'a>> for AnsiString {
167 fn from(context: DisplayContext) -> Self {
168 match context.matches {
169 Matches::CharIndices(indices) => AnsiString::from((context.text, indices, context.highlight_attr)),
170 Matches::CharRange(start, end) => {
171 AnsiString::new_string(context.text, vec![(context.highlight_attr, (start, end))])
172 }
173 Matches::ByteRange(start, end) => {
174 let ch_start = context.text[..start].chars().count();
175 let ch_end = ch_start + context.text[start..end].chars().count();
176 AnsiString::new_string(context.text, vec![(context.highlight_attr, (ch_start, ch_end))])
177 }
178 Matches::None => AnsiString::new_string(context.text, vec![]),
179 }
180 }
181}
182
183pub struct PreviewContext<'a> {
187 pub query: &'a str,
188 pub cmd_query: &'a str,
189 pub width: usize,
190 pub height: usize,
191 pub current_index: usize,
192 pub current_selection: &'a str,
193 pub selected_indices: &'a [usize],
195 pub selections: &'a [&'a str],
197}
198
199#[derive(Default, Copy, Clone, Debug)]
202pub struct PreviewPosition {
203 pub h_scroll: Size,
204 pub h_offset: Size,
205 pub v_scroll: Size,
206 pub v_offset: Size,
207}
208
209pub enum ItemPreview {
210 Command(String),
212 Text(String),
214 AnsiText(String),
216 CommandWithPos(String, PreviewPosition),
217 TextWithPos(String, PreviewPosition),
218 AnsiWithPos(String, PreviewPosition),
219 Global,
221}
222
223#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
227pub enum CaseMatching {
228 Respect,
229 Ignore,
230 #[default]
231 Smart,
232}
233
234impl clap::ValueEnum for CaseMatching {
235 fn value_variants<'a>() -> &'a [Self] {
236 &[Self::Respect, Self::Ignore, Self::Smart]
237 }
238
239 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
240 match self {
241 Self::Respect => Some(clap::builder::PossibleValue::new("respect")),
242 Self::Ignore => Some(clap::builder::PossibleValue::new("ignore")),
243 Self::Smart => Some(clap::builder::PossibleValue::new("smart")),
244 }
245 }
246}
247
248#[derive(PartialEq, Eq, Clone, Debug)]
249#[allow(dead_code)]
250pub enum MatchRange {
251 ByteRange(usize, usize),
252 Chars(Vec<usize>), }
255
256pub type Rank = [i32; 4];
257
258#[derive(Clone)]
259pub struct MatchResult {
260 pub rank: Rank,
261 pub matched_range: MatchRange,
262}
263
264impl MatchResult {
265 pub fn range_char_indices(&self, text: &str) -> Vec<usize> {
266 match &self.matched_range {
267 &MatchRange::ByteRange(start, end) => {
268 let first = text[..start].chars().count();
269 let last = first + text[start..end].chars().count();
270 (first..last).collect()
271 }
272 MatchRange::Chars(vec) => vec.clone(),
273 }
274 }
275}
276
277pub trait MatchEngine: Sync + Send + Display {
278 fn match_item(&self, item: Arc<dyn SkimItem>) -> Option<MatchResult>;
279}
280
281pub trait MatchEngineFactory {
282 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine>;
283 fn create_engine(&self, query: &str) -> Box<dyn MatchEngine> {
284 self.create_engine_with_case(query, CaseMatching::default())
285 }
286}
287
288pub trait Selector {
293 fn should_select(&self, index: usize, item: &dyn SkimItem) -> bool;
294}
295
296pub type SkimItemSender = Sender<Arc<dyn SkimItem>>;
298pub type SkimItemReceiver = Receiver<Arc<dyn SkimItem>>;
299
300pub struct Skim {}
301
302impl Skim {
303 pub fn run_with(options: &SkimOptions, source: Option<SkimItemReceiver>) -> Option<SkimOutput> {
312 let min_height = Skim::parse_height_string(&options.min_height);
313 let height = Skim::parse_height_string(&options.height);
314
315 let (tx, rx): (EventSender, EventReceiver) = channel();
316 let term = Arc::new(
317 Term::with_options(
318 TermOptions::default()
319 .min_height(min_height)
320 .height(height)
321 .clear_on_exit(!options.no_clear)
322 .disable_alternate_screen(options.no_clear_start)
323 .clear_on_start(!options.no_clear_start)
324 .hold(options.select_1 || options.exit_0 || options.sync),
325 )
326 .unwrap(),
327 );
328 if !options.no_mouse {
329 let _ = term.enable_mouse_support();
330 }
331
332 let mut input = input::Input::new();
335 input.parse_keymaps(&options.bind);
336 input.set_expect_keys(&options.expect);
337
338 let tx_clone = tx.clone();
339 let term_clone = term.clone();
340 let input_thread = thread::spawn(move || loop {
341 if let Ok(key) = term_clone.poll_event() {
342 if key == TermEvent::User(()) {
343 break;
344 }
345
346 let (key, action_chain) = input.translate_event(key);
347 for event in action_chain.into_iter() {
348 let _ = tx_clone.send((key, event));
349 }
350 }
351 });
352
353 debug!(" with nth --- {:?}", options.with_nth);
357 let reader = Reader::with_options(options).source(source);
358
359 let mut model = Model::new(rx, tx, reader, term.clone(), options);
362 let ret = model.start();
363 let _ = term.send_event(TermEvent::User(())); let _ = input_thread.join();
365 ret
366 }
367
368 fn parse_height_string(string: &str) -> TermHeight {
371 if string.ends_with('%') {
372 TermHeight::Percent(string[0..string.len() - 1].parse().unwrap_or(100))
373 } else {
374 TermHeight::Fixed(string.parse().unwrap_or(0))
375 }
376 }
377}