diff --git a/skim/examples/cmd_collector.rs b/skim/examples/cmd_collector.rs new file mode 100644 index 00000000..aef9e2f7 --- /dev/null +++ b/skim/examples/cmd_collector.rs @@ -0,0 +1,48 @@ +extern crate skim; +use reader::CommandCollector; +use skim::prelude::*; + +struct BasicSkimItem { + value: String, +} + +impl SkimItem for BasicSkimItem { + fn text(&self) -> Cow { + return Cow::Borrowed(&self.value); + } +} + +struct BasicCmdCollector { + pub items: Vec, +} + +impl CommandCollector for BasicCmdCollector { + fn invoke(&mut self, _cmd: &str, _components_to_stop: Arc) -> (SkimItemReceiver, Sender) { + let (tx, rx) = unbounded(); + let (tx_interrupt, _rx_interrupt) = unbounded(); + while let Some(value) = self.items.pop() { + let item = BasicSkimItem { value }; + tx.send(Arc::from(item) as Arc).unwrap(); + } + + (rx, tx_interrupt) + } +} + +pub fn main() { + let cmd_collector = BasicCmdCollector { + items: vec![String::from("foo"), String::from("bar"), String::from("baz")], + }; + let options = SkimOptionsBuilder::default() + .cmd_collector(Rc::from(RefCell::from(cmd_collector))) + .build() + .unwrap(); + + let selected_items = Skim::run_with(&options, None) + .map(|out| out.selected_items) + .unwrap_or_default(); + + for item in selected_items.iter() { + println!("{}", item.output()); + } +} diff --git a/skim/examples/selector.rs b/skim/examples/selector.rs new file mode 100644 index 00000000..21168aa3 --- /dev/null +++ b/skim/examples/selector.rs @@ -0,0 +1,32 @@ +extern crate skim; +use skim::prelude::*; + +struct BasicSelector { + pub pat: String, +} + +impl Selector for BasicSelector { + fn should_select(&self, _index: usize, item: &dyn SkimItem) -> bool { + return item.text().contains(&self.pat); + } +} + +pub fn main() { + let selector = BasicSelector { + pat: String::from("examples"), + }; + let options = SkimOptionsBuilder::default() + .multi(true) + .selector(Some(Rc::from(selector))) + .query(Some(String::from("skim/"))) + .build() + .unwrap(); + + let selected_items = Skim::run_with(&options, None) + .map(|out| out.selected_items) + .unwrap_or_default(); + + for item in selected_items.iter() { + println!("{}", item.output()); + } +} diff --git a/skim/src/bin/main.rs b/skim/src/bin/main.rs index cc1ca714..3e715aec 100644 --- a/skim/src/bin/main.rs +++ b/skim/src/bin/main.rs @@ -5,8 +5,6 @@ extern crate shlex; extern crate skim; extern crate time; -use self::context::SkimContext; -use self::reader::CommandCollector; use clap::{Error, Parser}; use derive_builder::Builder; use std::fs::File; @@ -77,7 +75,7 @@ impl From for SkMainError { } fn sk_main() -> Result { - let opts = parse_args()?; + let mut opts = parse_args()?; let reader_opts = SkimItemReaderOption::default() .ansi(opts.ansi) @@ -86,13 +84,8 @@ fn sk_main() -> Result { .nth(opts.nth.iter().map(|s| s.as_str())) .read0(opts.read0) .show_error(opts.show_cmd_error); - let mut ctx = SkimContext { - cmd_collector: Rc::new(RefCell::new(SkimItemReader::new(reader_opts))), - query_history: vec![], - cmd_history: vec![], - }; - ctx.init_histories(&opts); - + let cmd_collector = Rc::new(RefCell::new(SkimItemReader::new(reader_opts))); + opts.cmd_collector = cmd_collector.clone(); //------------------------------------------------------------------------------ let bin_options = BinOptions { filter: opts.filter.clone(), @@ -104,7 +97,7 @@ fn sk_main() -> Result { //------------------------------------------------------------------------------ // read from pipe or command let rx_item = if !io::stdin().is_terminal() { - let rx_item = ctx.cmd_collector.borrow().of_bufread(BufReader::new(std::io::stdin())); + let rx_item = cmd_collector.borrow().of_bufread(BufReader::new(std::io::stdin())); Some(rx_item) } else { None @@ -113,7 +106,7 @@ fn sk_main() -> Result { //------------------------------------------------------------------------------ // filter mode if opts.filter.is_some() { - return Ok(filter(&ctx, &bin_options, &opts, rx_item)?); + return Ok(filter(&bin_options, &opts, rx_item)?); } //------------------------------------------------------------------------------ @@ -156,14 +149,14 @@ fn sk_main() -> Result { //------------------------------------------------------------------------------ // write the history with latest item - if let Some(file) = opts.history { + if let Some(file) = opts.history_file { let limit = opts.history_size; - write_history_to_file(&ctx.query_history, &result.query, limit, &file)?; + write_history_to_file(&opts.query_history, &result.query, limit, &file)?; } - if let Some(file) = opts.cmd_history { + if let Some(file) = opts.cmd_history_file { let limit = opts.cmd_history_size; - write_history_to_file(&ctx.cmd_history, &result.cmd, limit, &file)?; + write_history_to_file(&opts.cmd_history, &result.cmd, limit, &file)?; } Ok(if result.selected_items.is_empty() { 1 } else { 0 }) @@ -204,7 +197,6 @@ pub struct BinOptions { } pub fn filter( - ctx: &SkimContext, bin_option: &BinOptions, options: &SkimOptions, source: Option, @@ -244,7 +236,7 @@ pub fn filter( let components_to_stop = Arc::new(AtomicUsize::new(0)); let stream_of_item = source.unwrap_or_else(|| { - let (ret, _control) = ctx.cmd_collector.borrow_mut().invoke(&cmd, components_to_stop); + let (ret, _control) = options.cmd_collector.borrow_mut().invoke(&cmd, components_to_stop); ret }); diff --git a/skim/src/context.rs b/skim/src/context.rs deleted file mode 100644 index c7dacaff..00000000 --- a/skim/src/context.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use util::read_file_lines; - -use crate::prelude::*; - -pub struct SkimContext { - pub cmd_collector: Rc>, - pub query_history: Vec, - pub cmd_history: Vec, -} - -impl SkimContext { - pub fn init_histories(&mut self, opts: &SkimOptions) { - if let Some(histfile) = &opts.history { - self.query_history.extend(read_file_lines(histfile).unwrap_or_default()); - } - - if let Some(cmd_histfile) = &opts.cmd_history { - self.cmd_history - .extend(read_file_lines(cmd_histfile).unwrap_or_default()); - } - } -} - -impl Default for SkimContext { - fn default() -> Self { - Self { - cmd_collector: Rc::new(RefCell::new(SkimItemReader::new(Default::default()))), - query_history: vec![], - cmd_history: vec![], - } - } -} diff --git a/skim/src/lib.rs b/skim/src/lib.rs index 29f7f3c6..66f8f16f 100644 --- a/skim/src/lib.rs +++ b/skim/src/lib.rs @@ -17,13 +17,13 @@ use tuikit::prelude::{Event as TermEvent, *}; pub use crate::ansi::AnsiString; pub use crate::engine::fuzzy::FuzzyAlgorithm; use crate::event::{EventReceiver, EventSender}; +pub use crate::item::RankCriteria; use crate::model::Model; pub use crate::options::SkimOptions; pub use crate::output::SkimOutput; use crate::reader::Reader; mod ansi; -pub mod context; mod engine; mod event; pub mod field; @@ -34,7 +34,7 @@ mod input; mod item; mod matcher; mod model; -mod options; +pub mod options; mod orderedvec; mod output; pub mod prelude; diff --git a/skim/src/options.rs b/skim/src/options.rs index cd05df09..f622772d 100644 --- a/skim/src/options.rs +++ b/skim/src/options.rs @@ -1,8 +1,14 @@ +use std::cell::RefCell; +use std::rc::Rc; + use clap::Parser; use derive_builder::Builder; use crate::item::RankCriteria; -use crate::{CaseMatching, FuzzyAlgorithm}; +use crate::prelude::SkimItemReader; +use crate::reader::CommandCollector; +use crate::util::read_file_lines; +use crate::{CaseMatching, FuzzyAlgorithm, Selector}; /// sk - fuzzy finder in Rust /// @@ -471,8 +477,8 @@ pub struct SkimOptions { /// 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. - #[arg(long, help_heading = "History")] - pub history: Option, + #[arg(long = "history", help_heading = "History")] + pub history_file: Option, /// Maximum number of query history entries to keep #[arg(long, default_value = "1000", help_heading = "History")] @@ -483,8 +489,8 @@ pub struct SkimOptions { /// 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. - #[arg(long, help_heading = "History")] - pub cmd_history: Option, + #[arg(long = "cmd-history", help_heading = "History")] + pub cmd_history_file: Option, /// Maximum number of query history entries to keep #[arg(long, default_value = "1000", help_heading = "History")] @@ -702,6 +708,15 @@ pub struct SkimOptions { /// Reserved for later use #[arg(long, hide = true, help_heading = "Reserved for later use")] pub phony: bool, + + #[clap(skip = Rc::new(RefCell::new(SkimItemReader::default())) as Rc>)] + pub cmd_collector: Rc>, + #[clap(skip)] + pub query_history: Vec, + #[clap(skip)] + pub cmd_history: Vec, + #[clap(skip)] + pub selector: Option>, } impl Default for SkimOptions { @@ -734,10 +749,21 @@ impl SkimOptions { self.layout = String::from("reverse"); } let history_binds = String::from("ctrl-p:previous-history,ctrl-n:next-history"); - if self.history.is_some() || self.cmd_history.is_some() { + if self.history_file.is_some() || self.cmd_history_file.is_some() { + self.init_histories(); self.bind.push(history_binds); } self } + 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()); + } + } } diff --git a/skim/src/query.rs b/skim/src/query.rs index 88665a0e..e66ae8c1 100644 --- a/skim/src/query.rs +++ b/skim/src/query.rs @@ -126,14 +126,14 @@ impl Query { self.replstr = options.replstr.clone(); - if let Some(history_file) = &options.history { + if let Some(file) = &options.history_file { self.fz_query_history_before = - read_file_lines(history_file).expect(&format!("Failed to open history file {}", history_file)); + read_file_lines(file).unwrap_or_else(|_| panic!("Failed to open history file {}", file)); } - if let Some(cmd_history_file) = &options.cmd_history { - self.cmd_history_before = read_file_lines(cmd_history_file) - .expect(&format!("Failed to open command history file {}", cmd_history_file)); + if let Some(file) = &options.cmd_history_file { + self.cmd_history_before = + read_file_lines(file).unwrap_or_else(|_| panic!("Failed to open command history file {}", file)); } } diff --git a/skim/src/reader.rs b/skim/src/reader.rs index c2cfe2aa..4e2a5d25 100644 --- a/skim/src/reader.rs +++ b/skim/src/reader.rs @@ -3,7 +3,6 @@ //! After reading in a line, reader will save an item into the pool(items) use crate::global::mark_new_run; use crate::options::SkimOptions; -use crate::prelude::*; use crate::spinlock::SpinLock; use crate::{SkimItem, SkimItemReceiver}; use crossbeam::channel::{bounded, select, Sender}; @@ -65,17 +64,8 @@ pub struct Reader { impl Reader { pub fn with_options(options: &SkimOptions) -> Self { - let item_reader_option = SkimItemReaderOption::default() - .ansi(options.ansi) - .delimiter(&options.delimiter) - .with_nth(options.with_nth.iter().map(|s| s.as_str())) - .nth(options.nth.iter().map(|s| s.as_str())) - .read0(options.read0) - .show_error(options.show_cmd_error) - .build(); - let cmd_collector = Rc::new(RefCell::new(SkimItemReader::new(item_reader_option))); Self { - cmd_collector, + cmd_collector: options.cmd_collector.clone(), rx_item: None, } } diff --git a/skim/src/selection.rs b/skim/src/selection.rs index 4d68c9da..edc22119 100644 --- a/skim/src/selection.rs +++ b/skim/src/selection.rs @@ -129,18 +129,26 @@ impl Selection { || !options.pre_select_pat.is_empty() || !pre_select_items.is_empty() || options.pre_select_file.is_some() + || options.selector.is_some() { - let mut preset_file_items: Vec = vec![]; - if let Some(pre_select_file) = options.pre_select_file.clone() { - preset_file_items = read_file_lines(&pre_select_file).unwrap(); - } + match options.selector.clone() { + None => { + let mut preset_file_items: Vec = vec![]; + if let Some(pre_select_file) = options.pre_select_file.clone() { + preset_file_items = read_file_lines(&pre_select_file).unwrap(); + } - let selector = DefaultSkimSelector::default() - .first_n(options.pre_select_n) - .regex(&options.pre_select_pat) - .preset(pre_select_items) - .preset(preset_file_items); - self.selector = Some(Rc::new(selector)); + let selector = DefaultSkimSelector::default() + .first_n(options.pre_select_n) + .regex(&options.pre_select_pat) + .preset(pre_select_items) + .preset(preset_file_items); + self.selector = Some(Rc::new(selector)); + } + Some(s) => { + self.selector = Some(s); + } + } } }