1use std::io::Write; 2 3use clap::builder::StyledStr; 4use clap::*; 5 6use crate::generator::{utils, Generator}; 7use crate::INTERNAL_ERROR_MSG; 8 9/// Generate elvish completion file 10#[derive(Copy, Clone, PartialEq, Eq, Debug)] 11pub struct Elvish; 12 13impl Generator for Elvish { 14 fn file_name(&self, name: &str) -> String { 15 format!("{name}.elv") 16 } 17 18 fn generate(&self, cmd: &Command, buf: &mut dyn Write) { 19 let bin_name = cmd 20 .get_bin_name() 21 .expect("crate::generate should have set the bin_name"); 22 23 let subcommands_cases = generate_inner(cmd, ""); 24 25 let result = format!( 26 r#" 27use builtin; 28use str; 29 30set edit:completion:arg-completer[{bin_name}] = {{|@words| 31 fn spaces {{|n| 32 builtin:repeat $n ' ' | str:join '' 33 }} 34 fn cand {{|text desc| 35 edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc 36 }} 37 var command = '{bin_name}' 38 for word $words[1..-1] {{ 39 if (str:has-prefix $word '-') {{ 40 break 41 }} 42 set command = $command';'$word 43 }} 44 var completions = [{subcommands_cases} 45 ] 46 $completions[$command] 47}} 48"#, 49 ); 50 51 w!(buf, result.as_bytes()); 52 } 53} 54 55// Escape string inside single quotes 56fn escape_string(string: &str) -> String { 57 string.replace('\'', "''") 58} 59 60fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String { 61 match help { 62 Some(help) => escape_string(&help.to_string()), 63 _ => data.to_string(), 64 } 65} 66 67fn generate_inner(p: &Command, previous_command_name: &str) -> String { 68 debug!("generate_inner"); 69 70 let command_name = if previous_command_name.is_empty() { 71 p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string() 72 } else { 73 format!("{};{}", previous_command_name, &p.get_name()) 74 }; 75 76 let mut completions = String::new(); 77 let preamble = String::from("\n cand "); 78 79 for option in p.get_opts() { 80 if let Some(shorts) = option.get_short_and_visible_aliases() { 81 let tooltip = get_tooltip(option.get_help(), shorts[0]); 82 for short in shorts { 83 completions.push_str(&preamble); 84 completions.push_str(format!("-{short} '{tooltip}'").as_str()); 85 } 86 } 87 88 if let Some(longs) = option.get_long_and_visible_aliases() { 89 let tooltip = get_tooltip(option.get_help(), longs[0]); 90 for long in longs { 91 completions.push_str(&preamble); 92 completions.push_str(format!("--{long} '{tooltip}'").as_str()); 93 } 94 } 95 } 96 97 for flag in utils::flags(p) { 98 if let Some(shorts) = flag.get_short_and_visible_aliases() { 99 let tooltip = get_tooltip(flag.get_help(), shorts[0]); 100 for short in shorts { 101 completions.push_str(&preamble); 102 completions.push_str(format!("-{short} '{tooltip}'").as_str()); 103 } 104 } 105 106 if let Some(longs) = flag.get_long_and_visible_aliases() { 107 let tooltip = get_tooltip(flag.get_help(), longs[0]); 108 for long in longs { 109 completions.push_str(&preamble); 110 completions.push_str(format!("--{long} '{tooltip}'").as_str()); 111 } 112 } 113 } 114 115 for subcommand in p.get_subcommands() { 116 let data = &subcommand.get_name(); 117 let tooltip = get_tooltip(subcommand.get_about(), data); 118 119 completions.push_str(&preamble); 120 completions.push_str(format!("{data} '{tooltip}'").as_str()); 121 } 122 123 let mut subcommands_cases = format!( 124 r" 125 &'{}'= {{{} 126 }}", 127 &command_name, completions 128 ); 129 130 for subcommand in p.get_subcommands() { 131 let subcommand_subcommands_cases = generate_inner(subcommand, &command_name); 132 subcommands_cases.push_str(&subcommand_subcommands_cases); 133 } 134 135 subcommands_cases 136} 137