Added complete support for the new type system syntax including: - concept: Base type declarations - sub_concept: Enum and record sub-type definitions - concept_comparison: Compile-time pattern matching with conditional guards Parser changes: - Added VariantPattern, FieldCondition, and Condition AST nodes - Implemented "is" keyword for pattern matching (e.g., "CupType is Glass or CupType is Plastic") - Added Value::Any variant to support universal type matching - Disambiguated enum-like vs record-like sub_concept syntax LSP updates: - Added Value::Any match arms across code_actions, completion, hover, inlay_hints, and semantic_tokens - Type inference and formatting support for Any values Example fixes: - Fixed syntax error in baker-family behaviors (missing closing brace in nested if) - Removed deprecated core_enums.sb file
208 lines
7.5 KiB
Rust
208 lines
7.5 KiB
Rust
//! 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<Vec<InlayHint>> {
|
|
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<InlayHint>,
|
|
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(),
|
|
}
|
|
}
|