fix: use actual timestamps for completion

This commit is contained in:
Konstantin Fickel 2026-04-19 20:56:34 +02:00
parent 61c4c00a57
commit 073a04c337
Signed by: kfickel
GPG key ID: A793722F9933C1A5

View file

@ -101,6 +101,7 @@ pub fn completions_for_line(
line: &str, line: &str,
cursor_col: u32, cursor_col: u32,
config: &RepositoryConfiguration, config: &RepositoryConfiguration,
tz: Tz,
) -> Vec<CompletionItem> { ) -> Vec<CompletionItem> {
let col = (cursor_col as usize).min(line.len()); let col = (cursor_col as usize).min(line.len());
let line_prefix = &line[..col]; let line_prefix = &line[..col];
@ -158,21 +159,22 @@ pub fn completions_for_line(
// Offer date/time snippets when prefix is empty or starts with a digit. // Offer date/time snippets when prefix is empty or starts with a digit.
if prefix.is_empty() || prefix.starts_with(|c: char| c.is_ascii_digit()) { if prefix.is_empty() || prefix.starts_with(|c: char| c.is_ascii_digit()) {
let now = Utc::now().with_timezone(&tz);
let date_str = now.format("%Y%m%d").to_string();
let time_str = now.format("%H%M%S").to_string();
items.push(CompletionItem { items.push(CompletionItem {
label: "@YYYYMMDD".to_string(), label: format!("@{}", date_str),
kind: Some(CompletionItemKind::SNIPPET), kind: Some(CompletionItemKind::VALUE),
detail: Some("Date marker (R16)".to_string()), detail: Some("Current date (R16)".to_string()),
insert_text: Some("${1:YYYYMMDD}".to_string()), insert_text: Some(date_str),
insert_text_format: Some(InsertTextFormat::SNIPPET),
sort_text: Some("2_date".to_string()), sort_text: Some("2_date".to_string()),
..Default::default() ..Default::default()
}); });
items.push(CompletionItem { items.push(CompletionItem {
label: "@HHMMSS".to_string(), label: format!("@{}", time_str),
kind: Some(CompletionItemKind::SNIPPET), kind: Some(CompletionItemKind::VALUE),
detail: Some("Time marker (R16)".to_string()), detail: Some("Current time (R16)".to_string()),
insert_text: Some("${1:HHMMSS}".to_string()), insert_text: Some(time_str),
insert_text_format: Some(InsertTextFormat::SNIPPET),
sort_text: Some("2_time".to_string()), sort_text: Some("2_time".to_string()),
..Default::default() ..Default::default()
}); });
@ -636,6 +638,7 @@ impl LanguageServer for Backend {
line, line,
position.character, position.character,
&state.config, &state.config,
state.tz,
)))) ))))
} }
@ -994,14 +997,14 @@ mod tests {
#[test] #[test]
fn test_completions_no_at_sign_returns_empty() { fn test_completions_no_at_sign_returns_empty() {
let config = make_config(); let config = make_config();
let items = completions_for_line("hello world", 5, &config); let items = completions_for_line("hello world", 5, &config, chrono_tz::UTC);
assert!(items.is_empty()); assert!(items.is_empty());
} }
#[test] #[test]
fn test_completions_at_sign_returns_all_markers() { fn test_completions_at_sign_returns_all_markers() {
let config = make_config(); let config = make_config();
let items = completions_for_line("@", 1, &config); let items = completions_for_line("@", 1, &config, chrono_tz::UTC);
assert!(!items.is_empty()); assert!(!items.is_empty());
let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect();
assert!(labels.contains(&"@Task")); assert!(labels.contains(&"@Task"));
@ -1011,7 +1014,7 @@ mod tests {
#[test] #[test]
fn test_completions_prefix_filters() { fn test_completions_prefix_filters() {
let config = make_config(); let config = make_config();
let items = completions_for_line("@Ta", 3, &config); let items = completions_for_line("@Ta", 3, &config, chrono_tz::UTC);
assert!(items assert!(items
.iter() .iter()
.all(|i| i.label.starts_with("@Ta") || i.label.starts_with("@ta"))); .all(|i| i.label.starts_with("@Ta") || i.label.starts_with("@ta")));
@ -1022,7 +1025,7 @@ mod tests {
fn test_completions_conditional_sorted_first() { fn test_completions_conditional_sorted_first() {
let config = make_config(); let config = make_config();
// @Task is on the line → Done and Waiting should be conditional suggestions // @Task is on the line → Done and Waiting should be conditional suggestions
let items = completions_for_line("@Task @", 7, &config); let items = completions_for_line("@Task @", 7, &config, chrono_tz::UTC);
let done = items.iter().find(|i| i.label == "@Done"); let done = items.iter().find(|i| i.label == "@Done");
let waiting = items.iter().find(|i| i.label == "@Waiting"); let waiting = items.iter().find(|i| i.label == "@Waiting");
assert!(done.is_some()); assert!(done.is_some());
@ -1040,32 +1043,35 @@ mod tests {
fn test_completions_temporal_snippet_at_sign() { fn test_completions_temporal_snippet_at_sign() {
let config = make_config(); let config = make_config();
// Timestamps suggested when prefix is empty (just @) // Timestamps suggested when prefix is empty (just @)
let items = completions_for_line("@", 1, &config); let items = completions_for_line("@", 1, &config, chrono_tz::UTC);
let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); // Labels contain actual timestamps (8-digit date and 6-digit time)
assert!(labels.contains(&"@YYYYMMDD"));
assert!(labels.contains(&"@HHMMSS"));
assert!(items assert!(items
.iter() .iter()
.any(|i| i.kind == Some(CompletionItemKind::SNIPPET))); .any(|i| i.kind == Some(CompletionItemKind::VALUE)
&& i.insert_text.as_deref().map_or(false, |t| t.len() == 8
&& t.chars().all(|c| c.is_ascii_digit()))));
assert!(items
.iter()
.any(|i| i.kind == Some(CompletionItemKind::VALUE)
&& i.insert_text.as_deref().map_or(false, |t| t.len() == 6
&& t.chars().all(|c| c.is_ascii_digit()))));
} }
#[test] #[test]
fn test_completions_temporal_snippet_digit_after_at() { fn test_completions_temporal_snippet_digit_after_at() {
let config = make_config(); let config = make_config();
let items = completions_for_line("@2", 2, &config); let items = completions_for_line("@2", 2, &config, chrono_tz::UTC);
assert!(!items.is_empty()); assert!(!items.is_empty());
let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); // Should contain VALUE kind timestamp items
assert!(labels.contains(&"@YYYYMMDD"));
assert!(labels.contains(&"@HHMMSS"));
assert!(items assert!(items
.iter() .iter()
.any(|i| i.kind == Some(CompletionItemKind::SNIPPET))); .any(|i| i.kind == Some(CompletionItemKind::VALUE)));
} }
#[test] #[test]
fn test_completions_no_double_at() { fn test_completions_no_double_at() {
let config = make_config(); let config = make_config();
let items = completions_for_line("@Br", 3, &config); let items = completions_for_line("@Br", 3, &config, chrono_tz::UTC);
let break_item = items.iter().find(|i| i.label == "@Break").unwrap(); let break_item = items.iter().find(|i| i.label == "@Break").unwrap();
assert_eq!(break_item.insert_text.as_deref(), Some("Break")); assert_eq!(break_item.insert_text.as_deref(), Some("Break"));
} }