summaryrefslogtreecommitdiff
path: root/src/bin
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/jvn_comp.rs261
1 files changed, 194 insertions, 67 deletions
diff --git a/src/bin/jvn_comp.rs b/src/bin/jvn_comp.rs
index 572164c..d0b258a 100644
--- a/src/bin/jvn_comp.rs
+++ b/src/bin/jvn_comp.rs
@@ -1,60 +1,97 @@
use std::{fs::OpenOptions, process::exit};
use clap::Parser;
+use cli_utils::env::locales::current_locales;
+use comp_system_macros::{file_suggest, suggest};
use env_logger::Target;
use jvcli::systems::{
cmd::_commands::jv_cmd_nodes,
comp::{
_comps::{jv_cmd_comp_nodes, match_comp},
- context::CompletionContext,
+ context::{CompletionContext, ShellFlag},
+ result::{CompletionResult, CompletionSuggestion},
},
render::renderer::jv_override_renderers,
};
#[cfg(debug_assertions)]
use log::debug;
+#[cfg(debug_assertions)]
use log::{LevelFilter, error, trace};
+use rust_i18n::{set_locale, t};
+
+rust_i18n::i18n!("resources/locales/jvn", fallback = "en");
+
+macro_rules! global_flags_suggest {
+ () => {
+ suggest!(
+ "--confirm" = t!("global_flag.confirm").trim(),
+ "-C" = t!("global_flag.confirm").trim(),
+ "--help" = t!("global_flag.help").trim(),
+ "-h" = t!("global_flag.help").trim(),
+ "--lang" = t!("global_flag.lang").trim(),
+ "--no-error-logs" = t!("global_flag.no_error_logs").trim(),
+ "--no-progress" = t!("global_flag.no_progress").trim(),
+ "--quiet" = t!("global_flag.quiet").trim(),
+ "-q" = t!("global_flag.quiet").trim(),
+ "--renderer" = t!("global_flag.renderer").trim(),
+ "--verbose" = t!("global_flag.verbose").trim(),
+ "-V" = t!("global_flag.verbose").trim(),
+ "--version" = t!("global_flag.version").trim(),
+ "-v" = t!("global_flag.version").trim(),
+ )
+ .into()
+ };
+}
-const GLOBAL_FLAGS: &[&str] = &[
- "--confirm",
- "-C",
- "--help",
- "-h",
- "--lang",
- "--no-error-logs",
- "--no-progress",
- "--quiet",
- "-q",
- "--renderer",
- "--verbose",
- "-V",
- "--version",
- "-v",
-];
-
-const LANGUAGES: [&str; 2] = [
- "en", // English
- "zh-CN", // 简体中文
-];
+macro_rules! language_suggest {
+ () => {
+ // Sort in A - Z order
+ suggest!(
+ // English
+ "en" = "English",
+ // Simplified Chinese
+ "zh-CN" = "简体中文"
+ )
+ .into()
+ };
+}
fn main() {
// If not in release mode, initialize env_logger to capture logs
#[cfg(debug_assertions)]
init_env_logger();
+ let lang = current_locales();
+ set_locale(&lang);
+
+ // Check if help flag is present in arguments
+ let args: Vec<String> = std::env::args().collect();
+ if args.iter().any(|arg| arg == "-h" || arg == "--help") {
+ println!(
+ "{}",
+ include_str!("../../resources/other/jvn_comp_help.txt").trim()
+ );
+ std::process::exit(0);
+ }
+
// Get context parameters from clap
let ctx = match CompletionContext::try_parse() {
- Ok(args) => CompletionContext {
- // In completion scripts, "-" is replaced with "^", need to convert back here
- command_line: args.command_line.replace('^', "-"),
- cursor_position: args.cursor_position,
- current_word: args.current_word.replace('^', "-"),
- previous_word: args.previous_word.replace('^', "-"),
- command_name: args.command_name.replace('^', "-"),
- word_index: args.word_index,
- all_words: args.all_words.iter().map(|w| w.replace('^', "-")).collect(),
- },
+ Ok(args) => {
+ CompletionContext {
+ // In completion scripts, "-" is replaced with "^", need to convert back here
+ command_line: args.command_line.replace('^', "-"),
+ cursor_position: args.cursor_position,
+ current_word: args.current_word.replace('^', "-"),
+ previous_word: args.previous_word.replace('^', "-"),
+ command_name: args.command_name.replace('^', "-"),
+ word_index: args.word_index,
+ all_words: args.all_words.iter().map(|w| w.replace('^', "-")).collect(),
+ shell_flag: args.shell_flag,
+ }
+ }
Err(e) => {
// An error occurred, collecting information for output
+ #[cfg(debug_assertions)]
error!(
"Error: {}, origin=\"{}\"",
e,
@@ -68,35 +105,56 @@ fn main() {
#[cfg(debug_assertions)]
trace_ctx(&ctx);
- trace!("Try using specific completion");
- let result = comp(&ctx);
- if let Some(suggestions) = result {
- handle_comp_result(&Some(suggestions));
- } else {
- trace!("Using default completion");
- let result = default_comp(&ctx);
- handle_comp_result(&result);
- }
+ #[cfg(debug_assertions)]
+ trace!("Generate specific completion");
+ let specific_result = specific_comp(&ctx);
+
+ #[cfg(debug_assertions)]
+ trace!("Generate default completion");
+ let default_result = default_comp(&ctx);
+
+ #[cfg(debug_assertions)]
+ trace!("specific_result: {}", specific_result.to_string());
+ #[cfg(debug_assertions)]
+ trace!("default_result: {}", default_result.to_string());
+
+ let combined_result = match (specific_result, default_result) {
+ (CompletionResult::FileCompletion, CompletionResult::FileCompletion) => {
+ CompletionResult::file_comp()
+ }
+ (CompletionResult::Suggestions(s), CompletionResult::FileCompletion) => {
+ CompletionResult::Suggestions(s)
+ }
+ (CompletionResult::FileCompletion, CompletionResult::Suggestions(d)) => {
+ CompletionResult::Suggestions(d)
+ }
+ (CompletionResult::Suggestions(mut s), CompletionResult::Suggestions(d)) => {
+ s.extend(d);
+ CompletionResult::Suggestions(s)
+ }
+ };
+
+ handle_comp_result(combined_result, &ctx);
}
-fn default_comp(ctx: &CompletionContext) -> Option<Vec<String>> {
+fn default_comp(ctx: &CompletionContext) -> CompletionResult {
if ctx.current_word.starts_with('-') {
- return Some(GLOBAL_FLAGS.iter().map(|s| s.to_string()).collect());
+ return global_flags_suggest!();
}
// Match and comp Override Renderers
if ctx.previous_word == "--renderer" {
- return Some(jv_override_renderers());
+ return jv_override_renderers().into();
}
if ctx.previous_word == "--lang" {
- return Some(LANGUAGES.iter().map(|s| s.to_string()).collect());
+ return language_suggest!();
}
- None
+ file_suggest!()
}
-fn comp(ctx: &CompletionContext) -> Option<Vec<String>> {
+fn specific_comp(ctx: &CompletionContext) -> CompletionResult {
let args: Vec<String> = ctx.all_words.iter().skip(1).cloned().collect();
let nodes = jv_cmd_comp_nodes();
let command = format!("{} ", args.join(" "));
@@ -117,23 +175,39 @@ fn comp(ctx: &CompletionContext) -> Option<Vec<String>> {
let match_node: Option<String> = match matching_nodes.len() {
0 => {
- if let Some(result) = try_comp_cmd_nodes(ctx) {
- return Some(result);
+ #[cfg(debug_assertions)]
+ trace!("No matching nodes found, trying command nodes");
+ let r = try_comp_cmd_nodes(ctx);
+ if r.is_suggestion() {
+ #[cfg(debug_assertions)]
+ trace!("try_comp_cmd_nodes returned suggestions");
+ return r;
}
+ #[cfg(debug_assertions)]
+ trace!("try_comp_cmd_nodes returned file completion");
// No matching node found
None
}
1 => {
// Single matching node found
+ #[cfg(debug_assertions)]
+ trace!("Single matching node found: {}", matching_nodes[0]);
Some(matching_nodes[0].clone())
}
_ => {
// Multiple matching nodes found
// Find the node with the longest length (most specific match)
- matching_nodes
+ #[cfg(debug_assertions)]
+ trace!("Multiple matching nodes found: {:?}", matching_nodes);
+ let longest_node = matching_nodes
.iter()
.max_by_key(|node| node.len())
- .map(|node| node.to_string())
+ .map(|node| node.to_string());
+ #[cfg(debug_assertions)]
+ if let Some(ref node) = longest_node {
+ trace!("Selected longest node: {}", node);
+ }
+ longest_node
}
};
@@ -143,17 +217,29 @@ fn comp(ctx: &CompletionContext) -> Option<Vec<String>> {
None => trace!("No completions matched."),
}
- let match_node = match_node?;
+ let match_node = match match_node {
+ Some(node) => node,
+ None => {
+ #[cfg(debug_assertions)]
+ trace!("No match node found, returning file completion");
+ return file_suggest!();
+ }
+ };
- match_comp(match_node, ctx.clone())
+ #[cfg(debug_assertions)]
+ trace!("Calling match_comp with node: {}", match_node);
+ let result = match_comp(match_node, ctx.clone());
+ #[cfg(debug_assertions)]
+ trace!("match_comp returned: {}", result.to_string());
+ result
}
-fn try_comp_cmd_nodes(ctx: &CompletionContext) -> Option<Vec<String>> {
+fn try_comp_cmd_nodes(ctx: &CompletionContext) -> CompletionResult {
let cmd_nodes = jv_cmd_nodes();
// If the current position is less than 1, do not perform completion
if ctx.word_index < 1 {
- return None;
+ return file_suggest!();
};
// Get the current input path
@@ -206,7 +292,7 @@ fn try_comp_cmd_nodes(ctx: &CompletionContext) -> Option<Vec<String>> {
"try_comp_cmd_nodes: current word suggestions = {:?}",
suggestions
);
- return Some(suggestions);
+ return suggestions.into();
}
}
@@ -259,28 +345,68 @@ fn try_comp_cmd_nodes(ctx: &CompletionContext) -> Option<Vec<String>> {
debug!("try_comp_cmd_nodes: suggestions = {:?}", suggestions);
if suggestions.is_empty() {
- None
+ file_suggest!()
} else {
- Some(suggestions)
+ suggestions.into()
}
}
-fn handle_comp_result(r: &Option<Vec<String>>) {
+fn handle_comp_result(r: CompletionResult, ctx: &CompletionContext) {
match r {
- Some(suggestions) => {
- suggestions
- .iter()
- .for_each(|suggest| println!("{}", suggest));
- exit(0)
- }
- None => {
- // Output "_file_" to notify the completion script to perform "file completion"
+ CompletionResult::FileCompletion => {
println!("_file_");
exit(0)
}
+ CompletionResult::Suggestions(suggestions) => match ctx.shell_flag {
+ ShellFlag::Zsh => print_suggest_with_description(suggestions),
+ ShellFlag::Fish => print_suggest_with_description_fish(suggestions),
+ _ => print_suggest(suggestions),
+ },
}
}
+fn print_suggest(mut suggestions: Vec<CompletionSuggestion>) {
+ suggestions.sort();
+ #[cfg(debug_assertions)]
+ trace!("print_suggest suggestions: {:?}", suggestions);
+ suggestions
+ .iter()
+ .for_each(|suggest| println!("{}", suggest.suggest));
+ exit(0)
+}
+
+fn print_suggest_with_description(mut suggestions: Vec<CompletionSuggestion>) {
+ suggestions.sort();
+ #[cfg(debug_assertions)]
+ trace!(
+ "print_suggest_with_description suggestions: {:?}",
+ suggestions
+ );
+ suggestions
+ .iter()
+ .for_each(|suggest| match &suggest.description {
+ Some(desc) => println!("{}$({})", suggest.suggest, desc),
+ None => println!("{}", suggest.suggest),
+ });
+ exit(0)
+}
+
+fn print_suggest_with_description_fish(mut suggestions: Vec<CompletionSuggestion>) {
+ suggestions.sort();
+ #[cfg(debug_assertions)]
+ trace!(
+ "print_suggest_with_description_fish suggestions: {:?}",
+ suggestions
+ );
+ suggestions
+ .iter()
+ .for_each(|suggest| match &suggest.description {
+ Some(desc) => println!("{}\t{}", suggest.suggest, desc),
+ None => println!("{}", suggest.suggest),
+ });
+ exit(0)
+}
+
#[cfg(debug_assertions)]
fn trace_ctx(ctx: &CompletionContext) {
log::trace!("command_line={}", ctx.command_line);
@@ -290,6 +416,7 @@ fn trace_ctx(ctx: &CompletionContext) {
log::trace!("command_name={}", ctx.command_name);
log::trace!("word_index={}", ctx.word_index);
log::trace!("all_words={:?}", ctx.all_words);
+ log::trace!("shell_flag={:?}", ctx.shell_flag);
}
#[cfg(debug_assertions)]