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
147 lines
3.8 KiB
Rust
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"));
|
|
}
|
|
}
|