Skip to content

Commit 5748e7f

Browse files
authored
Merge pull request #927 from multiplex55/codex/implement-timestamp-insertion-in-notes-dialog
Add timestamp insertion in notes editor context menu
2 parents c651347 + 98618eb commit 5748e7f

1 file changed

Lines changed: 89 additions & 5 deletions

File tree

src/gui/notes_dialog.rs

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
use crate::gui::LauncherApp;
22
use crate::plugins::note::{load_notes, save_notes, Note};
33
use crate::plugins::todo::{load_todos, TODO_FILE};
4+
use chrono::{DateTime, Local};
45
use eframe::egui;
56

7+
fn format_note_timestamp(dt: DateTime<Local>) -> String {
8+
dt.format("%Y-%m-%d %H:%M:%S").to_string()
9+
}
10+
11+
fn format_note_timestamp_now() -> String {
12+
format_note_timestamp(Local::now())
13+
}
14+
15+
fn insert_at_char_boundary(text: &str, idx: usize, insert: &str) -> String {
16+
let char_count = text.chars().count();
17+
let char_idx = idx.min(char_count);
18+
let byte_idx = text
19+
.char_indices()
20+
.nth(char_idx)
21+
.map(|(byte_idx, _)| byte_idx)
22+
.unwrap_or(text.len());
23+
24+
let mut out = String::with_capacity(text.len() + insert.len());
25+
out.push_str(&text[..byte_idx]);
26+
out.push_str(insert);
27+
out.push_str(&text[byte_idx..]);
28+
out
29+
}
30+
631
#[derive(Default)]
732
pub struct NotesDialog {
833
pub open: bool,
@@ -78,11 +103,30 @@ impl NotesDialog {
78103
egui::ScrollArea::vertical()
79104
.max_height(ui.available_height())
80105
.show(ui, |ui| {
81-
let resp = ui.add(
82-
egui::TextEdit::multiline(&mut self.text)
83-
.desired_width(f32::INFINITY)
84-
.desired_rows(10),
85-
);
106+
let output = egui::TextEdit::multiline(&mut self.text)
107+
.desired_width(f32::INFINITY)
108+
.desired_rows(10)
109+
.show(ui);
110+
let resp = output.response.clone();
111+
let caret_char_idx =
112+
output.cursor_range.map(|range| range.primary.ccursor.index);
113+
114+
let mut insert_timestamp = false;
115+
let mut insert_idx = None;
116+
resp.context_menu(|ui| {
117+
if ui.button("Insert timestamp").clicked() {
118+
insert_timestamp = true;
119+
insert_idx = caret_char_idx;
120+
ui.close_menu();
121+
}
122+
});
123+
124+
if insert_timestamp {
125+
let ts = format_note_timestamp_now();
126+
let idx = insert_idx.unwrap_or(usize::MAX);
127+
self.text = insert_at_char_boundary(&self.text, idx, &ts);
128+
}
129+
86130
if resp.has_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
87131
let modifiers = ctx.input(|i| i.modifiers);
88132
ctx.input_mut(|i| i.consume_key(modifiers, egui::Key::Enter));
@@ -218,3 +262,43 @@ impl NotesDialog {
218262
}
219263
}
220264
}
265+
266+
#[cfg(test)]
267+
mod tests {
268+
use super::{format_note_timestamp, insert_at_char_boundary};
269+
use chrono::{Local, TimeZone};
270+
271+
#[test]
272+
fn insert_in_middle() {
273+
assert_eq!(
274+
insert_at_char_boundary("hello world", 5, ","),
275+
"hello, world"
276+
);
277+
}
278+
279+
#[test]
280+
fn insert_at_start_and_end() {
281+
assert_eq!(insert_at_char_boundary("world", 0, "hello "), "hello world");
282+
assert_eq!(insert_at_char_boundary("hello", 5, " world"), "hello world");
283+
}
284+
285+
#[test]
286+
fn insert_out_of_range_falls_back_to_end() {
287+
assert_eq!(insert_at_char_boundary("hello", 999, "!"), "hello!");
288+
}
289+
290+
#[test]
291+
fn unicode_safe_char_boundary_handling() {
292+
assert_eq!(insert_at_char_boundary("a😀b", 2, "-"), "a😀-b");
293+
assert_eq!(insert_at_char_boundary("éß", 1, "-"), "é-ß");
294+
}
295+
296+
#[test]
297+
fn timestamp_format_is_deterministic() {
298+
let dt = Local
299+
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
300+
.single()
301+
.expect("valid local datetime");
302+
assert_eq!(format_note_timestamp(dt), "2024-01-02 03:04:05");
303+
}
304+
}

0 commit comments

Comments
 (0)