aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/asset
diff options
context:
space:
mode:
authorWeicao-CatilGrass <1992414357@qq.com>2026-05-09 13:21:01 +0800
committerWeicao-CatilGrass <1992414357@qq.com>2026-05-09 13:21:01 +0800
commitc0a29ccd2cd56e75c2b422b3cd9ca65d76a554da (patch)
tree26ce74a8f1138bed899d7896b48f1d5706ab96d4 /mingling_core/src/asset
parentc08ea7d8474335cadf13a2a7e45ca497fe375d90 (diff)
Move `comp` module from `asset` to crate root
Diffstat (limited to 'mingling_core/src/asset')
-rw-r--r--mingling_core/src/asset/comp.rs338
-rw-r--r--mingling_core/src/asset/comp/flags.rs45
-rw-r--r--mingling_core/src/asset/comp/shell_ctx.rs328
-rw-r--r--mingling_core/src/asset/comp/suggest.rs178
4 files changed, 0 insertions, 889 deletions
diff --git a/mingling_core/src/asset/comp.rs b/mingling_core/src/asset/comp.rs
deleted file mode 100644
index 4fb17c7..0000000
--- a/mingling_core/src/asset/comp.rs
+++ /dev/null
@@ -1,338 +0,0 @@
-mod flags;
-mod shell_ctx;
-mod suggest;
-
-use std::collections::BTreeSet;
-use std::fmt::Display;
-
-#[doc(hidden)]
-pub use flags::*;
-#[doc(hidden)]
-pub use shell_ctx::*;
-#[doc(hidden)]
-pub use suggest::*;
-
-use crate::{ProgramCollect, debug, only_debug, this, trace};
-
-#[cfg(not(feature = "dispatch_tree"))]
-use crate::exec::match_user_input;
-
-/// Trait for implementing completion logic.
-///
-/// This trait defines the interface for generating command-line completions.
-/// Types implementing this trait can provide custom completion suggestions
-/// based on the current shell context.
-pub trait Completion {
- type Previous;
- fn comp(ctx: &ShellContext) -> Suggest;
-}
-
-/// Trait for extracting user input arguments for completion.
-///
-/// When the `feat comp` feature is enabled, the `dispatcher!` macro will
-/// automatically implement this trait for `Entry` types to extract the
-/// arguments from user input for completion suggestions.
-pub trait CompletionEntry {
- fn get_input(self) -> Vec<String>;
-}
-
-/// A helper struct for handling command-line completion logic.
-///
-/// This struct provides static methods for executing completions based on
-/// the current shell context and rendering the resulting suggestions in a
-/// format appropriate for the target shell.
-pub struct CompletionHelper;
-impl CompletionHelper {
- pub fn exec_completion<P>(ctx: &ShellContext) -> Suggest
- where
- P: ProgramCollect<Enum = P> + Display + PartialEq + 'static,
- {
- only_debug! {
- crate::debug::init_env_logger();
- trace_ctx(ctx);
- };
-
- let args = ctx.all_words.iter().skip(1).cloned().collect::<Vec<_>>();
- trace!("arguments=\"{}\"", args.join(", "));
-
- #[cfg(not(feature = "dispatch_tree"))]
- let program = this::<P>();
-
- #[cfg(not(feature = "dispatch_tree"))]
- let suggest = if let Ok((dispatcher, args)) = match_user_input(program, &args) {
- trace!(
- "dispatcher matched, dispatcher=\"{}\"",
- dispatcher.node().to_string(),
- );
- let begin = dispatcher.begin(args);
- if let crate::ChainProcess::Ok((any, _)) = begin {
- trace!("entry type: {}", any.member_id);
- let result = P::do_comp(&any, ctx);
- trace!("do_comp result: {:?}", result);
- Some(result)
- } else {
- trace!("begin not Ok");
- None
- }
- } else {
- trace!("no dispatcher matched");
- None
- };
- #[cfg(feature = "dispatch_tree")]
- let suggest = if let Ok(any) = P::dispatch_args_trie(&args) {
- trace!("entry type: {}", any.member_id);
-
- let dispatcher_not_found = <P::DispatcherNotFound as crate::Groupped<P>>::member_id();
-
- if dispatcher_not_found == any.member_id {
- trace!("begin not Ok");
- None
- } else {
- let result = P::do_comp(&any, ctx);
- trace!("do_comp result: {:?}", result);
- Some(result)
- }
- } else {
- trace!("no dispatcher matched");
- None
- };
-
- match suggest {
- Some(suggest) => {
- trace!("using custom completion: {:?}", suggest);
- suggest
- }
- None => {
- trace!("using default completion");
- default_completion::<P>(ctx)
- }
- }
- }
-
- pub fn render_suggest<P>(ctx: ShellContext, suggest: Suggest)
- where
- P: ProgramCollect<Enum = P> + Display + 'static,
- {
- trace!("render_suggest called with: {:?}", suggest);
- match suggest {
- Suggest::FileCompletion => {
- trace!("rendering file completion");
- println!("_file_");
- std::process::exit(0);
- }
- Suggest::Suggest(suggestions) => {
- trace!("rendering {} suggestions", suggestions.len());
- match ctx.shell_flag {
- ShellFlag::Zsh | ShellFlag::Powershell => {
- trace!("using zsh/pwsh format");
- print_suggest_with_description(suggestions)
- }
- ShellFlag::Fish => {
- trace!("using fish format");
- print_suggest_with_description_fish(suggestions)
- }
- _ => {
- trace!("using default format");
- print_suggest(suggestions)
- }
- }
- }
- }
- }
-}
-
-fn default_completion<P>(ctx: &ShellContext) -> Suggest
-where
- P: ProgramCollect<Enum = P> + Display + 'static,
-{
- let cmd_nodes: Vec<String> = this::<P>()
- .get_nodes()
- .into_iter()
- .filter(|(s, _)| s != "__comp")
- .map(|(s, _)| s)
- .collect();
- debug!("cmd_nodes: {:?}", cmd_nodes);
-
- // If the current position is less than 1, do not perform completion
- if ctx.word_index < 1 {
- debug!("word_index < 1, returning file suggestions");
- return file_suggest();
- };
-
- // Get the current input path
- debug!(
- "input_path before filter: {:?}",
- &ctx.all_words.get(1..ctx.word_index).unwrap_or(&[])
- );
-
- let input_path: Vec<&str> = ctx
- .all_words
- .get(1..ctx.word_index)
- .unwrap_or(&[])
- .iter()
- .filter(|s| !s.is_empty())
- .map(|s| s.as_str())
- .collect();
- debug!("input_path after filter: {:?}", input_path);
-
- debug!(
- "default_completion: input_path = {:?}, word_index = {}, all_words = {:?}",
- input_path, ctx.word_index, ctx.all_words
- );
-
- // Filter command nodes that match the input path
- let mut suggestions = Vec::new();
-
- // Special case: if input_path is empty, return all first-level commands
- if input_path.is_empty() {
- for node in cmd_nodes {
- let node_parts: Vec<&str> = node.split(' ').collect();
- if !node_parts.is_empty() && !suggestions.contains(&node_parts[0].to_string()) {
- suggestions.push(node_parts[0].to_string());
- }
- }
- } else {
- // Get the current word
- let current_word = input_path.last().unwrap();
-
- // First, handle partial match completion for the current word
- // Only perform current word completion when current_word is not empty
- if input_path.len() == 1 && !ctx.current_word.is_empty() {
- for node in &cmd_nodes {
- let node_parts: Vec<&str> = node.split(' ').collect();
- if !node_parts.is_empty()
- && node_parts[0].starts_with(current_word)
- && !suggestions.contains(&node_parts[0].to_string())
- {
- suggestions.push(node_parts[0].to_string());
- }
- }
-
- // If suggestions for the current word are found, return directly
- if !suggestions.is_empty() {
- suggestions.sort();
- suggestions.dedup();
- debug!(
- "default_completion: current word suggestions = {:?}",
- suggestions
- );
- return suggestions.into();
- }
- }
-
- // Handle next-level command suggestions
- for node in cmd_nodes {
- let node_parts: Vec<&str> = node.split(' ').collect();
-
- debug!("Checking node: '{}', parts: {:?}", node, node_parts);
-
- // If input path is longer than node parts, skip
- if input_path.len() > node_parts.len() {
- continue;
- }
-
- // Check if input path matches the beginning of node parts
- let mut matches = true;
- for i in 0..input_path.len() {
- if i >= node_parts.len() {
- matches = false;
- break;
- }
-
- if i == input_path.len() - 1 {
- if !node_parts[i].starts_with(input_path[i]) {
- matches = false;
- break;
- }
- } else if input_path[i] != node_parts[i] {
- matches = false;
- break;
- }
- }
-
- if matches && input_path.len() <= node_parts.len() {
- if input_path.len() == node_parts.len() && !ctx.current_word.is_empty() {
- suggestions.push(node_parts[input_path.len() - 1].to_string());
- } else if input_path.len() < node_parts.len() {
- suggestions.push(node_parts[input_path.len()].to_string());
- }
- }
- }
- }
-
- // Remove duplicates and sort
- suggestions.sort();
- suggestions.dedup();
-
- debug!("default_completion: suggestions = {:?}", suggestions);
-
- if suggestions.is_empty() {
- file_suggest()
- } else {
- suggestions.into()
- }
-}
-
-fn file_suggest() -> Suggest {
- trace!("file_suggest called");
- Suggest::FileCompletion
-}
-
-fn print_suggest(suggestions: BTreeSet<SuggestItem>) {
- trace!("print_suggest called with {} items", suggestions.len());
- let mut sorted_suggestions: Vec<SuggestItem> = suggestions.into_iter().collect();
- sorted_suggestions.sort();
-
- for suggest in sorted_suggestions {
- println!("{}", suggest.suggest());
- }
- std::process::exit(0);
-}
-
-fn print_suggest_with_description(suggestions: BTreeSet<SuggestItem>) {
- trace!(
- "print_suggest_with_description called with {} items",
- suggestions.len()
- );
- let mut sorted_suggestions: Vec<SuggestItem> = suggestions.into_iter().collect();
- sorted_suggestions.sort();
-
- for suggest in sorted_suggestions {
- match suggest.description() {
- Some(desc) => println!("{}$({})", suggest.suggest(), desc),
- None => println!("{}", suggest.suggest()),
- }
- }
- std::process::exit(0);
-}
-
-fn print_suggest_with_description_fish(suggestions: BTreeSet<SuggestItem>) {
- trace!(
- "print_suggest_with_description_fish called with {} items",
- suggestions.len()
- );
- let mut sorted_suggestions: Vec<SuggestItem> = suggestions.into_iter().collect();
- sorted_suggestions.sort();
-
- for suggest in sorted_suggestions {
- match suggest.description() {
- Some(desc) => println!("{}\t{}", suggest.suggest(), desc),
- None => println!("{}", suggest.suggest()),
- }
- }
- std::process::exit(0);
-}
-
-#[cfg(feature = "debug")]
-fn trace_ctx(ctx: &ShellContext) {
- trace!("=== SHELL CTX BEGIN ===");
- trace!("command_line={}", ctx.command_line);
- trace!("cursor_position={}", ctx.cursor_position);
- trace!("current_word={}", ctx.current_word);
- trace!("previous_word={}", ctx.previous_word);
- trace!("command_name={}", ctx.command_name);
- trace!("word_index={}", ctx.word_index);
- trace!("all_words={:?}", ctx.all_words);
- trace!("shell_flag={:?}", ctx.shell_flag);
- trace!("=== SHELL CTX END ===");
-}
diff --git a/mingling_core/src/asset/comp/flags.rs b/mingling_core/src/asset/comp/flags.rs
deleted file mode 100644
index 452126b..0000000
--- a/mingling_core/src/asset/comp/flags.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use just_fmt::snake_case;
-
-/// Represents the shell environment for which the output format is intended.
-///
-/// This enum defines the supported shell types that can be used for
-/// generating shell-specific command syntax, scripts, or completions.
-#[derive(Default, Debug, Clone)]
-#[cfg_attr(feature = "general_renderer", derive(serde::Serialize))]
-pub enum ShellFlag {
- /// Represents the Bash shell.
- #[default]
- Bash,
- /// Represents the Zsh shell.
- Zsh,
- /// Represents the Fish shell.
- Fish,
- /// Represents PowerShell.
- Powershell,
- /// A custom or unsupported shell type, identified by the provided string.
- Other(String),
-}
-
-impl From<String> for ShellFlag {
- fn from(s: String) -> Self {
- match s.trim().to_lowercase().as_str() {
- "zsh" => ShellFlag::Zsh,
- "bash" => ShellFlag::Bash,
- "fish" => ShellFlag::Fish,
- "pwsh" | "ps1" | "powershell" => ShellFlag::Powershell,
- other => ShellFlag::Other(snake_case!(other)),
- }
- }
-}
-
-impl From<ShellFlag> for String {
- fn from(flag: ShellFlag) -> Self {
- match flag {
- ShellFlag::Zsh => "zsh".to_string(),
- ShellFlag::Bash => "bash".to_string(),
- ShellFlag::Fish => "fish".to_string(),
- ShellFlag::Powershell => "powershell".to_string(),
- ShellFlag::Other(s) => s,
- }
- }
-}
diff --git a/mingling_core/src/asset/comp/shell_ctx.rs b/mingling_core/src/asset/comp/shell_ctx.rs
deleted file mode 100644
index dd2aa86..0000000
--- a/mingling_core/src/asset/comp/shell_ctx.rs
+++ /dev/null
@@ -1,328 +0,0 @@
-use std::collections::HashSet;
-
-use crate::{Flag, ShellFlag, Suggest};
-
-/// Context passed from the shell to the completion system,
-/// providing information about the current command line state
-/// to guide how completions should be generated.
-#[derive(Default, Debug)]
-#[cfg_attr(feature = "general_renderer", derive(serde::Serialize))]
-pub struct ShellContext {
- /// The full command line (-f / --command-line)
- pub command_line: String,
-
- /// Cursor position (-C / --cursor-position)
- pub cursor_position: usize,
-
- /// Current word (-w / --current-word)
- pub current_word: String,
-
- /// Previous word (-p / --previous-word)
- pub previous_word: String,
-
- /// Command name (-c / --command-name)
- pub command_name: String,
-
- /// Word index (-i / --word-index)
- pub word_index: usize,
-
- /// All words (-a / --all-words)
- pub all_words: Vec<String>,
-
- /// Flag to indicate completion context (-F / --shell-flag)
- pub shell_flag: ShellFlag,
-}
-
-impl TryFrom<Vec<String>> for ShellContext {
- type Error = String;
-
- fn try_from(args: Vec<String>) -> Result<Self, Self::Error> {
- use std::collections::HashMap;
-
- // Parse arguments into a map for easy lookup
- let mut arg_map = HashMap::new();
- let mut i = 0;
- while i < args.len() {
- if args[i].starts_with('-') {
- let key = args[i].clone();
- if i + 1 < args.len() && !args[i + 1].starts_with('-') {
- arg_map.insert(key, args[i + 1].clone());
- i += 2;
- } else {
- arg_map.insert(key, String::new());
- i += 1;
- }
- } else {
- i += 1;
- }
- }
-
- // Extract values with defaults
- let command_line = arg_map.get("-f").cloned().unwrap_or_default();
- let cursor_position = arg_map
- .get("-C")
- .and_then(|s| s.parse().ok())
- .unwrap_or_default();
- let current_word = arg_map.get("-w").cloned().unwrap_or_default();
- let previous_word = arg_map.get("-p").cloned().unwrap_or_default();
- let command_name = arg_map.get("-c").cloned().unwrap_or_default();
- let word_index = arg_map
- .get("-i")
- .and_then(|s| s.parse().ok())
- .unwrap_or_default();
- let shell_flag = arg_map
- .get("-F")
- .cloned()
- .map(ShellFlag::from)
- .unwrap_or(ShellFlag::Other("unknown".to_string()));
-
- let all_words = command_line
- .split_whitespace()
- .map(|s| s.replace('^', "-"))
- .collect();
-
- Ok(ShellContext {
- command_line: command_line.replace('^', "-"),
- cursor_position,
- current_word: current_word.replace('^', "-"),
- previous_word: previous_word.replace('^', "-"),
- command_name: command_name.replace('^', "-"),
- word_index,
- all_words,
- shell_flag,
- })
- }
-}
-
-impl ShellContext {
- /// Checks if a flag appears exactly once in the command line arguments.
- ///
- /// This method is useful for determining whether a flag should be processed
- /// when it should only be applied once, even if it appears multiple times
- /// in the command line. It returns `true` if the flag is present and
- /// appears exactly once among all words in the shell context.
- ///
- /// # Example
- ///
- /// ```
- /// # use mingling_core::ShellContext;
- /// # use mingling_macros::suggest;
- /// # use mingling::comp_tools::ShellContextHelper;
- ///
- /// let ctx = ShellContext::default();
- /// let helper = ShellContextHelper::from(ctx);
- ///
- /// // Check if either "--insert" or "-i" appears exactly once
- /// if helper.filling_argument_first(["--insert", "-i"]) {
- /// // Perform action that should only happen once, example:
- /// // return suggest! {
- /// // "A", "B", "C"
- /// // }
- /// }
- /// ```
- pub fn filling_argument_first(&self, flag: impl Into<Flag>) -> bool {
- let flag = flag.into();
- if self.filling_argument(&flag) {
- let mut flag_appears = 0;
- for w in self.all_words.iter() {
- for f in flag.iter() {
- if *f == w {
- flag_appears += 1;
- }
- }
- }
- if flag_appears < 2 {
- return true;
- }
- }
- return false;
- }
-
- /// Checks if the previous word in the command line arguments matches any of the given flags.
- ///
- /// This method determines whether a flag is currently being processed
- /// by checking the word immediately before the cursor position. It returns
- /// `true` if the previous word matches any of the provided flag strings.
- ///
- /// # Example
- ///
- /// ```
- /// # use mingling_core::ShellContext;
- /// # use mingling_macros::suggest;
- /// # use mingling::comp_tools::ShellContextHelper;
- ///
- /// let ctx = ShellContext::default();
- /// let helper = ShellContextHelper::from(ctx);
- ///
- /// // Check if the previous word is either "--file" or "-f"
- /// if helper.filling_argument(["--file", "-f"]) {
- /// // The user is likely expecting a file argument next, example:
- /// // return suggest! {
- /// // "src/main.rs", "Cargo.toml", "README.md"
- /// // }
- /// }
- /// ```
- pub fn filling_argument(&self, flag: impl Into<Flag>) -> bool {
- for f in flag.into().iter() {
- if self.previous_word == **f {
- return true;
- }
- }
- return false;
- }
-
- /// Checks if the user is currently typing a flag argument.
- ///
- /// This method determines whether the current word being typed starts with
- /// a dash (`-`), indicating that the user is likely in the process of
- /// entering a command-line flag. On Windows, an empty current word is also
- /// considered as typing a flag to accommodate shell behavior differences.
- /// It returns `true` if the current word begins with a dash character.
- ///
- /// # Platform-specific behavior
- ///
- /// - **Windows**: Returns `true` if `current_word` is empty or starts with `-`
- /// - **Other platforms**: Returns `true` only if `current_word` starts with `-`
- ///
- /// # Example
- ///
- /// ```
- /// # use mingling_core::ShellContext;
- /// # use mingling_macros::suggest;
- /// # use mingling::comp_tools::ShellContextHelper;
- ///
- /// let ctx = ShellContext::default();
- /// let helper = ShellContextHelper::from(ctx);
- ///
- /// // Check if the user is typing a flag
- /// if helper.typing_argument() {
- /// // The user is likely entering a flag, example:
- /// // return suggest! {
- /// // "--help", "--version", "--verbose"
- /// // }
- /// }
- /// ```
- pub fn typing_argument(&self) -> bool {
- #[cfg(target_os = "windows")]
- {
- self.current_word.is_empty()
- }
- #[cfg(not(target_os = "windows"))]
- {
- self.current_word.starts_with("-")
- }
- }
-
- /// Filters out already typed flag arguments from suggestion results.
- ///
- /// This method removes any suggestions that match flag arguments already present
- /// in the command line. It is useful for preventing duplicate flag suggestions
- /// when the user has already typed certain flags. The method processes both
- /// regular suggestion sets and file completion suggestions differently.
- pub fn strip_typed_argument(&self, suggest: Suggest) -> Suggest {
- let typed = Self::get_typed_arguments(&self);
- match suggest {
- Suggest::Suggest(mut set) => {
- set.retain(|item| !typed.contains(item.suggest()));
- Suggest::Suggest(set)
- }
- Suggest::FileCompletion => Suggest::FileCompletion,
- }
- }
-
- /// Retrieves all flag arguments from the command line.
- ///
- /// This method collects all words in the shell context that start with a dash (`-`),
- /// which typically represent command-line flags or options. It returns a vector
- /// containing these flag strings, converted to owned `String` values.
- pub fn get_typed_arguments(&self) -> HashSet<String> {
- self.all_words
- .iter()
- .filter(|word| word.starts_with("-"))
- .map(|word| word.to_string())
- .collect()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_try_from_full_args() {
- let args = vec![
- "-f".to_string(),
- "git commit ^m 'test'".to_string(),
- "-C".to_string(),
- "12".to_string(),
- "-w".to_string(),
- "commit".to_string(),
- "-p".to_string(),
- "git".to_string(),
- "-c".to_string(),
- "git".to_string(),
- "-i".to_string(),
- "1".to_string(),
- "-F".to_string(),
- "bash".to_string(),
- ];
-
- let context = ShellContext::try_from(args).unwrap();
- assert_eq!(context.command_line, "git commit -m 'test'");
- assert_eq!(context.cursor_position, 12);
- assert_eq!(context.current_word, "commit");
- assert_eq!(context.previous_word, "git");
- assert_eq!(context.command_name, "git");
- assert_eq!(context.word_index, 1);
- assert_eq!(context.all_words, vec!["git", "commit", "-m", "'test'"]);
- assert!(matches!(context.shell_flag, ShellFlag::Bash));
- }
-
- #[test]
- fn test_try_from_partial_args() {
- let args = vec![
- "-f".to_string(),
- "ls ^la".to_string(),
- "-C".to_string(),
- "5".to_string(),
- ];
-
- let context = ShellContext::try_from(args).unwrap();
- assert_eq!(context.command_line, "ls -la");
- assert_eq!(context.cursor_position, 5);
- assert_eq!(context.current_word, "");
- assert_eq!(context.previous_word, "");
- assert_eq!(context.command_name, "");
- assert_eq!(context.word_index, 0);
- assert_eq!(context.all_words, vec!["ls", "-la"]);
- assert!(matches!(context.shell_flag, ShellFlag::Other(ref s) if s == "unknown"));
- }
-
- #[test]
- fn test_try_from_empty_args() {
- let args = vec![];
- let context = ShellContext::try_from(args).unwrap();
- assert_eq!(context.command_line, "");
- assert_eq!(context.cursor_position, 0);
- assert_eq!(context.current_word, "");
- assert_eq!(context.previous_word, "");
- assert_eq!(context.command_name, "");
- assert_eq!(context.word_index, 0);
- assert!(context.all_words.is_empty());
- assert!(matches!(context.shell_flag, ShellFlag::Other(ref s) if s == "unknown"));
- }
-
- #[test]
- fn test_try_from_flag_without_value() {
- let args = vec!["-F".to_string()];
- let context = ShellContext::try_from(args).unwrap();
- assert!(matches!(context.shell_flag, ShellFlag::Other(ref s) if s == ""));
- }
-
- #[test]
- fn test_all_words_splitting() {
- let args = vec!["-f".to_string(), " cmd arg1 arg2 ".to_string()];
- let context = ShellContext::try_from(args).unwrap();
- assert_eq!(context.all_words, vec!["cmd", "arg1", "arg2"]);
- }
-}
diff --git a/mingling_core/src/asset/comp/suggest.rs b/mingling_core/src/asset/comp/suggest.rs
deleted file mode 100644
index 6d64341..0000000
--- a/mingling_core/src/asset/comp/suggest.rs
+++ /dev/null
@@ -1,178 +0,0 @@
-use std::collections::BTreeSet;
-
-use crate::ShellContext;
-
-/// A completion suggestion that tells the shell how to perform completion.
-/// This can be either a set of specific suggestion items or a request for file completion.
-#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "general_renderer", derive(serde::Serialize))]
-pub enum Suggest {
- /// A set of specific suggestion items for the shell to display.
- Suggest(BTreeSet<SuggestItem>),
-
- /// A request for the shell to perform file‑path completion.
- #[default]
- FileCompletion,
-}
-
-impl Suggest {
- /// Creates a new Suggest variant containing a BTreeSet of suggestions.
- pub fn new() -> Self {
- Self::Suggest(BTreeSet::new())
- }
-
- /// Creates a FileCompletion variant.
- pub fn file_comp() -> Self {
- Self::FileCompletion
- }
-
- /// Filters out already typed flag arguments from suggestion results.
- pub fn strip_typed_argument(self, ctx: &ShellContext) -> Self {
- ctx.strip_typed_argument(self)
- }
-}
-
-impl<T> From<T> for Suggest
-where
- T: IntoIterator,
- T::Item: Into<String>,
-{
- fn from(items: T) -> Self {
- let suggests = items
- .into_iter()
- .map(|item| SuggestItem::new(item.into()))
- .collect();
- Suggest::Suggest(suggests)
- }
-}
-
-impl std::ops::Deref for Suggest {
- type Target = BTreeSet<SuggestItem>;
-
- fn deref(&self) -> &Self::Target {
- match self {
- Self::Suggest(suggests) => suggests,
- Self::FileCompletion => panic!("Cannot deref FileCompletion variant"),
- }
- }
-}
-
-impl std::ops::DerefMut for Suggest {
- fn deref_mut(&mut self) -> &mut Self::Target {
- match self {
- Self::Suggest(suggests) => suggests,
- Self::FileCompletion => panic!("Cannot deref_mut FileCompletion variant"),
- }
- }
-}
-
-/// Represents a single suggestion item for shell completion.
-///
-/// This enum has two variants:
-/// - `Simple(String)`: A suggestion without any description.
-/// - `WithDescription(String, String)`: A suggestion with an associated description.
-///
-/// The first `String` always holds the suggestion text, and the second `String` (if present)
-/// holds an optional description providing additional context.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "general_renderer", derive(serde::Serialize))]
-pub enum SuggestItem {
- /// A simple suggestion with only the suggestion text.
- Simple(String),
- /// A suggestion with both text and a description.
- WithDescription(String, String),
-}
-
-impl Default for SuggestItem {
- fn default() -> Self {
- SuggestItem::Simple(String::new())
- }
-}
-
-impl PartialOrd for SuggestItem {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for SuggestItem {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.suggest().cmp(other.suggest())
- }
-}
-
-impl SuggestItem {
- /// Creates a new simple suggestion without description.
- pub fn new(suggest: String) -> Self {
- Self::Simple(suggest)
- }
-
- /// Creates a new suggestion with a description.
- pub fn new_with_desc(suggest: String, description: String) -> Self {
- Self::WithDescription(suggest, description)
- }
-
- /// Adds a description to this suggestion, replacing any existing description.
- pub fn with_desc(self, description: String) -> Self {
- match self {
- Self::Simple(suggest) => Self::WithDescription(suggest, description),
- Self::WithDescription(suggest, _) => Self::WithDescription(suggest, description),
- }
- }
-
- /// Returns the suggestion text.
- pub fn suggest(&self) -> &String {
- match self {
- Self::Simple(suggest) => suggest,
- Self::WithDescription(suggest, _) => suggest,
- }
- }
-
- /// Updates the suggestion text.
- pub fn set_suggest(&mut self, new_suggest: String) {
- match self {
- Self::Simple(suggest) => *suggest = new_suggest,
- Self::WithDescription(suggest, _) => *suggest = new_suggest,
- }
- }
-
- /// Returns the description if present.
- pub fn description(&self) -> Option<&String> {
- match self {
- Self::Simple(_) => None,
- Self::WithDescription(_, description) => Some(description),
- }
- }
-
- /// Sets or replaces the description.
- pub fn set_description(&mut self, description: String) {
- match self {
- Self::Simple(suggest) => *self = Self::WithDescription(suggest.clone(), description),
- Self::WithDescription(_, desc) => *desc = description,
- }
- }
-
- /// Removes and returns the description if present.
- pub fn remove_desc(&mut self) -> Option<String> {
- match self {
- Self::Simple(_) => None,
- Self::WithDescription(suggest, description) => {
- let desc = std::mem::take(description);
- *self = Self::Simple(std::mem::take(suggest));
- Some(desc)
- }
- }
- }
-}
-
-impl From<String> for SuggestItem {
- fn from(suggest: String) -> Self {
- Self::new(suggest)
- }
-}
-
-impl From<(String, String)> for SuggestItem {
- fn from((suggest, description): (String, String)) -> Self {
- Self::new_with_desc(suggest, description)
- }
-}