diff options
30 files changed, 738 insertions, 129 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml index 213cffe..0e29823 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -41,17 +41,17 @@ platform = [ "linux", "macos" ] # Entries [copies.entry_bash] from = "scripts/deploy/jvn_bash.sh" -to = "jvn_bash.sh" +to = "jvn.sh" platform = [ "linux", "macos" ] [copies.entry_zsh] from = "scripts/deploy/jvn_zsh.zsh" -to = "jvn_zsh.zsh" +to = "jvn.zsh" platform = [ "linux", "macos" ] [copies.entry_fish] from = "scripts/deploy/jvn_fish.fish" -to = "jvn_fish.fish" +to = "jvn.fish" platform = [ "linux", "macos" ] ################## @@ -66,7 +66,7 @@ platform = [ "windows" ] # Entries [copies.entry_powershell] -from = "scripts/deploy/jvn.ps1" +from = "scripts/deploy/jvn_powershell.ps1" to = "jvn.ps1" platform = [ "windows" ] @@ -421,6 +421,15 @@ dependencies = [ ] [[package]] +name = "comp_system_macros" +version = "0.1.1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "config_system" version = "0.1.0" dependencies = [ @@ -1181,6 +1190,7 @@ dependencies = [ "cli_utils", "cmd_system_macros", "colored", + "comp_system_macros", "crossterm", "env_logger", "helpdoc_system_macros", @@ -15,6 +15,7 @@ members = [ "utils/", "tools/build_helper", "macros/cmd_system_macros", + "macros/comp_system_macros", "macros/helpdoc_system_macros", "macros/render_system_macros", ] @@ -61,6 +62,7 @@ serde = { version = "1", features = ["derive"] } cli_utils = { path = "utils" } just_enough_vcs = { path = "../VersionControl", features = ["all"] } cmd_system_macros = { path = "macros/cmd_system_macros" } +comp_system_macros = { path = "macros/comp_system_macros" } helpdoc_system_macros = { path = "macros/helpdoc_system_macros" } render_system_macros = { path = "macros/render_system_macros" } diff --git a/macros/comp_system_macros/Cargo.toml b/macros/comp_system_macros/Cargo.toml new file mode 100644 index 0000000..f571f29 --- /dev/null +++ b/macros/comp_system_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "comp_system_macros" +version.workspace = true +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn = { workspace = true, features = ["visit"] } diff --git a/macros/comp_system_macros/src/lib.rs b/macros/comp_system_macros/src/lib.rs new file mode 100644 index 0000000..dd1fb01 --- /dev/null +++ b/macros/comp_system_macros/src/lib.rs @@ -0,0 +1,73 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{Expr, LitStr, Token, parse_macro_input}; + +struct SuggestInput { + items: Punctuated<SuggestItem, Token![,]>, +} + +enum SuggestItem { + WithDesc(Box<(LitStr, Expr)>), // "-i" = "Insert something" + Simple(LitStr), // "-I" +} + +impl Parse for SuggestInput { + fn parse(input: ParseStream) -> syn::Result<Self> { + let items = Punctuated::parse_terminated(input)?; + Ok(SuggestInput { items }) + } +} + +impl Parse for SuggestItem { + fn parse(input: ParseStream) -> syn::Result<Self> { + let key: LitStr = input.parse()?; + + if input.peek(Token![=]) { + let _eq: Token![=] = input.parse()?; + let value: Expr = input.parse()?; + Ok(SuggestItem::WithDesc(Box::new((key, value)))) + } else { + Ok(SuggestItem::Simple(key)) + } + } +} + +#[proc_macro] +pub fn suggest(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as SuggestInput); + + let mut tokens = TokenStream2::new(); + + tokens.extend(quote! { + CompletionResult::empty_comp() + }); + + for item in input.items { + match item { + SuggestItem::WithDesc(boxed) => { + let (key, value) = *boxed; + tokens.extend(quote! { + .with_suggest_desc(#key, #value) + }); + } + SuggestItem::Simple(key) => { + tokens.extend(quote! { + .with_suggest(#key) + }); + } + } + } + + tokens.into() +} + +#[proc_macro] +pub fn file_suggest(_input: TokenStream) -> TokenStream { + quote! { + CompletionResult::file_comp() + } + .into() +} diff --git a/resources/locales/jvn/cmds/sheetdump/en.yml b/resources/locales/jvn/cmds/sheetdump/en.yml new file mode 100644 index 0000000..ddce64d --- /dev/null +++ b/resources/locales/jvn/cmds/sheetdump/en.yml @@ -0,0 +1,6 @@ +sheetdump: + comp: + no_sort: | + Output results without sorting + no_pretty: | + Enable simplified output diff --git a/resources/locales/jvn/cmds/sheetdump/zh-CN.yml b/resources/locales/jvn/cmds/sheetdump/zh-CN.yml new file mode 100644 index 0000000..177fc93 --- /dev/null +++ b/resources/locales/jvn/cmds/sheetdump/zh-CN.yml @@ -0,0 +1,6 @@ +sheetdump: + comp: + no_sort: | + 不排序输出结果 + no_pretty: | + 启用简易输出 diff --git a/resources/locales/jvn/cmds/sheetedit/en.yml b/resources/locales/jvn/cmds/sheetedit/en.yml index 9c3e15f..56e71ae 100644 --- a/resources/locales/jvn/cmds/sheetedit/en.yml +++ b/resources/locales/jvn/cmds/sheetedit/en.yml @@ -25,3 +25,7 @@ sheetedit: no_file_input: | No file specified for parsing Use `jvn sheetedit <sheet_file>` to edit + + comp: + editor: | + Specify a specific editor diff --git a/resources/locales/jvn/cmds/sheetedit/zh-CN.yml b/resources/locales/jvn/cmds/sheetedit/zh-CN.yml index cd95a52..261dd55 100644 --- a/resources/locales/jvn/cmds/sheetedit/zh-CN.yml +++ b/resources/locales/jvn/cmds/sheetedit/zh-CN.yml @@ -24,3 +24,7 @@ sheetedit: no_file_input: | 没有指定需要解析的文件 使用 `jvn sheetedit <文件>` 来编辑 + + comp: + editor: | + 指定特定的编辑器 diff --git a/resources/locales/jvn/cmds/version/en.yml b/resources/locales/jvn/cmds/version/en.yml index 2f48936..a9a1a17 100644 --- a/resources/locales/jvn/cmds/version/en.yml +++ b/resources/locales/jvn/cmds/version/en.yml @@ -18,3 +18,9 @@ version: __ CORE:[[cyan]]%{core_branch}[[/]] (Commit: %{core_commit}) __ CLI:[[cyan]]%{cli_branch}[[/]] (Commit: %{cli_commit}) + + comp: + with_compile_info: | + Show compile info + no_banner: | + Do not show banner diff --git a/resources/locales/jvn/cmds/version/zh-CN.yml b/resources/locales/jvn/cmds/version/zh-CN.yml index 0afd168..7844c5f 100644 --- a/resources/locales/jvn/cmds/version/zh-CN.yml +++ b/resources/locales/jvn/cmds/version/zh-CN.yml @@ -18,3 +18,9 @@ version: __ 核心库:[[cyan]]%{core_branch}[[/]] (Commit: %{core_commit}) __ 命令行:[[cyan]]%{cli_branch}[[/]] (Commit: %{cli_commit}) + + comp: + with_compile_info: | + 显示编译信息 + no_banner: | + 不显示横幅 diff --git a/resources/locales/jvn/cmds/workspace/alias/en.yml b/resources/locales/jvn/cmds/workspace/alias/en.yml index 0fe993a..6d7448e 100644 --- a/resources/locales/jvn/cmds/workspace/alias/en.yml +++ b/resources/locales/jvn/cmds/workspace/alias/en.yml @@ -9,3 +9,9 @@ workspace_alias: Local index `~%{local}` is mapped to `%{remote}` no_map: | Local index `~%{local}` has no mapping + + comp: + insert: Insert alias + query: Query alias + erase: Delete alias + to: Remote index diff --git a/resources/locales/jvn/cmds/workspace/alias/zh-CN.yml b/resources/locales/jvn/cmds/workspace/alias/zh-CN.yml index ee71845..f13ce61 100644 --- a/resources/locales/jvn/cmds/workspace/alias/zh-CN.yml +++ b/resources/locales/jvn/cmds/workspace/alias/zh-CN.yml @@ -9,3 +9,9 @@ workspace_alias: 本地索引 `~%{local}` 被指向 `%{remote}` no_map: | 本地索引 `~%{local}` 不存在指向 + + comp: + insert: 插入别名 + query: 查询别名 + erase: 删除别名 + to: 远程索引 diff --git a/resources/locales/jvn/cmds/workspace/sheet/en.yml b/resources/locales/jvn/cmds/workspace/sheet/en.yml index 0eaec26..4718afa 100644 --- a/resources/locales/jvn/cmds/workspace/sheet/en.yml +++ b/resources/locales/jvn/cmds/workspace/sheet/en.yml @@ -9,3 +9,13 @@ workspace_sheet: exactly_one_required: | Exactly one of `--new`, `--delete`, `--list-all`, or `--print-path` is required. You can use `jvn helpdoc commands/workspace/sheet` to view usage. + + comp: + list_all: | + List all structure sheets + print_path: | + Print the file path of the specified structure sheet + new: | + Create a new structure sheet + delete: | + Delete a structure sheet diff --git a/resources/locales/jvn/cmds/workspace/sheet/zh-CN.yml b/resources/locales/jvn/cmds/workspace/sheet/zh-CN.yml index 3998078..636a68f 100644 --- a/resources/locales/jvn/cmds/workspace/sheet/zh-CN.yml +++ b/resources/locales/jvn/cmds/workspace/sheet/zh-CN.yml @@ -9,3 +9,13 @@ workspace_sheet: exactly_one_required: | 需要指定 `--new`、`--delete`、`--list-all` 或 `--print-path` 中的任意符号 您可以使用 `jvn helpdoc commands/workspace/sheet` 来查看使用方式 + + comp: + list_all: | + 列出所有结构表 + print_path: | + 打印指定结构表的文件路径 + new: | + 创建一个新的结构表 + delete: | + 删除一个结构表 diff --git a/resources/locales/jvn/en.yml b/resources/locales/jvn/en.yml index bcc9cdc..2805f04 100644 --- a/resources/locales/jvn/en.yml +++ b/resources/locales/jvn/en.yml @@ -381,3 +381,23 @@ verbose: cmd_process_exec: Entering execution phase cmd_process_exec_failed: Execution phase failed! + +global_flag: + confirm: | + Confirm all operations without prompting + help: | + Show help information + lang: | + Set language + no_error_logs: | + Disable error output + no_progress: | + Disable progress bar + quiet: | + Suppress all non-error output + renderer: | + Specify output renderer + verbose: | + Enable verbose output + version: | + Show version information diff --git a/resources/locales/jvn/zh-CN.yml b/resources/locales/jvn/zh-CN.yml index f37e85e..e7ed8e9 100644 --- a/resources/locales/jvn/zh-CN.yml +++ b/resources/locales/jvn/zh-CN.yml @@ -358,3 +358,23 @@ verbose: cmd_process_exec: 进入执行阶段 cmd_process_exec_failed: 执行阶段失败! + +global_flag: + confirm: | + 确认所有操作,无需提示 + help: | + 显示帮助信息 + lang: | + 设置语言 + no_error_logs: | + 禁用错误输出 + no_progress: | + 禁用进度条 + quiet: | + 抑制所有非错误输出 + renderer: | + 指定输出渲染器 + verbose: | + 启用详细输出 + version: | + 显示版本信息 diff --git a/scripts/deploy/completions/fish.fish b/scripts/deploy/completions/fish.fish index 45205a3..cb31936 100644 --- a/scripts/deploy/completions/fish.fish +++ b/scripts/deploy/completions/fish.fish @@ -3,6 +3,7 @@ function __jvn_fish_complete set -l cmdline (commandline -opc) set -l buffer (commandline -b) set -l cursor (commandline -C) + set -l current_token (commandline -ct) # Calculate current word and word index set -l current_word "" @@ -10,27 +11,48 @@ function __jvn_fish_complete set -l word_index 0 set -l char_count 0 - for i in (seq (count $cmdline)) - set word $cmdline[$i] - if test $i -gt 1 - set char_count (math $char_count + 1) + set -l found false + if test -n "$current_token" + for i in (seq (count $cmdline)) + if test "$cmdline[$i]" = "$current_token" + set word_index $i + set current_word $current_token + if test $i -gt 1 + set previous_word $cmdline[(math $i - 1)] + end + set found true + break + end end - set char_count (math $char_count + (string length -- "$word")) + end - if test $cursor -le $char_count - set word_index $i - set current_word $word + if not $found + for i in (seq (count $cmdline)) + set word $cmdline[$i] if test $i -gt 1 - set previous_word $cmdline[(math $i - 1)] + set char_count (math $char_count + 1) + end + set char_count (math $char_count + (string length -- "$word")) + + if test $cursor -le $char_count + set word_index $i + set current_word $word + if test $i -gt 1 + set previous_word $cmdline[(math $i - 1)] + end + break end - break end end # Handle cursor after last word if test $word_index -eq 0 -a (count $cmdline) -gt 0 set word_index (count $cmdline) - set current_word "" + if test -n "$current_token" -a "$current_token" != "$cmdline[-1]" + set current_word $current_token + else + set current_word "" + end set previous_word $cmdline[-1] end @@ -62,6 +84,20 @@ function __jvn_fish_complete for word in $cmdline set -a all_words_replaced (string replace -a "-" "^" -- "$word") end + + if test -n "$current_token" -a "$current_word" = "$current_token" + set -l found_in_cmdline false + for word in $cmdline + if test "$word" = "$current_token" + set found_in_cmdline true + break + end + end + if not $found_in_cmdline -a $word_index -eq (math (count $cmdline) + 1) + set -a all_words_replaced (string replace -a "-" "^" -- "$current_token") + end + end + set -a args -a $all_words_replaced else set -a args -a "" diff --git a/scripts/deploy/completions/zsh.zsh b/scripts/deploy/completions/zsh.zsh index 2b9e7f9..99ecd09 100644 --- a/scripts/deploy/completions/zsh.zsh +++ b/scripts/deploy/completions/zsh.zsh @@ -34,10 +34,29 @@ _jvn_completion() { if [[ "${completions[1]}" == "_file_" ]]; then shift completions _files - elif (( $+functions[_describe] )); then - _describe 'jvn commands' completions else - compadd -a completions + local -a parsed_completions + for item in "${completions[@]}"; do + if [[ "$item" =~ '^([^$]+)\$\((.+)\)$' ]]; then + parsed_completions+=("${match[1]}:${match[2]}") + else + parsed_completions+=("$item") + fi + done + + if (( $+functions[_describe] )); then + _describe 'jvn commands' parsed_completions + else + local -a simple_completions + for item in "${parsed_completions[@]}"; do + if [[ "$item" =~ '^([^:]+):(.+)$' ]]; then + simple_completions+=("${match[1]}") + else + simple_completions+=("$item") + fi + done + compadd -a simple_completions + fi fi fi } diff --git a/scripts/deploy/jvn.ps1 b/scripts/deploy/jvn_powershell.ps1 index 495ed43..495ed43 100644 --- a/scripts/deploy/jvn.ps1 +++ b/scripts/deploy/jvn_powershell.ps1 diff --git a/src/bin/jvn_comp.rs b/src/bin/jvn_comp.rs index de52253..d0b258a 100644 --- a/src/bin/jvn_comp.rs +++ b/src/bin/jvn_comp.rs @@ -1,46 +1,69 @@ 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") { @@ -68,6 +91,7 @@ fn main() { } Err(e) => { // An error occurred, collecting information for output + #[cfg(debug_assertions)] error!( "Error: {}, origin=\"{}\"", e, @@ -81,42 +105,56 @@ fn main() { #[cfg(debug_assertions)] trace_ctx(&ctx); - trace!("Try using specific completion"); - let specific_result = comp(&ctx); - trace!("Using default completion"); + #[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) { - (None, None) => None, - (Some(s), None) => Some(s), - (None, Some(d)) => Some(d), - (Some(mut s), Some(d)) => { + (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); - Some(s) + CompletionResult::Suggestions(s) } }; - handle_comp_result(&combined_result); + 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(" ")); @@ -137,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 } }; @@ -163,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 @@ -226,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(); } } @@ -279,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); diff --git a/src/cmds/comp/helpdoc.rs b/src/cmds/comp/helpdoc.rs index 7f07cad..423e2bf 100644 --- a/src/cmds/comp/helpdoc.rs +++ b/src/cmds/comp/helpdoc.rs @@ -1,13 +1,17 @@ -use crate::systems::{comp::context::CompletionContext, helpdoc}; +use comp_system_macros::file_suggest; -pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> { +use crate::systems::{ + comp::{context::CompletionContext, result::CompletionResult}, + helpdoc, +}; + +pub fn comp(ctx: CompletionContext) -> CompletionResult { if ctx.previous_word == "helpdoc" { - return Some( - helpdoc::get_helpdoc_list() - .iter() - .map(|s| s.to_string()) - .collect(), - ); + return helpdoc::get_helpdoc_list() + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + .into(); } - None + file_suggest!() } diff --git a/src/cmds/comp/sheetdump.rs b/src/cmds/comp/sheetdump.rs index 3528cf3..e3105c0 100644 --- a/src/cmds/comp/sheetdump.rs +++ b/src/cmds/comp/sheetdump.rs @@ -1,11 +1,15 @@ -use cli_utils::string_vec; +use comp_system_macros::{file_suggest, suggest}; +use rust_i18n::t; -use crate::systems::comp::context::CompletionContext; +use crate::systems::comp::{context::CompletionContext, result::CompletionResult}; -pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> { +pub fn comp(ctx: CompletionContext) -> CompletionResult { if ctx.current_word.starts_with('-') { - return Some(string_vec!["--no-sort", "--no-pretty"]); + return suggest!( + "--no-sort" = t!("sheetdump.comp.no_sort").trim(), + "--no-pretty" = t!("sheetdump.comp.no_pretty").trim() + ) + .into(); } - - None + file_suggest!() } diff --git a/src/cmds/comp/sheetedit.rs b/src/cmds/comp/sheetedit.rs index d210028..1ebf63d 100644 --- a/src/cmds/comp/sheetedit.rs +++ b/src/cmds/comp/sheetedit.rs @@ -1,15 +1,20 @@ -use cli_utils::string_vec; +use comp_system_macros::{file_suggest, suggest}; +use rust_i18n::t; -use crate::systems::comp::context::CompletionContext; +use crate::systems::comp::{context::CompletionContext, result::CompletionResult}; -pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> { +pub fn comp(ctx: CompletionContext) -> CompletionResult { if ctx.current_word.starts_with('-') { - return Some(string_vec!["-e", "--editor"]); + return suggest!( + "-e" = t!("sheetedit.comp.editor").trim(), + "--editor" = t!("sheetedit.comp.editor").trim(), + ) + .into(); } if ctx.previous_word == "-e" || ctx.previous_word == "--editor" { - return Some(vec![]); + return suggest!().into(); } - None + file_suggest!() } diff --git a/src/cmds/comp/version.rs b/src/cmds/comp/version.rs index 1460214..2c6b674 100644 --- a/src/cmds/comp/version.rs +++ b/src/cmds/comp/version.rs @@ -1,11 +1,16 @@ -use cli_utils::string_vec; +use comp_system_macros::{file_suggest, suggest}; +use rust_i18n::t; -use crate::systems::comp::context::CompletionContext; +use crate::systems::comp::{context::CompletionContext, result::CompletionResult}; -pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> { +pub fn comp(ctx: CompletionContext) -> CompletionResult { if ctx.current_word.starts_with('-') { - return Some(string_vec!["-c", "--with-compile-info", "--no-banner"]); + return suggest!( + "-c" = t!("version.comp.with_compile_info").trim(), + "--with-compile-info" = t!("version.comp.with_compile_info").trim(), + "--no-banner" = t!("version.comp.no_banner").trim() + ) + .into(); } - - None + file_suggest!() } diff --git a/src/cmds/comp/workspace_alias.rs b/src/cmds/comp/workspace_alias.rs index a8ac495..cd39c9d 100644 --- a/src/cmds/comp/workspace_alias.rs +++ b/src/cmds/comp/workspace_alias.rs @@ -1,17 +1,25 @@ -use cli_utils::string_vec; +use comp_system_macros::{file_suggest, suggest}; +use rust_i18n::t; -use crate::systems::comp::context::CompletionContext; +use crate::systems::comp::{context::CompletionContext, result::CompletionResult}; -pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> { +pub fn comp(ctx: CompletionContext) -> CompletionResult { if ctx.current_word.starts_with('-') { - return Some(string_vec![ - "-i", "--insert", "-Q", "--query", "-e", "--erase", "--to", - ]); + return suggest!( + "-i" = t!("workspace_alias.comp.insert").trim(), + "--insert" = t!("workspace_alias.comp.insert").trim(), + "-Q" = t!("workspace_alias.comp.query").trim(), + "--query" = t!("workspace_alias.comp.query").trim(), + "-e" = t!("workspace_alias.comp.erase").trim(), + "--erase" = t!("workspace_alias.comp.erase").trim(), + "--to" = t!("workspace_alias.comp.to").trim() + ) + .into(); } if ctx.previous_word == "--to" { - return Some(vec![]); + return suggest!().into(); } - None + file_suggest!() } diff --git a/src/cmds/comp/workspace_sheet.rs b/src/cmds/comp/workspace_sheet.rs index 3162442..8318fbe 100644 --- a/src/cmds/comp/workspace_sheet.rs +++ b/src/cmds/comp/workspace_sheet.rs @@ -1,25 +1,39 @@ -use crate::systems::comp::context::CompletionContext; -use cli_utils::string_vec; +use comp_system_macros::{file_suggest, suggest}; use just_enough_vcs::system::workspace::workspace::manager::WorkspaceManager; +use rust_i18n::t; -pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> { +use crate::systems::comp::{context::CompletionContext, result::CompletionResult}; + +pub fn comp(ctx: CompletionContext) -> CompletionResult { if ctx.current_word.starts_with('-') { - return Some(string_vec![ - "-A", - "--list-all", - "-p", - "--print-path", - "-n", - "--new", - "-d", - "--delete", - ]); + return suggest!( + "-A" = t!("workspace_sheet.comp.list_all").trim(), + "--list-all" = t!("workspace_sheet.comp.list_all").trim(), + "-p" = t!("workspace_sheet.comp.print_path").trim(), + "--print-path" = t!("workspace.sheet.comp.print_path").trim(), + "-n" = t!("workspace_sheet.comp.new").trim(), + "--new" = t!("workspace_sheet.comp.new").trim(), + "-d" = t!("workspace_sheet.comp.delete").trim(), + "--delete" = t!("workspace_sheet.comp.delete").trim() + ) + .into(); } if ctx.previous_word == "--new" || ctx.previous_word == "-n" { - return Some(vec![]); + return suggest!().into(); + } + + if ctx.previous_word == "--list-all" + || ctx.previous_word == "-A" + || ctx.previous_word == "--print-path" + || ctx.previous_word == "-p" + || ctx.previous_word == "--delete" + || ctx.previous_word == "-d" + { + let rt = tokio::runtime::Runtime::new().unwrap(); + let names = rt.block_on(WorkspaceManager::new().list_sheet_names()); + return names.into(); } - let rt = tokio::runtime::Runtime::new().unwrap(); - Some(rt.block_on(WorkspaceManager::new().list_sheet_names())) + file_suggest!() } diff --git a/src/systems/comp.rs b/src/systems/comp.rs index c7c6577..077fb7f 100644 --- a/src/systems/comp.rs +++ b/src/systems/comp.rs @@ -1,2 +1,3 @@ pub mod _comps; pub mod context; +pub mod result; diff --git a/src/systems/comp/result.rs b/src/systems/comp/result.rs new file mode 100644 index 0000000..e64021d --- /dev/null +++ b/src/systems/comp/result.rs @@ -0,0 +1,205 @@ +pub enum CompletionResult { + FileCompletion, + Suggestions(Vec<CompletionSuggestion>), +} + +impl CompletionResult { + /// Creates a `CompletionResult` representing file completion. + pub fn file_comp() -> Self { + CompletionResult::FileCompletion + } + + /// Creates a `CompletionResult` with an empty suggestions list. + pub fn empty_comp() -> CompletionSuggestInsert { + CompletionSuggestInsert { + suggestions: Vec::new(), + } + } + + /// Returns `true` if this is a `FileCompletion` variant. + pub fn is_file(&self) -> bool { + matches!(self, Self::FileCompletion) + } + + /// Returns `true` if this is a `Suggestions` variant. + pub fn is_suggestion(&self) -> bool { + matches!(self, Self::Suggestions(_)) + } + + /// Returns a reference to the suggestions vector if this is a `Suggestions` variant, + /// otherwise returns `None`. + pub fn suggestion(&self) -> Option<&Vec<CompletionSuggestion>> { + match self { + Self::FileCompletion => None, + Self::Suggestions(v) => Some(v), + } + } + + /// Returns the unit value if this is a `FileCompletion` variant, otherwise panics. + pub fn unwrap_file(&self) { + match self { + Self::FileCompletion => (), + Self::Suggestions(_) => { + panic!("called `CompletionResult::unwrap_file()` on a `Suggestions` value") + } + } + } + + /// Returns a reference to the suggestions vector if this is a `Suggestions` variant, + /// otherwise panics. + pub fn unwrap_suggestion(&self) -> &Vec<CompletionSuggestion> { + match self { + Self::FileCompletion => { + panic!("called `CompletionResult::unwrap_suggestion()` on a `FileCompletion` value") + } + Self::Suggestions(v) => v, + } + } + + /// Returns the unit value if this is a `FileCompletion` variant, otherwise returns `default`. + pub fn unwrap_file_or(&self, default: ()) { + match self { + Self::FileCompletion => (), + Self::Suggestions(_) => default, + } + } + + /// Returns a reference to the suggestions vector if this is a `Suggestions` variant, + /// otherwise returns `default`. + pub fn unwrap_suggestion_or<'a>( + &'a self, + default: &'a Vec<CompletionSuggestion>, + ) -> &'a Vec<CompletionSuggestion> { + match self { + Self::FileCompletion => default, + Self::Suggestions(v) => v, + } + } + + /// Returns the unit value if this is a `FileCompletion` variant, otherwise calls `f`. + pub fn unwrap_file_or_else<F>(&self, f: F) + where + F: FnOnce(), + { + match self { + Self::FileCompletion => (), + Self::Suggestions(_) => f(), + } + } + + /// Returns a reference to the suggestions vector if this is a `Suggestions` variant, + /// otherwise calls `f`. + pub fn unwrap_suggestion_or_else<'a, F>(&'a self, f: F) -> &'a Vec<CompletionSuggestion> + where + F: FnOnce() -> &'a Vec<CompletionSuggestion>, + { + match self { + Self::FileCompletion => f(), + Self::Suggestions(v) => v, + } + } +} + +pub struct CompletionSuggestInsert { + suggestions: Vec<CompletionSuggestion>, +} + +impl CompletionSuggestInsert { + /// Adds a suggestion with the given text and no description. + pub fn with_suggest<S: Into<String>>(mut self, suggest: S) -> Self { + self.suggestions.push(CompletionSuggestion { + suggest: suggest.into(), + description: None, + }); + self + } + + /// Adds a suggestion with the given text and description. + pub fn with_suggest_desc<S: Into<String>, D: Into<String>>( + mut self, + suggest: S, + description: D, + ) -> Self { + self.suggestions.push(CompletionSuggestion { + suggest: suggest.into(), + description: Some(description.into()), + }); + self + } +} + +impl From<CompletionSuggestInsert> for CompletionResult { + fn from(insert: CompletionSuggestInsert) -> Self { + CompletionResult::Suggestions(insert.suggestions) + } +} + +impl From<Vec<String>> for CompletionResult { + fn from(strings: Vec<String>) -> Self { + CompletionResult::Suggestions( + strings + .into_iter() + .map(|s| CompletionSuggestion { + suggest: s, + description: None, + }) + .collect(), + ) + } +} + +impl std::fmt::Display for CompletionResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompletionResult::FileCompletion => write!(f, "_file_"), + CompletionResult::Suggestions(suggestions) => { + let suggestions_str: Vec<String> = + suggestions.iter().map(|s| s.suggest.clone()).collect(); + write!(f, "{}", suggestions_str.join(", ")) + } + } + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct CompletionSuggestion { + pub suggest: String, + pub description: Option<String>, +} + +impl std::ops::Deref for CompletionSuggestion { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.suggest + } +} + +impl std::ops::DerefMut for CompletionSuggestion { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.suggest + } +} + +impl AsRef<str> for CompletionSuggestion { + fn as_ref(&self) -> &str { + &self.suggest + } +} + +impl PartialOrd for CompletionSuggestion { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for CompletionSuggestion { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (&self.description, &other.description) { + (None, None) => self.suggest.cmp(&other.suggest), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (Some(_), Some(_)) => self.suggest.cmp(&other.suggest), + } + } +} diff --git a/templates/_comps.rs.template b/templates/_comps.rs.template index b7f10db..9561243 100644 --- a/templates/_comps.rs.template +++ b/templates/_comps.rs.template @@ -1,11 +1,12 @@ // Auto generated by build.rs -use crate::systems::comp::context::CompletionContext; +use comp_system_macros::file_suggest; +use crate::systems::comp::{context::CompletionContext, result::CompletionResult}; -pub fn match_comp(node: String, ctx: CompletionContext) -> Option<Vec<String>> { +pub fn match_comp(node: String, ctx: CompletionContext) -> CompletionResult { let node_str = node.as_str(); match node_str { >>>>>>>>>> comp_match_arms - _ => None, + _ => file_suggest!(), } } |
