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
This commit is contained in:
146
src/lsp/formatting.rs
Normal file
146
src/lsp/formatting.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
//! 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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user