fix(lsp): context-aware completions now use correct token variants

The Logos lexer produces dedicated token variants for all keywords
(Token::Behavior, Token::LifeArc, etc.) — Token::Ident is only for
user-defined identifiers. determine_context() was matching against
Token::Ident(keyword), so last_keyword was always None and every
nested block returned CompletionContext::Unknown, causing
all_keyword_completions() to dump top-level declaration keywords
everywhere.

Changes:
- determine_context(): match actual token variants (Token::Behavior,
  Token::LifeArc, Token::Relationship, Token::Character, etc.) and
  track last_context directly instead of a String keyword
- behavior_keyword_completions(): add all word-based BT keywords
  (choose, then, repeat, if, when, invert, retry, timeout, cooldown,
  succeed_always, fail_always)
- InFieldBlock arm: always include field_keyword_completions() so
  generic fields (age, bond, etc.) appear even without a species
- Tests: add negative assertions confirming top-level keywords are
  absent inside behavior/life_arc blocks; add new test
  test_behavior_no_toplevel_keywords for nested block isolation
This commit is contained in:
2026-02-23 21:00:03 +00:00
parent dec79fe9e5
commit dcc27a6988
2 changed files with 136 additions and 41 deletions

View File

@@ -110,10 +110,33 @@ mod tests {
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have behavior tree keywords
// Should have symbolic behavior tree keywords
assert!(items.iter().any(|item| item.label == "?"));
assert!(items.iter().any(|item| item.label == ">"));
assert!(items.iter().any(|item| item.label == "*"));
// Should have word-based behavior tree keywords
assert!(items.iter().any(|item| item.label == "choose"));
assert!(items.iter().any(|item| item.label == "then"));
assert!(items.iter().any(|item| item.label == "repeat"));
assert!(items.iter().any(|item| item.label == "if"));
assert!(items.iter().any(|item| item.label == "when"));
// Should NOT have top-level declaration keywords
assert!(
!items.iter().any(|item| item.label == "institution"),
"institution should not appear inside behavior block"
);
assert!(
!items.iter().any(|item| item.label == "species"),
"species should not appear inside behavior block"
);
assert!(
!items.iter().any(|item| item.label == "character"),
"character should not appear inside behavior block"
);
assert!(
!items.iter().any(|item| item.label == "location"),
"location should not appear inside behavior block"
);
},
| _ => panic!("Expected array response"),
}
@@ -136,6 +159,61 @@ mod tests {
// Should have life arc keywords
assert!(items.iter().any(|item| item.label == "state"));
assert!(items.iter().any(|item| item.label == "on"));
// Should NOT have top-level declaration keywords
assert!(
!items.iter().any(|item| item.label == "institution"),
"institution should not appear inside life_arc block"
);
assert!(
!items.iter().any(|item| item.label == "behavior"),
"behavior should not appear inside life_arc block"
);
assert!(
!items.iter().any(|item| item.label == "character"),
"character should not appear inside life_arc block"
);
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_behavior_no_toplevel_keywords() {
// Cursor inside nested `then` block — context should remain InBehavior
let source = "behavior Test {\n then {\n \n }\n}";
let doc = Document::new(source.to_string());
// Position inside nested then block (line 2, after spaces)
let params = make_params(2, 8);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Behavior keywords should still be present at any nesting depth
assert!(
items.iter().any(|item| item.label == "?"),
"? should appear in nested behavior block"
);
assert!(
items.iter().any(|item| item.label == "choose"),
"choose should appear in nested behavior block"
);
// Top-level keywords must not appear
assert!(
!items.iter().any(|item| item.label == "institution"),
"institution must not appear in nested behavior block"
);
assert!(
!items.iter().any(|item| item.label == "species"),
"species must not appear in nested behavior block"
);
assert!(
!items.iter().any(|item| item.label == "character"),
"character must not appear in nested behavior block"
);
},
| _ => panic!("Expected array response"),
}