1#[macro_use]
2extern crate lazy_static;
3#[macro_use]
4extern crate log;
5
6use std::any::Any;
7use std::borrow::Cow;
8use std::fmt::Display;
9use std::sync::mpsc::channel;
10use std::sync::Arc;
11use std::thread;
12
13use crossbeam::channel::{Receiver, Sender};
14use tuikit::prelude::{Event as TermEvent, *};
15
16pub use crate::ansi::AnsiString;
17pub use crate::engine::fuzzy::FuzzyAlgorithm;
18use crate::event::{EventReceiver, EventSender};
19use crate::model::Model;
20pub use crate::options::{SkimOptions, SkimOptionsBuilder};
21pub use crate::output::SkimOutput;
22use crate::reader::Reader;
23
24mod ansi;
25mod engine;
26mod event;
27pub mod field;
28mod global;
29mod header;
30mod helper;
31mod input;
32mod item;
33mod matcher;
34mod model;
35mod options;
36mod orderedvec;
37mod output;
38pub mod prelude;
39mod previewer;
40mod query;
41mod reader;
42mod selection;
43mod spinlock;
44mod theme;
45mod util;
46
47pub trait AsAny {
49 fn as_any(&self) -> &dyn Any;
50 fn as_any_mut(&mut self) -> &mut dyn Any;
51}
52
53impl<T: Any> AsAny for T {
54 fn as_any(&self) -> &dyn Any {
55 self
56 }
57
58 fn as_any_mut(&mut self) -> &mut dyn Any {
59 self
60 }
61}
62
63pub trait SkimItem: AsAny + Send + Sync + 'static {
104 fn text(&self) -> Cow<str>;
106
107 fn display<'a>(&'a self, context: DisplayContext<'a>) -> AnsiString<'a> {
109 AnsiString::from(context)
110 }
111
112 fn preview(&self, _context: PreviewContext) -> ItemPreview {
115 ItemPreview::Global
116 }
117
118 fn output(&self) -> Cow<str> {
123 self.text()
124 }
125
126 fn get_matching_ranges(&self) -> Option<&[(usize, usize)]> {
129 None
130 }
131}
132
133impl<T: AsRef<str> + Send + Sync + 'static> SkimItem for T {
137 fn text(&self) -> Cow<str> {
138 Cow::Borrowed(self.as_ref())
139 }
140}
141
142pub enum Matches<'a> {
145 None,
146 CharIndices(&'a [usize]),
147 CharRange(usize, usize),
148 ByteRange(usize, usize),
149}
150
151pub struct DisplayContext<'a> {
152 pub text: &'a str,
153 pub score: i32,
154 pub matches: Matches<'a>,
155 pub container_width: usize,
156 pub highlight_attr: Attr,
157}
158
159impl<'a> From<DisplayContext<'a>> for AnsiString<'a> {
160 fn from(context: DisplayContext<'a>) -> Self {
161 match context.matches {
162 Matches::CharIndices(indices) => AnsiString::from((context.text, indices, context.highlight_attr)),
163 Matches::CharRange(start, end) => {
164 AnsiString::new_str(context.text, vec![(context.highlight_attr, (start as u32, end as u32))])
165 }
166 Matches::ByteRange(start, end) => {
167 let ch_start = context.text[..start].chars().count();
168 let ch_end = ch_start + context.text[start..end].chars().count();
169 AnsiString::new_str(
170 context.text,
171 vec![(context.highlight_attr, (ch_start as u32, ch_end as u32))],
172 )
173 }
174 Matches::None => AnsiString::new_str(context.text, vec![]),
175 }
176 }
177}
178
179pub struct PreviewContext<'a> {
183 pub query: &'a str,
184 pub cmd_query: &'a str,
185 pub width: usize,
186 pub height: usize,
187 pub current_index: usize,
188 pub current_selection: &'a str,
189 pub selected_indices: &'a [usize],
191 pub selections: &'a [&'a str],
193}
194
195#[derive(Default, Copy, Clone, Debug)]
198pub struct PreviewPosition {
199 pub h_scroll: Size,
200 pub h_offset: Size,
201 pub v_scroll: Size,
202 pub v_offset: Size,
203}
204
205pub enum ItemPreview {
206 Command(String),
208 Text(String),
210 AnsiText(String),
212 CommandWithPos(String, PreviewPosition),
213 TextWithPos(String, PreviewPosition),
214 AnsiWithPos(String, PreviewPosition),
215 Global,
217}
218
219#[derive(Eq, PartialEq, Debug, Copy, Clone)]
223pub enum CaseMatching {
224 Respect,
225 Ignore,
226 Smart,
227}
228
229impl Default for CaseMatching {
230 fn default() -> Self {
231 CaseMatching::Smart
232 }
233}
234
235#[derive(PartialEq, Eq, Clone, Debug)]
236#[allow(dead_code)]
237pub enum MatchRange {
238 ByteRange(usize, usize),
239 Chars(Vec<usize>), }
242
243pub type Rank = [i32; 4];
244
245#[derive(Clone)]
246pub struct MatchResult {
247 pub rank: Rank,
248 pub matched_range: MatchRange,
249}
250
251impl MatchResult {
252 pub fn range_char_indices(&self, text: &str) -> Vec<usize> {
253 match &self.matched_range {
254 &MatchRange::ByteRange(start, end) => {
255 let first = text[..start].chars().count();
256 let last = first + text[start..end].chars().count();
257 (first..last).collect()
258 }
259 MatchRange::Chars(vec) => vec.clone(),
260 }
261 }
262}
263
264pub trait MatchEngine: Sync + Send + Display {
265 fn match_item(&self, item: Arc<dyn SkimItem>) -> Option<MatchResult>;
266}
267
268pub trait MatchEngineFactory {
269 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine>;
270 fn create_engine(&self, query: &str) -> Box<dyn MatchEngine> {
271 self.create_engine_with_case(query, CaseMatching::default())
272 }
273}
274
275pub trait Selector {
280 fn should_select(&self, index: usize, item: &dyn SkimItem) -> bool;
281}
282
283pub type SkimItemSender = Sender<Arc<dyn SkimItem>>;
285pub type SkimItemReceiver = Receiver<Arc<dyn SkimItem>>;
286
287pub struct Skim {
288 pub term: Arc<Term>,
290}
291
292impl Skim {
293 pub fn new_from_term(term: Arc<Term>) -> Self {
294 Self { term }
295 }
296
297 fn default_cmd(path_str: String) -> String {
298 format!("find {}", path_str)
299 }
300
301 pub fn run_internal(
310 self: &Self,
311 source: Option<SkimItemReceiver>,
312 path_str: String,
313 preview: Option<&str>,
314 cmd: Option<String>,
315 ) -> Option<SkimOutput> {
316 let mut options = SkimOptions::default();
317 let cmd = match cmd {
318 None => Self::default_cmd(path_str),
319 Some(cmd) => cmd,
320 };
321 options.cmd = Some(&cmd);
322 options.preview = preview;
323 options.multi = true;
324 let (tx, rx): (EventSender, EventReceiver) = channel();
325 if !options.no_mouse {
326 let _ = self.term.enable_mouse_support();
327 }
328
329 let mut input = input::Input::new();
332 input.parse_keymaps(&options.bind);
333 input.parse_expect_keys(options.expect.as_ref().map(String::as_str));
334
335 let tx_clone = tx.clone();
336 let term_clone = self.term.clone();
337 let input_thread = thread::spawn(move || loop {
338 if let Ok(key) = term_clone.poll_event() {
339 if key == TermEvent::User(()) {
340 break;
341 }
342
343 let (key, action_chain) = input.translate_event(key);
344 for event in action_chain.into_iter() {
345 let _ = tx_clone.send((key, event));
346 }
347 }
348 });
349
350 let reader = Reader::with_options(&options).source(source);
354
355 let mut model = Model::new(rx, tx, reader, self.term.clone(), &options);
358 let ret = model.start();
359 let _ = self.term.send_event(TermEvent::User(())); let _ = input_thread.join();
361 ret
362 }
363
364 pub fn run_with(options: &SkimOptions, source: Option<SkimItemReceiver>) -> Option<SkimOutput> {
373 let min_height = options
374 .min_height
375 .map(Skim::parse_height_string)
376 .expect("min_height should have default values");
377 let height = options
378 .height
379 .map(Skim::parse_height_string)
380 .expect("height should have default values");
381
382 let (tx, rx): (EventSender, EventReceiver) = channel();
383 let term = Arc::new(
384 Term::with_options(
385 TermOptions::default()
386 .min_height(min_height)
387 .height(height)
388 .clear_on_exit(!options.no_clear)
389 .hold(options.select1 || options.exit0 || options.sync),
390 )
391 .unwrap(),
392 );
393 if !options.no_mouse {
394 let _ = term.enable_mouse_support();
395 }
396
397 let mut input = input::Input::new();
400 input.parse_keymaps(&options.bind);
401 input.parse_expect_keys(options.expect.as_ref().map(String::as_str));
402
403 let tx_clone = tx.clone();
404 let term_clone = term.clone();
405 let input_thread = thread::spawn(move || loop {
406 if let Ok(key) = term_clone.poll_event() {
407 if key == TermEvent::User(()) {
408 break;
409 }
410
411 let (key, action_chain) = input.translate_event(key);
412 for event in action_chain.into_iter() {
413 let _ = tx_clone.send((key, event));
414 }
415 }
416 });
417
418 let reader = Reader::with_options(&options).source(source);
422
423 let mut model = Model::new(rx, tx, reader, term.clone(), &options);
426 let ret = model.start();
427 let _ = term.send_event(TermEvent::User(())); let _ = input_thread.join();
429 ret
430 }
431
432 fn parse_height_string(string: &str) -> TermHeight {
435 if string.ends_with('%') {
436 TermHeight::Percent(string[0..string.len() - 1].parse().unwrap_or(100))
437 } else {
438 TermHeight::Fixed(string.parse().unwrap_or(0))
439 }
440 }
441}