use crate::error::{PadzError, Result};
use crate::model::extract_title_and_body;
use std::env;
use std::path::Path;
use std::process::Command;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EditorContent {
pub title: String,
pub content: String,
}
impl EditorContent {
pub fn new(title: String, content: String) -> Self {
Self { title, content }
}
pub fn to_buffer(&self) -> String {
if self.content.is_empty() {
format!("{}\n\n", self.title)
} else {
format!("{}\n\n{}", self.title, self.content)
}
}
pub fn from_buffer(buffer: &str) -> Self {
if let Some((title, body)) = extract_title_and_body(buffer) {
return Self {
title,
content: body,
};
}
Self {
title: String::new(),
content: String::new(),
}
}
}
pub fn get_editor() -> Result<String> {
if let Ok(editor) = env::var("EDITOR") {
if !editor.is_empty() {
return Ok(editor);
}
}
if let Ok(editor) = env::var("VISUAL") {
if !editor.is_empty() {
return Ok(editor);
}
}
for fallback in &["vim", "vi", "nano"] {
if Command::new("which")
.arg(fallback)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
{
return Ok((*fallback).to_string());
}
}
Err(PadzError::Api(
"No editor found. Set $EDITOR environment variable.".to_string(),
))
}
pub fn open_in_editor<P: AsRef<Path>>(file_path: P) -> Result<()> {
let editor = get_editor()?;
let path = file_path.as_ref();
let status = Command::new(&editor)
.arg(path)
.status()
.map_err(|e| PadzError::Api(format!("Failed to launch editor '{}': {}", editor, e)))?;
if !status.success() {
return Err(PadzError::Api(format!(
"Editor '{}' exited with non-zero status",
editor
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_editor_content_to_buffer_with_content() {
let ec = EditorContent::new("My Title".to_string(), "Some content here.".to_string());
assert_eq!(ec.to_buffer(), "My Title\n\nSome content here.");
}
#[test]
fn test_editor_content_to_buffer_empty_content() {
let ec = EditorContent::new("My Title".to_string(), String::new());
assert_eq!(ec.to_buffer(), "My Title\n\n");
}
#[test]
fn test_editor_content_from_buffer_normal() {
let buffer = "My Title\n\nThis is content.\nMore content.";
let ec = EditorContent::from_buffer(buffer);
assert_eq!(ec.title, "My Title");
assert_eq!(ec.content, "This is content.\nMore content.");
}
#[test]
fn test_editor_content_from_buffer_empty_content() {
let buffer = "My Title\n\n";
let ec = EditorContent::from_buffer(buffer);
assert_eq!(ec.title, "My Title");
assert_eq!(ec.content, "");
}
#[test]
fn test_editor_content_from_buffer_title_only() {
let buffer = "My Title";
let ec = EditorContent::from_buffer(buffer);
assert_eq!(ec.title, "My Title");
assert_eq!(ec.content, "");
}
#[test]
fn test_editor_content_from_buffer_empty() {
let buffer = "";
let ec = EditorContent::from_buffer(buffer);
assert_eq!(ec.title, "");
assert_eq!(ec.content, "");
}
#[test]
fn test_editor_content_from_buffer_no_blank_separator() {
let buffer = "Title\nContent without blank";
let ec = EditorContent::from_buffer(buffer);
assert_eq!(ec.title, "Title");
assert_eq!(ec.content, "Content without blank");
}
#[test]
fn test_roundtrip() {
let original = EditorContent::new(
"Test Title".to_string(),
"Test content\nwith lines".to_string(),
);
let buffer = original.to_buffer();
let parsed = EditorContent::from_buffer(&buffer);
assert_eq!(original, parsed);
}
}