diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7dfb5d70a7..b0d4aab446 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,6 +35,9 @@ jobs: run: cargo test --workspace --all-targets - name: Run doctests run: cargo test --workspace --doc + - name: Test features + if: matrix.os != 'windows-latest' + run: cargo test --workspace --all-targets --all-features env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index b87f8ca041..86c548da75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,9 @@ smallvec = "1.6.1" radix_trie = "0.2" [target.'cfg(unix)'.dependencies] -nix = "0.19" +nix = "0.20" utf8parse = "0.2" -skim = { version = "0.7", optional = true } +skim = { version = "0.9", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["consoleapi", "handleapi", "minwindef", "processenv", "winbase", "wincon", "winuser"] } diff --git a/src/command.rs b/src/command.rs index 1dcbd3d8d6..ab3c91fdb8 100644 --- a/src/command.rs +++ b/src/command.rs @@ -168,7 +168,7 @@ pub fn execute( } Cmd::CapitalizeWord => { // capitalize word after point - s.edit_word(WordAction::CAPITALIZE)? + s.edit_word(WordAction::Capitalize)? } Cmd::Kill(ref mvt) => { s.edit_kill(mvt)?; @@ -193,7 +193,7 @@ pub fn execute( } Cmd::DowncaseWord => { // lowercase word after point - s.edit_word(WordAction::LOWERCASE)? + s.edit_word(WordAction::Lowercase)? } Cmd::TransposeWords(n) => { // transpose words @@ -201,7 +201,7 @@ pub fn execute( } Cmd::UpcaseWord => { // uppercase word after point - s.edit_word(WordAction::UPPERCASE)? + s.edit_word(WordAction::Uppercase)? } Cmd::YankPop => { // yank-pop diff --git a/src/completion.rs b/src/completion.rs index 89006f62bb..279954a339 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -357,22 +357,20 @@ fn filename_complete( // if any of the below IO operations have errors, just ignore them if let Ok(read_dir) = dir.read_dir() { let file_name = normalize(file_name); - for entry in read_dir { - if let Ok(entry) = entry { - if let Some(s) = entry.file_name().to_str() { - let ns = normalize(s); - if ns.starts_with(file_name.as_ref()) { - if let Ok(metadata) = fs::metadata(entry.path()) { - let mut path = String::from(dir_name) + s; - if metadata.is_dir() { - path.push(sep); - } - entries.push(Pair { - display: String::from(s), - replacement: escape(path, esc_char, break_chars, quote), - }); - } // else ignore PermissionDenied - } + for entry in read_dir.flatten() { + if let Some(s) = entry.file_name().to_str() { + let ns = normalize(s); + if ns.starts_with(file_name.as_ref()) { + if let Ok(metadata) = fs::metadata(entry.path()) { + let mut path = String::from(dir_name) + s; + if metadata.is_dir() { + path.push(sep); + } + entries.push(Pair { + display: String::from(s), + replacement: escape(path, esc_char, break_chars, quote), + }); + } // else ignore PermissionDenied } } } diff --git a/src/keymap.rs b/src/keymap.rs index 13d94a661f..c70dee3e02 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -938,19 +938,14 @@ impl InputState { E(K::Char('E'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)), E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = self.vi_char_search(rdr, c)?; - match cs { - Some(cs) => Some(Movement::ViCharSearch(n, cs)), - None => None, - } + cs.map(|cs| Movement::ViCharSearch(n, cs)) } - E(K::Char(';'), M::NONE) => match self.last_char_search { - Some(cs) => Some(Movement::ViCharSearch(n, cs)), - None => None, - }, - E(K::Char(','), M::NONE) => match self.last_char_search { - Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())), - None => None, - }, + E(K::Char(';'), M::NONE) => self + .last_char_search + .map(|cs| Movement::ViCharSearch(n, cs)), + E(K::Char(','), M::NONE) => self + .last_char_search + .map(|cs| Movement::ViCharSearch(n, cs.opposite())), E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => { Some(Movement::BackwardChar(n)) } diff --git a/src/lib.rs b/src/lib.rs index f108a844eb..a79802c54d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,9 @@ fn complete_line( config: &Config, ) -> Result> { #[cfg(all(unix, feature = "with-fuzzy"))] - use skim::{Skim, SkimOptionsBuilder}; + use skim::prelude::{ + unbounded, Skim, SkimItem, SkimItemReceiver, SkimItemSender, SkimOptionsBuilder, + }; let completer = s.helper.unwrap(); // get a list of completions @@ -192,13 +194,31 @@ fn complete_line( // corresponding completion_type #[cfg(all(unix, feature = "with-fuzzy"))] { + use std::borrow::Cow; if CompletionType::Fuzzy == config.completion_type() { - // skim takes input of candidates separated by new line - let input = candidates + struct Candidate { + index: usize, + text: String, + } + impl SkimItem for Candidate { + fn text(&self) -> Cow { + Cow::Borrowed(&self.text) + } + } + + let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded(); + + candidates .iter() - .map(|c| c.display()) - .collect::>() - .join("\n"); + .enumerate() + .map(|(i, c)| Candidate { + index: i, + text: c.display().to_owned(), + }) + .for_each(|c| { + let _ = tx_item.send(Arc::new(c)); + }); + drop(tx_item); // so that skim could know when to stop waiting for more items. // setup skim and run with input options // will display UI for fuzzy search and return selected results @@ -211,15 +231,17 @@ fn complete_line( .build() .unwrap(); - let selected_items = - Skim::run_with(&options, Some(Box::new(std::io::Cursor::new(input)))) - .map(|out| out.selected_items) - .unwrap_or_else(Vec::new); + let selected_items = Skim::run_with(&options, Some(rx_item)) + .map(|out| out.selected_items) + .unwrap_or_else(Vec::new); // match the first (and only) returned option with the candidate and update the // line otherwise only refresh line to clear the skim UI changes if let Some(item) = selected_items.first() { - if let Some(candidate) = candidates.get(item.get_index()) { + let item: &Candidate = (*item).as_any() // cast to Any + .downcast_ref::() // downcast to concrete type + .expect("something wrong with downcast"); + if let Some(candidate) = candidates.get(item.index) { completer.update(&mut s.line, start, candidate.replacement()); } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 14042a8bab..705923c32b 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -18,11 +18,11 @@ pub(crate) const INDENT: &str = " "; #[derive(Clone, Copy)] pub enum WordAction { /// Capitalize word - CAPITALIZE, + Capitalize, /// lowercase word - LOWERCASE, + Lowercase, /// uppercase word - UPPERCASE, + Uppercase, } /// Delete (kill) direction @@ -694,23 +694,19 @@ impl LineBuffer { } } }; - if let Some(pos) = search_result { - Some(match cs { - CharSearch::Backward(_) => pos, - CharSearch::BackwardAfter(c) => pos + c.len_utf8(), - CharSearch::Forward(_) => shift + pos, - CharSearch::ForwardBefore(_) => { - shift + pos - - self.buf[..shift + pos] - .chars() - .next_back() - .unwrap() - .len_utf8() - } - }) - } else { - None - } + search_result.map(|pos| match cs { + CharSearch::Backward(_) => pos, + CharSearch::BackwardAfter(c) => pos + c.len_utf8(), + CharSearch::Forward(_) => shift + pos, + CharSearch::ForwardBefore(_) => { + shift + pos + - self.buf[..shift + pos] + .chars() + .next_back() + .unwrap() + .len_utf8() + } + }) } /// Move cursor to the matching character position. @@ -792,13 +788,13 @@ impl LineBuffer { .drain(start..end, Direction::default()) .collect::(); let result = match a { - WordAction::CAPITALIZE => { + WordAction::Capitalize => { let ch = (&word).graphemes(true).next().unwrap(); let cap = ch.to_uppercase(); cap + &word[ch.len()..].to_lowercase() } - WordAction::LOWERCASE => word.to_lowercase(), - WordAction::UPPERCASE => word.to_uppercase(), + WordAction::Lowercase => word.to_lowercase(), + WordAction::Uppercase => word.to_uppercase(), }; self.insert_str(start, &result); self.pos = start + result.len(); @@ -923,10 +919,9 @@ impl LineBuffer { Movement::ViFirstPrint => { if self.pos == 0 { None - } else if let Some(pos) = self.next_word_pos(0, At::Start, Word::Big, 1) { - Some(self.buf[pos..self.pos].to_owned()) } else { - None + self.next_word_pos(0, At::Start, Word::Big, 1) + .map(|pos| self.buf[pos..self.pos].to_owned()) } } Movement::EndOfLine => { @@ -958,51 +953,31 @@ impl LineBuffer { Some(self.buf[..self.pos].to_owned()) } } - Movement::BackwardWord(n, word_def) => { - if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) { - Some(self.buf[pos..self.pos].to_owned()) - } else { - None - } - } - Movement::ForwardWord(n, at, word_def) => { - if let Some(pos) = self.next_word_pos(self.pos, at, word_def, n) { - Some(self.buf[self.pos..pos].to_owned()) - } else { - None - } - } + Movement::BackwardWord(n, word_def) => self + .prev_word_pos(self.pos, word_def, n) + .map(|pos| self.buf[pos..self.pos].to_owned()), + Movement::ForwardWord(n, at, word_def) => self + .next_word_pos(self.pos, at, word_def, n) + .map(|pos| self.buf[self.pos..pos].to_owned()), Movement::ViCharSearch(n, cs) => { let search_result = match cs { CharSearch::ForwardBefore(c) => self.search_char_pos(CharSearch::Forward(c), n), _ => self.search_char_pos(cs, n), }; - if let Some(pos) = search_result { - Some(match cs { - CharSearch::Backward(_) | CharSearch::BackwardAfter(_) => { - self.buf[pos..self.pos].to_owned() - } - CharSearch::ForwardBefore(_) => self.buf[self.pos..pos].to_owned(), - CharSearch::Forward(c) => self.buf[self.pos..pos + c.len_utf8()].to_owned(), - }) - } else { - None - } - } - Movement::BackwardChar(n) => { - if let Some(pos) = self.prev_pos(n) { - Some(self.buf[pos..self.pos].to_owned()) - } else { - None - } - } - Movement::ForwardChar(n) => { - if let Some(pos) = self.next_pos(n) { - Some(self.buf[self.pos..pos].to_owned()) - } else { - None - } + search_result.map(|pos| match cs { + CharSearch::Backward(_) | CharSearch::BackwardAfter(_) => { + self.buf[pos..self.pos].to_owned() + } + CharSearch::ForwardBefore(_) => self.buf[self.pos..pos].to_owned(), + CharSearch::Forward(c) => self.buf[self.pos..pos + c.len_utf8()].to_owned(), + }) } + Movement::BackwardChar(n) => self + .prev_pos(n) + .map(|pos| self.buf[pos..self.pos].to_owned()), + Movement::ForwardChar(n) => self + .next_pos(n) + .map(|pos| self.buf[self.pos..pos].to_owned()), Movement::LineUp(n) => { if let Some((start, end)) = self.n_lines_up(n) { Some(self.buf[start..end].to_owned()) @@ -1798,22 +1773,22 @@ mod test { #[test] fn edit_word() { let mut s = LineBuffer::init("a ßeta c", 1, None); - assert!(s.edit_word(WordAction::UPPERCASE)); + assert!(s.edit_word(WordAction::Uppercase)); assert_eq!("a SSETA c", s.buf); assert_eq!(7, s.pos); let mut s = LineBuffer::init("a ßetA c", 1, None); - assert!(s.edit_word(WordAction::LOWERCASE)); + assert!(s.edit_word(WordAction::Lowercase)); assert_eq!("a ßeta c", s.buf); assert_eq!(7, s.pos); let mut s = LineBuffer::init("a ßETA c", 1, None); - assert!(s.edit_word(WordAction::CAPITALIZE)); + assert!(s.edit_word(WordAction::Capitalize)); assert_eq!("a SSeta c", s.buf); assert_eq!(7, s.pos); let mut s = LineBuffer::init("test", 1, None); - assert!(s.edit_word(WordAction::CAPITALIZE)); + assert!(s.edit_word(WordAction::Capitalize)); assert_eq!("tEst", s.buf); assert_eq!(4, s.pos); } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 558cca476b..01e3ec76d1 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -117,7 +117,7 @@ impl RawReader for ConsoleRawReader { loop { // TODO GetNumberOfConsoleInputEvents check(unsafe { - consoleapi::ReadConsoleInputW(self.handle, &mut rec, 1 as DWORD, &mut count) + consoleapi::ReadConsoleInputW(self.handle, &mut rec, 1, &mut count) })?; if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT { @@ -188,7 +188,7 @@ impl RawReader for ConsoleRawReader { } else if utf16 == 27 { KeyEvent(K::Esc, mods) } else { - if utf16 >= 0xD800 && utf16 < 0xDC00 { + if (0xD800..0xDC00).contains(&utf16) { surrogate = utf16; continue; } @@ -449,7 +449,7 @@ impl Renderer for ConsoleRenderer { } fn sigwinch(&self) -> bool { - SIGWINCH.compare_and_swap(true, false, Ordering::SeqCst) + SIGWINCH.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).unwrap_or(false) } /// Try to get the number of columns in the current terminal,