texlab/features/formatting/
bibtex_internal.rs

1use 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}