Files
storybook/src/lsp/formatting.rs
Sienna Meridian Satterwhite 16deb5d237 release: Storybook v0.2.0 - Major syntax and features update
BREAKING CHANGES:
- Relationship syntax now requires blocks for all participants
- Removed self/other perspective blocks from relationships
- Replaced 'guard' keyword with 'if' for behavior tree decorators

Language Features:
- Add tree-sitter grammar with improved if/condition disambiguation
- Add comprehensive tutorial and reference documentation
- Add SBIR v0.2.0 binary format specification
- Add resource linking system for behaviors and schedules
- Add year-long schedule patterns (day, season, recurrence)
- Add behavior tree enhancements (named nodes, decorators)

Documentation:
- Complete tutorial series (9 chapters) with baker family examples
- Complete reference documentation for all language features
- SBIR v0.2.0 specification with binary format details
- Added locations and institutions documentation

Examples:
- Convert all examples to baker family scenario
- Add comprehensive working examples

Tooling:
- Zed extension with LSP integration
- Tree-sitter grammar for syntax highlighting
- Build scripts and development tools

Version Updates:
- Main package: 0.1.0 → 0.2.0
- Tree-sitter grammar: 0.1.0 → 0.2.0
- Zed extension: 0.1.0 → 0.2.0
- Storybook editor: 0.1.0 → 0.2.0
2026-02-13 21:52:03 +00:00

147 lines
3.8 KiB
Rust

//! Document formatting provider
//!
//! Provides auto-formatting for Storybook files
use tower_lsp::lsp_types::{
FormattingOptions,
Position,
Range,
TextEdit,
};
use super::document::Document;
/// Format the entire document
pub fn format_document(doc: &Document, _options: &FormattingOptions) -> Option<Vec<TextEdit>> {
// Don't format if there are parse errors - the AST would be invalid
// and formatting could produce garbage output
doc.ast.as_ref()?;
// For now, implement basic formatting rules:
// 1. 4-space indentation
// 2. Consistent spacing around colons
// 3. Blank lines between top-level declarations
let formatted = format_text(&doc.text);
if formatted == doc.text {
return None; // No changes needed
}
// Return a single edit that replaces the entire document
Some(vec![TextEdit {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: doc.positions.line_count() as u32,
character: 0,
},
},
new_text: formatted,
}])
}
/// Format the text according to Storybook style rules
pub(crate) fn format_text(text: &str) -> String {
let mut result = String::new();
let mut indent_level = 0;
let mut prev_was_blank = false;
let mut in_prose_block = false;
for line in text.lines() {
let trimmed = line.trim();
// Handle prose blocks - don't format their content
if trimmed.starts_with("---") {
in_prose_block = !in_prose_block;
result.push_str(&" ".repeat(indent_level));
result.push_str(trimmed);
result.push('\n');
continue;
}
if in_prose_block {
// Preserve prose content exactly as-is
result.push_str(line);
result.push('\n');
continue;
}
// Skip blank lines
if trimmed.is_empty() {
if !prev_was_blank {
result.push('\n');
prev_was_blank = true;
}
continue;
}
prev_was_blank = false;
// Adjust indentation based on braces
if trimmed.starts_with('}') {
indent_level = indent_level.saturating_sub(1);
}
// Add indentation
result.push_str(&" ".repeat(indent_level));
// Format the line content
let formatted_line = format_line(trimmed);
result.push_str(&formatted_line);
result.push('\n');
// Increase indentation for opening braces
if trimmed.ends_with('{') {
indent_level += 1;
}
}
result
}
/// Format a single line
fn format_line(line: &str) -> String {
// Ensure consistent spacing around colons in field assignments
if let Some(colon_pos) = line.find(':') {
if !line.starts_with("//") && !line.contains("::") {
let before = line[..colon_pos].trim_end();
let after = line[colon_pos + 1..].trim_start();
return format!("{}: {}", before, after);
}
}
line.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_formatting() {
let input = "character Alice{age:25}";
let formatted = format_text(input);
// Check key formatting features
assert!(
formatted.contains("age: 25"),
"Should have space after colon"
);
assert!(
formatted.contains("character Alice"),
"Should have character declaration"
);
}
#[test]
fn test_preserve_prose() {
let input = "---backstory\nSome irregular spacing\n---";
let formatted = format_text(input);
assert!(formatted.contains("Some irregular spacing"));
}
}