use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub editor: String,
pub markdown_viewer: String,
#[serde(default)]
pub hidden_projects: Vec<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
editor: detect_editor(),
markdown_viewer: detect_markdown_viewer(),
hidden_projects: Vec::new(),
}
}
}
pub fn get_config_path() -> PathBuf {
let home = std::env::var("HOME").expect("HOME environment variable not set");
PathBuf::from(home).join(".kanban").join("config.toml")
}
pub fn load_config() -> Result<Config> {
let config_path = get_config_path();
if !config_path.exists() {
return Ok(Config::default());
}
let content = std::fs::read_to_string(config_path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
pub fn save_config(config: &Config) -> Result<()> {
let config_path = get_config_path();
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = toml::to_string_pretty(config)?;
std::fs::write(config_path, content)?;
Ok(())
}
pub fn hide_project(config: &mut Config, project_name: &str) -> Result<()> {
if !config.hidden_projects.contains(&project_name.to_string()) {
config.hidden_projects.push(project_name.to_string());
save_config(config)?;
}
Ok(())
}
pub fn unhide_project(config: &mut Config, project_name: &str) -> Result<()> {
config.hidden_projects.retain(|p| p != project_name);
save_config(config)?;
Ok(())
}
pub fn is_project_hidden(config: &Config, project_name: &str) -> bool {
config.hidden_projects.contains(&project_name.to_string())
}
fn detect_editor() -> String {
if let Ok(editor) = std::env::var("VISUAL") {
return editor;
}
if let Ok(editor) = std::env::var("EDITOR") {
return editor;
}
let common_editors = vec![
"nvim",
"vim",
"nano",
"emacs",
"code", "subl", ];
for editor in common_editors {
if which(editor).is_ok() {
return editor.to_string();
}
}
"vim".to_string()
}
fn detect_markdown_viewer() -> String {
let os = std::env::consts::OS;
match os {
"macos" => {
let viewers = vec![
"open -a Marked\\ 2", "open -a iA\\ Writer", "open -a Typora", "glow", "open", ];
for viewer in viewers {
let cmd = viewer.split_whitespace().next().unwrap();
if cmd == "open" || which(cmd).is_ok() {
return viewer.to_string();
}
}
"open".to_string()
}
"linux" => {
let viewers = vec![
"glow", "mdcat", "xdg-open", ];
for viewer in viewers {
if which(viewer).is_ok() {
return viewer.to_string();
}
}
"xdg-open".to_string()
}
_ => {
"notepad".to_string()
}
}
}
fn which(cmd: &str) -> Result<PathBuf> {
use std::process::Command;
let output = Command::new("which")
.arg(cmd)
.output()?;
if output.status.success() {
let path = String::from_utf8(output.stdout)?
.trim()
.to_string();
Ok(PathBuf::from(path))
} else {
Err(anyhow::anyhow!("Command not found: {}", cmd))
}
}
pub fn is_config_complete(config: &Config) -> bool {
!config.editor.is_empty() && !config.markdown_viewer.is_empty()
}
pub fn check_first_run() -> Result<(Config, bool)> {
let config_path = get_config_path();
if !config_path.exists() {
let config = Config::default();
save_config(&config)?;
Ok((config, true))
} else {
Ok((load_config()?, false))
}
}
fn print_welcome_message(config: &Config) {
println!("🎉 欢迎使用 Kanban!");
println!();
println!("已自动检测并配置以下工具:");
println!(" 编辑器: {}", config.editor);
println!(" Markdown 预览: {}", config.markdown_viewer);
println!();
println!("配置文件位置: {}", get_config_path().display());
println!();
println!("如需修改配置,请使用以下命令:");
println!(" kanban config editor <命令> # 设置编辑器");
println!(" kanban config viewer <命令> # 设置预览器");
println!(" kanban config show # 查看当前配置");
println!();
}
pub fn set_editor(editor: String) -> Result<()> {
let mut config = load_config()?;
config.editor = editor;
save_config(&config)?;
println!("✓ 编辑器已设置为: {}", config.editor);
Ok(())
}
pub fn set_viewer(viewer: String) -> Result<()> {
let mut config = load_config()?;
config.markdown_viewer = viewer;
save_config(&config)?;
println!("✓ Markdown 预览器已设置为: {}", config.markdown_viewer);
Ok(())
}
pub fn show_config() -> Result<()> {
let config = load_config()?;
println!("当前配置:");
println!(" 编辑器: {}", config.editor);
println!(" Markdown 预览: {}", config.markdown_viewer);
println!();
println!("配置文件: {}", get_config_path().display());
Ok(())
}