//! Inlay hints for implicit information //! //! Provides inline annotations showing: //! - Parameter names in action calls //! - Inferred types for field values //! - Template/species field sources use tower_lsp::lsp_types::{ InlayHint, InlayHintKind, InlayHintLabel, Position, }; use super::document::Document; use crate::syntax::ast::{ Declaration, Field, Value, }; /// Get inlay hints for a document range pub fn get_inlay_hints(doc: &Document, start: Position, end: Position) -> Option> { let ast = doc.ast.as_ref()?; let mut hints = Vec::new(); let mut positions = doc.positions.clone(); // Convert positions to offsets let start_line = start.line as usize; let end_line = end.line as usize; // Process all declarations for decl in &ast.declarations { match decl { | Declaration::Character(character) => { // Skip if character is outside requested range if character.span.start_line > end_line || character.span.end_line < start_line { continue; } // Add type hints for character fields for field in &character.fields { if field.span.start_line >= start_line && field.span.start_line <= end_line { add_type_hint(&mut hints, &mut positions, field); } } }, | Declaration::Template(template) => { // Skip if template is outside requested range if template.span.start_line > end_line || template.span.end_line < start_line { continue; } // Add type hints for template fields for field in &template.fields { if field.span.start_line >= start_line && field.span.start_line <= end_line { add_type_hint(&mut hints, &mut positions, field); } } }, | Declaration::Species(species) => { // Skip if species is outside requested range if species.span.start_line > end_line || species.span.end_line < start_line { continue; } // Add type hints for species fields for field in &species.fields { if field.span.start_line >= start_line && field.span.start_line <= end_line { add_type_hint(&mut hints, &mut positions, field); } } }, | Declaration::Institution(institution) => { // Skip if institution is outside requested range if institution.span.start_line > end_line || institution.span.end_line < start_line { continue; } // Add type hints for institution fields for field in &institution.fields { if field.span.start_line >= start_line && field.span.start_line <= end_line { add_type_hint(&mut hints, &mut positions, field); } } }, | Declaration::Location(location) => { // Skip if location is outside requested range if location.span.start_line > end_line || location.span.end_line < start_line { continue; } // Add type hints for location fields for field in &location.fields { if field.span.start_line >= start_line && field.span.start_line <= end_line { add_type_hint(&mut hints, &mut positions, field); } } }, | Declaration::Relationship(relationship) => { // Skip if relationship is outside requested range if relationship.span.start_line > end_line || relationship.span.end_line < start_line { continue; } // Add type hints for relationship fields for field in &relationship.fields { if field.span.start_line >= start_line && field.span.start_line <= end_line { add_type_hint(&mut hints, &mut positions, field); } } }, | Declaration::Behavior(behavior) => { // Skip if behavior is outside requested range if behavior.span.start_line > end_line || behavior.span.end_line < start_line { continue; } // TODO: Add parameter name hints for action calls in behavior // trees Would need to traverse BehaviorNode // tree and match actions to schema }, | _ => {}, } } if hints.is_empty() { None } else { Some(hints) } } /// Add type hint for a field value fn add_type_hint( hints: &mut Vec, positions: &mut crate::position::PositionTracker, field: &Field, ) { let type_str = infer_value_type(&field.value); // Only add hints for non-obvious types // Skip if the type is clear from the literal (e.g., "string", 123, true) let should_hint = match &field.value { | Value::String(_) | Value::Int(_) | Value::Float(_) | Value::Bool(_) => false, | Value::Identifier(_) => true, // Show type for identifier references | Value::List(_) => true, // Show list element type | Value::Object(_) => false, // Object structure is visible | Value::Range(_, _) => false, // Range syntax is clear | Value::Time(_) => false, // Time format is clear | Value::Duration(_) => false, // Duration format is clear | Value::ProseBlock(_) => false, // Prose is obvious | Value::Override(_) => true, // Show what's being overridden | Value::Any => true, // Show Any type marker }; if !should_hint { return; } // Position the hint at the end of the field value let (line, col) = positions.offset_to_position(field.span.end); hints.push(InlayHint { position: Position { line: line as u32, character: col as u32, }, label: InlayHintLabel::String(format!(": {}", type_str)), kind: Some(InlayHintKind::TYPE), text_edits: None, tooltip: None, padding_left: Some(true), padding_right: None, data: None, }); } /// Infer the type of a value for display fn infer_value_type(value: &Value) -> String { match value { | Value::Identifier(path) => path.join("."), | Value::String(_) => "String".to_string(), | Value::Int(_) => "Int".to_string(), | Value::Float(_) => "Float".to_string(), | Value::Bool(_) => "Bool".to_string(), | Value::List(items) => { if items.is_empty() { "[]".to_string() } else { format!("[{}]", infer_value_type(&items[0])) } }, | Value::Object(_) => "Object".to_string(), | Value::Range(start, end) => { format!("{}..{}", infer_value_type(start), infer_value_type(end)) }, | Value::Time(_) => "Time".to_string(), | Value::Duration(_) => "Duration".to_string(), | Value::ProseBlock(_) => "Prose".to_string(), | Value::Override(_) => "Override".to_string(), | Value::Any => "Any".to_string(), } }