use std::path::PathBuf;
use clap::Subcommand;
use color_eyre::eyre::{eyre, Result};
use kimun_core::NoteVault;
use crate::settings::{
workspace_config::WorkspaceConfig,
AppSettings,
};
#[derive(Subcommand, Debug)]
pub enum WorkspaceSubcommand {
Init {
#[arg(long)]
name: Option<String>,
path: PathBuf,
},
List,
Use {
name: String,
},
Rename {
old_name: String,
new_name: String,
},
Remove {
name: String,
},
Reindex {
#[arg(long)]
name: Option<String>,
},
}
pub async fn run(
subcommand: WorkspaceSubcommand,
settings: &mut AppSettings,
) -> Result<()> {
match subcommand {
WorkspaceSubcommand::Init { name, path } => run_init(settings, name, path).await,
WorkspaceSubcommand::List => run_list(settings),
WorkspaceSubcommand::Use { name } => run_use(settings, name),
WorkspaceSubcommand::Rename { old_name, new_name } => {
run_rename(settings, old_name, new_name)
}
WorkspaceSubcommand::Remove { name } => run_remove(settings, name),
WorkspaceSubcommand::Reindex { name } => run_reindex(settings, name).await,
}
}
async fn run_init(
settings: &mut AppSettings,
name: Option<String>,
path: PathBuf,
) -> Result<()> {
if settings.workspace_config.is_none() {
settings.workspace_config = Some(WorkspaceConfig::new_empty());
}
let ws_config = settings.workspace_config.as_ref().unwrap();
let workspace_name = match name {
Some(n) => n,
None => {
if ws_config.workspaces.is_empty() {
"default".to_string()
} else {
return Err(eyre!(
"A workspace name is required when other workspaces already exist. \
Use: kimun workspace init --name <name> <path>"
));
}
}
};
if ws_config.workspaces.contains_key(&workspace_name) {
let existing_path = &ws_config.workspaces[&workspace_name].path;
return Err(eyre!(
"Workspace '{}' already exists at {}. \
Use a different name or remove the existing workspace first.",
workspace_name,
existing_path.display()
));
}
if !path.exists() {
std::fs::create_dir_all(&path).map_err(|e| {
eyre!(
"Failed to create workspace directory {}: {}",
path.display(),
e
)
})?;
println!("Created directory: {}", path.display());
}
let canonical_path = path.canonicalize().map_err(|e| {
eyre!(
"Failed to resolve workspace path {}: {}",
path.display(),
e
)
})?;
println!("Initializing workspace database...");
let vault = NoteVault::new(&canonical_path).await.map_err(|e| {
eyre!("Failed to create vault at {}: {}", canonical_path.display(), e)
})?;
vault.validate_and_init().await.map_err(|e| {
eyre!("Failed to initialize vault database: {}", e)
})?;
let ws_config_mut = settings.workspace_config.as_mut().unwrap();
ws_config_mut
.add_workspace(workspace_name.clone(), canonical_path.clone())
.map_err(|e| eyre!("{}", e))?;
settings.config_version = 2;
settings.save_to_disk()?;
println!(
"Workspace '{}' initialized at {}",
workspace_name,
canonical_path.display()
);
let ws_config = settings.workspace_config.as_ref().unwrap();
if ws_config.global.current_workspace == workspace_name {
println!("Set as current workspace.");
}
Ok(())
}
fn run_list(settings: &AppSettings) -> Result<()> {
match &settings.workspace_config {
None => {
println!("No workspaces configured. Run 'kimun workspace init <path>' to create one.");
}
Some(ws_config) => {
if ws_config.workspaces.is_empty() {
println!("No workspaces configured. Run 'kimun workspace init <path>' to create one.");
} else {
println!("Configured workspaces:");
let mut names: Vec<&String> = ws_config.workspaces.keys().collect();
names.sort();
for name in names {
let entry = &ws_config.workspaces[name];
let marker = if name == &ws_config.global.current_workspace {
"* "
} else {
" "
};
println!("{}{} ({})", marker, name, entry.path.display());
}
}
}
}
Ok(())
}
fn run_use(settings: &mut AppSettings, name: String) -> Result<()> {
let ws_config = settings
.workspace_config
.as_ref()
.ok_or_else(|| eyre!("No workspaces configured."))?;
let entry = ws_config
.get_workspace(&name)
.ok_or_else(|| {
let available: Vec<&String> = ws_config.workspaces.keys().collect();
eyre!(
"Workspace '{}' not found. Available workspaces: {}",
name,
available
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
)
})?;
if !entry.path.exists() {
return Err(eyre!(
"Workspace '{}' path no longer exists: {}. \
Update the path or remove this workspace.",
name,
entry.path.display()
));
}
settings.workspace_config.as_mut().unwrap().global.current_workspace = name.clone();
settings.save_to_disk()?;
println!("Switched to workspace '{}'.", name);
Ok(())
}
fn run_rename(
settings: &mut AppSettings,
old_name: String,
new_name: String,
) -> Result<()> {
let ws_config = settings
.workspace_config
.as_ref()
.ok_or_else(|| eyre!("No workspaces configured."))?;
if !ws_config.workspaces.contains_key(&old_name) {
return Err(eyre!(
"Workspace '{}' not found.",
old_name
));
}
if ws_config.workspaces.contains_key(&new_name) {
return Err(eyre!(
"Workspace '{}' already exists. Choose a different name.",
new_name
));
}
let ws_config_mut = settings.workspace_config.as_mut().unwrap();
let entry = ws_config_mut
.workspaces
.remove(&old_name)
.expect("entry must exist (checked above)");
ws_config_mut.workspaces.insert(new_name.clone(), entry);
if ws_config_mut.global.current_workspace == old_name {
ws_config_mut.global.current_workspace = new_name.clone();
}
settings.save_to_disk()?;
println!("Workspace '{}' renamed to '{}'.", old_name, new_name);
Ok(())
}
fn run_remove(settings: &mut AppSettings, name: String) -> Result<()> {
let ws_config = settings
.workspace_config
.as_ref()
.ok_or_else(|| eyre!("No workspaces configured."))?;
if !ws_config.workspaces.contains_key(&name) {
return Err(eyre!("Workspace '{}' not found.", name));
}
if ws_config.global.current_workspace == name {
return Err(eyre!(
"Cannot remove the current workspace '{}'. \
Switch to a different workspace first with: kimun workspace use <name>",
name
));
}
settings
.workspace_config
.as_mut()
.unwrap()
.workspaces
.remove(&name);
settings.save_to_disk()?;
println!("Workspace '{}' removed.", name);
Ok(())
}
async fn run_reindex(settings: &AppSettings, name: Option<String>) -> Result<()> {
let ws_config = settings
.workspace_config
.as_ref()
.ok_or_else(|| eyre!("No workspaces configured."))?;
let workspace_name = match name {
Some(n) => n,
None => ws_config.global.current_workspace.clone(),
};
if workspace_name.is_empty() {
return Err(eyre!("No current workspace set. Specify a workspace name."));
}
let entry = ws_config
.get_workspace(&workspace_name)
.ok_or_else(|| eyre!("Workspace '{}' not found.", workspace_name))?;
if !entry.path.exists() {
return Err(eyre!(
"Workspace '{}' path no longer exists: {}",
workspace_name,
entry.path.display()
));
}
println!("Reindexing workspace '{}'...", workspace_name);
let vault = NoteVault::new(&entry.path).await.map_err(|e| {
eyre!("Failed to open vault at {}: {}", entry.path.display(), e)
})?;
let report = vault.recreate_index().await.map_err(|e| {
eyre!("Failed to reindex workspace '{}': {}", workspace_name, e)
})?;
let _ = report; println!(
"Reindex complete for workspace '{}'.",
workspace_name
);
Ok(())
}