fix(lsp): fix remaining completion scoping issues
- is_typing_declaration_name(): had the same Token::Ident bug as determine_context() — matched string keywords against Token::Ident but the lexer produces dedicated variants. Extracted shared is_declaration_keyword() helper that matches actual token variants. - determine_context(): add Token::Schedule so schedule blocks get InFieldBlock context instead of falling through to Unknown. - Tests: add negative assertions to test_top_level_completions, test_field_block_completions, and test_relationship_completions. Add test_no_completions_after_declaration_keyword, test_no_completions_while_typing_name, and test_schedule_completions_no_toplevel.
This commit is contained in:
@@ -44,6 +44,28 @@ mod tests {
|
||||
assert!(items.iter().any(|item| item.label == "character"));
|
||||
assert!(items.iter().any(|item| item.label == "template"));
|
||||
assert!(items.iter().any(|item| item.label == "behavior"));
|
||||
// Should NOT have behavior-only keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "?"),
|
||||
"? should not appear at top level"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "choose"),
|
||||
"choose should not appear at top level"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "then"),
|
||||
"then should not appear at top level"
|
||||
);
|
||||
// Should NOT have field-only keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "age"),
|
||||
"age should not appear at top level"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "bond"),
|
||||
"bond should not appear at top level"
|
||||
);
|
||||
},
|
||||
| _ => panic!("Expected array response"),
|
||||
}
|
||||
@@ -67,6 +89,24 @@ mod tests {
|
||||
assert!(items.iter().any(|item| item.label == "from"));
|
||||
assert!(items.iter().any(|item| item.label == "age"));
|
||||
assert!(items.iter().any(|item| item.label == "bond"));
|
||||
// Should NOT have top-level declaration keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "character"),
|
||||
"character should not appear inside character block"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "behavior"),
|
||||
"behavior should not appear inside character block"
|
||||
);
|
||||
// Should NOT have behavior-only keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "?"),
|
||||
"? should not appear inside character block"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "choose"),
|
||||
"choose should not appear inside character block"
|
||||
);
|
||||
},
|
||||
| _ => panic!("Expected array response"),
|
||||
}
|
||||
@@ -237,6 +277,24 @@ mod tests {
|
||||
assert!(items.iter().any(|item| item.label == "as"));
|
||||
assert!(items.iter().any(|item| item.label == "self"));
|
||||
assert!(items.iter().any(|item| item.label == "other"));
|
||||
// Should NOT have top-level declaration keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "institution"),
|
||||
"institution should not appear inside relationship block"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "behavior"),
|
||||
"behavior should not appear inside relationship block"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "character"),
|
||||
"character keyword should not appear inside relationship block"
|
||||
);
|
||||
// Should NOT have behavior-only keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "?"),
|
||||
"? should not appear inside relationship block"
|
||||
);
|
||||
},
|
||||
| _ => panic!("Expected array response"),
|
||||
}
|
||||
@@ -377,6 +435,66 @@ character Bob {}"#;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_completions_after_declaration_keyword() {
|
||||
// After typing "behavior " the user is naming a new entity — no completions
|
||||
let source = "behavior ";
|
||||
let doc = Document::new(source.to_string());
|
||||
let params = make_params(0, 9); // right after "behavior "
|
||||
|
||||
let result = completion::get_completions(&doc, ¶ms);
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Should suppress completions when typing a name after a declaration keyword"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_completions_while_typing_name() {
|
||||
// User is typing "behavior Fo" — in the middle of naming
|
||||
let source = "behavior Fo";
|
||||
let doc = Document::new(source.to_string());
|
||||
let params = make_params(0, 11); // right after "Fo"
|
||||
|
||||
let result = completion::get_completions(&doc, ¶ms);
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Should suppress completions when in the middle of typing a declaration name"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_schedule_completions_no_toplevel() {
|
||||
let source = "schedule Daily {\n \n}";
|
||||
let doc = Document::new(source.to_string());
|
||||
let params = make_params(1, 4);
|
||||
|
||||
let result = completion::get_completions(&doc, ¶ms);
|
||||
assert!(result.is_some());
|
||||
|
||||
if let Some(response) = result {
|
||||
match response {
|
||||
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
|
||||
// Should NOT have top-level declaration keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "institution"),
|
||||
"institution should not appear inside schedule block"
|
||||
);
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "behavior"),
|
||||
"behavior should not appear inside schedule block"
|
||||
);
|
||||
// Should NOT have behavior-only keywords
|
||||
assert!(
|
||||
!items.iter().any(|item| item.label == "?"),
|
||||
"? should not appear inside schedule block"
|
||||
);
|
||||
},
|
||||
| _ => panic!("Expected array response"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_duplicate_completions() {
|
||||
let source = "character Alice {}\ncharacter Alice {}"; // Duplicate name
|
||||
|
||||
Reference in New Issue
Block a user