diff --git a/Cargo.lock b/Cargo.lock index 9aa80eb..d44b60a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,14 +570,15 @@ dependencies = [ [[package]] name = "dialoguer" -version = "0.10.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ "console", "fuzzy-matcher", "shell-words", "tempfile", + "thiserror", "zeroize", ] @@ -849,9 +850,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1655,21 +1656,21 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.105", + "syn 2.0.15", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 85588ac..cd3815c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ chrono = { version = "0.4.24", features = ["serde"] } chrono-english = "0.1.7" # Taking user input and showing progress -dialoguer = {version = "0.10.4", features = ["completion", "history", "fuzzy-select"]} +dialoguer = {version = "0.11.0", features = ["completion", "history", "fuzzy-select"]} indicatif = "0.17.3" # Fuzzy search @@ -58,8 +58,8 @@ termcolor = "1.2.0" # Sync to Gist/GitLab ureq = { version = "2.6.2", features = ["json"] } -strum = "0.24.1" -strum_macros = "0.24.3" +strum = "0.25.0" +strum_macros = "0.25.3" # pattern filter and filling shell script variables regex = "1.8.1" diff --git a/src/the_way/cli.rs b/src/the_way/cli.rs index 0412174..be62a10 100644 --- a/src/the_way/cli.rs +++ b/src/the_way/cli.rs @@ -142,6 +142,16 @@ pub enum TheWaySubcommand { /// Index of snippet to show index: usize, }, + /// Lists (optionally filtered) tags + Tags { + #[clap(flatten)] + filters: Filters, + }, + /// Lists (optionally filtered) languages + Languages { + #[clap(flatten)] + filters: Filters, + }, } #[derive(Parser, Debug)] diff --git a/src/the_way/mod.rs b/src/the_way/mod.rs index 073aaa3..98db484 100644 --- a/src/the_way/mod.rs +++ b/src/the_way/mod.rs @@ -46,6 +46,12 @@ pub struct TheWay { plain: bool, } +pub enum ListType { + Snippet, + Tag, + Language, +} + // All command-line related functions impl TheWay { /// Initialize program with command line input. @@ -89,7 +95,7 @@ impl TheWay { TheWaySubcommand::Edit { index } => self.edit(index), TheWaySubcommand::Del { index, force } => self.delete(index, force), TheWaySubcommand::View { index } => self.view(index), - TheWaySubcommand::List { filters } => self.list(&filters), + TheWaySubcommand::List { filters } => self.list(&filters, ListType::Snippet), TheWaySubcommand::Import { file, gist_url, @@ -107,6 +113,8 @@ impl TheWay { ConfigCommand::Get => TheWayConfig::print_config_location(), }, TheWaySubcommand::Sync { cmd, force } => self.sync(cmd, force), + TheWaySubcommand::Tags { filters } => self.list(&filters, ListType::Tag), + TheWaySubcommand::Languages { filters } => self.list(&filters, ListType::Language), } } @@ -296,11 +304,55 @@ impl TheWay { Ok(()) } + fn show_counts( + &self, + object_to_count: HashMap, + list_type: ListType, + ) -> color_eyre::Result<()> { + let mut objects = object_to_count.iter().collect::>(); + objects.sort_by(|(_, a), (_, b)| b.cmp(a)); + let mut colorized = Vec::new(); + for (object, count) in objects { + match list_type { + ListType::Tag => { + colorized.push((self.highlighter.tag_style, object.to_string())); + } + ListType::Language => { + colorized.push((self.highlighter.accent_style, object.to_string())); + } + _ => unreachable!(), + } + colorized.push((self.highlighter.main_style, format!(" ({count})\n"))); + } + utils::smart_print(&colorized, false, self.colorize, self.plain)?; + Ok(()) + } + /// Lists snippets (optionally filtered) - fn list(&self, filters: &Filters) -> color_eyre::Result<()> { + fn list(&self, filters: &Filters, list_type: ListType) -> color_eyre::Result<()> { let mut snippets = self.filter_snippets(filters)?; - snippets.sort_by(|a, b| a.index.cmp(&b.index)); - self.show_snippets(&snippets)?; + match list_type { + ListType::Snippet => { + snippets.sort_by(|a, b| a.index.cmp(&b.index)); + self.show_snippets(&snippets)?; + } + ListType::Tag => { + let mut tags = HashMap::new(); + for snippet in &snippets { + for tag in &snippet.tags { + *tags.entry(tag.to_owned()).or_insert(0) += 1; + } + } + self.show_counts(tags, list_type)?; + } + ListType::Language => { + let mut languages = HashMap::new(); + for snippet in &snippets { + *languages.entry(snippet.language.to_owned()).or_insert(0) += 1; + } + self.show_counts(languages, list_type)?; + } + } Ok(()) } diff --git a/src/the_way/snippet.rs b/src/the_way/snippet.rs index fb38532..5f5fdf4 100644 --- a/src/the_way/snippet.rs +++ b/src/the_way/snippet.rs @@ -93,8 +93,8 @@ impl Snippet { pub(crate) fn from_user( index: usize, languages: &HashMap, - all_tags: Vec, - all_used_languages: Vec, + used_tags: Vec, + used_languages: Vec, old_snippet: Option<&Self>, ) -> color_eyre::Result { let (old_description, old_language, old_tags, old_date, old_code) = match old_snippet { @@ -114,14 +114,20 @@ impl Snippet { false, utils::TheWayCompletion::Empty, )?; - let mut all_languages = all_used_languages; - all_languages.extend(languages.keys().map(|s| s.to_ascii_lowercase())); + let mut all_languages = used_languages; + let mut unused_languages = languages + .keys() + .map(|s| s.to_ascii_lowercase()) + .collect::>(); + unused_languages.sort(); + all_languages.extend(unused_languages); + let language_completions = utils::TheWayCompletion::Language(all_languages); let language = utils::user_input("Language", old_language, true, false, language_completions)? .to_ascii_lowercase(); let extension = Language::get_extension(&language, languages); - let tag_completions = utils::TheWayCompletion::Tag(all_tags); + let tag_completions = utils::TheWayCompletion::Tag(used_tags); let tags = utils::user_input( "Tags (space separated)", old_tags.as_deref(), diff --git a/src/utils.rs b/src/utils.rs index 714c175..3425d6d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::io::Write; use std::process::{Command, Stdio}; use std::str; @@ -169,15 +170,14 @@ pub fn user_input( let theme = dialoguer::theme::ColorfulTheme::default(); match default { Some(default) => { - let mut input = Input::with_theme(&theme); - input + let mut input = Input::with_theme(&theme) .with_prompt(message) .completion_with(&completions) .allow_empty(allow_empty) .default(default.to_owned()) .show_default(false); if show_default { - input.with_initial_text(default); + input = input.with_initial_text(default); } Ok(input.interact_text()?.trim().to_owned()) } @@ -283,14 +283,25 @@ impl Completion for TheWayCompletion { } } Self::Tag(tags) => { + let current_tags_list = input + .split(' ') + .map(|s| s.to_string()) + .collect::>(); let last_input = input.split(' ').last().unwrap_or(""); + let last_space = input.rfind(' ').unwrap_or(0); let matches = tags .iter() - .filter(|option| option.starts_with(last_input)) + .filter(|option| { + option.starts_with(last_input) && !current_tags_list.contains(*option) + }) .collect::>(); if !matches.is_empty() { - Some(input.replace(last_input, matches[0])) + Some( + (input[..last_space].trim().to_string() + " " + matches[0]) + .trim() + .to_string(), + ) } else { None }