summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo/registry.toml3
-rw-r--r--.gitignore1
-rw-r--r--build.rs18
-rw-r--r--gen.rs1
-rw-r--r--gen/constants.rs7
-rw-r--r--gen/gen_completions_entries.rs79
-rw-r--r--gen/gen_override_renderer.rs64
-rw-r--r--scripts/deploy/completions/bash.sh8
-rw-r--r--scripts/deploy/completions/fish.fish8
-rw-r--r--scripts/deploy/completions/powershell.ps18
-rw-r--r--scripts/deploy/completions/zsh.sh8
-rw-r--r--src/bin/jvn_comp.rs245
-rw-r--r--src/cmds.rs1
-rw-r--r--src/cmds/comp/helpdoc.rs13
-rw-r--r--src/systems.rs1
-rw-r--r--src/systems/comp.rs2
-rw-r--r--src/systems/comp/context.rs33
-rw-r--r--src/systems/render/renderer.rs2
-rw-r--r--templates/_comps.rs.template26
-rw-r--r--templates/_override_renderers.rs.template10
20 files changed, 487 insertions, 51 deletions
diff --git a/.cargo/registry.toml b/.cargo/registry.toml
index d019c45..4f3d64b 100644
--- a/.cargo/registry.toml
+++ b/.cargo/registry.toml
@@ -54,6 +54,9 @@ path = "src/cmds/cmd.rs"
[collect.collects]
path = "src/cmds/collect.rs"
+[collect.completions]
+path = "src/cmds/comp.rs"
+
[collect.converters]
path = "src/cmds/converter.rs"
diff --git a/.gitignore b/.gitignore
index a8f472b..7a87e83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ _*.rs
/src/cmds/arg.rs
/src/cmds/cmd.rs
/src/cmds/collect.rs
+/src/cmds/comp.rs
/src/cmds/converter.rs
/src/cmds/in.rs
/src/cmds/out.rs
diff --git a/build.rs b/build.rs
index ad02511..ac0701f 100644
--- a/build.rs
+++ b/build.rs
@@ -2,9 +2,13 @@ use std::env;
use std::path::PathBuf;
use crate::r#gen::{
- gen_commands_file::generate_commands_file, gen_compile_info::generate_compile_info,
- gen_iscc_script::generate_installer_script, gen_mod_files::generate_collect_files,
- gen_override_renderer::generate_override_renderer, gen_renderers_file::generate_renderers_file,
+ gen_commands_file::generate_commands_file,
+ gen_compile_info::generate_compile_info,
+ gen_completions_entries::generate_completions_file,
+ gen_iscc_script::generate_installer_script,
+ gen_mod_files::generate_collect_files,
+ gen_override_renderer::{generate_override_renderer, generate_override_renderers_list},
+ gen_renderers_file::generate_renderers_file,
gen_specific_renderer::generate_specific_renderer,
};
@@ -31,6 +35,10 @@ async fn main() {
}),
tokio::spawn({
let repo_root = repo_root.clone();
+ async move { generate_completions_file(&repo_root).await }
+ }),
+ tokio::spawn({
+ let repo_root = repo_root.clone();
async move { generate_collect_files(&repo_root).await }
}),
tokio::spawn({
@@ -39,6 +47,10 @@ async fn main() {
}),
tokio::spawn({
let repo_root = repo_root.clone();
+ async move { generate_override_renderers_list(&repo_root).await }
+ }),
+ tokio::spawn({
+ let repo_root = repo_root.clone();
async move { generate_specific_renderer(&repo_root).await }
}),
tokio::spawn({
diff --git a/gen.rs b/gen.rs
index da64173..3bd2719 100644
--- a/gen.rs
+++ b/gen.rs
@@ -2,6 +2,7 @@ pub mod constants;
pub mod env;
pub mod gen_commands_file;
pub mod gen_compile_info;
+pub mod gen_completions_entries;
pub mod gen_iscc_script;
pub mod gen_mod_files;
pub mod gen_override_renderer;
diff --git a/gen/constants.rs b/gen/constants.rs
index e26317f..140743d 100644
--- a/gen/constants.rs
+++ b/gen/constants.rs
@@ -1,4 +1,5 @@
pub const COMMANDS_PATH: &str = "./src/cmds/cmd/";
+pub const COMPLETIONS_PATH: &str = "./src/cmds/comp/";
pub const RENDERERS_PATH: &str = "./src/cmds/renderer/";
pub const COMPILE_INFO_RS_TEMPLATE: &str = "./templates/compile_info.rs.template";
@@ -10,6 +11,9 @@ pub const SETUP_JV_CLI_ISS: &str = "./scripts/setup/windows/setup_jv_cli.iss";
pub const COMMAND_LIST_TEMPLATE: &str = "./templates/_commands.rs.template";
pub const COMMAND_LIST: &str = "./src/systems/cmd/_commands.rs";
+pub const COMPLETIONS_TEMPLATE: &str = "./templates/_comps.rs.template";
+pub const COMPLETIONS: &str = "./src/systems/comp/_comps.rs";
+
pub const OVERRIDE_RENDERER_DISPATCHER_TEMPLATE: &str =
"./templates/_override_renderer_dispatcher.rs.template";
pub const OVERRIDE_RENDERER_DISPATCHER: &str =
@@ -19,6 +23,9 @@ pub const OVERRIDE_RENDERER_ENTRY_TEMPLATE: &str =
"./templates/_override_renderer_entry.rs.template";
pub const OVERRIDE_RENDERER_ENTRY: &str = "./src/systems/render/_override_renderer_entry.rs";
+pub const OVERRIDE_RENDERERS_TEMPLATE: &str = "./templates/_override_renderers.rs.template";
+pub const OVERRIDE_RENDERERS: &str = "./src/systems/render/_override_renderers.rs";
+
pub const SPECIFIC_RENDERER_MATCHING_TEMPLATE: &str =
"./templates/_specific_renderer_matching.rs.template";
pub const SPECIFIC_RENDERER_MATCHING: &str = "./src/systems/render/_specific_renderer_matching.rs";
diff --git a/gen/gen_completions_entries.rs b/gen/gen_completions_entries.rs
new file mode 100644
index 0000000..0e030e6
--- /dev/null
+++ b/gen/gen_completions_entries.rs
@@ -0,0 +1,79 @@
+use just_template::{Template, tmpl};
+use std::path::PathBuf;
+
+use crate::r#gen::constants::{COMPLETIONS, COMPLETIONS_PATH, COMPLETIONS_TEMPLATE};
+
+/// Generate completions file from comp directory using just_template
+pub async fn generate_completions_file(repo_root: &PathBuf) {
+ let template_path = repo_root.join(COMPLETIONS_TEMPLATE);
+ let output_path = repo_root.join(COMPLETIONS);
+ let comps_dir = repo_root.join(COMPLETIONS_PATH);
+
+ // Read the template
+ let template_content = tokio::fs::read_to_string(&template_path).await.unwrap();
+
+ // Collect all completion files
+ let mut all_completions: Vec<(String, String)> = Vec::new();
+ let mut all_nodes: Vec<String> = Vec::new();
+
+ if comps_dir.exists() && comps_dir.is_dir() {
+ let mut entries = tokio::fs::read_dir(&comps_dir).await.unwrap();
+ while let Some(entry) = entries.next_entry().await.unwrap() {
+ let path = entry.path();
+
+ if !path.is_file() {
+ continue;
+ }
+
+ let extension = match path.extension() {
+ Some(ext) => ext,
+ None => continue,
+ };
+
+ if extension != "rs" {
+ continue;
+ }
+
+ let file_name = match path.file_stem().and_then(|s| s.to_str()) {
+ Some(name) => name,
+ None => continue,
+ };
+
+ let node_name = just_fmt::lower_case!(file_name);
+
+ all_completions.push((file_name.to_string(), node_name.clone()));
+ all_nodes.push(node_name);
+ }
+ }
+
+ // Create template
+ let mut template = Template::from(template_content);
+
+ // Generate match arms for each completion
+ for (comp_name, node_name) in &all_completions {
+ tmpl!(template += {
+ comp_match_arms {
+ (comp_name = comp_name, comp_node_name = node_name)
+ }
+ });
+ }
+
+ for node in all_nodes {
+ tmpl!(template += {
+ comp_node_name {
+ (comp_node_name = just_fmt::lower_case!(node))
+ }
+ });
+ }
+
+ // Expand the template
+ let final_content = template.expand().unwrap();
+
+ // Write the generated code
+ tokio::fs::write(output_path, final_content).await.unwrap();
+
+ println!(
+ "Generated completions file with {} completions using just_template",
+ all_completions.len()
+ );
+}
diff --git a/gen/gen_override_renderer.rs b/gen/gen_override_renderer.rs
index 1e67be4..2a8ba37 100644
--- a/gen/gen_override_renderer.rs
+++ b/gen/gen_override_renderer.rs
@@ -5,7 +5,10 @@ use regex::Regex;
use tokio::fs;
use crate::r#gen::{
- constants::{COMMANDS_PATH, OVERRIDE_RENDERER_ENTRY, OVERRIDE_RENDERER_ENTRY_TEMPLATE},
+ constants::{
+ COMMANDS_PATH, OVERRIDE_RENDERER_ENTRY, OVERRIDE_RENDERER_ENTRY_TEMPLATE,
+ OVERRIDE_RENDERERS, OVERRIDE_RENDERERS_TEMPLATE, REGISTRY_TOML,
+ },
resolve_types::resolve_type_paths,
};
@@ -41,6 +44,65 @@ pub async fn generate_override_renderer(repo_root: &PathBuf) {
);
}
+/// Generate override renderers list file from Registry.toml configuration using just_template
+pub async fn generate_override_renderers_list(repo_root: &PathBuf) {
+ let template_path = repo_root.join(OVERRIDE_RENDERERS_TEMPLATE);
+ let output_path = repo_root.join(OVERRIDE_RENDERERS);
+ let config_path = repo_root.join(REGISTRY_TOML);
+
+ // Read the template
+ let template_content = tokio::fs::read_to_string(&template_path).await.unwrap();
+
+ // Read and parse the TOML configuration
+ let config_content = tokio::fs::read_to_string(&config_path).await.unwrap();
+ let config: toml::Value = toml::from_str(&config_content).unwrap();
+
+ // Collect all renderer names
+ let mut renderer_names = Vec::new();
+
+ let Some(table) = config.as_table() else {
+ return;
+ };
+ let Some(renderer_table) = table.get("renderer") else {
+ return;
+ };
+ let Some(renderer_table) = renderer_table.as_table() else {
+ return;
+ };
+
+ for (_, renderer_value) in renderer_table {
+ let Some(renderer_config) = renderer_value.as_table() else {
+ continue;
+ };
+ let Some(name) = renderer_config.get("name").and_then(|v| v.as_str()) else {
+ continue;
+ };
+
+ renderer_names.push(name.to_string());
+ }
+
+ // Create template
+ let mut template = Template::from(template_content);
+
+ for renderer_name in &renderer_names {
+ tmpl!(template += {
+ renderer {
+ (renderer_name = renderer_name)
+ }
+ });
+ }
+
+ let final_content = template.expand().unwrap();
+
+ // Write the generated code
+ tokio::fs::write(output_path, final_content).await.unwrap();
+
+ println!(
+ "Generated override renderers list with {} renderers using just_template",
+ renderer_names.len()
+ );
+}
+
pub async fn collect_all_possible_types(dir: &PathBuf) -> HashSet<String> {
let mut all_types = HashSet::new();
let mut dirs_to_visit = vec![dir.clone()];
diff --git a/scripts/deploy/completions/bash.sh b/scripts/deploy/completions/bash.sh
index 418105d..e1b2a31 100644
--- a/scripts/deploy/completions/bash.sh
+++ b/scripts/deploy/completions/bash.sh
@@ -40,13 +40,13 @@ _jvn_bash_completion() {
fi
local args=(
- -f "$COMP_LINE"
+ -f "${COMP_LINE//-/^}"
-C "$COMP_POINT"
- -w "$cur"
- -p "$prev"
+ -w "${cur//-/^}"
+ -p "${prev//-/^}"
-c "${words[0]}"
-i "$cword"
- -a "${words[@]}"
+ -a "${words[@]//-/^}"
)
local suggestions
diff --git a/scripts/deploy/completions/fish.fish b/scripts/deploy/completions/fish.fish
index 2904495..558b602 100644
--- a/scripts/deploy/completions/fish.fish
+++ b/scripts/deploy/completions/fish.fish
@@ -24,13 +24,13 @@ function __jvn_fish_complete
end
set -l args \
- -f "$buffer" \
+ -f (string replace -a - ^ -- "$buffer") \
-C "$cursor" \
- -w "$current_word" \
- -p "$previous_word" \
+ -w (string replace -a - ^ -- "$current_word") \
+ -p (string replace -a - ^ -- "$previous_word") \
-c "$cmdline[1]" \
-i "$word_index" \
- -a $cmdline
+ -a (string replace -a - ^ -- "$cmdline")
set -l output (jvn_comp $args 2>/dev/null)
if test "$output" = "_file_"
diff --git a/scripts/deploy/completions/powershell.ps1 b/scripts/deploy/completions/powershell.ps1
index 0c3cddc..ec91038 100644
--- a/scripts/deploy/completions/powershell.ps1
+++ b/scripts/deploy/completions/powershell.ps1
@@ -18,13 +18,13 @@ Register-ArgumentCompleter -CommandName jvn -ScriptBlock {
}
$args = @(
- "-f", $line
+ "-f", ($line -replace '-', '^')
"-C", $cursorPosition.ToString()
- "-w", $wordToComplete
- "-p", if ($words.Count -gt 1) { $words[-2] } else { "" }
+ "-w", ($wordToComplete -replace '-', '^')
+ "-p", (if ($words.Count -gt 1) { $words[-2] } else { "" }) -replace '-', '^'
"-c", $commandName
"-i", ($words.Count - 1).ToString()
- "-a", $words
+ "-a", ($words | ForEach-Object { $_ -replace '-', '^' })
)
$suggestions = jvn_comp $args 2>$null
diff --git a/scripts/deploy/completions/zsh.sh b/scripts/deploy/completions/zsh.sh
index 2b2a96b..dd1ff38 100644
--- a/scripts/deploy/completions/zsh.sh
+++ b/scripts/deploy/completions/zsh.sh
@@ -15,13 +15,13 @@ _jvn_completion() {
fi
args=(
- -f "$buffer"
+ -f "${buffer//-/^}"
-C "$cursor"
- -w "$current_word"
- -p "$previous_word"
+ -w "${current_word//-/^}"
+ -p "${previous_word//-/^}"
-c "$command_name"
-i "$word_index"
- -a "${words[@]}"
+ -a "${(@)words//-/^}"
)
suggestions=$(jvn_comp "${args[@]}" 2>/dev/null)
diff --git a/src/bin/jvn_comp.rs b/src/bin/jvn_comp.rs
index d00916c..30a9f81 100644
--- a/src/bin/jvn_comp.rs
+++ b/src/bin/jvn_comp.rs
@@ -1,54 +1,237 @@
+use std::{fs::OpenOptions, process::exit};
+
use clap::Parser;
+use env_logger::Target;
+use just_enough_vcs_cli::systems::{
+ cmd::_commands::jv_cmd_nodes,
+ comp::{
+ _comps::{jv_cmd_comp_nodes, match_comp},
+ context::CompletionContext,
+ },
+ render::renderer::jv_override_renderers,
+};
+#[cfg(debug_assertions)]
+use log::debug;
+use log::{LevelFilter, error, trace};
+
+fn main() {
+ // If not in release mode, initialize env_logger to capture logs
+ #[cfg(debug_assertions)]
+ init_env_logger();
+
+ // 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(),
+ },
+ Err(e) => {
+ // An error occurred, collecting information for output
+ error!(
+ "Error: {}, origin=\"{}\"",
+ e,
+ std::env::args().collect::<Vec<String>>().join(" ")
+ );
+ std::process::exit(1);
+ }
+ };
+
+ // Trace context information
+ #[cfg(debug_assertions)]
+ trace_ctx(&ctx);
+
+ // Perform pre-completion for common flags;
+ // if completion fails, start matching command nodes for completion
+ let result = pre_comp(&ctx);
+ if let Some(suggestions) = result {
+ handle_comp_result(&Some(suggestions));
+ } else {
+ trace!("Using specific completion");
+ let result = comp(ctx);
+ handle_comp_result(&result);
+ }
+}
+
+fn pre_comp(ctx: &CompletionContext) -> Option<Vec<String>> {
+ // Match and comp Override Renderers
+ if ctx.previous_word == "--renderer" {
+ return Some(jv_override_renderers());
+ }
-#[derive(Parser, Debug)]
-#[command(author, version, about, long_about = None)]
-struct CompletionContext {
- /// The full command line
- #[arg(short = 'f', long)]
- command_line: String,
+ None
+}
+
+fn comp(ctx: CompletionContext) -> Option<Vec<String>> {
+ let args: Vec<String> = ctx.all_words.iter().skip(1).cloned().collect();
+ let nodes = jv_cmd_comp_nodes();
+ let command = format!("{} ", args.join(" "));
- /// Cursor position
- #[arg(short = 'C', long)]
- cursor_position: usize,
+ #[cfg(debug_assertions)]
+ debug!("Arguments: `{}`", command);
- /// Current word
- #[arg(short = 'w', long)]
- current_word: String,
+ // Find all nodes that match the command prefix
+ let matching_nodes: Vec<&String> = nodes
+ .iter()
+ .filter(|node| {
+ let matches = command.starts_with(&format!("{} ", node));
+ #[cfg(debug_assertions)]
+ debug!("Checking node '{}': matches = {}", node, matches);
+ matches
+ })
+ .collect();
- /// Previous word
- #[arg(short = 'p', long)]
- previous_word: String,
+ let match_node: Option<String> = match matching_nodes.len() {
+ 0 => {
+ if let Some(result) = try_comp_cmd_nodes(&ctx) {
+ return Some(result);
+ }
+ // No matching node found
+ None
+ }
+ 1 => {
+ // Single matching node found
+ Some(matching_nodes[0].clone())
+ }
+ _ => {
+ // Multiple matching nodes found
+ // Find the node with the longest length (most specific match)
+ matching_nodes
+ .iter()
+ .max_by_key(|node| node.len())
+ .map(|node| node.to_string())
+ }
+ };
- /// Command name
- #[arg(short = 'c', long)]
- command_name: String,
+ #[cfg(debug_assertions)]
+ match &match_node {
+ Some(node) => trace!("Matched `{}`", node),
+ None => trace!("No competions matched."),
+ }
- /// Word index
- #[arg(short = 'i', long)]
- word_index: usize,
+ let Some(match_node) = match_node else {
+ return None;
+ };
- /// All words
- #[arg(short = 'a', long, num_args = 1..)]
- all_words: Vec<String>,
+ match_comp(match_node, ctx)
}
-fn main() {
- let args = match CompletionContext::try_parse() {
- Ok(args) => args,
- Err(_) => std::process::exit(1),
+fn try_comp_cmd_nodes(ctx: &CompletionContext) -> Option<Vec<String>> {
+ 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;
};
- match comp(args) {
+
+ // Get the current input path
+ let input_path: Vec<&str> = ctx.all_words[1..ctx.word_index]
+ .iter()
+ .filter(|s| !s.is_empty())
+ .map(|s| s.as_str())
+ .collect();
+
+ #[cfg(debug_assertions)]
+ debug!(
+ "try_comp_cmd_nodes: 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();
+
+ for node in cmd_nodes {
+ let node_parts: Vec<&str> = node.split(' ').collect();
+
+ #[cfg(debug_assertions)]
+ 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() || input_path[i] != node_parts[i] {
+ matches = false;
+ break;
+ }
+ }
+
+ if matches && input_path.len() < node_parts.len() {
+ // Add the next part as suggestion
+ suggestions.push(node_parts[input_path.len()].to_string());
+ }
+ }
+
+ // Remove duplicates and sort
+ suggestions.sort();
+ suggestions.dedup();
+
+ #[cfg(debug_assertions)]
+ debug!("try_comp_cmd_nodes: suggestions = {:?}", suggestions);
+
+ if suggestions.is_empty() {
+ None
+ } else {
+ Some(suggestions)
+ }
+}
+
+fn handle_comp_result(r: &Option<Vec<String>>) {
+ 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"
println!("_file_");
+ exit(0)
}
}
}
-fn comp(_args: CompletionContext) -> Option<Vec<String>> {
- None
+#[cfg(debug_assertions)]
+fn trace_ctx(ctx: &CompletionContext) {
+ log::trace!("command_line={}", ctx.command_line);
+ log::trace!("cursor_position={}", ctx.cursor_position);
+ log::trace!("current_word={}", ctx.current_word);
+ log::trace!("previous_word={}", ctx.previous_word);
+ log::trace!("command_name={}", ctx.command_name);
+ log::trace!("word_index={}", ctx.word_index);
+ log::trace!("all_words={:?}", ctx.all_words);
+}
+
+#[cfg(debug_assertions)]
+fn init_env_logger() {
+ let mut log_path = std::env::current_exe()
+ .expect("Failed to get current executable path")
+ .parent()
+ .expect("Failed to get parent directory")
+ .to_path_buf();
+ log_path.push("../../jv_comp_log.txt");
+
+ // Only initialize logger if log file exists
+ if log_path.exists() {
+ let log_file = OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&log_path)
+ .expect("Failed to open log file");
+
+ env_logger::Builder::new()
+ .filter_level(LevelFilter::Trace)
+ .target(Target::Pipe(Box::new(log_file)))
+ .init();
+ }
}
diff --git a/src/cmds.rs b/src/cmds.rs
index d3c7a27..aa41aec 100644
--- a/src/cmds.rs
+++ b/src/cmds.rs
@@ -1,6 +1,7 @@
pub mod arg;
pub mod cmd;
pub mod collect;
+pub mod comp;
pub mod converter;
pub mod r#in;
pub mod out;
diff --git a/src/cmds/comp/helpdoc.rs b/src/cmds/comp/helpdoc.rs
new file mode 100644
index 0000000..7f07cad
--- /dev/null
+++ b/src/cmds/comp/helpdoc.rs
@@ -0,0 +1,13 @@
+use crate::systems::{comp::context::CompletionContext, helpdoc};
+
+pub fn comp(ctx: CompletionContext) -> Option<Vec<String>> {
+ if ctx.previous_word == "helpdoc" {
+ return Some(
+ helpdoc::get_helpdoc_list()
+ .iter()
+ .map(|s| s.to_string())
+ .collect(),
+ );
+ }
+ None
+}
diff --git a/src/systems.rs b/src/systems.rs
index e0d4491..5ca0801 100644
--- a/src/systems.rs
+++ b/src/systems.rs
@@ -1,4 +1,5 @@
pub mod cmd;
+pub mod comp;
pub mod debug;
pub mod helpdoc;
pub mod render;
diff --git a/src/systems/comp.rs b/src/systems/comp.rs
new file mode 100644
index 0000000..c7c6577
--- /dev/null
+++ b/src/systems/comp.rs
@@ -0,0 +1,2 @@
+pub mod _comps;
+pub mod context;
diff --git a/src/systems/comp/context.rs b/src/systems/comp/context.rs
new file mode 100644
index 0000000..36d1840
--- /dev/null
+++ b/src/systems/comp/context.rs
@@ -0,0 +1,33 @@
+use clap::{Parser, command};
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+pub struct CompletionContext {
+ /// The full command line
+ #[arg(short = 'f', long)]
+ pub command_line: String,
+
+ /// Cursor position
+ #[arg(short = 'C', long)]
+ pub cursor_position: usize,
+
+ /// Current word
+ #[arg(short = 'w', long)]
+ pub current_word: String,
+
+ /// Previous word
+ #[arg(short = 'p', long)]
+ pub previous_word: String,
+
+ /// Command name
+ #[arg(short = 'c', long)]
+ pub command_name: String,
+
+ /// Word index
+ #[arg(short = 'i', long)]
+ pub word_index: usize,
+
+ /// All words
+ #[arg(short = 'a', long, num_args = 1..)]
+ pub all_words: Vec<String>,
+}
diff --git a/src/systems/render/renderer.rs b/src/systems/render/renderer.rs
index dab4c23..13de8c6 100644
--- a/src/systems/render/renderer.rs
+++ b/src/systems/render/renderer.rs
@@ -60,3 +60,5 @@ macro_rules! r_println {
$result.println(&format!($($arg)*))
};
}
+
+include!("_override_renderers.rs");
diff --git a/templates/_comps.rs.template b/templates/_comps.rs.template
new file mode 100644
index 0000000..b7f10db
--- /dev/null
+++ b/templates/_comps.rs.template
@@ -0,0 +1,26 @@
+// Auto generated by build.rs
+use crate::systems::comp::context::CompletionContext;
+
+pub fn match_comp(node: String, ctx: CompletionContext) -> Option<Vec<String>> {
+ let node_str = node.as_str();
+ match node_str {
+>>>>>>>>>> comp_match_arms
+ _ => None,
+ }
+}
+
+pub fn jv_cmd_comp_nodes() -> Vec<String> {
+ vec![
+>>>>>>>>>> comp_node_name
+ ]
+}
+
+@@@ >>> comp_match_arms
+ // <<<comp_name>>>.rs
+ "<<<comp_node_name>>>" => crate::cmds::comp::<<<comp_name>>>::comp(ctx),
+@@@ <<<
+
+@@@ >>> comp_node_name
+ // <<<comp_node_name>>>
+ "<<<comp_node_name>>>".to_string(),
+@@@ <<<
diff --git a/templates/_override_renderers.rs.template b/templates/_override_renderers.rs.template
new file mode 100644
index 0000000..a947c49
--- /dev/null
+++ b/templates/_override_renderers.rs.template
@@ -0,0 +1,10 @@
+/// Get all override renderers
+pub fn jv_override_renderers() -> Vec<String> {
+ vec![
+>>>>>>>>>> renderer
+ ]
+}
+
+@@@ >>> renderer
+ "<<<renderer_name>>>".to_string(),
+@@@ <<<