diff --git a/Cargo.lock b/Cargo.lock index 3190209..3a099a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,24 +1,64 @@ -[root] -name = "icepick" -version = "0.0.1" -dependencies = [ - "ansi_term 0.5.2 (git+https://github.com/ogham/rust-ansi-term.git)", - "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", -] +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 [[package]] name = "ansi_term" -version = "0.5.2" -source = "git+https://github.com/ogham/rust-ansi-term.git#5ced0a3f3347850a3cbb6ab558fe32fb141690ce" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] [[package]] name = "getopts" -version = "0.2.14" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "icepick" +version = "0.0.2" +dependencies = [ + "ansi_term", + "getopts", + "libc", +] [[package]] name = "libc" -version = "0.2.4" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index d49303c..4d63c53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icepick" -version = "0.0.1" +version = "0.0.2" authors = ["Felipe Sere ", "Uku Taht ", "Carol Nichols "] @@ -22,11 +22,9 @@ test = true doc = false [dependencies] -libc = "0.2.4" -getopts = "0.2.14" - -[dependencies.ansi_term] -git = "https://github.com/ogham/rust-ansi-term.git" +libc = "0.2.139" +getopts = "0.2.21" +ansi_term = "0.12" [profile.release] opt-level = 3 diff --git a/README.md b/README.md index 867d6f3..787ac2b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Then you can pipe input to it and fuzzy select on it: find . -name "*.css" | icepick | xargs rm ``` -The above commend would allow you to match on all CSS files in your current +The above commend would allow you to match on all CSS files in your current directory and remove the selected one. For more uses see [the original Ruby implementation](https://github.com/garybernhardt/selecta) by Gary Bernhardt. @@ -45,4 +45,5 @@ If you have an idea to improve performance, run `cargo bench` and see how the re @felipesere @heruku @carols10cents +@erwinvaneijk diff --git a/makefile b/makefile index d7255a2..3847cdf 100644 --- a/makefile +++ b/makefile @@ -15,3 +15,5 @@ install: build uninstall: rm $(prefix)/bin/icepick +bench: + rustup run nightly cargo bench diff --git a/src/ansi.rs b/src/ansi.rs index 94da954..352694c 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -1,10 +1,10 @@ -use tty::IO; +use crate::tty::IO; pub struct Ansi<'a> { - pub io: Box<(IO + 'a)>, + pub io: Box<(dyn IO + 'a)>, } -impl <'a> Ansi<'a> { +impl<'a> Ansi<'a> { pub fn escape(&mut self, message: &str) { let out = Ansi::esc(message); self.io.write(out.as_ref()); diff --git a/src/fake_tty.rs b/src/fake_tty.rs index 8b76978..441a2dc 100644 --- a/src/fake_tty.rs +++ b/src/fake_tty.rs @@ -1,4 +1,4 @@ -use tty::IO; +use crate::tty::IO; pub struct FakeIO { last: String, diff --git a/src/main.rs b/src/main.rs index 1d001ca..a8545e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,8 @@ extern crate getopts; extern crate icepick; use getopts::Options; -use std::io::BufRead; use std::io; +use std::io::BufRead; use icepick::screen::Screen; @@ -25,8 +25,10 @@ fn extract_initial_query() -> Option { opts.optopt("s", "search", "initial search query", ""); let matches = match opts.parse(&args[1..]) { - Ok(m) => { m } - Err(f) => { panic!(f.to_string()) } + Ok(m) => m, + Err(f) => { + panic!("{}", f) + } }; matches.opt_str("s") @@ -39,8 +41,9 @@ fn get_args() -> Vec { fn read_lines() -> Vec { let stdin = io::stdin(); let reader = stdin.lock(); - let l = reader.lines().map( |line| { - line.unwrap().trim().to_string() - }).collect(); + let l = reader + .lines() + .map(|line| line.unwrap().trim().to_string()) + .collect(); l } diff --git a/src/renderer.rs b/src/renderer.rs index 99609ba..d9525c7 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,5 +1,5 @@ -use search::Search; -use text::Text; +use crate::search::Search; +use crate::text::Text; pub struct Renderer; diff --git a/src/score.rs b/src/score.rs index 0b55fe3..d7a1a4d 100644 --- a/src/score.rs +++ b/src/score.rs @@ -1,8 +1,7 @@ use std::cmp::min; -use std::ascii::AsciiExt; use std::ops::Range; -#[derive(Clone, Debug,PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Quality(pub f32); impl Quality { @@ -12,32 +11,37 @@ impl Quality { } } -#[derive(Clone, Debug,PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Match<'a> { pub quality: Quality, pub range: Range, pub original: &'a String, - } -impl <'a> Match<'a> { +impl<'a> Match<'a> { pub fn parts(&self) -> (String, String, String) { - let start = self.range.start; - let end = self.range.end; - let input = self.original; - (input[..start].to_string(), - input[start..end].to_string(), - input[end..].to_string()) + let start = self.range.start; + let end = self.range.end; + let input = self.original; + ( + input[..start].to_string(), + input[start..end].to_string(), + input[end..].to_string(), + ) } } -impl <'a>Match<'a>{ +impl<'a> Match<'a> { pub fn new(quality: Quality, range: Range, original: &'a String) -> Match<'a> { - Match { quality: quality, range: range, original: original } + Match { + quality: quality, + range: range, + original: original, + } } pub fn with_empty_range(original: &'a String) -> Match<'a> { - Match::new(Quality(1.0), Range{start: 0,end: 0}, original) + Match::new(Quality(1.0), Range { start: 0, end: 0 }, original) } } @@ -45,17 +49,21 @@ pub fn score<'a>(choice: &'a String, query: &String) -> Option> { let choice_length = choice.len() as f32; let query_length = query.len() as f32; - if query_length == 0.0 { return Some(Match::with_empty_range(choice)) } + if query_length == 0.0 { + return Some(Match::with_empty_range(choice)); + } let lower_choice = choice.to_ascii_lowercase(); - // TODO convert this over to compute_match_length(...).map(...) match compute_match_length(&lower_choice, query) { Some((start, match_length)) => { - let quality = Quality( (query_length / match_length as f32) / choice_length); - let substring = Range {start: start, end: start+match_length}; + let quality = Quality((query_length / match_length as f32) / choice_length); + let substring = Range { + start: start, + end: start + match_length, + }; Some(Match::new(quality, substring, choice)) - }, + } None => None, } } @@ -67,7 +75,7 @@ fn slice_shift_char(line: &str) -> Option<(char, &str)> { let mut chars = line.chars(); let ch = chars.next().unwrap(); let len = line.len(); - let next_s = &line[ch.len_utf8().. len]; + let next_s = &line[ch.len_utf8()..len]; Some((ch, next_s)) } } @@ -85,14 +93,18 @@ fn compute_match_length(choice: &String, query: &String) -> Option<(usize, usize for_each_beginning(choice, first, |beginning| { match match_length_from(choice, rest, beginning) { Some(length) => { - shortest_match = min(length, shortest_match); - shortest_start = beginning; - }, - None => {}, + shortest_match = min(length, shortest_match); + shortest_start = beginning; + } + None => {} }; }); - if shortest_match == impossible_match {None} else {Some((shortest_start, shortest_match))} + if shortest_match == impossible_match { + None + } else { + Some((shortest_start, shortest_match)) + } } fn for_each_beginning(choice: &String, beginning: char, mut f: F) { @@ -107,16 +119,14 @@ fn match_length_from(choice: &String, query: &str, beginning: usize) -> Option match_index = n, - None => return None, - }; + match find_first_after(choice, query_char, match_index + 1) { + Some(n) => match_index = n, + None => return None, + }; } Some(match_index - beginning + 1) } fn find_first_after(choice: &String, query: char, offset: usize) -> Option { - choice[offset..] - .find(query) - .map(|index| index + offset) + choice[offset..].find(query).map(|index| index + offset) } diff --git a/src/screen.rs b/src/screen.rs index 94a2af6..9f872f4 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -1,11 +1,11 @@ -use search::Search; -use ansi::Ansi; -use tty::TTY; -use fake_tty::FakeIO; -use renderer::Renderer; -use text::Text; +use crate::search::Search; +use crate::ansi::Ansi; +use crate::tty::TTY; +use crate::fake_tty::FakeIO; +use crate::renderer::Renderer; +use crate::text::Text; use std::cmp::min; -use text::Printable; +use crate::text::Printable; pub struct Screen <'a> { pub ansi: Ansi<'a>, diff --git a/src/search.rs b/src/search.rs index 514aa86..1ee588d 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,7 +1,6 @@ -use score; -use score::Match; -use sorted_result_set::SortedResultSet; -use std::ascii::AsciiExt; +use crate::score; +use crate::score::Match; +use crate::sorted_result_set::SortedResultSet; #[derive(Debug)] pub struct Search<'s> { @@ -18,11 +17,13 @@ struct ChoiceStack<'s> { content: Vec>, } -impl <'s>ChoiceStack<'s> { +impl<'s> ChoiceStack<'s> { pub fn new(input: &'s Vec) -> ChoiceStack<'s> { let initial_choices = input.iter().map(|x| x).collect(); - ChoiceStack { content: vec![initial_choices] } + ChoiceStack { + content: vec![initial_choices], + } } pub fn push(&mut self, frame: Vec<&'s String>) { @@ -45,25 +46,40 @@ impl <'s>ChoiceStack<'s> { } impl<'s> Search<'s> { - pub fn blank(choices: &'s Vec, - initial_search: Option, - visible_limit: usize) -> Search<'s> { + pub fn blank( + choices: &'s Vec, + initial_search: Option, + visible_limit: usize, + ) -> Search<'s> { let query = initial_search.unwrap_or("".to_string()); let choice_stack = ChoiceStack::new(&choices); - let result = choices.iter().take(visible_limit).map(|x| Match::with_empty_range(x)).collect(); + let result = choices + .iter() + .take(visible_limit) + .map(|x| Match::with_empty_range(x)) + .collect(); Search::new(query, choice_stack, result, 0, visible_limit, false) } - fn new(query: String, choice_stack: ChoiceStack<'s>, result: Vec>, index: usize, visible_limit: usize, done: bool) -> Search<'s> { - Search { current: index, - query: query, - result: result, - choice_stack: choice_stack, - visible_limit: visible_limit, - done: done} + fn new( + query: String, + choice_stack: ChoiceStack<'s>, + result: Vec>, + index: usize, + visible_limit: usize, + done: bool, + ) -> Search<'s> { + Search { + current: index, + query: query, + result: result, + choice_stack: choice_stack, + visible_limit: visible_limit, + done: done, + } } pub fn is_done(&self) -> bool { @@ -71,15 +87,29 @@ impl<'s> Search<'s> { } pub fn done(self) -> Search<'s> { - Search::new(self.query, self.choice_stack, self.result, self.current, self.visible_limit, true) + Search::new( + self.query, + self.choice_stack, + self.result, + self.current, + self.visible_limit, + true, + ) } pub fn selection(&self) -> Option { - self.result.get(self.current).map( |t| t.original.clone()) + self.result.get(self.current).map(|t| t.original.clone()) } fn new_for_index(self, index: usize) -> Search<'s> { - Search::new(self.query, self.choice_stack, self.result, index,self.visible_limit, self.done) + Search::new( + self.query, + self.choice_stack, + self.result, + index, + self.visible_limit, + self.done, + ) } pub fn iter_matches)>(query: &str, choices: &Vec<&'s String>, mut f: F) { @@ -87,7 +117,7 @@ impl<'s> Search<'s> { for choice in choices.iter() { match score::score(&choice, &lower_query) { - None => continue, + None => continue, Some(m) => f(m), }; } @@ -109,17 +139,23 @@ impl<'s> Search<'s> { let mut result = SortedResultSet::new(self.visible_limit); let mut filtered_choices: Vec<&String> = Vec::new(); - Search::iter_matches(new_query.as_ref(), &self.choice_stack.peek(), - |matching| { - let quality = matching.quality.to_f32(); - let choice = matching.original; - result.push(matching.clone(), quality); - filtered_choices.push(&choice) - }); + Search::iter_matches(new_query.as_ref(), &self.choice_stack.peek(), |matching| { + let quality = matching.quality.to_f32(); + let choice = matching.original; + result.push(matching.clone(), quality); + filtered_choices.push(&choice) + }); self.choice_stack.push(filtered_choices); - Search::new(new_query, self.choice_stack, result.as_sorted_vec(), 0, self.visible_limit, self.done) + Search::new( + new_query, + self.choice_stack, + result.as_sorted_vec(), + 0, + self.visible_limit, + self.done, + ) } pub fn backspace(mut self) -> Search<'s> { @@ -129,13 +165,19 @@ impl<'s> Search<'s> { self.choice_stack.pop(); let mut result = SortedResultSet::new(self.visible_limit); - Search::iter_matches(new_query.as_ref(), &self.choice_stack.peek(), - |matching| { - let quality = matching.quality.to_f32(); - result.push(matching, quality) - } ); + Search::iter_matches(new_query.as_ref(), &self.choice_stack.peek(), |matching| { + let quality = matching.quality.to_f32(); + result.push(matching, quality) + }); - Search::new(new_query, self.choice_stack, result.as_sorted_vec(), 0, self.visible_limit, self.done) + Search::new( + new_query, + self.choice_stack, + result.as_sorted_vec(), + 0, + self.visible_limit, + self.done, + ) } fn next_index(&self) -> usize { @@ -145,7 +187,7 @@ impl<'s> Search<'s> { if self.current + 1 == self.num_matches() { 0 } else { - self.current+1 + self.current + 1 } } @@ -155,7 +197,7 @@ impl<'s> Search<'s> { } else if self.current == 0 { self.num_matches() - 1 } else { - self.current-1 + self.current - 1 } } diff --git a/src/text.rs b/src/text.rs index 8e5f287..8e66f1a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,5 +1,5 @@ -use score::Match; -use ansi::Ansi; +use crate::ansi::Ansi; +use crate::score::Match; use ansi_term::Colour::Blue; #[derive(PartialEq, Debug)] @@ -14,12 +14,12 @@ pub trait Printable { fn print(self, ansi: &mut Ansi); } -impl <'a> Printable for Text<'a> { +impl<'a> Printable for Text<'a> { fn print(self, ansi: &mut Ansi) { match self { Text::Colored(ref matching) => { let (start, middle, end) = matching.parts(); - let text = format!("{}{}{}", start, Blue.paint(middle.as_ref()), end); + let text = format!("{}{}{}", start, Blue.paint(middle), end); ansi.print(&text); } Text::Normal(ref text) => { diff --git a/src/tty.rs b/src/tty.rs index ca571d1..0a6dacd 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -1,18 +1,17 @@ -use std::io::prelude::*; +use libc::{c_int, c_ulong, c_ushort}; +use std::cmp::min; use std::fs::{File, OpenOptions}; +use std::io::prelude::*; +use std::os::unix::prelude::AsRawFd; +use std::path::Path; use std::process::Command; use std::process::Stdio; -use std::path::Path; -use std::os::unix::prelude::AsRawFd; -use libc::{c_ushort, c_int, c_ulong}; use std::str; -use std::cmp::min; - pub struct TTY { file: File, dimensions: (usize, usize), - original_state: String + original_state: String, } pub trait IO { @@ -26,20 +25,19 @@ pub trait IO { impl IO for TTY { fn write(&mut self, line: &str) { - let it = self.trim(line); match self.file.write(it.as_bytes()) { - Ok(k) => { k }, - Err(e) => { panic!(e.to_string()) } + Ok(k) => k, + Err(e) => { + panic!("{}", e) + } }; } fn read(&mut self) -> Option { - let mut buffer = [0] ; + let mut buffer = [0]; let res = match self.file.read(&mut buffer) { - Ok(c) if c > 0 => { - str::from_utf8(&buffer).ok().map(|x| x.to_string()) - }, + Ok(c) if c > 0 => str::from_utf8(&buffer).ok().map(|x| x.to_string()), _ => None, }; res @@ -67,7 +65,12 @@ impl IO for TTY { impl TTY { pub fn new() -> TTY { let path = Path::new("/dev/tty"); - let file = OpenOptions::new().read(true).write(true).append(true).open(&path).unwrap(); + let file = OpenOptions::new() + .read(true) + .write(true) + .append(true) + .open(&path) + .unwrap(); let dimension = TTY::get_window_size(&file); let orig_state = TTY::previous_state(&file); @@ -86,7 +89,7 @@ impl TTY { } fn get_window_size(file: &File) -> (usize, usize) { - extern { + extern "C" { fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int; } #[cfg(any(target_os = "macos", target_os = "freebsd"))] @@ -103,7 +106,12 @@ impl TTY { y: c_ushort, } - let size = TermSize { rows: 0, cols: 0, x: 0, y: 0 }; + let size = TermSize { + rows: 0, + cols: 0, + x: 0, + y: 0, + }; if unsafe { ioctl(file.as_raw_fd(), TIOCGWINSZ, &size) } == 0 { (size.cols as usize, size.rows as usize) } else { @@ -112,14 +120,22 @@ impl TTY { } fn stty(file: &File, args: &[&str]) -> Option { - extern { fn dup2(src: c_int, dst: c_int) -> c_int; } + extern "C" { + fn dup2(src: c_int, dst: c_int) -> c_int; + } unsafe { // This is a hack until a replacement for InheritFd from old_io is available. let raw_fd = file.as_raw_fd(); dup2(raw_fd, 0); - match Command::new("stty").args(args).stdin(Stdio::inherit()).output() { - Err(k) => { panic!(k.to_string()) } - Ok(output) => { String::from_utf8(output.stdout).ok() } + match Command::new("stty") + .args(args) + .stdin(Stdio::inherit()) + .output() + { + Err(k) => { + panic!("{}", k) + } + Ok(output) => String::from_utf8(output.stdout).ok(), } } } diff --git a/tests/ansi_test.rs b/tests/ansi_test.rs index e894a8b..44c0409 100644 --- a/tests/ansi_test.rs +++ b/tests/ansi_test.rs @@ -2,12 +2,13 @@ extern crate icepick; #[cfg(test)] mod tests { - use icepick::fake_tty::FakeIO; - use icepick::tty::IO; use icepick::ansi::Ansi; + use icepick::fake_tty::FakeIO; - pub fn assert_results_in (expected: &str, mut f: F) { - let mut ansi = Ansi { io: Box::new(FakeIO::new()) }; + pub fn assert_results_in(expected: &str, mut f: F) { + let mut ansi = Ansi { + io: Box::new(FakeIO::new()), + }; f(&mut ansi); let inner_box = ansi.io; @@ -16,31 +17,35 @@ mod tests { #[test] fn escapes_a_str() { - assert_results_in("\x1b[something", |ansi| { ansi.escape("something") }); + assert_results_in("\x1b[something", |ansi| ansi.escape("something")); } #[test] fn clears_the_screen() { - assert_results_in("\x1b[2J", |ansi| { ansi.clear() }); + assert_results_in("\x1b[2J", |ansi| ansi.clear()); } #[test] fn hides_the_cursor() { - assert_results_in("\x1b[?251", |ansi| { ansi.hide_cursor() }); + assert_results_in("\x1b[?251", |ansi| ansi.hide_cursor()); } #[test] fn shows_the_cursor() { - assert_results_in("\x1b[?25h", |ansi| { ansi.show_cursor() }); + assert_results_in("\x1b[?25h", |ansi| ansi.show_cursor()); } #[test] fn sets_the_position() { - assert_results_in("\x1b[9;13H", |ansi| { ansi.set_position(8,12); }); + assert_results_in("\x1b[9;13H", |ansi| { + ansi.set_position(8, 12); + }); } #[test] fn prints_inverted() { - assert_results_in("\x1b[7mtest\x1b[0m", |ansi| { ansi.inverted("test"); }); + assert_results_in("\x1b[7mtest\x1b[0m", |ansi| { + ansi.inverted("test"); + }); } }