use anyhow::{Context, Result};
use console::style;
use std::path::PathBuf;
use std::process::Command;
use tokio::fs;
use crate::workspace::templates::DEFAULT_WEZTERMOCIL_TEMPLATE;
use crate::workspace::{Repository, TemplateManager, WorkspaceConfig};
fn is_weztermocil_available() -> bool {
Command::new("which")
.arg("weztermocil")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
fn get_weztermocil_config_dir() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| dirs::home_dir().unwrap_or_default().join(".config"))
.join("weztermocil")
}
#[allow(dead_code)]
pub async fn open_with_wezterm(
config: &WorkspaceConfig,
repo: &Repository,
template_manager: &TemplateManager,
) -> Result<()> {
open_with_wezterm_options(config, repo, template_manager, false).await
}
pub async fn open_with_wezterm_options(
config: &WorkspaceConfig,
repo: &Repository,
template_manager: &TemplateManager,
no_weztermocil: bool,
) -> Result<()> {
let wezterm_integration = config
.apps
.wezterm
.as_ref()
.context("WezTerm integration is not configured")?;
if !wezterm_integration.enabled {
anyhow::bail!("WezTerm integration is disabled in configuration");
}
if !no_weztermocil && is_weztermocil_available() {
println!(
"{} Using weztermocil for advanced pane layout",
style("🎯").blue()
);
match create_and_launch_weztermocil_layout(config, repo, template_manager).await {
Ok(_) => return Ok(()),
Err(e) => {
println!("{} weztermocil launch failed: {}", style("⚠️").yellow(), e);
println!(
"{} Falling back to Lua configuration method",
style("ℹ️").blue()
);
}
}
} else {
if no_weztermocil {
println!(
"{} weztermocil disabled by --no-weztermocil flag",
style("ℹ️").blue()
);
} else {
println!(
"{} weztermocil not found. Install it for automatic pane layouts:",
style("💡").yellow()
);
println!(" brew update && brew install alexcaza/tap/weztermocil");
}
println!("{} Using Lua configuration instead", style("ℹ️").blue());
}
let template_name = repo
.get_app_template("wezterm")
.unwrap_or(&wezterm_integration.default_template);
let template_content = template_manager
.load_template("wezterm", template_name)
.await
.with_context(|| format!("Failed to load template '{template_name}'"))?;
let variables = TemplateManager::create_variables(config, repo);
let config_content = template_manager.substitute_variables(&template_content, &variables);
let config_name = format!("vibe-{}-{}.lua", config.workspace.name, repo.name);
let config_path = wezterm_integration.config_dir.join(&config_name);
fs::create_dir_all(&wezterm_integration.config_dir)
.await
.with_context(|| {
format!(
"Failed to create WezTerm config directory: {}",
wezterm_integration.config_dir.display()
)
})?;
fs::write(&config_path, config_content)
.await
.with_context(|| format!("Failed to write WezTerm config: {}", config_path.display()))?;
println!(
"{} Created WezTerm configuration: {}",
style("✅").green(),
style(config_path.display()).cyan()
);
let methods = [
(
"--config-file",
vec!["--config-file", config_path.to_str().unwrap()],
),
("WEZTERM_CONFIG_FILE", vec![]),
];
let mut launched = false;
for (method_name, args) in &methods {
let mut cmd = Command::new("wezterm");
if method_name == &"WEZTERM_CONFIG_FILE" {
cmd.env("WEZTERM_CONFIG_FILE", &config_path);
} else {
for arg in args {
cmd.arg(arg);
}
}
match cmd.spawn() {
Ok(_) => {
println!(
"{} Launched WezTerm with custom configuration (method: {})",
style("✓").green().bold(),
method_name
);
launched = true;
break;
}
Err(e) => {
println!(
"{} Failed to launch WezTerm with {}: {}",
style("⚠️").yellow(),
method_name,
e
);
}
}
}
if !launched {
println!("\n{} Manual instructions:", style("📋").blue());
println!("1. Open WezTerm");
println!("2. Set WEZTERM_CONFIG_FILE={}", config_path.display());
println!("3. Or copy config to: ~/.wezterm.lua");
println!("4. Or run: wezterm --config-file {}", config_path.display());
}
Ok(())
}
pub async fn cleanup_wezterm_config(config: &WorkspaceConfig, repo: &Repository) -> Result<()> {
let wezterm_integration = config
.apps
.wezterm
.as_ref()
.context("WezTerm integration is not configured")?;
if !wezterm_integration.enabled {
return Ok(());
}
let config_name = format!("vibe-{}-{}.lua", config.workspace.name, repo.name);
let config_path = wezterm_integration.config_dir.join(&config_name);
if config_path.exists() {
fs::remove_file(&config_path).await.with_context(|| {
format!("Failed to remove WezTerm config: {}", config_path.display())
})?;
println!(
"{} Removed WezTerm configuration: {}",
style("🗑️").red(),
style(config_path.display()).cyan()
);
}
let layout_name = format!("vibe-{}-{}", config.workspace.name, repo.name);
let layout_path = get_weztermocil_config_dir().join(format!("{layout_name}.yml"));
if layout_path.exists() {
fs::remove_file(&layout_path).await.with_context(|| {
format!(
"Failed to remove weztermocil layout: {}",
layout_path.display()
)
})?;
println!(
"{} Removed weztermocil layout: {}",
style("🗑️").red(),
style(layout_path.display()).cyan()
);
}
Ok(())
}
async fn create_and_launch_weztermocil_layout(
config: &WorkspaceConfig,
repo: &Repository,
template_manager: &TemplateManager,
) -> Result<()> {
let wezterm_integration = config
.apps
.wezterm
.as_ref()
.context("WezTerm integration is not configured")?;
let template_name = repo
.get_app_template("wezterm")
.unwrap_or(&wezterm_integration.default_template);
let yaml_content = match template_manager
.load_template("wezterm", &format!("{template_name}-weztermocil"))
.await
{
Ok(content) => {
let variables = TemplateManager::create_variables(config, repo);
template_manager.substitute_variables(&content, &variables)
}
Err(_) => {
generate_weztermocil_yaml(config, repo)
}
};
let weztermocil_dir = get_weztermocil_config_dir();
fs::create_dir_all(&weztermocil_dir)
.await
.with_context(|| {
format!(
"Failed to create weztermocil directory: {}",
weztermocil_dir.display()
)
})?;
let layout_name = format!("vibe-{}-{}", config.workspace.name, repo.name);
let layout_path = weztermocil_dir.join(format!("{layout_name}.yml"));
fs::write(&layout_path, yaml_content)
.await
.with_context(|| {
format!(
"Failed to write weztermocil layout: {}",
layout_path.display()
)
})?;
println!(
"{} Created weztermocil layout: {}",
style("✅").green(),
style(layout_path.display()).cyan()
);
let result = Command::new("weztermocil").arg(&layout_name).output();
match result {
Ok(output) => {
if output.status.success() {
println!(
"{} Launched WezTerm with 3-pane layout via weztermocil",
style("✓").green().bold()
);
println!("{} Panes:", style("📋").blue());
println!(" • Left: Agent launcher");
println!(" • Top-right: Git manager");
println!(" • Bottom-right: Project commands");
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
anyhow::bail!(
"weztermocil failed with exit code {:?}\nStdout: {}\nStderr: {}",
output.status.code(),
stdout,
stderr
);
}
}
Err(e) => {
anyhow::bail!("Failed to execute weztermocil: {}", e);
}
}
Ok(())
}
fn generate_weztermocil_yaml(config: &WorkspaceConfig, repo: &Repository) -> String {
let template = DEFAULT_WEZTERMOCIL_TEMPLATE;
let variables = TemplateManager::create_variables(config, repo);
let temp_dir = std::env::temp_dir();
let template_manager = TemplateManager::new(temp_dir);
template_manager.substitute_variables(template, &variables)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::workspace::{Repository, WezTermIntegration, WorkspaceInfo};
use std::path::PathBuf;
use tempfile::TempDir;
fn create_test_config() -> WorkspaceConfig {
let temp_dir = TempDir::new().unwrap();
let mut config = WorkspaceConfig::default();
config.workspace = WorkspaceInfo {
name: "test-workspace".to_string(),
root: PathBuf::from("/tmp/test"),
auto_discover: false,
};
config.repositories = vec![
Repository::new("frontend", "./frontend"),
Repository::new("backend", "./backend"),
];
config.apps.wezterm = Some(WezTermIntegration {
enabled: true,
config_dir: temp_dir.path().to_path_buf(),
template_dir: temp_dir.path().join("templates").join("wezterm"),
default_template: "default".to_string(),
});
config
}
}