From 3913e1e7aea63a57405038b1358351a4538bcf77 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Sun, 12 Apr 2020 12:29:13 +0800 Subject: [PATCH] Refactor icon painter and cmd (#386) * Refactor icon painter in light_command * Refactor exec subcommand * Use IconPainter * Introduce ContentFiltering * Add --content-filtering * Update CHANGELOG.md * Allow :Clap files +name-only ~ * Add --icon-painter * Fix deprecated --enable-icon in sync_grep * Improve icon for truncated text in dyn filter * Imporve icon highlight for truncated grep line * . * . --- CHANGELOG.md | 9 ++ Cargo.lock | 1 + autoload/clap/filter/async/dyn.vim | 19 +++- autoload/clap/job/stdio.vim | 2 +- autoload/clap/maple.vim | 10 +- crates/icon/Cargo.toml | 1 + crates/icon/src/lib.rs | 38 +++++-- crates/maple_cli/src/cmd/blines.rs | 6 +- crates/maple_cli/src/cmd/exec.rs | 104 +++++++++++------- crates/maple_cli/src/cmd/filter/dynamic.rs | 94 +++------------- crates/maple_cli/src/cmd/filter/mod.rs | 17 +-- .../maple_cli/src/cmd/filter/scoring_line.rs | 102 +++++++++++++++++ crates/maple_cli/src/cmd/grep.rs | 23 ++-- crates/maple_cli/src/cmd/mod.rs | 36 +++--- crates/maple_cli/src/lib.rs | 2 + crates/maple_cli/src/light_command.rs | 41 +++---- crates/maple_cli/src/utils.rs | 7 +- src/main.rs | 33 ++---- syntax/clap_grep.vim | 4 + 19 files changed, 310 insertions(+), 239 deletions(-) create mode 100644 crates/maple_cli/src/cmd/filter/scoring_line.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 51edfce38..6c57e0e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ CHANGELOG ## [unreleased] +### Added + +- Add `--content-filtering` in maple. You can use `:Clap files +name-only ~` to filter the file name instead of full file path, but you can only use it when clap is using the cached tempfile inside vim. + +### Improved + +- icon highlight for truncated grep text. + ### Changed - `grep2` will not match the file path by default. ([#385](https://github.com/liuchengxu/vim-clap/pull/385)) @@ -10,6 +18,7 @@ CHANGELOG ### Fixed - `ITEMS_TO_SHOW` is fixed at the moment, only 30 rows can be shown correctly for dyn filter. https://github.com/liuchengxu/vim-clap/pull/385#issuecomment-611601076 +- Fix wrong icon for dyn filter when the text is truncated. ## [0.11] 2020-04-09 diff --git a/Cargo.lock b/Cargo.lock index cc1f3b0ad..1efe0aae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,6 +230,7 @@ version = "0.1.0" dependencies = [ "lazy_static", "regex", + "structopt", ] [[package]] diff --git a/autoload/clap/filter/async/dyn.vim b/autoload/clap/filter/async/dyn.vim index ecd2f4d9b..bc1df25cd 100644 --- a/autoload/clap/filter/async/dyn.vim +++ b/autoload/clap/filter/async/dyn.vim @@ -28,17 +28,26 @@ endfunction function! clap#filter#async#dyn#from_tempfile(tempfile) abort let s:last_query = g:clap.input.get() + if g:clap_enable_icon && index(['files', 'git_files'], g:clap.provider.id) > -1 - let enable_icon_opt = '--enable-icon' + let enable_icon_opt = '--icon-painter=File' else let enable_icon_opt = '' endif - let filter_cmd = printf('%s --number %d --winwidth %d filter "%s" --input "%s"', + + if g:clap.provider.id ==# 'files' && has_key(g:clap.context, 'name-only') + let content_filtering = '--content-filtering=FileNameOnly' + else + let content_filtering = '' + endif + + let filter_cmd = printf('%s --number %d --winwidth %d filter "%s" --input "%s" %s', \ enable_icon_opt, \ s:DYN_ITEMS_TO_SHOW, \ winwidth(g:clap.display.winid), \ g:clap.input.get(), - \ a:tempfile + \ a:tempfile, + \ content_filtering, \ ) call clap#job#stdio#start_service(function('s:handle_message'), clap#maple#build_cmd(filter_cmd)) endfunction @@ -46,7 +55,7 @@ endfunction function! clap#filter#async#dyn#start_grep() abort let s:last_query = g:clap.input.get() let grep_cmd = printf('%s --number %d --winwidth %d grep "" "%s" --cmd-dir "%s"', - \ g:clap_enable_icon ? '--enable-icon' : '', + \ g:clap_enable_icon ? '--icon-painter=Grep' : '', \ s:DYN_ITEMS_TO_SHOW, \ winwidth(g:clap.display.winid), \ g:clap.input.get(), @@ -58,7 +67,7 @@ endfunction function! clap#filter#async#dyn#grep_from_cache(tempfile) abort let s:last_query = g:clap.input.get() let grep_cmd = printf('%s %s --number %d --winwidth %d grep "" "%s" --input "%s"', - \ g:clap_enable_icon ? '--enable-icon' : '', + \ g:clap_enable_icon ? '--icon-painter=Grep' : '', \ has_key(g:clap.context, 'no-cache') ? '--no-cache' : '', \ s:DYN_ITEMS_TO_SHOW, \ winwidth(g:clap.display.winid), diff --git a/autoload/clap/job/stdio.vim b/autoload/clap/job/stdio.vim index 0417b0196..634072b42 100644 --- a/autoload/clap/job/stdio.vim +++ b/autoload/clap/job/stdio.vim @@ -141,7 +141,7 @@ function! clap#job#stdio#start_dyn_filter_service(MessageHandler, cmd) abort let s:MessageHandler = a:MessageHandler let filter_cmd = printf('%s --number 100 --winwidth %d filter "%s" --cmd "%s" --cmd-dir "%s"', - \ g:clap_enable_icon ? '--enable-icon' : '', + \ g:clap_enable_icon ? '--icon-painter=File' : '', \ winwidth(g:clap.display.winid), \ g:clap.input.get(), \ a:cmd, diff --git a/autoload/clap/maple.vim b/autoload/clap/maple.vim index 92872f9fe..7e34bbd4b 100644 --- a/autoload/clap/maple.vim +++ b/autoload/clap/maple.vim @@ -181,7 +181,7 @@ let s:can_enable_icon = ['files', 'git_files'] function! clap#maple#get_enable_icon_opt() abort if g:clap_enable_icon \ && index(s:can_enable_icon, g:clap.provider.id) > -1 - return '--enable-icon' + return '--icon-painter=File' else return '' endif @@ -210,7 +210,7 @@ function! clap#maple#sync_filter_subcommand(query) abort let global_opt = '--number '.g:clap.display.preload_capacity.' --winwidth '.winwidth(g:clap.display.winid) if g:clap.provider.id ==# 'files' && g:clap_enable_icon - let global_opt .= ' --enable-icon' + let global_opt .= ' --icon-painter=File' endif let cmd = printf('%s %s filter "%s" --sync', s:maple_bin, global_opt, a:query) @@ -222,7 +222,7 @@ function! clap#maple#ripgrep_forerunner_subcommand() abort " let global_opt = '--number '.g:clap.display.preload_capacity " TODO: add max_output if g:clap_enable_icon - let global_opt = '--enable-icon' + let global_opt = '--icon-painter=Grep' else let global_opt = '' endif @@ -243,7 +243,7 @@ endfunction function! clap#maple#run_exec(cmd) abort let global_opt = '--number '.g:clap.display.preload_capacity if g:clap.provider.id ==# 'files' && g:clap_enable_icon - let global_opt .= ' --enable-icon' + let global_opt .= ' --icon-painter=File' endif let cmd_dir = clap#rooter#working_dir() @@ -257,7 +257,7 @@ endfunction function! clap#maple#run_sync_grep(cmd, query, enable_icon, glob) abort let global_opt = '--number '.g:clap.display.preload_capacity if a:enable_icon - let global_opt .= ' --enable-icon' + let global_opt .= ' --icon-painter=Grep' endif let cmd_dir = clap#rooter#working_dir() diff --git a/crates/icon/Cargo.toml b/crates/icon/Cargo.toml index 9467dba6a..3e89abf4f 100644 --- a/crates/icon/Cargo.toml +++ b/crates/icon/Cargo.toml @@ -13,3 +13,4 @@ icon is a tool for drawing an icon according to the path name for vim-clap. [dependencies] regex = "1" lazy_static = "1.4.0" +structopt = "0.3" diff --git a/crates/icon/src/lib.rs b/crates/icon/src/lib.rs index 564d03d64..7acf096fc 100644 --- a/crates/icon/src/lib.rs +++ b/crates/icon/src/lib.rs @@ -2,10 +2,10 @@ mod constants; pub use constants::{bsearch_icon_table, EXACTMATCH_ICON_TABLE, EXTENSION_ICON_TABLE}; -use std::path::Path; - use lazy_static::lazy_static; use regex::Regex; +use std::path::Path; +use structopt::clap::arg_enum; pub const DEFAULT_ICON: char = ''; pub const FOLDER_ICON: char = ''; @@ -64,23 +64,29 @@ pub fn prepend_filer_icon(path: &Path, line: &str) -> String { format!("{} {}", icon_for_filer(path), line) } -/// Prepend an icon to the output line of ripgrep. -pub fn prepend_grep_icon(line: &str) -> String { +#[inline] +fn grep_icon_for(line: &str) -> Icon { lazy_static! { static ref RE: Regex = Regex::new(r"^(.*):\d+:\d+:").unwrap(); } - let icon = RE - .captures(line) + RE.captures(line) .and_then(|cap| cap.get(1)) .map(|m| icon_for(m.as_str())) - .unwrap_or(DEFAULT_ICON); - format!("{} {}", icon, line) + .unwrap_or(DEFAULT_ICON) +} + +/// Prepend an icon to the output line of ripgrep. +pub fn prepend_grep_icon(line: &str) -> String { + format!("{} {}", grep_icon_for(line), line) } -/// Prepend an icon for various kind of output line. -pub enum IconPainter { - File, - Grep, +arg_enum! { + /// Prepend an icon for various kind of output line. + #[derive(Clone, Debug)] + pub enum IconPainter { + File, + Grep, + } } impl IconPainter { @@ -91,4 +97,12 @@ impl IconPainter { Self::Grep => prepend_grep_icon(raw_str), } } + + /// Returns appropriate icon for the given text. + pub fn get_icon(&self, text: &str) -> Icon { + match *self { + Self::File => icon_for(text), + Self::Grep => grep_icon_for(text), + } + } } diff --git a/crates/maple_cli/src/cmd/blines.rs b/crates/maple_cli/src/cmd/blines.rs index 7a4a7eb74..30e56302d 100644 --- a/crates/maple_cli/src/cmd/blines.rs +++ b/crates/maple_cli/src/cmd/blines.rs @@ -1,3 +1,4 @@ +use crate::cmd::filter::ContentFiltering; use anyhow::Result; use fuzzy_filter::Source; use std::path::PathBuf; @@ -28,10 +29,9 @@ impl Blines { ), None, number, - false, winwidth, - false, - false, + None, + ContentFiltering::Full, ) } } diff --git a/crates/maple_cli/src/cmd/exec.rs b/crates/maple_cli/src/cmd/exec.rs index d4aa2df4b..b1e78abd4 100644 --- a/crates/maple_cli/src/cmd/exec.rs +++ b/crates/maple_cli/src/cmd/exec.rs @@ -1,52 +1,74 @@ +use crate::light_command::{set_current_dir, LightCommand}; +use anyhow::Result; +use icon::IconPainter; use std::path::PathBuf; use std::process::Command; +use structopt::StructOpt; -use anyhow::Result; +/// Execute the shell command +#[derive(StructOpt, Debug, Clone)] +pub struct Exec { + /// Specify the system command to run. + #[structopt(index = 1, short, long)] + cmd: String, -use crate::light_command::{set_current_dir, LightCommand}; + /// Specify the output file path when the output of command exceeds the threshold. + #[structopt(long = "output")] + output: Option, + + /// Specify the threshold for writing the output of command to a tempfile. + #[structopt(long = "output-threshold", default_value = "100000")] + output_threshold: usize, + + /// Specify the working directory of CMD + #[structopt(long = "cmd-dir", parse(from_os_str))] + cmd_dir: Option, +} + +impl Exec { + // This can work with the piped command, e.g., git ls-files | uniq. + fn prepare_exec_cmd(&self) -> Command { + let mut cmd = if cfg!(target_os = "windows") { + let mut cmd = Command::new("cmd"); + cmd.args(&["/C", &self.cmd]); + cmd + } else { + let mut cmd = Command::new("bash"); + cmd.arg("-c").arg(&self.cmd); + cmd + }; + + set_current_dir(&mut cmd, self.cmd_dir.clone()); -// This can work with the piped command, e.g., git ls-files | uniq. -fn prepare_exec_cmd(cmd_str: &str, cmd_dir: Option) -> Command { - let mut cmd = if cfg!(target_os = "windows") { - let mut cmd = Command::new("cmd"); - cmd.args(&["/C", cmd_str]); - cmd - } else { - let mut cmd = Command::new("bash"); - cmd.arg("-c").arg(cmd_str); cmd - }; + } - set_current_dir(&mut cmd, cmd_dir); + pub fn run( + &self, + number: Option, + icon_painter: Option, + no_cache: bool, + ) -> Result<()> { + let mut exec_cmd = self.prepare_exec_cmd(); - cmd -} + let mut light_cmd = LightCommand::new( + &mut exec_cmd, + number, + self.output.clone(), + icon_painter, + self.output_threshold, + ); -pub fn run( - cmd: String, - output: Option, - output_threshold: usize, - cmd_dir: Option, - number: Option, - enable_icon: bool, - no_cache: bool, -) -> Result<()> { - let mut exec_cmd = prepare_exec_cmd(&cmd, cmd_dir.clone()); - - let mut light_cmd = LightCommand::new( - &mut exec_cmd, - number, - output, - enable_icon, - false, - output_threshold, - ); - - let args = cmd.split_whitespace().map(Into::into).collect::>(); - - if !no_cache && cmd_dir.is_some() { - light_cmd.try_cache_or_execute(&args, cmd_dir.unwrap()) - } else { - light_cmd.execute(&args) + let args = self + .cmd + .split_whitespace() + .map(Into::into) + .collect::>(); + + if !no_cache && self.cmd_dir.is_some() { + light_cmd.try_cache_or_execute(&args, self.cmd_dir.clone().unwrap()) + } else { + light_cmd.execute(&args) + } } } diff --git a/crates/maple_cli/src/cmd/filter/dynamic.rs b/crates/maple_cli/src/cmd/filter/dynamic.rs index 95192706a..bbf8ceaa1 100644 --- a/crates/maple_cli/src/cmd/filter/dynamic.rs +++ b/crates/maple_cli/src/cmd/filter/dynamic.rs @@ -1,11 +1,8 @@ +use super::scoring_line::*; use super::*; -use extracted_fzy::match_and_score_with_positions; use fuzzy_filter::FuzzyMatchedLineInfo; -use fuzzy_matcher::skim::fuzzy_indices; use icon::ICON_LEN; -use lazy_static::lazy_static; use rayon::slice::ParallelSliceMut; -use regex::Regex; use std::io::{self, BufRead}; use std::time::{Duration, Instant}; @@ -317,7 +314,7 @@ macro_rules! source_iter_file { ( $scorer:ident, $fpath:ident ) => { // To avoid Err(Custom { kind: InvalidData, error: "stream did not contain valid UTF-8" }) // The line stream can contain invalid UTF-8 data. - std::io::BufReader::new(std::fs::File::open($fpath).unwrap()) + std::io::BufReader::new(std::fs::File::open($fpath)?) .lines() .filter(|x| x.is_ok()) .map(|x| x.unwrap()) @@ -332,78 +329,29 @@ macro_rules! source_iter_list { }; } -lazy_static! { - // match the file path and line number of grep line. - static ref GREP_RE: Regex = Regex::new(r"^.*:\d+:\d+:").unwrap(); -} - -/// Do not match the file path when using ripgrep. -#[inline] -fn strip_grep_filepath(line: &str) -> Option<(&str, usize)> { - GREP_RE - .find(line) - .map(|mat| (&line[mat.end()..], mat.end())) -} - -#[inline] -fn apply_skim_on_grep_line(line: &str, query: &str) -> Option<(i64, Vec)> { - strip_grep_filepath(line).and_then(|(truncated_line, offset)| { - fuzzy_indices(truncated_line, query) - .map(|(score, indices)| (score, indices.into_iter().map(|x| x + offset).collect())) - }) -} - -#[inline] -fn apply_fzy_on_grep_line(line: &str, query: &str) -> Option<(i64, Vec)> { - strip_grep_filepath(line).and_then(|(truncated_line, offset)| { - match_and_score_with_positions(query, truncated_line).map(|(score, indices)| { - ( - score as i64, - indices.into_iter().map(|x| x + offset).collect(), - ) - }) - }) -} - /// Returns the ranked results after applying fuzzy filter given the query string and a list of candidates. pub fn dyn_fuzzy_filter_and_rank>( query: &str, source: Source, algo: Option, number: Option, - enable_icon: bool, winwidth: Option, - grep_icon: bool, - exclude_grep_filepath: bool, + icon_painter: Option, + content_filtering: ContentFiltering, ) -> Result<()> { let algo = algo.unwrap_or(Algo::Fzy); let scorer = |line: &str| match algo { - Algo::Skim => { - if exclude_grep_filepath { - apply_skim_on_grep_line(line, query) - } else { - fuzzy_indices(line, query) - } - } - Algo::Fzy => { - if exclude_grep_filepath { - apply_fzy_on_grep_line(line, query) - } else { - match_and_score_with_positions(query, line) - .map(|(score, indices)| (score as i64, indices)) - } - } - }; - - let icon_painter = if enable_icon { - if grep_icon { - Some(IconPainter::Grep) - } else { - Some(IconPainter::File) - } - } else { - None + Algo::Skim => match content_filtering { + ContentFiltering::Full => fuzzy_indices_skim(line, query), + ContentFiltering::FileNameOnly => apply_skim_on_file_line(line, query), + ContentFiltering::GrepExcludeFilePath => apply_skim_on_grep_line(line, query), + }, + Algo::Fzy => match content_filtering { + ContentFiltering::Full => fuzzy_indices_fzy(line, query), + ContentFiltering::FileNameOnly => apply_fzy_on_file_line(line, query), + ContentFiltering::GrepExcludeFilePath => apply_fzy_on_grep_line(line, query), + }, }; if let Some(number) = number { @@ -512,20 +460,10 @@ mod tests { ), Some(Algo::Fzy), Some(100), - false, None, - false, - false, + None, + ContentFiltering::Full, ) .unwrap() } - - #[test] - fn test_exclude_grep_filepath() { - let query = "macro"; - let line = "crates/maple_cli/src/lib.rs:2:1:macro_rules! println_json {"; - let (_, origin_indices) = fuzzy_indices(line, query).unwrap(); - let (_, indices) = apply_fzy_on_grep_line(line, query).unwrap(); - assert_eq!(origin_indices, indices); - } } diff --git a/crates/maple_cli/src/cmd/filter/mod.rs b/crates/maple_cli/src/cmd/filter/mod.rs index 5db9de40f..dd773626f 100644 --- a/crates/maple_cli/src/cmd/filter/mod.rs +++ b/crates/maple_cli/src/cmd/filter/mod.rs @@ -1,6 +1,8 @@ pub mod dynamic; +mod scoring_line; pub use dynamic::dyn_fuzzy_filter_and_rank as dyn_run; +pub use scoring_line::ContentFiltering; use std::collections::HashMap; @@ -21,7 +23,12 @@ fn process_top_items( let mut indices = Vec::with_capacity(top_size); if let Some(painter) = icon_painter { for (text, _, idxs) in truncated_lines { - lines.push(painter.paint(&text)); + let iconized = if let Some(origin_text) = truncated_map.get(&text) { + format!("{} {}", painter.get_icon(origin_text), text) + } else { + painter.paint(&text) + }; + lines.push(iconized); indices.push(idxs.into_iter().map(|x| x + ICON_LEN).collect()); } } else { @@ -38,7 +45,7 @@ pub fn run>( source: Source, algo: Option, number: Option, - enable_icon: bool, + icon_painter: Option, winwidth: Option, ) -> Result<()> { let ranked = fuzzy_filter_and_rank(query, source, algo.unwrap_or(Algo::Fzy))?; @@ -49,11 +56,7 @@ pub fn run>( number, ranked.into_iter().take(number), winwidth.unwrap_or(62), - if enable_icon { - Some(IconPainter::File) - } else { - None - }, + icon_painter, ); if truncated_map.is_empty() { println_json!(total, lines, indices); diff --git a/crates/maple_cli/src/cmd/filter/scoring_line.rs b/crates/maple_cli/src/cmd/filter/scoring_line.rs new file mode 100644 index 000000000..be9edd085 --- /dev/null +++ b/crates/maple_cli/src/cmd/filter/scoring_line.rs @@ -0,0 +1,102 @@ +use extracted_fzy::match_and_score_with_positions; +pub use fuzzy_matcher::skim::fuzzy_indices as fuzzy_indices_skim; +use lazy_static::lazy_static; +use regex::Regex; +use structopt::clap::arg_enum; + +// Implement arg_enum so that we could control it from the command line. +arg_enum! { + #[derive(Debug, Clone)] + pub enum ContentFiltering { + Full, + FileNameOnly, + GrepExcludeFilePath, + } +} + +// Returns the score and indices of matched chars +// when the line is matched given the query, +type ScorerOutput = Option<(i64, Vec)>; + +lazy_static! { + // match the file path and line number of grep line. + static ref GREP_RE: Regex = Regex::new(r"^.*:\d+:\d+:").unwrap(); +} + +/// Make the arguments order same to Skim's `fuzzy_indices()`. +#[inline] +pub(super) fn fuzzy_indices_fzy(line: &str, query: &str) -> ScorerOutput { + match_and_score_with_positions(query, line).map(|(score, indices)| (score as i64, indices)) +} + +/// Do not match the file path when using ripgrep. +#[inline] +fn strip_grep_filepath(line: &str) -> Option<(&str, usize)> { + GREP_RE + .find(line) + .map(|mat| (&line[mat.end()..], mat.end())) +} + +#[inline] +pub(super) fn apply_skim_on_grep_line(line: &str, query: &str) -> ScorerOutput { + strip_grep_filepath(line).and_then(|(truncated_line, offset)| { + fuzzy_indices_skim(truncated_line, query) + .map(|(score, indices)| (score, indices.into_iter().map(|x| x + offset).collect())) + }) +} + +#[inline] +pub(super) fn apply_fzy_on_grep_line(line: &str, query: &str) -> ScorerOutput { + strip_grep_filepath(line).and_then(|(truncated_line, offset)| { + fuzzy_indices_fzy(truncated_line, query) + .map(|(score, indices)| (score, indices.into_iter().map(|x| x + offset).collect())) + }) +} + +#[inline] +fn file_name_only(line: &str) -> Option<(&str, usize)> { + let fpath: std::path::PathBuf = line.into(); + fpath + .file_name() + .map(|x| x.to_string_lossy().into_owned()) + .map(|fname| (&line[line.len() - fname.len()..], line.len() - fname.len())) +} + +#[inline] +pub(super) fn apply_skim_on_file_line(line: &str, query: &str) -> ScorerOutput { + file_name_only(line).and_then(|(truncated_line, offset)| { + fuzzy_indices_skim(truncated_line, query) + .map(|(score, indices)| (score, indices.into_iter().map(|x| x + offset).collect())) + }) +} + +#[inline] +pub(super) fn apply_fzy_on_file_line(line: &str, query: &str) -> ScorerOutput { + file_name_only(line).and_then(|(truncated_line, offset)| { + fuzzy_indices_fzy(truncated_line, query) + .map(|(score, indices)| (score, indices.into_iter().map(|x| x + offset).collect())) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_exclude_grep_filepath() { + let query = "macro"; + let line = "crates/maple_cli/src/lib.rs:2:1:macro_rules! println_json {"; + let (_, origin_indices) = fuzzy_indices_fzy(line, query).unwrap(); + let (_, indices) = apply_fzy_on_grep_line(line, query).unwrap(); + assert_eq!(origin_indices, indices); + } + + #[test] + fn test_file_name_only() { + let query = "lib"; + let line = "crates/extracted_fzy/src/lib.rs"; + let (_, origin_indices) = fuzzy_indices_fzy(line, query).unwrap(); + let (_, indices) = apply_fzy_on_file_line(line, query).unwrap(); + assert_eq!(origin_indices, indices); + } +} diff --git a/crates/maple_cli/src/cmd/grep.rs b/crates/maple_cli/src/cmd/grep.rs index 778d19303..62ba25304 100644 --- a/crates/maple_cli/src/cmd/grep.rs +++ b/crates/maple_cli/src/cmd/grep.rs @@ -1,9 +1,10 @@ use crate::cmd::cache::CacheEntry; +use crate::cmd::filter::ContentFiltering; use crate::light_command::{set_current_dir, LightCommand}; use crate::utils::{get_cached_entry, is_git_repo, read_first_lines}; use anyhow::{anyhow, Result}; use fuzzy_filter::{subprocess, Source}; -use icon::prepend_grep_icon; +use icon::{prepend_grep_icon, IconPainter}; use std::path::PathBuf; use std::process::Command; @@ -36,7 +37,7 @@ pub fn run( glob: Option<&str>, cmd_dir: Option, number: Option, - enable_icon: bool, + icon_painter: Option, ) -> Result<()> { let (mut cmd, mut args) = prepare_grep_and_args(&grep_cmd, cmd_dir); @@ -56,7 +57,7 @@ pub fn run( cmd.args(&args[1..]); - let mut light_cmd = LightCommand::new_grep(&mut cmd, None, number, enable_icon); + let mut light_cmd = LightCommand::new_grep(&mut cmd, None, number, icon_painter); light_cmd.execute(&args)?; @@ -107,7 +108,7 @@ pub fn dyn_grep( cmd_dir: Option, input: Option, number: Option, - enable_icon: bool, + icon_painter: Option, no_cache: bool, ) -> Result<()> { let rg_cmd = "rg --column --line-number --no-heading --color=never --smart-case ''"; @@ -123,10 +124,9 @@ pub fn dyn_grep( cached_source, None, number, - enable_icon, None, - true, - true, + icon_painter, + ContentFiltering::GrepExcludeFilePath, ); } } @@ -140,17 +140,16 @@ pub fn dyn_grep( source, None, number, - enable_icon, None, - true, - true, + icon_painter, + ContentFiltering::GrepExcludeFilePath, ) } pub fn run_forerunner( cmd_dir: Option, number: Option, - enable_icon: bool, + icon_painter: Option, no_cache: bool, ) -> Result<()> { if !no_cache { @@ -178,7 +177,7 @@ pub fn run_forerunner( set_current_dir(&mut cmd, cmd_dir.clone()); - let mut light_cmd = LightCommand::new_grep(&mut cmd, cmd_dir, number, enable_icon); + let mut light_cmd = LightCommand::new_grep(&mut cmd, cmd_dir, number, icon_painter); light_cmd.execute(&RG_ARGS)?; diff --git a/crates/maple_cli/src/cmd/mod.rs b/crates/maple_cli/src/cmd/mod.rs index a2184fd31..d58efa9ba 100644 --- a/crates/maple_cli/src/cmd/mod.rs +++ b/crates/maple_cli/src/cmd/mod.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; +use crate::cmd::filter::ContentFiltering; use fuzzy_filter::Algo; +use icon::IconPainter; use structopt::clap::AppSettings; use structopt::StructOpt; @@ -43,25 +45,10 @@ pub enum Cmd { /// Read input from a file instead of stdin, only absolute file path is supported. #[structopt(long = "input", parse(from_os_str))] input: Option, - }, - /// Execute the command - #[structopt(name = "exec")] - Exec { - /// Specify the system command to run. - #[structopt(index = 1, short, long)] - cmd: String, - /// Specify the output file path when the output of command exceeds the threshold. - #[structopt(long = "output")] - output: Option, - - /// Specify the threshold for writing the output of command to a tempfile. - #[structopt(long = "output-threshold", default_value = "100000")] - output_threshold: usize, - - /// Specify the working directory of CMD - #[structopt(long = "cmd-dir", parse(from_os_str))] - cmd_dir: Option, + /// Apply the filter on the full line content or parial of it. + #[structopt(short, long, possible_values = &ContentFiltering::variants(), case_insensitive = true)] + content_filtering: Option, }, /// Execute the grep command to avoid the escape issue #[structopt(name = "grep")] @@ -94,14 +81,19 @@ pub enum Cmd { #[structopt(long = "input", parse(from_os_str))] input: Option, }, + /// Start the stdio-based service, currently there is only filer support. #[structopt(name = "rpc")] RPC, + /// Start the forerunner job of grep. #[structopt(name = "ripgrep-forerunner")] RipgrepForerunner { /// Specify the working directory of CMD #[structopt(long = "cmd-dir", parse(from_os_str))] cmd_dir: Option, }, + /// Execute the command + #[structopt(name = "exec")] + Exec(crate::cmd::exec::Exec), #[structopt(name = "blines")] Blines(crate::cmd::blines::Blines), #[structopt(name = "helptags")] @@ -130,14 +122,14 @@ pub struct Maple { #[structopt(short = "w", long = "winwidth")] pub winwidth: Option, - /// Prepend an icon for item of files and grep provider, valid only when --number is used. - #[structopt(long = "enable-icon")] - pub enable_icon: bool, - /// Do not use the cached file for exec subcommand. #[structopt(long = "no-cache")] pub no_cache: bool, + /// Prepend an icon for item of files and grep provider, valid only when --number is used. + #[structopt(short, long, possible_values = &IconPainter::variants(), case_insensitive = true)] + pub icon_painter: Option, + #[structopt(subcommand)] pub command: Cmd, } diff --git a/crates/maple_cli/src/lib.rs b/crates/maple_cli/src/lib.rs index 91eac4358..d26a77481 100644 --- a/crates/maple_cli/src/lib.rs +++ b/crates/maple_cli/src/lib.rs @@ -23,8 +23,10 @@ macro_rules! print_json_with_length { pub mod cmd; pub use { + crate::cmd::filter::ContentFiltering, anyhow::Result, fuzzy_filter::{subprocess, Source}, + icon::IconPainter, structopt::StructOpt, }; diff --git a/crates/maple_cli/src/light_command.rs b/crates/maple_cli/src/light_command.rs index 261f8a25b..68a117eb2 100644 --- a/crates/maple_cli/src/light_command.rs +++ b/crates/maple_cli/src/light_command.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use std::process::{Command, Output}; use anyhow::Result; -use icon::{prepend_grep_icon, prepend_icon}; +use icon::IconPainter; use crate::cmd::cache::CacheEntry; use crate::error::DummyError; @@ -43,8 +43,7 @@ pub struct LightCommand<'a> { total: usize, number: Option, output: Option, - enable_icon: bool, - grep_enable_icon: bool, + icon_painter: Option, output_threshold: usize, } @@ -54,8 +53,7 @@ impl<'a> LightCommand<'a> { cmd: &'a mut Command, number: Option, output: Option, - enable_icon: bool, - grep_enable_icon: bool, + icon_painter: Option, output_threshold: usize, ) -> Self { Self { @@ -64,8 +62,7 @@ impl<'a> LightCommand<'a> { number, total: 0usize, output, - enable_icon, - grep_enable_icon, + icon_painter, output_threshold, } } @@ -75,7 +72,7 @@ impl<'a> LightCommand<'a> { cmd: &'a mut Command, cmd_dir: Option, number: Option, - grep_enable_icon: bool, + icon_painter: Option, ) -> Self { Self { cmd, @@ -83,8 +80,7 @@ impl<'a> LightCommand<'a> { number, total: 0usize, output: None, - enable_icon: false, - grep_enable_icon, + icon_painter, output_threshold: 0usize, } } @@ -119,10 +115,8 @@ impl<'a> LightCommand<'a> { } fn try_prepend_icon<'b>(&self, top_n: impl std::iter::Iterator) -> Vec { - let mut lines = if self.grep_enable_icon { - top_n.map(prepend_grep_icon).collect::>() - } else if self.enable_icon { - top_n.map(prepend_icon).collect::>() + let mut lines = if let Some(ref painter) = self.icon_painter { + top_n.map(|x| painter.paint(x)).collect::>() } else { top_n.map(Into::into).collect::>() }; @@ -163,19 +157,6 @@ impl<'a> LightCommand<'a> { } } - fn prepend_icon_for_cached_lines( - &self, - lines_iter: impl Iterator, - ) -> Vec { - if self.grep_enable_icon { - lines_iter.map(|x| prepend_grep_icon(&x)).collect() - } else if self.enable_icon { - lines_iter.map(|x| prepend_icon(&x)).collect() - } else { - lines_iter.collect() - } - } - /// Firstly try the cache given the command args and working dir. /// If the cache exists, returns the cache file directly. pub fn try_cache_or_execute(&mut self, args: &[&str], cmd_dir: PathBuf) -> Result<()> { @@ -184,7 +165,11 @@ impl<'a> LightCommand<'a> { let using_cache = true; let tempfile = cached_entry.path(); if let Ok(lines_iter) = read_first_lines(&tempfile, 100) { - let lines = self.prepend_icon_for_cached_lines(lines_iter); + let lines: Vec = if let Some(ref painter) = self.icon_painter { + lines_iter.map(|x| painter.paint(&x)).collect() + } else { + lines_iter.collect() + }; println_json!(using_cache, total, tempfile, lines); } else { println_json!(using_cache, total, tempfile); diff --git a/crates/maple_cli/src/utils.rs b/crates/maple_cli/src/utils.rs index 4603eda58..a730a823c 100644 --- a/crates/maple_cli/src/utils.rs +++ b/crates/maple_cli/src/utils.rs @@ -5,6 +5,8 @@ use std::hash::{Hash, Hasher}; use std::io::{self, BufRead}; use std::path::{Path, PathBuf}; +const CLAP_CACHE: &str = "vim.clap"; + /// Removes all the file and directories under `target_dir`. pub fn remove_dir_contents(target_dir: &PathBuf) -> Result<()> { let entries = read_dir(target_dir)?; @@ -60,7 +62,7 @@ pub fn calculate_hash(t: &T) -> u64 { #[inline] pub(crate) fn clap_cache_dir() -> PathBuf { let mut dir = std::env::temp_dir(); - dir.push("vim.clap"); + dir.push(CLAP_CACHE); dir } @@ -84,7 +86,8 @@ pub fn get_cached_entry(args: &[&str], cmd_dir: &PathBuf) -> Result { if cache_dir.exists() { let mut entries = read_dir(cache_dir)?; - // TODO: get latest modifed cache file? + // Everytime when we are about to create a new cache entry, the old entry will be removed, + // so there is only one cache entry, therefore it should be always the latest one. if let Some(Ok(first_entry)) = entries.next() { return Ok(first_entry); } diff --git a/src/main.rs b/src/main.rs index 86f779b9f..2ba663423 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use maple_cli::{ cmd::{Cmd, Maple}, - subprocess, Result, Source, StructOpt, + subprocess, ContentFiltering, Result, Source, StructOpt, }; pub mod built_info { @@ -35,7 +35,7 @@ fn run(maple: Maple) -> Result<()> { Cmd::RipgrepForerunner { cmd_dir } => maple_cli::cmd::grep::run_forerunner( cmd_dir, maple.number, - maple.enable_icon, + maple.icon_painter, maple.no_cache, )?, Cmd::Cache(cache) => cache.run()?, @@ -46,6 +46,7 @@ fn run(maple: Maple) -> Result<()> { cmd, cmd_dir, sync, + content_filtering, } => { let source = if let Some(cmd_str) = cmd { if let Some(dir) = cmd_dir { @@ -64,7 +65,7 @@ fn run(maple: Maple) -> Result<()> { source, algo, maple.number, - maple.enable_icon, + maple.icon_painter, maple.winwidth, )?; } else { @@ -73,28 +74,14 @@ fn run(maple: Maple) -> Result<()> { source, algo, maple.number, - maple.enable_icon, maple.winwidth, - false, - false, + maple.icon_painter, + content_filtering.unwrap_or(ContentFiltering::Full), )?; } } - Cmd::Exec { - cmd, - output, - cmd_dir, - output_threshold, - } => { - maple_cli::cmd::exec::run( - cmd, - output, - output_threshold, - cmd_dir, - maple.number, - maple.enable_icon, - maple.no_cache, - )?; + Cmd::Exec(exec) => { + exec.run(maple.number, maple.icon_painter, maple.no_cache)?; } Cmd::Grep { grep_cmd, @@ -116,7 +103,7 @@ fn run(maple: Maple) -> Result<()> { g, cmd_dir, maple.number, - maple.enable_icon, + maple.icon_painter, )?; } else { maple_cli::cmd::grep::dyn_grep( @@ -124,7 +111,7 @@ fn run(maple: Maple) -> Result<()> { cmd_dir, input, maple.number, - maple.enable_icon, + maple.icon_painter, maple.no_cache, )?; } diff --git a/syntax/clap_grep.vim b/syntax/clap_grep.vim index ea6590d49..3bb3ff479 100644 --- a/syntax/clap_grep.vim +++ b/syntax/clap_grep.vim @@ -1,4 +1,5 @@ " No usual did_ftplugin_loaded check +scriptencoding utf-8 syntax match ClapLinNr /^.*:\zs\d\+\ze:\d\+:/hs=s+1,he=e-1 contained syntax match ClapColumn /:\d\+:\zs\d\+\ze:/ contains=ClapLinNr contained @@ -8,8 +9,11 @@ syntax match ClapLinNrColumn /\zs:\d\+:\d\+:\ze/ contains=ClapLinNr,ClapColumn c syntax match ClapIconUnknown /^\s*/ execute 'syntax match ClapFpath' '/^.*:\d\+:\d\+:/' 'contains=ClapLinNrColumn,'.join(clap#icon#add_head_hl_groups(), ',') +execute 'syntax match ClapFpathTruncated' '/^.*\.\.\./' 'contains='.join(clap#icon#add_head_hl_groups(), ',').',ClapFpathDots' +syntax match ClapFpathDots '\.\.\.' contained hi default link ClapFpath Keyword +hi default link ClapFpathTruncated Keyword hi default link ClapLinNr LineNr hi default link ClapColumn Comment hi default link ClapLinNrColumn Type