mirror of
https://github.com/lotabout/skim.git
synced 2026-09-10 07:16:23 -04:00
refactor: demangle lib and bin implementations
- remove bin options from lib options - move default implementations into its own sub module
This commit is contained in:
parent
7f6736da68
commit
08bc067f35
|
|
@ -6,6 +6,7 @@ extern crate shlex;
|
||||||
extern crate skim;
|
extern crate skim;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader, BufWriter, Write};
|
use std::io::{BufRead, BufReader, BufWriter, Write};
|
||||||
|
|
@ -226,17 +227,25 @@ fn real_main() -> Result<i32, std::io::Error> {
|
||||||
writeln!(stdout, "{}", VERSION)?;
|
writeln!(stdout, "{}", VERSION)?;
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// initialize collector
|
||||||
|
let collector_option = CollectorOption::default()
|
||||||
|
.ansi(opts.is_present("ansi"))
|
||||||
|
.delimiter(opts.values_of("delimiter").and_then(|vals| vals.last()).unwrap_or(""))
|
||||||
|
.with_nth(opts.values_of("with-nth").and_then(|vals| vals.last()).unwrap_or(""))
|
||||||
|
.nth(opts.values_of("nth").and_then(|vals| vals.last()).unwrap_or(""))
|
||||||
|
.read0(opts.is_present("read0"))
|
||||||
|
.replace_str(opts.values_of("replstr").and_then(|vals| vals.last()).unwrap_or(""))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let cmd_collector = Rc::new(RefCell::new(DefaultSkimCollector::new(collector_option)));
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// read in the history file
|
// read in the history file
|
||||||
let fz_query_histories = opts.values_of("history").and_then(|vals| vals.last());
|
let fz_query_histories = opts.values_of("history").and_then(|vals| vals.last());
|
||||||
let cmd_query_histories = opts.values_of("cmd-history").and_then(|vals| vals.last());
|
let cmd_query_histories = opts.values_of("cmd-history").and_then(|vals| vals.last());
|
||||||
debug!("query_history_file: {:?}", fz_query_histories);
|
|
||||||
debug!("cmd_history_file: {:?}", cmd_query_histories);
|
|
||||||
let query_history = fz_query_histories.and_then(|filename| read_file_lines(filename).ok()).unwrap_or_else(|| vec![]);
|
let query_history = fz_query_histories.and_then(|filename| read_file_lines(filename).ok()).unwrap_or_else(|| vec![]);
|
||||||
let cmd_history = cmd_query_histories.and_then(|filename| read_file_lines(filename).ok()).unwrap_or_else(|| vec![]);
|
let cmd_history = cmd_query_histories.and_then(|filename| read_file_lines(filename).ok()).unwrap_or_else(|| vec![]);
|
||||||
debug!("query_history: {:?}", query_history);
|
|
||||||
debug!("cmd_history: {:?}", query_history);
|
|
||||||
|
|
||||||
let mut options = parse_options(&opts);
|
let mut options = parse_options(&opts);
|
||||||
if fz_query_histories.is_some() || cmd_query_histories.is_some() {
|
if fz_query_histories.is_some() || cmd_query_histories.is_some() {
|
||||||
|
|
@ -246,16 +255,26 @@ fn real_main() -> Result<i32, std::io::Error> {
|
||||||
options.bind.insert(0, "ctrl-p:previous-history,ctrl-n:next-history");
|
options.bind.insert(0, "ctrl-p:previous-history,ctrl-n:next-history");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.cmd_collector = cmd_collector.clone();
|
||||||
|
|
||||||
let options = options;
|
let options = options;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
let bin_options = BinOptionsBuilder::default()
|
||||||
|
.filter(opts.values_of("filter").and_then(|vals| vals.last()))
|
||||||
|
.print_query(opts.is_present("print_query"))
|
||||||
|
.print_cmd(opts.is_present("print_cmd"))
|
||||||
|
.output_ending(if opts.is_present("print0") { "\0" } else { "\n" })
|
||||||
|
.build()
|
||||||
|
.expect("");
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// read from pipe or command
|
// read from pipe or command
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
let components_to_stop = Arc::new(AtomicUsize::new(0));
|
let components_to_stop = Arc::new(AtomicUsize::new(0));
|
||||||
let rx_item = match isatty(stdin.as_raw_fd()) {
|
let rx_item = match isatty(stdin.as_raw_fd()) {
|
||||||
Ok(false) | Err(nix::Error::Sys(nix::errno::Errno::EINVAL)) => {
|
Ok(false) | Err(nix::Error::Sys(nix::errno::Errno::EINVAL)) => {
|
||||||
let collector_option = CollectorOption::with_options(&options);
|
let (rx_item, _) = cmd_collector.borrow().read_and_collect_from_command(components_to_stop, CollectorInput::Pipe(Box::new(BufReader::new(stdin))));
|
||||||
let (rx_item, _) = read_and_collect_from_command(components_to_stop, CollectorInput::Pipe(Box::new(BufReader::new(stdin))), collector_option);
|
|
||||||
Some(rx_item)
|
Some(rx_item)
|
||||||
}
|
}
|
||||||
Ok(true) | Err(_) => None,
|
Ok(true) | Err(_) => None,
|
||||||
|
|
@ -264,12 +283,10 @@ fn real_main() -> Result<i32, std::io::Error> {
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// filter mode
|
// filter mode
|
||||||
if opts.is_present("filter") {
|
if opts.is_present("filter") {
|
||||||
return filter(&options, rx_item);
|
return filter(&bin_options, &options, rx_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
let output_ending = if options.print0 { "\0" } else { "\n" };
|
|
||||||
|
|
||||||
let output = Skim::run_with(&options, rx_item);
|
let output = Skim::run_with(&options, rx_item);
|
||||||
if output.is_none() {
|
if output.is_none() {
|
||||||
return Ok(130);
|
return Ok(130);
|
||||||
|
|
@ -280,20 +297,20 @@ fn real_main() -> Result<i32, std::io::Error> {
|
||||||
let output = output.unwrap();
|
let output = output.unwrap();
|
||||||
|
|
||||||
// output query
|
// output query
|
||||||
if options.print_query {
|
if bin_options.print_query {
|
||||||
write!(stdout, "{}{}", output.query, output_ending)?;
|
write!(stdout, "{}{}", output.query, bin_options.output_ending)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.print_cmd {
|
if bin_options.print_cmd {
|
||||||
write!(stdout, "{}{}", output.cmd, output_ending)?;
|
write!(stdout, "{}{}", output.cmd, bin_options.output_ending)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(key) = output.accept_key {
|
if let Some(key) = output.accept_key {
|
||||||
write!(stdout, "{}{}", key, output_ending)?;
|
write!(stdout, "{}{}", key, bin_options.output_ending)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in output.selected_items.iter() {
|
for item in output.selected_items.iter() {
|
||||||
write!(stdout, "{}{}", item.output(), output_ending)?;
|
write!(stdout, "{}{}", item.output(), bin_options.output_ending)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
@ -327,15 +344,9 @@ fn parse_options<'a>(options: &'a ArgMatches) -> SkimOptions<'a> {
|
||||||
.cmd(options.values_of("cmd").and_then(|vals| vals.last()))
|
.cmd(options.values_of("cmd").and_then(|vals| vals.last()))
|
||||||
.query(options.values_of("query").and_then(|vals| vals.last()))
|
.query(options.values_of("query").and_then(|vals| vals.last()))
|
||||||
.cmd_query(options.values_of("cmd-query").and_then(|vals| vals.last()))
|
.cmd_query(options.values_of("cmd-query").and_then(|vals| vals.last()))
|
||||||
.replstr(options.values_of("replstr").and_then(|vals| vals.last()))
|
|
||||||
.interactive(options.is_present("interactive"))
|
.interactive(options.is_present("interactive"))
|
||||||
.prompt(options.values_of("prompt").and_then(|vals| vals.last()))
|
.prompt(options.values_of("prompt").and_then(|vals| vals.last()))
|
||||||
.cmd_prompt(options.values_of("cmd-prompt").and_then(|vals| vals.last()))
|
.cmd_prompt(options.values_of("cmd-prompt").and_then(|vals| vals.last()))
|
||||||
.ansi(options.is_present("ansi"))
|
|
||||||
.delimiter(options.values_of("delimiter").and_then(|vals| vals.last()))
|
|
||||||
.with_nth(options.values_of("with-nth").and_then(|vals| vals.last()))
|
|
||||||
.nth(options.values_of("nth").and_then(|vals| vals.last()))
|
|
||||||
.read0(options.is_present("read0"))
|
|
||||||
.bind(
|
.bind(
|
||||||
options
|
options
|
||||||
.values_of("bind")
|
.values_of("bind")
|
||||||
|
|
@ -350,9 +361,6 @@ fn parse_options<'a>(options: &'a ArgMatches) -> SkimOptions<'a> {
|
||||||
})
|
})
|
||||||
.layout(options.values_of("layout").and_then(|vals| vals.last()).unwrap_or(""))
|
.layout(options.values_of("layout").and_then(|vals| vals.last()).unwrap_or(""))
|
||||||
.reverse(options.is_present("reverse"))
|
.reverse(options.is_present("reverse"))
|
||||||
.print0(options.is_present("print0"))
|
|
||||||
.print_query(options.is_present("print-query"))
|
|
||||||
.print_cmd(options.is_present("print-cmd"))
|
|
||||||
.no_hscroll(options.is_present("no-hscroll"))
|
.no_hscroll(options.is_present("no-hscroll"))
|
||||||
.no_mouse(options.is_present("no-mouse"))
|
.no_mouse(options.is_present("no-mouse"))
|
||||||
.tabstop(options.values_of("tabstop").and_then(|vals| vals.last()))
|
.tabstop(options.values_of("tabstop").and_then(|vals| vals.last()))
|
||||||
|
|
@ -371,7 +379,6 @@ fn parse_options<'a>(options: &'a ArgMatches) -> SkimOptions<'a> {
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
)
|
)
|
||||||
.layout(options.values_of("layout").and_then(|vals| vals.last()).unwrap_or(""))
|
.layout(options.values_of("layout").and_then(|vals| vals.last()).unwrap_or(""))
|
||||||
.filter(options.values_of("filter").and_then(|vals| vals.last()).unwrap_or(""))
|
|
||||||
.algorithm(FuzzyAlgorithm::of(
|
.algorithm(FuzzyAlgorithm::of(
|
||||||
options.values_of("algorithm").and_then(|vals| vals.last()).unwrap(),
|
options.values_of("algorithm").and_then(|vals| vals.last()).unwrap(),
|
||||||
))
|
))
|
||||||
|
|
@ -417,24 +424,35 @@ fn write_history_to_file(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter(options: &SkimOptions, source: Option<SkimItemReceiver>) -> Result<i32, std::io::Error> {
|
#[derive(Builder)]
|
||||||
|
pub struct BinOptions<'a> {
|
||||||
|
filter: Option<&'a str>,
|
||||||
|
output_ending: &'a str,
|
||||||
|
print_query: bool,
|
||||||
|
print_cmd: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter(
|
||||||
|
bin_option: &BinOptions,
|
||||||
|
options: &SkimOptions,
|
||||||
|
source: Option<SkimItemReceiver>,
|
||||||
|
) -> Result<i32, std::io::Error> {
|
||||||
let mut stdout = std::io::stdout();
|
let mut stdout = std::io::stdout();
|
||||||
|
|
||||||
let output_ending = if options.print0 { "\0" } else { "\n" };
|
|
||||||
let query = options.filter;
|
|
||||||
let default_command = match env::var("SKIM_DEFAULT_COMMAND").as_ref().map(String::as_ref) {
|
let default_command = match env::var("SKIM_DEFAULT_COMMAND").as_ref().map(String::as_ref) {
|
||||||
Ok("") | Err(_) => "find .".to_owned(),
|
Ok("") | Err(_) => "find .".to_owned(),
|
||||||
Ok(val) => val.to_owned(),
|
Ok(val) => val.to_owned(),
|
||||||
};
|
};
|
||||||
|
let query = bin_option.filter.unwrap_or(&"");
|
||||||
let cmd = options.cmd.unwrap_or(&default_command);
|
let cmd = options.cmd.unwrap_or(&default_command);
|
||||||
|
|
||||||
// output query
|
// output query
|
||||||
if options.print_query {
|
if bin_option.print_query {
|
||||||
write!(stdout, "{}{}", query, output_ending)?;
|
write!(stdout, "{}{}", query, bin_option.output_ending)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.print_cmd {
|
if bin_option.print_cmd {
|
||||||
write!(stdout, "{}{}", cmd, output_ending)?;
|
write!(stdout, "{}{}", cmd, bin_option.output_ending)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
@ -454,11 +472,10 @@ pub fn filter(options: &SkimOptions, source: Option<SkimItemReceiver>) -> Result
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// start
|
// start
|
||||||
let components_to_stop = Arc::new(AtomicUsize::new(0));
|
let components_to_stop = Arc::new(AtomicUsize::new(0));
|
||||||
let collector_option = CollectorOption::with_options(&options);
|
|
||||||
|
|
||||||
let stream_of_item = source.unwrap_or_else(|| {
|
let stream_of_item = source.unwrap_or_else(|| {
|
||||||
let collector_input = CollectorInput::Command(cmd.to_string());
|
let cmd_collector = options.cmd_collector.clone();
|
||||||
let (ret, _control) = read_and_collect_from_command(components_to_stop, collector_input, collector_option);
|
let (ret, _control) = cmd_collector.borrow_mut().invoke(cmd, components_to_stop);
|
||||||
ret
|
ret
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -468,7 +485,7 @@ pub fn filter(options: &SkimOptions, source: Option<SkimItemReceiver>) -> Result
|
||||||
.filter_map(|item| engine.match_item(item))
|
.filter_map(|item| engine.match_item(item))
|
||||||
.try_for_each(|matched| {
|
.try_for_each(|matched| {
|
||||||
num_matched += 1;
|
num_matched += 1;
|
||||||
write!(stdout, "{}{}", matched.item.output(), output_ending)
|
write!(stdout, "{}{}", matched.item.output(), bin_option.output_ending)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(if num_matched == 0 { 1 } else { 0 })
|
Ok(if num_matched == 0 { 1 } else { 0 })
|
||||||
|
|
|
||||||
112
src/helper/item.rs
Normal file
112
src/helper/item.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
use crate::ansi::ANSIParser;
|
||||||
|
use crate::field::{parse_matching_fields, parse_transform_fields, FieldRange};
|
||||||
|
use crate::{AnsiString, SkimItem};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/// An item will store everything that one line input will need to be operated and displayed.
|
||||||
|
///
|
||||||
|
/// What's special about an item?
|
||||||
|
/// The simplest version of an item is a line of string, but things are getting more complex:
|
||||||
|
/// - The conversion of lower/upper case is slow in rust, because it involds unicode.
|
||||||
|
/// - We may need to interpret the ANSI codes in the text.
|
||||||
|
/// - The text can be transformed and limited while searching.
|
||||||
|
///
|
||||||
|
/// About the ANSI, we made assumption that it is linewise, that means no ANSI codes will affect
|
||||||
|
/// more than one line.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DefaultSkimItem {
|
||||||
|
/// The text that will be output when user press `enter`
|
||||||
|
/// `Some(..)` => the original input is transformed, could not output `text` directly
|
||||||
|
/// `None` => that it is safe to output `text` directly
|
||||||
|
orig_text: Option<String>,
|
||||||
|
|
||||||
|
/// The text that will be shown on screen and matched.
|
||||||
|
text: AnsiString<'static>,
|
||||||
|
|
||||||
|
// Option<Box<_>> to reduce memory use in normal cases where no matching ranges are specified.
|
||||||
|
#[allow(clippy::box_vec)]
|
||||||
|
matching_ranges: Option<Box<Vec<(usize, usize)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DefaultSkimItem {
|
||||||
|
pub fn new(
|
||||||
|
orig_text: String,
|
||||||
|
ansi_enabled: bool,
|
||||||
|
trans_fields: &[FieldRange],
|
||||||
|
matching_fields: &[FieldRange],
|
||||||
|
delimiter: &Regex,
|
||||||
|
) -> Self {
|
||||||
|
let using_transform_fields = !trans_fields.is_empty();
|
||||||
|
|
||||||
|
// transformed | ANSI | output
|
||||||
|
//------------------------------------------------------
|
||||||
|
// +- T -> trans+ANSI | ANSI
|
||||||
|
// | |
|
||||||
|
// +- T -> trans +- F -> trans | orig
|
||||||
|
// orig | |
|
||||||
|
// +- F -> orig +- T -> ANSI ==| ANSI
|
||||||
|
// | |
|
||||||
|
// +- F -> orig | orig
|
||||||
|
|
||||||
|
let mut ansi_parser: ANSIParser = Default::default();
|
||||||
|
|
||||||
|
let (orig_text, text) = if using_transform_fields && ansi_enabled {
|
||||||
|
// ansi and transform
|
||||||
|
let transformed = ansi_parser.parse_ansi(&parse_transform_fields(delimiter, &orig_text, trans_fields));
|
||||||
|
(Some(orig_text), transformed)
|
||||||
|
} else if using_transform_fields {
|
||||||
|
// transformed, not ansi
|
||||||
|
let transformed = parse_transform_fields(delimiter, &orig_text, trans_fields).into();
|
||||||
|
(Some(orig_text), transformed)
|
||||||
|
} else if ansi_enabled {
|
||||||
|
// not transformed, ansi
|
||||||
|
(None, ansi_parser.parse_ansi(&orig_text))
|
||||||
|
} else {
|
||||||
|
// normal case
|
||||||
|
(None, orig_text.into())
|
||||||
|
};
|
||||||
|
|
||||||
|
let matching_ranges = if !matching_fields.is_empty() {
|
||||||
|
Some(Box::new(parse_matching_fields(
|
||||||
|
delimiter,
|
||||||
|
text.stripped(),
|
||||||
|
matching_fields,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
DefaultSkimItem {
|
||||||
|
orig_text,
|
||||||
|
text,
|
||||||
|
matching_ranges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkimItem for DefaultSkimItem {
|
||||||
|
#[inline]
|
||||||
|
fn text(&self) -> Cow<str> {
|
||||||
|
Cow::Borrowed(self.text.stripped())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(&self) -> Cow<str> {
|
||||||
|
if self.orig_text.is_some() {
|
||||||
|
if self.text.has_attrs() {
|
||||||
|
let mut ansi_parser: ANSIParser = Default::default();
|
||||||
|
let text = ansi_parser.parse_ansi(self.orig_text.as_ref().unwrap());
|
||||||
|
text.into_inner()
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(self.orig_text.as_ref().unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(self.text.stripped())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_matching_ranges(&self) -> Option<&[(usize, usize)]> {
|
||||||
|
self.matching_ranges.as_ref().map(|vec| vec as &[(usize, usize)])
|
||||||
|
}
|
||||||
|
}
|
||||||
246
src/helper/item_collector.rs
Normal file
246
src/helper/item_collector.rs
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
use crate::field::FieldRange;
|
||||||
|
use crate::helper::item::DefaultSkimItem;
|
||||||
|
use crate::reader::CommandCollector;
|
||||||
|
use crate::{SkimItem, SkimItemReceiver, SkimItemSender};
|
||||||
|
use crossbeam::channel::{bounded, Receiver, Sender};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::env;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::process::{Child, Command, Stdio};
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
const CMD_CHANNEL_SIZE: usize = 1024;
|
||||||
|
const ITEM_CHANNEL_SIZE: usize = 10240;
|
||||||
|
const DELIMITER_STR: &str = r"[\t\n ]+";
|
||||||
|
const READ_BUFFER_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CollectorOption {
|
||||||
|
use_ansi_color: bool,
|
||||||
|
transform_fields: Vec<FieldRange>,
|
||||||
|
matching_fields: Vec<FieldRange>,
|
||||||
|
delimiter: Regex,
|
||||||
|
replace_str: String,
|
||||||
|
line_ending: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CollectorOption {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
use_ansi_color: false,
|
||||||
|
transform_fields: Vec::new(),
|
||||||
|
matching_fields: Vec::new(),
|
||||||
|
delimiter: Regex::new(DELIMITER_STR).unwrap(),
|
||||||
|
replace_str: "{}".to_string(),
|
||||||
|
line_ending: b'\n',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollectorOption {
|
||||||
|
pub fn ansi(mut self, enable: bool) -> Self {
|
||||||
|
self.use_ansi_color = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delimiter(mut self, delimiter: &str) -> Self {
|
||||||
|
if !delimiter.is_empty() {
|
||||||
|
self.delimiter = Regex::new(delimiter).unwrap_or_else(|_| Regex::new(DELIMITER_STR).unwrap());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_nth(mut self, with_nth: &str) -> Self {
|
||||||
|
if !with_nth.is_empty() {
|
||||||
|
self.transform_fields = with_nth
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|string| FieldRange::from_str(string))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_fields(mut self, transform_fields: Vec<FieldRange>) -> Self {
|
||||||
|
self.transform_fields = transform_fields;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nth(mut self, nth: &str) -> Self {
|
||||||
|
if !nth.is_empty() {
|
||||||
|
self.matching_fields = nth
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|string| FieldRange::from_str(string))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matching_fields(mut self, matching_fields: Vec<FieldRange>) -> Self {
|
||||||
|
self.matching_fields = matching_fields;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_str(mut self, replace_str: &str) -> Self {
|
||||||
|
if !replace_str.is_empty() {
|
||||||
|
self.replace_str = replace_str.to_string();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read0(mut self, enable: bool) -> Self {
|
||||||
|
if enable {
|
||||||
|
self.line_ending = b'\0';
|
||||||
|
} else {
|
||||||
|
self.line_ending = b'\n';
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CollectorInput {
|
||||||
|
Pipe(Box<dyn BufRead + Send>),
|
||||||
|
Command(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultSkimCollector {
|
||||||
|
option: Arc<CollectorOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultSkimCollector {
|
||||||
|
pub fn new(option: CollectorOption) -> Self {
|
||||||
|
Self {
|
||||||
|
option: Arc::new(option),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// components_to_stop == 0 => all the threads have been stopped
|
||||||
|
/// return (channel_for_receive_item, channel_to_stop_command)
|
||||||
|
pub fn read_and_collect_from_command(
|
||||||
|
&self,
|
||||||
|
components_to_stop: Arc<AtomicUsize>,
|
||||||
|
input: CollectorInput,
|
||||||
|
) -> (Receiver<Arc<dyn SkimItem>>, Sender<i32>) {
|
||||||
|
let (command, mut source) = match input {
|
||||||
|
CollectorInput::Pipe(pipe) => (None, pipe),
|
||||||
|
CollectorInput::Command(cmd) => get_command_output(&cmd).expect("command not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (tx_interrupt, rx_interrupt) = bounded(CMD_CHANNEL_SIZE);
|
||||||
|
let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = bounded(ITEM_CHANNEL_SIZE);
|
||||||
|
|
||||||
|
let started = Arc::new(AtomicBool::new(false));
|
||||||
|
let started_clone = started.clone();
|
||||||
|
let components_to_stop_clone = components_to_stop.clone();
|
||||||
|
let option = self.option.clone();
|
||||||
|
// listening to close signal and kill command if needed
|
||||||
|
thread::spawn(move || {
|
||||||
|
debug!("collector: command killer start");
|
||||||
|
components_to_stop_clone.fetch_add(1, Ordering::SeqCst);
|
||||||
|
started_clone.store(true, Ordering::SeqCst); // notify parent that it is started
|
||||||
|
|
||||||
|
let _ = rx_interrupt.recv(); // block waiting
|
||||||
|
// clean up resources
|
||||||
|
if let Some(mut x) = command {
|
||||||
|
let _ = x.kill();
|
||||||
|
let _ = x.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
components_to_stop_clone.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
debug!("collector: command killer stop");
|
||||||
|
});
|
||||||
|
|
||||||
|
while !started.load(Ordering::SeqCst) {
|
||||||
|
// busy waiting for the thread to start. (components_to_stop is added)
|
||||||
|
}
|
||||||
|
|
||||||
|
let started = Arc::new(AtomicBool::new(false));
|
||||||
|
let started_clone = started.clone();
|
||||||
|
let tx_interrupt_clone = tx_interrupt.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
debug!("collector: command collector start");
|
||||||
|
components_to_stop.fetch_add(1, Ordering::SeqCst);
|
||||||
|
started_clone.store(true, Ordering::SeqCst); // notify parent that it is started
|
||||||
|
|
||||||
|
let mut buffer = Vec::with_capacity(READ_BUFFER_SIZE);
|
||||||
|
loop {
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
// start reading
|
||||||
|
match source.read_until(option.line_ending, &mut buffer) {
|
||||||
|
Ok(n) => {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.ends_with(&[b'\r', b'\n']) {
|
||||||
|
buffer.pop();
|
||||||
|
buffer.pop();
|
||||||
|
} else if buffer.ends_with(&[b'\n']) || buffer.ends_with(&[b'\0']) {
|
||||||
|
buffer.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = String::from_utf8_lossy(&buffer).to_string();
|
||||||
|
|
||||||
|
let raw_item = DefaultSkimItem::new(
|
||||||
|
line,
|
||||||
|
option.use_ansi_color,
|
||||||
|
&option.transform_fields,
|
||||||
|
&option.matching_fields,
|
||||||
|
&option.delimiter,
|
||||||
|
);
|
||||||
|
|
||||||
|
match tx_item.send(Arc::new(raw_item)) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(_) => {
|
||||||
|
debug!("collector: failed to send item, quit");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_err) => {} // String not UTF8 or other error, skip.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = tx_interrupt_clone.send(1); // ensure the waiting thread will exit
|
||||||
|
components_to_stop.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
debug!("collector: command collector stop");
|
||||||
|
});
|
||||||
|
|
||||||
|
while !started.load(Ordering::SeqCst) {
|
||||||
|
// busy waiting for the thread to start. (components_to_stop is added)
|
||||||
|
}
|
||||||
|
|
||||||
|
(rx_item, tx_interrupt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandCollector for DefaultSkimCollector {
|
||||||
|
fn invoke(&mut self, cmd: &str, components_to_stop: Arc<AtomicUsize>) -> (SkimItemReceiver, Sender<i32>) {
|
||||||
|
self.read_and_collect_from_command(components_to_stop, CollectorInput::Command(cmd.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandOutput = (Option<Child>, Box<dyn BufRead + Send>);
|
||||||
|
fn get_command_output(cmd: &str) -> Result<CommandOutput, Box<dyn Error>> {
|
||||||
|
let shell = env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
|
||||||
|
let mut command: Child = Command::new(shell)
|
||||||
|
.arg("-c")
|
||||||
|
.arg(cmd)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let stdout = command
|
||||||
|
.stdout
|
||||||
|
.take()
|
||||||
|
.ok_or_else(|| "command output: unwrap failed".to_owned())?;
|
||||||
|
|
||||||
|
Ok((Some(command), Box::new(BufReader::new(stdout))))
|
||||||
|
}
|
||||||
3
src/helper/mod.rs
Normal file
3
src/helper/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod item;
|
||||||
|
pub mod item_collector;
|
||||||
|
pub mod string_reader;
|
||||||
71
src/helper/string_reader.rs
Normal file
71
src/helper/string_reader.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use crate::prelude::bounded;
|
||||||
|
use crate::{SkimItemReceiver, SkimItemSender};
|
||||||
|
/// helper for turn a BufRead into a skim stream
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
const ITEM_CHANNEL_SIZE: usize = 10240;
|
||||||
|
|
||||||
|
pub struct SkimItemReader {
|
||||||
|
buf_size: usize,
|
||||||
|
line_ending: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SkimItemReader {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
buf_size: ITEM_CHANNEL_SIZE,
|
||||||
|
line_ending: b'\n',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkimItemReader {
|
||||||
|
pub fn buf_size(mut self, buf_size: usize) -> Self {
|
||||||
|
self.buf_size = buf_size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_ending(mut self, line_ending: u8) -> Self {
|
||||||
|
self.line_ending = line_ending;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkimItemReader {
|
||||||
|
/// helper: convert bufread into SkimItemReceiver
|
||||||
|
pub fn of_bufread(&self, mut source: impl BufRead + Send + 'static) -> SkimItemReceiver {
|
||||||
|
let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = bounded(self.buf_size);
|
||||||
|
let line_ending = self.line_ending;
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut buffer = Vec::with_capacity(1024);
|
||||||
|
loop {
|
||||||
|
buffer.clear();
|
||||||
|
// start reading
|
||||||
|
match source.read_until(line_ending, &mut buffer) {
|
||||||
|
Ok(n) => {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.ends_with(&[b'\r', b'\n']) {
|
||||||
|
buffer.pop();
|
||||||
|
buffer.pop();
|
||||||
|
} else if buffer.ends_with(&[b'\n']) || buffer.ends_with(&[b'\0']) {
|
||||||
|
buffer.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = String::from_utf8_lossy(&buffer);
|
||||||
|
let result = tx_item.send(Arc::new(string.into_owned()));
|
||||||
|
if result.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_err) => {} // String not UTF8 or other error, skip.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rx_item
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/item.rs
112
src/item.rs
|
|
@ -1,126 +1,14 @@
|
||||||
///! An item is line of text that read from `find` command or stdin together with
|
///! An item is line of text that read from `find` command or stdin together with
|
||||||
///! the internal states, such as selected or not
|
///! the internal states, such as selected or not
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use crate::ansi::{ANSIParser, AnsiString};
|
|
||||||
use crate::field::{parse_matching_fields, parse_transform_fields, FieldRange};
|
|
||||||
use crate::spinlock::{SpinLock, SpinLockGuard};
|
use crate::spinlock::{SpinLock, SpinLockGuard};
|
||||||
use crate::SkimItem;
|
use crate::SkimItem;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/// An item will store everything that one line input will need to be operated and displayed.
|
|
||||||
///
|
|
||||||
/// What's special about an item?
|
|
||||||
/// The simplest version of an item is a line of string, but things are getting more complex:
|
|
||||||
/// - The conversion of lower/upper case is slow in rust, because it involds unicode.
|
|
||||||
/// - We may need to interpret the ANSI codes in the text.
|
|
||||||
/// - The text can be transformed and limited while searching.
|
|
||||||
///
|
|
||||||
/// About the ANSI, we made assumption that it is linewise, that means no ANSI codes will affect
|
|
||||||
/// more than one line.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DefaultSkimItem {
|
|
||||||
/// The text that will be output when user press `enter`
|
|
||||||
/// `Some(..)` => the original input is transformed, could not output `text` directly
|
|
||||||
/// `None` => that it is safe to output `text` directly
|
|
||||||
orig_text: Option<String>,
|
|
||||||
|
|
||||||
/// The text that will be shown on screen and matched.
|
|
||||||
text: AnsiString<'static>,
|
|
||||||
|
|
||||||
// Option<Box<_>> to reduce memory use in normal cases where no matching ranges are specified.
|
|
||||||
#[allow(clippy::box_vec)]
|
|
||||||
matching_ranges: Option<Box<Vec<(usize, usize)>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DefaultSkimItem {
|
|
||||||
pub fn new(
|
|
||||||
orig_text: String,
|
|
||||||
ansi_enabled: bool,
|
|
||||||
trans_fields: &[FieldRange],
|
|
||||||
matching_fields: &[FieldRange],
|
|
||||||
delimiter: &Regex,
|
|
||||||
) -> Self {
|
|
||||||
let using_transform_fields = !trans_fields.is_empty();
|
|
||||||
|
|
||||||
// transformed | ANSI | output
|
|
||||||
//------------------------------------------------------
|
|
||||||
// +- T -> trans+ANSI | ANSI
|
|
||||||
// | |
|
|
||||||
// +- T -> trans +- F -> trans | orig
|
|
||||||
// orig | |
|
|
||||||
// +- F -> orig +- T -> ANSI ==| ANSI
|
|
||||||
// | |
|
|
||||||
// +- F -> orig | orig
|
|
||||||
|
|
||||||
let mut ansi_parser: ANSIParser = Default::default();
|
|
||||||
|
|
||||||
let (orig_text, text) = if using_transform_fields && ansi_enabled {
|
|
||||||
// ansi and transform
|
|
||||||
let transformed = ansi_parser.parse_ansi(&parse_transform_fields(delimiter, &orig_text, trans_fields));
|
|
||||||
(Some(orig_text), transformed)
|
|
||||||
} else if using_transform_fields {
|
|
||||||
// transformed, not ansi
|
|
||||||
let transformed = parse_transform_fields(delimiter, &orig_text, trans_fields).into();
|
|
||||||
(Some(orig_text), transformed)
|
|
||||||
} else if ansi_enabled {
|
|
||||||
// not transformed, ansi
|
|
||||||
(None, ansi_parser.parse_ansi(&orig_text))
|
|
||||||
} else {
|
|
||||||
// normal case
|
|
||||||
(None, orig_text.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
let matching_ranges = if !matching_fields.is_empty() {
|
|
||||||
Some(Box::new(parse_matching_fields(
|
|
||||||
delimiter,
|
|
||||||
text.stripped(),
|
|
||||||
matching_fields,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
DefaultSkimItem {
|
|
||||||
orig_text,
|
|
||||||
text,
|
|
||||||
matching_ranges,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SkimItem for DefaultSkimItem {
|
|
||||||
#[inline]
|
|
||||||
fn text(&self) -> Cow<str> {
|
|
||||||
Cow::Borrowed(self.text.stripped())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output(&self) -> Cow<str> {
|
|
||||||
if self.orig_text.is_some() {
|
|
||||||
if self.text.has_attrs() {
|
|
||||||
let mut ansi_parser: ANSIParser = Default::default();
|
|
||||||
let text = ansi_parser.parse_ansi(self.orig_text.as_ref().unwrap());
|
|
||||||
text.into_inner()
|
|
||||||
} else {
|
|
||||||
Cow::Borrowed(self.orig_text.as_ref().unwrap())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Cow::Borrowed(self.text.stripped())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_matching_ranges(&self) -> Option<&[(usize, usize)]> {
|
|
||||||
self.matching_ranges.as_ref().map(|vec| vec as &[(usize, usize)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
pub type ItemIndex = (u32, u32);
|
pub type ItemIndex = (u32, u32);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,268 +0,0 @@
|
||||||
use crate::field::FieldRange;
|
|
||||||
use crate::item::DefaultSkimItem;
|
|
||||||
use crate::{SkimItem, SkimItemReceiver, SkimItemSender, SkimOptions};
|
|
||||||
use crossbeam::channel::{bounded, Receiver, Sender};
|
|
||||||
use regex::Regex;
|
|
||||||
use std::env;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::io::{BufRead, BufReader};
|
|
||||||
use std::process::{Child, Command, Stdio};
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
const CMD_CHANNEL_SIZE: usize = 1024;
|
|
||||||
const ITEM_CHANNEL_SIZE: usize = 10240;
|
|
||||||
const DELIMITER_STR: &str = r"[\t\n ]+";
|
|
||||||
const READ_BUFFER_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CollectorOption {
|
|
||||||
pub use_ansi_color: bool,
|
|
||||||
pub default_arg: String,
|
|
||||||
pub transform_fields: Vec<FieldRange>,
|
|
||||||
pub matching_fields: Vec<FieldRange>,
|
|
||||||
pub delimiter: Regex,
|
|
||||||
pub replace_str: String,
|
|
||||||
pub line_ending: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CollectorOption {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
use_ansi_color: false,
|
|
||||||
default_arg: String::new(),
|
|
||||||
transform_fields: Vec::new(),
|
|
||||||
matching_fields: Vec::new(),
|
|
||||||
delimiter: Regex::new(DELIMITER_STR).unwrap(),
|
|
||||||
replace_str: "{}".to_string(),
|
|
||||||
line_ending: b'\n',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CollectorOption {
|
|
||||||
pub fn with_options(options: &SkimOptions) -> Self {
|
|
||||||
let mut reader_option = Self::default();
|
|
||||||
reader_option.parse_options(&options);
|
|
||||||
reader_option
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_options(&mut self, options: &SkimOptions) {
|
|
||||||
if options.ansi {
|
|
||||||
self.use_ansi_color = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(delimiter) = options.delimiter {
|
|
||||||
self.delimiter = Regex::new(delimiter).unwrap_or_else(|_| Regex::new(DELIMITER_STR).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(transform_fields) = options.with_nth {
|
|
||||||
self.transform_fields = transform_fields
|
|
||||||
.split(',')
|
|
||||||
.filter_map(|string| FieldRange::from_str(string))
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(matching_fields) = options.nth {
|
|
||||||
self.matching_fields = matching_fields
|
|
||||||
.split(',')
|
|
||||||
.filter_map(|string| FieldRange::from_str(string))
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.read0 {
|
|
||||||
self.line_ending = b'\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum CollectorInput {
|
|
||||||
Pipe(Box<dyn BufRead + Send>),
|
|
||||||
Command(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// components_to_stop == 0 => all the threads have been stopped
|
|
||||||
/// return (channel_for_receive_item, channel_to_stop_command)
|
|
||||||
pub fn read_and_collect_from_command(
|
|
||||||
components_to_stop: Arc<AtomicUsize>,
|
|
||||||
input: CollectorInput,
|
|
||||||
option: CollectorOption,
|
|
||||||
) -> (Receiver<Arc<dyn SkimItem>>, Sender<i32>) {
|
|
||||||
let (command, mut source) = match input {
|
|
||||||
CollectorInput::Pipe(pipe) => (None, pipe),
|
|
||||||
CollectorInput::Command(cmd) => get_command_output(&cmd).expect("command not found"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (tx_interrupt, rx_interrupt) = bounded(CMD_CHANNEL_SIZE);
|
|
||||||
let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = bounded(ITEM_CHANNEL_SIZE);
|
|
||||||
|
|
||||||
let started = Arc::new(AtomicBool::new(false));
|
|
||||||
let started_clone = started.clone();
|
|
||||||
let components_to_stop_clone = components_to_stop.clone();
|
|
||||||
// listening to close signal and kill command if needed
|
|
||||||
thread::spawn(move || {
|
|
||||||
debug!("collector: command killer start");
|
|
||||||
components_to_stop_clone.fetch_add(1, Ordering::SeqCst);
|
|
||||||
started_clone.store(true, Ordering::SeqCst); // notify parent that it is started
|
|
||||||
|
|
||||||
let _ = rx_interrupt.recv(); // block waiting
|
|
||||||
// clean up resources
|
|
||||||
if let Some(mut x) = command {
|
|
||||||
let _ = x.kill();
|
|
||||||
let _ = x.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
components_to_stop_clone.fetch_sub(1, Ordering::SeqCst);
|
|
||||||
debug!("collector: command killer stop");
|
|
||||||
});
|
|
||||||
|
|
||||||
while !started.load(Ordering::SeqCst) {
|
|
||||||
// busy waiting for the thread to start. (components_to_stop is added)
|
|
||||||
}
|
|
||||||
|
|
||||||
let started = Arc::new(AtomicBool::new(false));
|
|
||||||
let started_clone = started.clone();
|
|
||||||
let tx_interrupt_clone = tx_interrupt.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
debug!("collector: command collector start");
|
|
||||||
components_to_stop.fetch_add(1, Ordering::SeqCst);
|
|
||||||
started_clone.store(true, Ordering::SeqCst); // notify parent that it is started
|
|
||||||
|
|
||||||
let opt = option;
|
|
||||||
// set the proper run number
|
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity(READ_BUFFER_SIZE);
|
|
||||||
loop {
|
|
||||||
buffer.clear();
|
|
||||||
|
|
||||||
// start reading
|
|
||||||
match source.read_until(opt.line_ending, &mut buffer) {
|
|
||||||
Ok(n) => {
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.ends_with(&[b'\r', b'\n']) {
|
|
||||||
buffer.pop();
|
|
||||||
buffer.pop();
|
|
||||||
} else if buffer.ends_with(&[b'\n']) || buffer.ends_with(&[b'\0']) {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = String::from_utf8_lossy(&buffer).to_string();
|
|
||||||
|
|
||||||
let raw_item = DefaultSkimItem::new(
|
|
||||||
line,
|
|
||||||
opt.use_ansi_color,
|
|
||||||
&opt.transform_fields,
|
|
||||||
&opt.matching_fields,
|
|
||||||
&opt.delimiter,
|
|
||||||
);
|
|
||||||
|
|
||||||
match tx_item.send(Arc::new(raw_item)) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_) => {
|
|
||||||
debug!("collector: failed to send item, quit");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_err) => {} // String not UTF8 or other error, skip.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = tx_interrupt_clone.send(1); // ensure the waiting thread will exit
|
|
||||||
components_to_stop.fetch_sub(1, Ordering::SeqCst);
|
|
||||||
debug!("collector: command collector stop");
|
|
||||||
});
|
|
||||||
|
|
||||||
while !started.load(Ordering::SeqCst) {
|
|
||||||
// busy waiting for the thread to start. (components_to_stop is added)
|
|
||||||
}
|
|
||||||
|
|
||||||
(rx_item, tx_interrupt)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandOutput = (Option<Child>, Box<dyn BufRead + Send>);
|
|
||||||
fn get_command_output(cmd: &str) -> Result<CommandOutput, Box<dyn Error>> {
|
|
||||||
let shell = env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
|
|
||||||
let mut command: Child = Command::new(shell)
|
|
||||||
.arg("-c")
|
|
||||||
.arg(cmd)
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.spawn()?;
|
|
||||||
|
|
||||||
let stdout = command
|
|
||||||
.stdout
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| "command output: unwrap failed".to_owned())?;
|
|
||||||
|
|
||||||
Ok((Some(command), Box::new(BufReader::new(stdout))))
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// helper
|
|
||||||
pub struct SkimItemReader {
|
|
||||||
buf_size: usize,
|
|
||||||
line_ending: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SkimItemReader {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
buf_size: ITEM_CHANNEL_SIZE,
|
|
||||||
line_ending: b'\n',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SkimItemReader {
|
|
||||||
pub fn buf_size(mut self, buf_size: usize) -> Self {
|
|
||||||
self.buf_size = buf_size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_ending(mut self, line_ending: u8) -> Self {
|
|
||||||
self.line_ending = line_ending;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SkimItemReader {
|
|
||||||
/// helper: convert bufread into SkimItemReceiver
|
|
||||||
pub fn of_bufread(&self, mut source: impl BufRead + Send + 'static) -> SkimItemReceiver {
|
|
||||||
let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = bounded(self.buf_size);
|
|
||||||
let line_ending = self.line_ending;
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut buffer = Vec::with_capacity(1024);
|
|
||||||
loop {
|
|
||||||
buffer.clear();
|
|
||||||
// start reading
|
|
||||||
match source.read_until(line_ending, &mut buffer) {
|
|
||||||
Ok(n) => {
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.ends_with(&[b'\r', b'\n']) {
|
|
||||||
buffer.pop();
|
|
||||||
buffer.pop();
|
|
||||||
} else if buffer.ends_with(&[b'\n']) || buffer.ends_with(&[b'\0']) {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let string = String::from_utf8_lossy(&buffer);
|
|
||||||
let result = tx_item.send(Arc::new(string.into_owned()));
|
|
||||||
if result.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_err) => {} // String not UTF8 or other error, skip.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
rx_item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -25,12 +25,13 @@ use crate::reader::Reader;
|
||||||
mod ansi;
|
mod ansi;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod event;
|
mod event;
|
||||||
mod field;
|
/// provide default implementation
|
||||||
|
pub mod field;
|
||||||
mod global;
|
mod global;
|
||||||
mod header;
|
mod header;
|
||||||
|
mod helper;
|
||||||
mod input;
|
mod input;
|
||||||
mod item;
|
mod item;
|
||||||
mod item_collector;
|
|
||||||
mod matcher;
|
mod matcher;
|
||||||
mod model;
|
mod model;
|
||||||
mod options;
|
mod options;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
|
||||||
|
use crate::helper::item_collector::DefaultSkimCollector;
|
||||||
|
use crate::reader::CommandCollector;
|
||||||
use crate::{CaseMatching, FuzzyAlgorithm, MatchEngineFactory};
|
use crate::{CaseMatching, FuzzyAlgorithm, MatchEngineFactory};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
#[derive(Builder)]
|
#[derive(Builder)]
|
||||||
#[builder(build_fn(name = "final_build"))]
|
#[builder(build_fn(name = "final_build"))]
|
||||||
|
|
@ -16,7 +19,6 @@ pub struct SkimOptions<'a> {
|
||||||
pub tac: bool,
|
pub tac: bool,
|
||||||
pub nosort: bool,
|
pub nosort: bool,
|
||||||
pub tiebreak: Option<String>,
|
pub tiebreak: Option<String>,
|
||||||
pub ansi: bool,
|
|
||||||
pub exact: bool,
|
pub exact: bool,
|
||||||
pub cmd: Option<&'a str>,
|
pub cmd: Option<&'a str>,
|
||||||
pub interactive: bool,
|
pub interactive: bool,
|
||||||
|
|
@ -24,8 +26,6 @@ pub struct SkimOptions<'a> {
|
||||||
pub cmd_query: Option<&'a str>,
|
pub cmd_query: Option<&'a str>,
|
||||||
pub regex: bool,
|
pub regex: bool,
|
||||||
pub delimiter: Option<&'a str>,
|
pub delimiter: Option<&'a str>,
|
||||||
pub nth: Option<&'a str>,
|
|
||||||
pub with_nth: Option<&'a str>,
|
|
||||||
pub replstr: Option<&'a str>,
|
pub replstr: Option<&'a str>,
|
||||||
pub color: Option<&'a str>,
|
pub color: Option<&'a str>,
|
||||||
pub margin: Option<&'a str>,
|
pub margin: Option<&'a str>,
|
||||||
|
|
@ -35,24 +35,19 @@ pub struct SkimOptions<'a> {
|
||||||
pub preview: Option<&'a str>,
|
pub preview: Option<&'a str>,
|
||||||
pub preview_window: Option<&'a str>,
|
pub preview_window: Option<&'a str>,
|
||||||
pub reverse: bool,
|
pub reverse: bool,
|
||||||
pub read0: bool,
|
|
||||||
pub print0: bool,
|
|
||||||
pub tabstop: Option<&'a str>,
|
pub tabstop: Option<&'a str>,
|
||||||
pub print_query: bool,
|
|
||||||
pub print_cmd: bool,
|
|
||||||
pub print_score: bool,
|
|
||||||
pub no_hscroll: bool,
|
pub no_hscroll: bool,
|
||||||
pub no_mouse: bool,
|
pub no_mouse: bool,
|
||||||
pub inline_info: bool,
|
pub inline_info: bool,
|
||||||
pub header: Option<&'a str>,
|
pub header: Option<&'a str>,
|
||||||
pub header_lines: usize,
|
pub header_lines: usize,
|
||||||
pub layout: &'a str,
|
pub layout: &'a str,
|
||||||
pub filter: &'a str,
|
|
||||||
pub algorithm: FuzzyAlgorithm,
|
pub algorithm: FuzzyAlgorithm,
|
||||||
pub case: CaseMatching,
|
pub case: CaseMatching,
|
||||||
pub engine_factory: Option<Rc<dyn MatchEngineFactory>>,
|
pub engine_factory: Option<Rc<dyn MatchEngineFactory>>,
|
||||||
pub query_history: &'a [String],
|
pub query_history: &'a [String],
|
||||||
pub cmd_history: &'a [String],
|
pub cmd_history: &'a [String],
|
||||||
|
pub cmd_collector: Rc<RefCell<dyn CommandCollector>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default for SkimOptions<'a> {
|
impl<'a> Default for SkimOptions<'a> {
|
||||||
|
|
@ -66,7 +61,6 @@ impl<'a> Default for SkimOptions<'a> {
|
||||||
tac: false,
|
tac: false,
|
||||||
nosort: false,
|
nosort: false,
|
||||||
tiebreak: None,
|
tiebreak: None,
|
||||||
ansi: false,
|
|
||||||
exact: false,
|
exact: false,
|
||||||
cmd: None,
|
cmd: None,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
|
|
@ -74,8 +68,6 @@ impl<'a> Default for SkimOptions<'a> {
|
||||||
cmd_query: None,
|
cmd_query: None,
|
||||||
regex: false,
|
regex: false,
|
||||||
delimiter: None,
|
delimiter: None,
|
||||||
nth: None,
|
|
||||||
with_nth: None,
|
|
||||||
replstr: Some("{}"),
|
replstr: Some("{}"),
|
||||||
color: None,
|
color: None,
|
||||||
margin: Some("0,0,0,0"),
|
margin: Some("0,0,0,0"),
|
||||||
|
|
@ -85,24 +77,19 @@ impl<'a> Default for SkimOptions<'a> {
|
||||||
preview: None,
|
preview: None,
|
||||||
preview_window: Some("right:50%"),
|
preview_window: Some("right:50%"),
|
||||||
reverse: false,
|
reverse: false,
|
||||||
read0: false,
|
|
||||||
print0: false,
|
|
||||||
tabstop: None,
|
tabstop: None,
|
||||||
print_query: false,
|
|
||||||
print_cmd: false,
|
|
||||||
print_score: false,
|
|
||||||
no_hscroll: false,
|
no_hscroll: false,
|
||||||
no_mouse: false,
|
no_mouse: false,
|
||||||
inline_info: false,
|
inline_info: false,
|
||||||
header: None,
|
header: None,
|
||||||
header_lines: 0,
|
header_lines: 0,
|
||||||
layout: "",
|
layout: "",
|
||||||
filter: "",
|
|
||||||
algorithm: FuzzyAlgorithm::default(),
|
algorithm: FuzzyAlgorithm::default(),
|
||||||
case: CaseMatching::default(),
|
case: CaseMatching::default(),
|
||||||
engine_factory: None,
|
engine_factory: None,
|
||||||
query_history: &[],
|
query_history: &[],
|
||||||
cmd_history: &[],
|
cmd_history: &[],
|
||||||
|
cmd_collector: Rc::new(RefCell::new(DefaultSkimCollector::new(Default::default()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
pub use crate::ansi::AnsiString;
|
pub use crate::ansi::AnsiString;
|
||||||
pub use crate::engine::{factory::*, fuzzy::FuzzyAlgorithm};
|
pub use crate::engine::{factory::*, fuzzy::FuzzyAlgorithm};
|
||||||
pub use crate::item_collector::{read_and_collect_from_command, CollectorInput, CollectorOption, SkimItemReader};
|
pub use crate::helper::item_collector::{CollectorInput, CollectorOption, DefaultSkimCollector};
|
||||||
|
pub use crate::helper::string_reader::SkimItemReader;
|
||||||
pub use crate::options::{SkimOptions, SkimOptionsBuilder};
|
pub use crate::options::{SkimOptions, SkimOptionsBuilder};
|
||||||
pub use crate::output::SkimOutput;
|
pub use crate::output::SkimOutput;
|
||||||
pub use crate::*;
|
pub use crate::*;
|
||||||
pub use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
|
pub use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
|
||||||
pub use std::borrow::Cow;
|
pub use std::borrow::Cow;
|
||||||
|
pub use std::cell::RefCell;
|
||||||
|
pub use std::rc::Rc;
|
||||||
pub use std::sync::atomic::{AtomicUsize, Ordering};
|
pub use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
pub use std::sync::Arc;
|
pub use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,29 @@ use crate::global::mark_new_run;
|
||||||
///! Reader is used for reading items from datasource (e.g. stdin or command output)
|
///! Reader is used for reading items from datasource (e.g. stdin or command output)
|
||||||
///!
|
///!
|
||||||
///! After reading in a line, reader will save an item into the pool(items)
|
///! After reading in a line, reader will save an item into the pool(items)
|
||||||
use crate::item_collector::{read_and_collect_from_command, CollectorInput, CollectorOption};
|
|
||||||
use crate::options::SkimOptions;
|
use crate::options::SkimOptions;
|
||||||
use crate::spinlock::SpinLock;
|
use crate::spinlock::SpinLock;
|
||||||
use crate::{SkimItem, SkimItemReceiver};
|
use crate::{SkimItem, SkimItemReceiver};
|
||||||
use crossbeam::channel::{bounded, select, Sender};
|
use crossbeam::channel::{bounded, select, Sender};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
const CHANNEL_SIZE: usize = 1024;
|
const CHANNEL_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
pub trait CommandCollector {
|
||||||
|
/// execute the `cmd` and produce a
|
||||||
|
/// - skim item producer
|
||||||
|
/// - a channel sender, any message send would mean to terminate the `cmd` process (for now).
|
||||||
|
///
|
||||||
|
/// Internally, the command collector may start several threads(components), the collector
|
||||||
|
/// should add `1` on every thread creation and sub `1` on thread termination. reader would use
|
||||||
|
/// this information to determine whether the collector had stopped or not.
|
||||||
|
fn invoke(&mut self, cmd: &str, components_to_stop: Arc<AtomicUsize>) -> (SkimItemReceiver, Sender<i32>);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ReaderControl {
|
pub struct ReaderControl {
|
||||||
tx_interrupt: Sender<i32>,
|
tx_interrupt: Sender<i32>,
|
||||||
tx_interrupt_cmd: Option<Sender<i32>>,
|
tx_interrupt_cmd: Option<Sender<i32>>,
|
||||||
|
|
@ -46,14 +58,14 @@ impl ReaderControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Reader {
|
pub struct Reader {
|
||||||
option: CollectorOption,
|
cmd_collector: Rc<RefCell<dyn CommandCollector>>,
|
||||||
rx_item: Option<SkimItemReceiver>,
|
rx_item: Option<SkimItemReceiver>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Reader {
|
impl Reader {
|
||||||
pub fn with_options(options: &SkimOptions) -> Self {
|
pub fn with_options(options: &SkimOptions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
option: CollectorOption::with_options(&options),
|
cmd_collector: options.cmd_collector.clone(),
|
||||||
rx_item: None,
|
rx_item: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,13 +81,10 @@ impl Reader {
|
||||||
let components_to_stop: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0));
|
let components_to_stop: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0));
|
||||||
let items = Arc::new(SpinLock::new(Vec::new()));
|
let items = Arc::new(SpinLock::new(Vec::new()));
|
||||||
let items_clone = items.clone();
|
let items_clone = items.clone();
|
||||||
let option_clone = self.option.clone();
|
|
||||||
let cmd = cmd.to_string();
|
|
||||||
|
|
||||||
let (rx_item, tx_interrupt_cmd) = self.rx_item.take().map(|rx| (rx, None)).unwrap_or_else(|| {
|
let (rx_item, tx_interrupt_cmd) = self.rx_item.take().map(|rx| (rx, None)).unwrap_or_else(|| {
|
||||||
let components_to_stop_clone = components_to_stop.clone();
|
let components_to_stop_clone = components_to_stop.clone();
|
||||||
let (rx_item, tx_interrupt_cmd) =
|
let (rx_item, tx_interrupt_cmd) = self.cmd_collector.borrow_mut().invoke(cmd, components_to_stop_clone);
|
||||||
read_and_collect_from_command(components_to_stop_clone, CollectorInput::Command(cmd), option_clone);
|
|
||||||
(rx_item, Some(tx_interrupt_cmd))
|
(rx_item, Some(tx_interrupt_cmd))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue