texlab/features/formatting/
bibtex_internal.rs1use lsp_types::{DocumentFormattingParams, TextEdit};
2use rowan::{ast::AstNode, NodeOrToken};
3
4use crate::{
5 features::FeatureRequest,
6 syntax::bibtex::{self, HasName, HasType, HasValue},
7 LineIndex, LineIndexExt,
8};
9
10pub fn format_bibtex_internal(
11 request: &FeatureRequest<DocumentFormattingParams>,
12) -> Option<Vec<TextEdit>> {
13 let mut indent = String::new();
14 if request.params.options.insert_spaces {
15 for _ in 0..request.params.options.tab_size {
16 indent.push(' ');
17 }
18 } else {
19 indent.push('\t');
20 }
21
22 let line_length = request
23 .workspace
24 .environment
25 .options
26 .formatter_line_length
27 .map(|value| {
28 if value <= 0 {
29 usize::MAX
30 } else {
31 value as usize
32 }
33 })
34 .unwrap_or(80);
35
36 let document = request.main_document();
37 let data = document.data().as_bibtex()?;
38 let mut edits = Vec::new();
39
40 for node in bibtex::SyntaxNode::new_root(data.green.clone())
41 .children()
42 .filter(|node| {
43 matches!(
44 node.kind(),
45 bibtex::PREAMBLE | bibtex::STRING | bibtex::ENTRY
46 )
47 })
48 {
49 let range = node.text_range();
50
51 let mut formatter = Formatter::new(
52 indent.clone(),
53 request.params.options.tab_size,
54 line_length,
55 document.line_index(),
56 );
57
58 formatter.visit_node(node);
59 edits.push(TextEdit {
60 range: document.line_index().line_col_lsp_range(range),
61 new_text: formatter.output,
62 });
63 }
64
65 Some(edits)
66}
67
68struct Formatter<'a> {
69 indent: String,
70 tab_size: u32,
71 line_length: usize,
72 output: String,
73 align: Vec<usize>,
74 line_index: &'a LineIndex,
75}
76
77impl<'a> Formatter<'a> {
78 fn new(indent: String, tab_size: u32, line_length: usize, line_index: &'a LineIndex) -> Self {
79 Self {
80 indent,
81 tab_size,
82 line_length,
83 output: String::new(),
84 align: Vec::new(),
85 line_index,
86 }
87 }
88
89 fn visit_token_lowercase(&mut self, token: &bibtex::SyntaxToken) {
90 self.output.push_str(&token.text().to_lowercase());
91 }
92
93 fn should_insert_space(
94 &self,
95 previous: &bibtex::SyntaxToken,
96 current: &bibtex::SyntaxToken,
97 ) -> bool {
98 let previous_range = self.line_index.line_col_lsp_range(previous.text_range());
99 let current_range = self.line_index.line_col_lsp_range(current.text_range());
100 previous_range.start.line != current_range.start.line
101 || previous_range.end.character < current_range.start.character
102 }
103
104 fn base_align(&self) -> usize {
105 self.output[self.output.rfind('\n').unwrap_or(0)..]
106 .chars()
107 .count()
108 }
109
110 fn visit_node(&mut self, parent: bibtex::SyntaxNode) {
111 match parent.kind() {
112 bibtex::PREAMBLE => {
113 let preamble = bibtex::Preamble::cast(parent).unwrap();
114 self.visit_token_lowercase(&preamble.type_token().unwrap());
115 self.output.push('{');
116 if preamble.syntax().children().next().is_some() {
117 self.align.push(self.base_align());
118 for node in preamble.syntax().children() {
119 self.visit_node(node);
120 }
121 self.output.push('}');
122 }
123 }
124 bibtex::STRING => {
125 let string = bibtex::StringDef::cast(parent).unwrap();
126 self.visit_token_lowercase(&string.type_token().unwrap());
127 self.output.push('{');
128 if let Some(name) = string.name_token() {
129 self.output.push_str(name.text());
130 self.output.push_str(" = ");
131 if let Some(value) = string.value() {
132 self.align.push(self.base_align());
133 self.visit_node(value.syntax().clone());
134 self.output.push('}');
135 }
136 }
137 }
138 bibtex::ENTRY => {
139 let entry = bibtex::Entry::cast(parent).unwrap();
140 self.visit_token_lowercase(&entry.type_token().unwrap());
141 self.output.push('{');
142 if let Some(key) = entry.name_token() {
143 self.output.push_str(&key.to_string());
144 self.output.push(',');
145 self.output.push('\n');
146 for field in entry.fields() {
147 self.visit_node(field.syntax().clone());
148 }
149 self.output.push('}');
150 }
151 }
152 bibtex::FIELD => {
153 let field = bibtex::Field::cast(parent).unwrap();
154 self.output.push_str(&self.indent);
155 let name = field.name_token().unwrap();
156 self.output.push_str(name.text());
157 self.output.push_str(" = ");
158 if let Some(value) = field.value() {
159 let count = name.text().chars().count();
160 self.align.push(self.tab_size as usize + count + 3);
161 self.visit_node(value.syntax().clone());
162 self.output.push(',');
163 self.output.push('\n');
164 }
165 }
166 kind if bibtex::Value::can_cast(kind) => {
167 let tokens: Vec<_> = parent
168 .descendants_with_tokens()
169 .filter_map(|element| element.into_token())
170 .filter(|token| token.kind() != bibtex::WHITESPACE)
171 .collect();
172
173 self.output.push_str(tokens[0].text());
174
175 let align = self.align.pop().unwrap_or_default();
176 let mut length = align + tokens[0].text().chars().count();
177 for i in 1..tokens.len() {
178 let previous = &tokens[i - 1];
179 let current = &tokens[i];
180 let current_length = current.text().chars().count();
181
182 let insert_space = self.should_insert_space(previous, current);
183 let space_length = if insert_space { 1 } else { 0 };
184
185 if length + current_length + space_length > self.line_length {
186 self.output.push('\n');
187 self.output.push_str(self.indent.as_ref());
188 for _ in 0..=align - self.tab_size as usize {
189 self.output.push(' ');
190 }
191 length = align;
192 } else if insert_space {
193 self.output.push(' ');
194 length += 1;
195 }
196 self.output.push_str(current.text());
197 length += current_length;
198 }
199 }
200 bibtex::ROOT | bibtex::JUNK => {
201 for element in parent.children_with_tokens() {
202 match element {
203 NodeOrToken::Token(token) => {
204 self.output.push_str(token.text());
205 }
206 NodeOrToken::Node(node) => {
207 self.visit_node(node);
208 }
209 }
210 }
211 }
212 _ => unreachable!(),
213 }
214 }
215}