lex_lsp/features/
formatting.rs1use lex_babel::formats::lex::formatting_rules::FormattingRules;
2use lex_babel::transforms::{serialize_to_lex, serialize_to_lex_with_rules};
3use lex_core::lex::ast::Document;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct TextEditSpan {
8 pub start: usize,
9 pub end: usize,
10 pub new_text: String,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct LineRange {
16 pub start: usize,
17 pub end: usize,
18}
19
20pub fn format_document(
26 document: &Document,
27 source: &str,
28 rules: Option<FormattingRules>,
29) -> Vec<TextEditSpan> {
30 let formatted = match rules {
31 Some(r) => serialize_to_lex_with_rules(document, r),
32 None => serialize_to_lex(document),
33 };
34 let formatted = match formatted {
35 Ok(text) => text,
36 Err(_) => return Vec::new(),
37 };
38
39 if formatted == source {
41 return Vec::new();
42 }
43
44 vec![TextEditSpan {
46 start: 0,
47 end: source.len(),
48 new_text: formatted,
49 }]
50}
51
52pub fn format_range(
57 document: &Document,
58 source: &str,
59 _range: LineRange,
60 rules: Option<FormattingRules>,
61) -> Vec<TextEditSpan> {
62 format_document(document, source, rules)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use lex_core::lex::parsing;
69
70 const FULL_FIXTURE: &str = "Section:\n\n - item one \n\n\n\n\n - item two\n\n";
71
72 fn parse(source: &str) -> Document {
73 parsing::parse_document(source).expect("parse fixture")
74 }
75
76 fn apply_span(source: &str, edit: &TextEditSpan) -> String {
77 let mut result = source.to_string();
78 result.replace_range(edit.start..edit.end, &edit.new_text);
79 result
80 }
81
82 #[test]
83 fn formats_entire_document() {
84 let source = FULL_FIXTURE;
85 let document = parse(source);
86 let formatted = serialize_to_lex(&document).unwrap();
87 assert_ne!(formatted, source);
88
89 let edits = format_document(&document, source, None);
90 assert_eq!(edits.len(), 1, "should return single full-replacement edit");
91
92 let edit = &edits[0];
93 assert_eq!(edit.start, 0);
94 assert_eq!(edit.end, source.len());
95
96 let applied = apply_span(source, edit);
97 assert_eq!(applied, formatted);
98 }
99
100 #[test]
101 fn range_formatting_does_full_replacement() {
102 let source = FULL_FIXTURE;
104 let document = parse(source);
105 let range = LineRange { start: 2, end: 5 }; let edits = format_range(&document, source, range, None);
107
108 assert_eq!(edits.len(), 1);
109 assert_eq!(edits[0].start, 0);
110 assert_eq!(edits[0].end, source.len());
111 }
112
113 #[test]
114 fn no_edits_when_already_formatted() {
115 let source = "Section:\n - item\n";
116 let document = parse(source);
117 let edits = format_document(&document, source, None);
118 assert!(edits.is_empty());
119 }
120
121 #[test]
122 fn format_with_custom_rules() {
123 let source = "Section:\n - item\n";
124 let document = parse(source);
125 let rules = FormattingRules {
126 indent_string: " ".to_string(), ..Default::default()
128 };
129
130 let edits = format_document(&document, source, Some(rules));
131 assert_eq!(edits.len(), 1);
132 let applied = apply_span(source, &edits[0]);
133 assert!(applied.contains(" - item")); }
135}