[collector] add helper SkimItemReader

This commit is contained in:
Jinzhou Zhang 2020-02-05 17:48:54 +08:00
parent 83080e305e
commit e3f3d35561
3 changed files with 97 additions and 22 deletions

View file

@ -372,10 +372,7 @@ Also you can use `--with-nth` to re-arrange the order of fields.
## Use as a library
Skim can now be used as a library in your Rust crates. The basic idea is to
throw anything that is `BufRead`(we can easily turn a `File` for `String` into
`BufRead`) and skim will do its job and bring us back the user selection
including the selected items(with their indices), the query, etc.
Skim can be used as a library in your Rust crates.
First, add skim into your `Cargo.toml`:
@ -388,7 +385,7 @@ Then try to run this simple example:
```rust
extern crate skim;
use skim::{Skim, SkimOptionsBuilder};
use skim::prelude::*;
use std::io::Cursor;
pub fn main() {
@ -400,7 +397,13 @@ pub fn main() {
let input = "aaaaa\nbbbb\nccc".to_string();
let selected_items = Skim::run_with(&options, Some(Box::new(Cursor::new(input))))
// `SkimItemReader` is a helper to turn any `BufRead` into a stream of `SkimItem`
// `SkimItem` was implemented for `AsRef<str>` by default
let item_reader = SkimItemReader::default();
let items = item_reader.of_bufread(Cursor::new(input));
// `run_with` would read and show items from the stream
let selected_items = Skim::run_with(&options, Some(items))
.map(|out| out.selected_items)
.unwrap_or_else(|| Vec::new());
@ -410,6 +413,21 @@ pub fn main() {
}
```
Given an `Option<SkimItemReceiver>`, skim will read items accordingly, do its
job and bring us back the user selection including the selected items(with
their indices), the query, etc. Note that:
- `SkimItemReceiver` is `crossbeam::channel::Receiver<Arc<dyn SkimItem>>`
- If it is none, it will invoke the given command and read items from command output
- Otherwise, it will read the items from the (crossbeam) channel.
Trait `SkimItem` is provided to customize how a line could be displayed,
compared and previewed. It is implemented by default for `AsRef<str>`
Plus, `SkimItemReader` is a helper to convert a `BufRead` into
`SkimItemReceiver` (we can easily turn a `File` for `String` into `BufRead`).
So that you could deal with strings or files easily.
Check more examples under [examples/](https://github.com/lotabout/skim/tree/master/examples) directory.
# FAQ

View file

@ -1,7 +1,6 @@
extern crate skim;
use crossbeam::channel::unbounded;
use skim::prelude::*;
use std::sync::Arc;
use std::io::Cursor;
pub fn main() {
let options = SkimOptionsBuilder::default()
@ -9,16 +8,13 @@ pub fn main() {
.multi(true)
.build()
.unwrap();
let item_reader = SkimItemReader::default();
//==================================================
// first run
let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded();
let _ = tx_item.send(Arc::new("aaaaa"));
let _ = tx_item.send(Arc::new("bbbb"));
let _ = tx_item.send(Arc::new("ccc"));
drop(tx_item);
let selected_items = Skim::run_with(&options, Some(rx_item))
let input = "aaaaa\nbbbb\nccc".to_string();
let items = item_reader.of_bufread(Cursor::new(input));
let selected_items = Skim::run_with(&options, Some(items))
.map(|out| out.selected_items)
.unwrap_or_else(|| Vec::new());
@ -28,13 +24,9 @@ pub fn main() {
//==================================================
// second run
let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded();
let _ = tx_item.send(Arc::new("11111"));
let _ = tx_item.send(Arc::new("22222"));
let _ = tx_item.send(Arc::new("333333333"));
drop(tx_item);
let selected_items = Skim::run_with(&options, Some(rx_item))
let input = "11111\n22222\n333333333".to_string();
let items = item_reader.of_bufread(Cursor::new(input));
let selected_items = Skim::run_with(&options, Some(items))
.map(|out| out.selected_items)
.unwrap_or_else(|| Vec::new());

View file

@ -182,3 +182,68 @@ fn get_command_output(cmd: &str) -> Result<CommandOutput, Box<dyn Error>> {
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
}
}