use chrono::{Days, Local};
use crate::app::blocks::format_roam_daily_title;
use crate::edit_buffer::EditBuffer;
use super::types::{DateOffset, SlashAction};
pub fn execute(action: &SlashAction, buffer: &mut EditBuffer, slash_pos: usize, query_len: usize) {
let replace_end = slash_pos + 1 + query_len;
match action {
SlashAction::PrependText(prefix) => {
buffer.replace_range(slash_pos, replace_end, "");
let text: String = buffer.chars.iter().collect();
let stripped = text
.strip_prefix("{{[[TODO]]}} ")
.or_else(|| text.strip_prefix("{{[[DONE]]}} "));
if let Some(rest) = stripped {
let prefix_len = text.len() - rest.len();
buffer.chars.drain(..prefix_len);
buffer.cursor = buffer.cursor.saturating_sub(prefix_len);
}
let prefix_chars: Vec<char> = prefix.chars().collect();
let prefix_len = prefix_chars.len();
for (i, ch) in prefix_chars.into_iter().enumerate() {
buffer.chars.insert(i, ch);
}
buffer.cursor += prefix_len;
}
SlashAction::InsertText(text) => {
buffer.replace_range(slash_pos, replace_end, text);
}
SlashAction::InsertPair { open, close } => {
let replacement = format!("{}{}", open, close);
buffer.replace_range(slash_pos, replace_end, &replacement);
buffer.cursor = slash_pos + open.chars().count();
}
SlashAction::InsertDate(offset) => {
let today = Local::now().date_naive();
let date = match offset {
DateOffset::Today => today,
DateOffset::Yesterday => today.checked_sub_days(Days::new(1)).unwrap_or(today),
DateOffset::Tomorrow => today.checked_add_days(Days::new(1)).unwrap_or(today),
};
let title = format_roam_daily_title(date);
let link = format!("[[{}]]", title);
buffer.replace_range(slash_pos, replace_end, &link);
}
SlashAction::InsertTime => {
let time_str = Local::now().format("%H:%M").to_string();
buffer.replace_range(slash_pos, replace_end, &time_str);
}
SlashAction::InsertCodeBlock => {
let code = "```\n\n```";
buffer.replace_range(slash_pos, replace_end, code);
buffer.cursor = slash_pos + 4; }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edit_buffer::EditBuffer;
#[test]
fn execute_insert_text_hr() {
let mut buf = EditBuffer::new("hello /");
buf.cursor = 7;
execute(&SlashAction::InsertText("---"), &mut buf, 6, 0);
assert_eq!(buf.to_string(), "hello ---");
}
#[test]
fn execute_insert_text_with_query() {
let mut buf = EditBuffer::new("text /hr");
buf.cursor = 8;
execute(&SlashAction::InsertText("---"), &mut buf, 5, 2);
assert_eq!(buf.to_string(), "text ---");
}
#[test]
fn execute_insert_pair_bold() {
let mut buf = EditBuffer::new("/");
buf.cursor = 1;
execute(
&SlashAction::InsertPair {
open: "**",
close: "**",
},
&mut buf,
0,
0,
);
assert_eq!(buf.to_string(), "****");
assert_eq!(buf.cursor, 2); }
#[test]
fn execute_insert_pair_embed() {
let mut buf = EditBuffer::new("test /");
buf.cursor = 6;
execute(
&SlashAction::InsertPair {
open: "{{[[embed]]: ",
close: "}}",
},
&mut buf,
5,
0,
);
assert_eq!(buf.to_string(), "test {{[[embed]]: }}");
assert_eq!(buf.cursor, 18); }
#[test]
fn execute_prepend_todo() {
let mut buf = EditBuffer::new("buy milk /");
buf.cursor = 10;
execute(&SlashAction::PrependText("{{[[TODO]]}} "), &mut buf, 9, 0);
assert_eq!(buf.to_string(), "{{[[TODO]]}} buy milk ");
}
#[test]
fn execute_prepend_replaces_existing_todo() {
let mut buf = EditBuffer::new("{{[[TODO]]}} task /");
buf.cursor = 19;
execute(&SlashAction::PrependText("{{[[DONE]]}} "), &mut buf, 18, 0);
assert_eq!(buf.to_string(), "{{[[DONE]]}} task ");
}
#[test]
fn execute_prepend_h1() {
let mut buf = EditBuffer::new("title /");
buf.cursor = 7;
execute(&SlashAction::PrependText("# "), &mut buf, 6, 0);
assert_eq!(buf.to_string(), "# title ");
}
#[test]
fn execute_prepend_replaces_existing_done() {
let mut buf = EditBuffer::new("{{[[DONE]]}} task /");
buf.cursor = 19;
execute(&SlashAction::PrependText("{{[[TODO]]}} "), &mut buf, 18, 0);
assert_eq!(buf.to_string(), "{{[[TODO]]}} task ");
}
#[test]
fn execute_insert_date_today() {
let mut buf = EditBuffer::new("/");
buf.cursor = 1;
execute(&SlashAction::InsertDate(DateOffset::Today), &mut buf, 0, 0);
let result = buf.to_string();
assert!(result.starts_with("[["));
assert!(result.ends_with("]]"));
assert!(result.contains("2026") || result.contains("2025") || result.contains("2027"));
}
#[test]
fn execute_insert_time() {
let mut buf = EditBuffer::new("/");
buf.cursor = 1;
execute(&SlashAction::InsertTime, &mut buf, 0, 0);
let result = buf.to_string();
assert_eq!(result.len(), 5); assert_eq!(result.chars().nth(2), Some(':'));
}
#[test]
fn execute_insert_code_block() {
let mut buf = EditBuffer::new("text /");
buf.cursor = 6;
execute(&SlashAction::InsertCodeBlock, &mut buf, 5, 0);
assert_eq!(buf.to_string(), "text ```\n\n```");
assert_eq!(buf.cursor, 9); }
#[test]
fn execute_with_query_in_middle() {
let mut buf = EditBuffer::new("start /bo end");
buf.cursor = 10; execute(
&SlashAction::InsertPair {
open: "**",
close: "**",
},
&mut buf,
6,
2,
);
assert_eq!(buf.to_string(), "start **** end");
assert_eq!(buf.cursor, 8); }
}