From 2307891d0d26910becead1e2149e9a8c265ba8cc Mon Sep 17 00:00:00 2001 From: Konstantin Fickel Date: Sun, 19 Apr 2026 20:48:56 +0200 Subject: [PATCH] fix: fix lsp adding two @ --- src/cli/commands/lsp.rs | 71 ++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/cli/commands/lsp.rs b/src/cli/commands/lsp.rs index 99e221d..2c6a5cf 100644 --- a/src/cli/commands/lsp.rs +++ b/src/cli/commands/lsp.rs @@ -113,28 +113,6 @@ pub fn completions_for_line( let after_at = &line_prefix[at_pos + 1..]; - // Temporal snippet: `@` followed by a digit → offer date/time format snippets. - if after_at.starts_with(|c: char| c.is_ascii_digit()) { - return vec![ - CompletionItem { - label: "YYYYMMDD".to_string(), - kind: Some(CompletionItemKind::SNIPPET), - detail: Some("Date marker (R16)".to_string()), - insert_text: Some("${1:YYYYMMDD}".to_string()), - insert_text_format: Some(InsertTextFormat::SNIPPET), - ..Default::default() - }, - CompletionItem { - label: "HHMMSS".to_string(), - kind: Some(CompletionItemKind::SNIPPET), - detail: Some("Time marker (R16)".to_string()), - insert_text: Some("${1:HHMMSS}".to_string()), - insert_text_format: Some(InsertTextFormat::SNIPPET), - ..Default::default() - }, - ]; - } - // Markers already on this line (for conditional suggestion logic). let existing: Vec = extract_markers_from_line(line); @@ -166,6 +144,7 @@ pub fn completions_for_line( label: format!("@{}", name), kind: Some(CompletionItemKind::KEYWORD), detail: Some(marker.display_name.clone()), + insert_text: Some(name.clone()), // Conditional completions sort before unconditional ones. sort_text: Some(if is_conditional { format!("0_{}", name) @@ -177,6 +156,28 @@ pub fn completions_for_line( }) .collect(); + // 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()) { + items.push(CompletionItem { + label: "@YYYYMMDD".to_string(), + kind: Some(CompletionItemKind::SNIPPET), + detail: Some("Date marker (R16)".to_string()), + insert_text: Some("${1:YYYYMMDD}".to_string()), + insert_text_format: Some(InsertTextFormat::SNIPPET), + sort_text: Some("2_date".to_string()), + ..Default::default() + }); + items.push(CompletionItem { + label: "@HHMMSS".to_string(), + kind: Some(CompletionItemKind::SNIPPET), + detail: Some("Time marker (R16)".to_string()), + insert_text: Some("${1:HHMMSS}".to_string()), + insert_text_format: Some(InsertTextFormat::SNIPPET), + sort_text: Some("2_time".to_string()), + ..Default::default() + }); + } + items.sort_by(|a, b| { a.sort_text .as_deref() @@ -1035,17 +1036,37 @@ mod tests { .starts_with("0_")); } + #[test] + fn test_completions_temporal_snippet_at_sign() { + let config = make_config(); + // Timestamps suggested when prefix is empty (just @) + let items = completions_for_line("@", 1, &config); + let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); + assert!(labels.contains(&"@YYYYMMDD")); + assert!(labels.contains(&"@HHMMSS")); + assert!(items + .iter() + .any(|i| i.kind == Some(CompletionItemKind::SNIPPET))); + } + #[test] fn test_completions_temporal_snippet_digit_after_at() { let config = make_config(); let items = completions_for_line("@2", 2, &config); assert!(!items.is_empty()); let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); - assert!(labels.contains(&"YYYYMMDD")); - assert!(labels.contains(&"HHMMSS")); - // Temporal items use snippet format + assert!(labels.contains(&"@YYYYMMDD")); + assert!(labels.contains(&"@HHMMSS")); assert!(items .iter() .any(|i| i.kind == Some(CompletionItemKind::SNIPPET))); } + + #[test] + fn test_completions_no_double_at() { + let config = make_config(); + let items = completions_for_line("@Br", 3, &config); + let break_item = items.iter().find(|i| i.label == "@Break").unwrap(); + assert_eq!(break_item.insert_text.as_deref(), Some("Break")); + } }