Skip to main content

genemichaels_lib/
lib.rs

1pub(crate) mod normalize_declarations;
2pub(crate) mod normalize_imports;
3pub(crate) mod sg_expr;
4pub(crate) mod sg_general;
5pub(crate) mod sg_general_lists;
6pub(crate) mod sg_pat;
7pub(crate) mod sg_root;
8pub(crate) mod sg_statement;
9pub(crate) mod sg_type;
10pub(crate) mod whitespace;
11
12pub use whitespace::{
13    HashLineColumn,
14    extract_whitespaces,
15    format_md,
16};
17use {
18    loga::{
19        Error,
20        ResultContext,
21        ea,
22    },
23    proc_macro2::{
24        Ident,
25        LineColumn,
26    },
27    quote::ToTokens,
28    serde::{
29        Deserialize,
30        Serialize,
31    },
32    sg_general::append_whitespace,
33    std::{
34        cell::{
35            Cell,
36            RefCell,
37        },
38        collections::BTreeMap,
39        fs,
40        io::Write,
41        process::{
42            Command,
43            Stdio,
44        },
45        rc::Rc,
46    },
47    syn::File,
48    tempfile::NamedTempFile,
49};
50
51pub trait Formattable {
52    fn has_attrs(&self) -> bool;
53    fn make_segs(&self, out: &mut MakeSegsState, base_indent: &Alignment) -> SplitGroupIdx;
54
55    fn normalize_declarations(
56        &mut self,
57        _config: &FormatConfig,
58        _whitespaces: &mut BTreeMap<HashLineColumn, (usize, Vec<Whitespace>)>,
59    ) {
60
61    }
62
63    fn normalize_imports(
64        &mut self,
65        _config: &FormatConfig,
66        _whitespaces: &mut BTreeMap<HashLineColumn, (usize, Vec<Whitespace>)>,
67    ) {
68
69    }
70}
71
72impl Formattable for &Ident {
73    fn has_attrs(&self) -> bool {
74        false
75    }
76
77    fn make_segs(&self, out: &mut MakeSegsState, base_indent: &Alignment) -> SplitGroupIdx {
78        (*self).make_segs(out, base_indent)
79    }
80}
81
82impl<F: Fn(&mut MakeSegsState, &Alignment) -> SplitGroupIdx> Formattable for F {
83    fn has_attrs(&self) -> bool {
84        false
85    }
86
87    fn make_segs(&self, line: &mut MakeSegsState, base_indent: &Alignment) -> SplitGroupIdx {
88        self(line, base_indent)
89    }
90}
91
92impl Formattable for Ident {
93    fn has_attrs(&self) -> bool {
94        false
95    }
96
97    fn make_segs(&self, out: &mut MakeSegsState, base_indent: &Alignment) -> SplitGroupIdx {
98        new_sg_lit(out, Some((base_indent, vec![self.span().start()])), self)
99    }
100}
101
102pub(crate) trait FormattablePunct {
103    fn span_start(&self) -> LineColumn;
104}
105
106pub(crate) trait FormattableStmt: ToTokens + Formattable {
107    fn want_margin(&self) -> (MarginGroup, bool);
108}
109
110#[derive(Clone)]
111pub struct Alignment(Rc<RefCell<Alignment_>>);
112
113impl Alignment {
114    pub(crate) fn activate(&self) {
115        self.0.borrow_mut().active = true;
116    }
117
118    pub(crate) fn get(&self) -> IndentLevel {
119        let parent = match &self.0.as_ref().borrow().parent {
120            Some(p) => p.get(),
121            None => {
122                return IndentLevel(0usize);
123            },
124        };
125        if self.0.as_ref().borrow_mut().active {
126            IndentLevel(parent.0 + 1)
127        } else {
128            parent
129        }
130    }
131
132    pub(crate) fn indent(&self) -> Alignment {
133        Alignment(Rc::new(RefCell::new(Alignment_ {
134            parent: Some(self.clone()),
135            active: false,
136        })))
137    }
138}
139
140impl std::fmt::Debug for Alignment {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        "(align)".fmt(f)
143    }
144}
145
146pub(crate) struct Alignment_ {
147    pub(crate) active: bool,
148    pub(crate) parent: Option<Alignment>,
149}
150
151pub(crate) fn check_split_brace_threshold(out: &MakeSegsState, count: usize) -> bool {
152    out.config.split_brace_threshold.map(|t| count >= t).unwrap_or(false)
153}
154
155#[derive(Debug, Clone)]
156pub struct Comment {
157    pub lines: String,
158    pub mode: CommentMode,
159    /// The offset in the original source where the comment started (location of // or
160    /// /* that initiated this specific comment mode)
161    pub orig_start_offset: usize,
162}
163
164#[derive(PartialEq, Clone, Copy, Debug)]
165pub enum CommentMode {
166    Directive,
167    DocInner,
168    DocOuter,
169    ExplicitNormal,
170    Normal,
171    Verbatim,
172}
173
174#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
175#[serde(rename_all = "snake_case")]
176pub enum DeclarationNormalizationCategory {
177    Concrete,
178    Const,
179    Enum,
180    Fn,
181    ForeignMod,
182    Macro,
183    MacroCall,
184    Mod,
185    Struct,
186    Trait,
187    TypeAlias,
188    Union,
189    Use,
190}
191
192
193#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
194#[serde(rename_all = "snake_case")]
195pub enum DeclarationNormalizationMode {
196    Auto,
197    ByCategory(Vec<DeclarationNormalizationCategory>),
198    ByName,
199    #[default]
200    None,
201}
202
203fn default_true() -> bool {
204    true
205}
206
207#[derive(Debug, Clone, Deserialize, Serialize)]
208pub struct ExternalFormatterConfig {
209    #[serde(default = "default_true")]
210    pub adjust_indent: bool,
211    pub commandline: Vec<String>,
212}
213
214pub fn format_ast(
215    mut ast: impl Formattable,
216    config: &FormatConfig,
217    whitespaces: BTreeMap<HashLineColumn, Vec<Whitespace>>,
218) -> Result<FormatRes, loga::Error> {
219    normalize_declarations::validate_declaration_normalization(config)?;
220    let mut whitespaces: BTreeMap<HashLineColumn, (usize, Vec<Whitespace>)> =
221        whitespaces.into_iter().map(|(k, v)| (k, (1, v))).collect();
222    ast.normalize_imports(config, &mut whitespaces);
223    ast.normalize_declarations(config, &mut whitespaces);
224
225    // Ensure any orphaned whitespace from import normalization (e.g. removed commas)
226    // is preserved in order to alert for formatting bugs (i.e. import normalization
227    // removed a symbol and didn't transpose the associated whitespace).
228    for (_, (count, _)) in whitespaces.iter_mut() {
229        if *count == 0 {
230            *count = 1;
231        }
232    }
233
234    // Build text
235    let mut out = MakeSegsState {
236        nodes: vec![],
237        segs: vec![],
238        whitespaces,
239        config: config.clone(),
240        macro_depth: Default::default(),
241        warnings: vec![],
242    };
243    let base_indent = Alignment(Rc::new(RefCell::new(Alignment_ {
244        parent: None,
245        active: false,
246    })));
247    let root = ast.make_segs(&mut out, &base_indent);
248    if out.whitespaces.contains_key(&HashLineColumn(LineColumn {
249        line: 0,
250        column: 1,
251    })) {
252        let mut sg = new_sg(&mut out);
253        append_whitespace(&mut out, &base_indent, &mut sg, LineColumn {
254            line: 0,
255            column: 1,
256        });
257        sg.build(&mut out);
258    }
259    let mut lines = Lines {
260        lines: vec![],
261        owned_lines: vec![],
262    };
263    {
264        let line_i = LineIdx(lines.owned_lines.len());
265        lines.owned_lines.push(Line {
266            index: 0,
267            segs: out.segs.iter().enumerate().map(|(i, _)| SegmentIdx(i)).collect(),
268        });
269        lines.lines.push(line_i);
270        for line_i in &lines.lines {
271            for (j, seg_i) in lines.owned_lines.get(line_i.0).unwrap().segs.iter().enumerate() {
272                out.segs.get_mut(seg_i.0).unwrap().line = Some(SegmentLine {
273                    line: *line_i,
274                    seg_index: j,
275                });
276            }
277        }
278    }
279    for (i, line_i) in lines.lines.iter().enumerate() {
280        let line = lines.owned_lines.get(line_i.0).unwrap();
281        assert_eq!(line.index, i, "line index wrong; initial");
282        for (j, seg_i) in line.segs.iter().enumerate() {
283            assert_eq!(
284                out.segs.get(seg_i.0).unwrap().line.as_ref().unwrap().seg_index,
285                j,
286                "seg index wrong; on line {}, initial",
287                i
288            );
289        }
290    }
291
292    // Do initial splits
293    //
294    // * initially split nodes
295    //
296    // * always split break segments
297    //
298    // * comments segments
299    {
300        let synth_seg_node = new_sg(&mut out).build(&mut out);
301        let mut i = 0usize;
302        let mut skip_first = false;
303        let mut prev_comment = None;
304        while i < lines.lines.len() {
305            let mut res = None;
306            {
307                let line_i = lines.lines.get(i).unwrap();
308                'segs : loop {
309                    for (i, seg_i) in lines.owned_lines.get(line_i.0).unwrap().segs.iter().enumerate() {
310                        if i == 0 && skip_first {
311                            skip_first = false;
312                            continue;
313                        }
314                        let seg = out.segs.get(seg_i.0).unwrap();
315                        let node = out.nodes.get(seg.node.0).unwrap();
316                        match (&seg.content, match (&seg.mode, node.split) {
317                            (SegmentMode::All, true) => true,
318                            (SegmentMode::All, false) => true,
319                            (SegmentMode::Unsplit, true) => false,
320                            (SegmentMode::Unsplit, false) => true,
321                            (SegmentMode::Split, true) => true,
322                            (SegmentMode::Split, false) => false,
323                        }) {
324                            (SegmentContent::Break(_, _), true) => {
325                                res = Some((*line_i, i, None));
326                                prev_comment = None;
327                                break 'segs;
328                            },
329                            (SegmentContent::Whitespace(c), _) => {
330                                res = Some((*line_i, i, None));
331                                prev_comment = Some(c.0.clone());
332                                break 'segs;
333                            },
334                            (_, _) => {
335                                if let Some(a) = prev_comment.take() {
336                                    let seg_i = SegmentIdx(out.segs.len());
337                                    out.segs.push(Segment {
338                                        node: synth_seg_node,
339                                        line: None,
340                                        mode: SegmentMode::All,
341                                        content: SegmentContent::Break(a, true),
342                                    });
343                                    res = Some((*line_i, i, Some(seg_i)));
344                                    break 'segs;
345                                }
346                            },
347                        };
348                    }
349                    prev_comment = None;
350                    break;
351                }
352            }
353            if let Some((line_i, at, insert_start)) = res {
354                split_line_at(&mut out, &mut lines, line_i, at, insert_start);
355                skip_first = true;
356            }
357            i += 1;
358        }
359    }
360
361    // Do width based splitting, other splitting
362    fn recurse(out: &mut MakeSegsState, lines: &mut Lines, config: &FormatConfig, sg_i: SplitGroupIdx) -> bool {
363        let mut split = false;
364        for seg_i in &out.nodes.get(sg_i.0).unwrap().segments {
365            let seg = out.segs.get(seg_i.0).unwrap();
366            let len = line_length(out, lines, seg.line.as_ref().unwrap().line);
367            if len > config.max_width {
368                split = true;
369                break;
370            }
371        }
372        if split {
373            split_group(out, lines, sg_i);
374        }
375        let mut split_from_child = false;
376        for child_sg_i in &out.nodes.get(sg_i.0).unwrap().children.clone() {
377            let new_split_from_child = recurse(out, lines, config, *child_sg_i);
378            split_from_child = split_from_child || new_split_from_child;
379        }
380        if !split && split_from_child {
381            split_group(out, lines, sg_i);
382        }
383        config.root_splits && (split || split_from_child)
384    }
385
386    recurse(&mut out, &mut lines, config, root);
387
388    struct Rendered {
389        col: usize,
390        text: String,
391    }
392
393    impl Rendered {
394        fn push(&mut self, c: char) {
395            self.text.push(c);
396            if c == '\n' {
397                self.col = 0;
398            } else {
399                self.col += 1;
400            }
401        }
402
403        fn push_str(&mut self, s: &str) {
404            self.text.push_str(s);
405            if let Some(nl) = s.rfind('\n') {
406                self.col = s[nl + 1..].chars().count();
407            } else {
408                self.col += s.chars().count();
409            }
410        }
411    }
412
413    // Render
414    let mut rendered = Rendered {
415        text: String::new(),
416        col: 0,
417    };
418    let mut warnings = vec![];
419    let lines = lines;
420    let mut line_i = 0usize;
421    while line_i < lines.lines.len() {
422        'continue_lineloop : loop {
423            let segs =
424                lines.owned_lines.get(lines.lines.get(line_i).unwrap().0).unwrap().segs.iter().filter_map(|seg_i| {
425                    let res = {
426                        let seg = out.segs.get(seg_i.0).unwrap();
427                        let node = out.nodes.get(seg.node.0).unwrap();
428                        match (&seg.mode, node.split) {
429                            (SegmentMode::All, _) => true,
430                            (SegmentMode::Unsplit, true) => false,
431                            (SegmentMode::Unsplit, false) => true,
432                            (SegmentMode::Split, true) => true,
433                            (SegmentMode::Split, false) => false,
434                        }
435                    };
436                    if res {
437                        Some(*seg_i)
438                    } else {
439                        None
440                    }
441                }).collect::<Vec<SegmentIdx>>();
442            if segs.is_empty() {
443                break 'continue_lineloop;
444            }
445            for (seg_i, seg_mem_i) in segs.iter().enumerate() {
446                let seg = out.segs.get(seg_mem_i.0).unwrap();
447                match &seg.content {
448                    SegmentContent::Text(t) => {
449                        let t = if seg_i == 1 && line_i > 0 {
450                            // Work around comments splitting lines at weird places (seg_i_i 0 == break,
451                            // except on first line)
452                            t.trim_start()
453                        } else {
454                            t
455                        };
456                        rendered.push_str(t);
457                    },
458                    SegmentContent::RawTextAdjustIndent(t) => {
459                        // Find the prefix length (chars up to and including the opening `"`) so lines 1+
460                        // align with the first line's content position.
461                        let prefix_len = t.find('"').map(|i| i + 1).unwrap_or(0);
462                        let indent_col = rendered.col + prefix_len;
463                        let mut text_lines = t.split('\n');
464                        if let Some(first_line) = text_lines.next() {
465                            rendered.push_str(first_line);
466                        }
467                        for line in text_lines {
468                            rendered.push('\n');
469                            let indent_str = " ".repeat(indent_col);
470                            rendered.push_str(&indent_str);
471                            rendered.push_str(line);
472                        }
473                    },
474                    SegmentContent::Break(b, activate) => {
475                        let next_line_first_seg_comment =
476                            lines
477                                .lines
478                                .get(line_i + 1)
479                                .map(|i| lines.owned_lines.get(i.0).unwrap())
480                                .and_then(|l| l.segs.first())
481                                .map(|seg_i| {
482                                    let seg = out.segs.get(seg_i.0).unwrap();
483                                    matches!(&seg.content, SegmentContent::Whitespace(_))
484                                })
485                                .unwrap_or(false);
486
487                        // since comments are always new lines we end up with duped newlines sometimes if
488                        // there's a (break), (comment) on consec lines. skip the break
489                        if segs.len() == 1 && next_line_first_seg_comment {
490                            break 'continue_lineloop;
491                        }
492                        if *activate {
493                            b.activate();
494                        }
495                        if segs.len() > 1 {
496                            // if empty line (=just break), don't write indent
497                            rendered.push_str(&render_indent(config, b.get()));
498                        }
499                    },
500                    SegmentContent::Whitespace((b, whitespaces)) => {
501                        for (comment_i, whitespace) in whitespaces.iter().enumerate() {
502                            match &whitespace.mode {
503                                WhitespaceMode::BlankLines(count) => {
504                                    if *count > 0 {
505                                        for _ in 0 .. *count {
506                                            rendered.push('\n');
507                                        }
508                                        continue;
509                                    }
510                                },
511                                WhitespaceMode::Comment(comment) => {
512                                    if comment_i > 0 {
513                                        rendered.push('\n');
514                                    }
515                                    let prefix = format!(
516                                        //. .
517                                        "{}//{} ",
518                                        render_indent(config, b.get()),
519                                        match comment.mode {
520                                            CommentMode::Normal => "",
521                                            CommentMode::ExplicitNormal => "?",
522                                            CommentMode::DocInner => "!",
523                                            CommentMode::DocOuter => "/",
524                                            CommentMode::Verbatim => ".",
525                                            CommentMode::Directive => "#",
526                                        }
527                                    );
528                                    let verbatim;
529                                    match comment.mode {
530                                        CommentMode::Directive => {
531                                            for (i, line) in comment.lines.lines().enumerate() {
532                                                if i > 0 {
533                                                    rendered.push('\n');
534                                                }
535                                                if let Some((k, v)) = line.split_once(":") {
536                                                    rendered.push_str(
537                                                        &format!("{}{}: {}", prefix, k.trim(), v.trim()),
538                                                    );
539                                                } else {
540                                                    rendered.push_str(&format!("{}{}", prefix, line.trim()));
541                                                }
542                                            }
543                                            continue;
544                                        },
545                                        CommentMode::Verbatim => {
546                                            verbatim = true;
547                                        },
548                                        CommentMode::Normal if config.explicit_markdown_comments => {
549                                            verbatim = true;
550                                        },
551                                        _ => {
552                                            match format_md(&mut rendered.text, config, &prefix, &comment.lines) {
553                                                Err(e) => {
554                                                    let err =
555                                                        loga::err_with(
556                                                            "Error formatting comments",
557                                                            ea!(
558                                                                line = whitespace.loc.line,
559                                                                column = whitespace.loc.column,
560                                                                comments = comment.lines
561                                                            ),
562                                                        );
563                                                    if config.comment_errors_fatal {
564                                                        return Err(err);
565                                                    } else {
566                                                        warnings.push(e);
567                                                    }
568                                                    verbatim = true;
569                                                },
570                                                Ok(_) => {
571                                                    let text = &rendered.text;
572                                                    rendered.col = if let Some(nl_pos) = text.rfind('\n') {
573                                                        text[nl_pos + 1..].chars().count()
574                                                    } else {
575                                                        text.chars().count()
576                                                    };
577                                                    verbatim = false;
578                                                },
579                                            }
580                                        },
581                                    };
582                                    if verbatim {
583                                        for (i, line) in comment.lines.lines().enumerate() {
584                                            if i > 0 {
585                                                rendered.push('\n');
586                                            }
587                                            let line = line.strip_prefix(' ').unwrap_or(line);
588                                            rendered.push_str(&format!("{}{}", prefix, line.trim_end()));
589                                        }
590                                    }
591                                },
592                            }
593                        }
594                    },
595                }
596            }
597            rendered.push('\n');
598            break;
599        }
600        line_i += 1;
601    }
602    let mk_warnings = std::mem::take(&mut out.warnings);
603    Ok(FormatRes {
604        rendered: rendered.text,
605        lost_comments: out.whitespaces.into_iter().map(|(k, (_, v))| (k, v)).collect(),
606        warnings: {
607            let mut all_warnings = mk_warnings;
608            all_warnings.extend(warnings);
609            all_warnings
610        },
611    })
612}
613
614pub fn format_str(source: &str, config: &FormatConfig) -> Result<FormatRes, loga::Error> {
615    let shebang;
616    let shebang_line_off;
617    let source1;
618    if source.starts_with("#!/") {
619        let shebang_end = match source.find("\n") {
620            Some(o) => o + 1,
621            None => source.len(),
622        };
623        shebang = Some(&source[..shebang_end]);
624        source1 = &source[shebang_end..];
625        shebang_line_off = 1;
626    } else {
627        shebang = None;
628        source1 = source;
629        shebang_line_off = 0;
630    }
631    let stripped_source = source1;
632    let (whitespaces, tokens) = extract_whitespaces(config.keep_max_blank_lines, stripped_source)?;
633    for ws_list in whitespaces.values() {
634        for ws in ws_list {
635            if let WhitespaceMode::Comment(comment) = &ws.mode {
636                if comment.mode == CommentMode::Directive {
637                    for line in comment.lines.lines() {
638                        if line.trim() == "genemichaels-file-skip" {
639                            return Ok(FormatRes {
640                                rendered: source.to_string(),
641                                lost_comments: BTreeMap::new(),
642                                warnings: vec![],
643                            });
644                        }
645                    }
646                }
647            }
648        }
649    }
650    let out =
651        format_ast(
652            syn::parse2::<File>(
653                tokens,
654            ).map_err(
655                |e| loga::err_with(
656                    "Syn error parsing Rust code",
657                    ea!(line = e.span().start().line + shebang_line_off, column = e.span().start().column, err = e),
658                ),
659            )?,
660            config,
661            whitespaces,
662        )?;
663    if let Some(shebang) = shebang {
664        return Ok(FormatRes {
665            rendered: format!("{}{}", shebang, out.rendered),
666            lost_comments: out.lost_comments,
667            warnings: out.warnings,
668        });
669    } else {
670        return Ok(out);
671    }
672}
673
674#[derive(Debug, Clone, Deserialize, Serialize)]
675#[serde(default)]
676pub struct FormatConfig {
677    pub comment_errors_fatal: bool,
678    pub comment_width: Option<usize>,
679    pub declaration_normalization: DeclarationNormalizationMode,
680    pub explicit_markdown_comments: bool,
681    pub external_formatters: BTreeMap<String, ExternalFormatterConfig>,
682    pub import_normalization: ImportNormalizationMode,
683    pub indent_spaces: usize,
684    /// Indent with spaces or tabs.
685    pub indent_unit: IndentUnit,
686    pub keep_max_blank_lines: usize,
687    pub max_width: usize,
688    pub root_splits: bool,
689    pub split_attributes: bool,
690    pub split_brace_threshold: Option<usize>,
691    pub split_where: bool,
692}
693
694impl Default for FormatConfig {
695    fn default() -> Self {
696        Self {
697            max_width: 120,
698            root_splits: false,
699            split_brace_threshold: Some(1usize),
700            split_attributes: true,
701            split_where: true,
702            comment_width: Some(80usize),
703            comment_errors_fatal: false,
704            keep_max_blank_lines: 0,
705            indent_spaces: 4,
706            indent_unit: IndentUnit::Spaces,
707            explicit_markdown_comments: false,
708            import_normalization: ImportNormalizationMode::None,
709            declaration_normalization: DeclarationNormalizationMode::None,
710            external_formatters: BTreeMap::new(),
711        }
712    }
713}
714
715pub struct FormatRes {
716    pub lost_comments: BTreeMap<HashLineColumn, Vec<Whitespace>>,
717    pub rendered: String,
718    pub warnings: Vec<Error>,
719}
720
721#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Default)]
722#[serde(rename_all = "snake_case")]
723pub enum ImportNormalizationMode {
724    Combine,
725    #[default]
726    None,
727    Split,
728}
729
730pub struct IncMacroDepth(Rc<Cell<usize>>);
731
732impl IncMacroDepth {
733    fn new(s: &MakeSegsState) -> Self {
734        s.macro_depth.update(|x| x + 1);
735        return Self(s.macro_depth.clone());
736    }
737}
738
739impl Drop for IncMacroDepth {
740    fn drop(&mut self) {
741        self.0.update(|x| x - 1);
742    }
743}
744
745struct IndentLevel(usize);
746
747#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
748#[serde(rename_all = "snake_case")]
749pub enum IndentUnit {
750    Spaces,
751    Tabs,
752}
753
754pub(crate) fn insert_line(out: &mut MakeSegsState, lines: &mut Lines, at: usize, segs: Vec<SegmentIdx>) {
755    let line_i = LineIdx(lines.owned_lines.len());
756    lines.owned_lines.push(Line {
757        index: at,
758        segs,
759    });
760    for (i, seg_i) in lines.owned_lines.get(line_i.0).unwrap().segs.iter().enumerate() {
761        let seg = out.segs.get_mut(seg_i.0).unwrap();
762        match seg.line.as_mut() {
763            Some(l) => {
764                l.line = line_i;
765                l.seg_index = i;
766            },
767            None => {
768                seg.line = Some(SegmentLine {
769                    line: line_i,
770                    seg_index: i,
771                });
772            },
773        };
774    }
775    lines.lines.insert(at, line_i);
776    for (i, line_i) in lines.lines.iter().enumerate().skip(at + 1) {
777        lines.owned_lines.get_mut(line_i.0).unwrap().index = i;
778    }
779    for (i, line_i) in lines.lines.iter().enumerate() {
780        let line = lines.owned_lines.get(line_i.0).unwrap();
781        assert_eq!(line.index, i, "line index wrong; after insert at line {}", at);
782        for (j, seg_i) in line.segs.iter().enumerate() {
783            assert_eq!(
784                out.segs.get(seg_i.0).unwrap().line.as_ref().unwrap().seg_index,
785                j,
786                "seg index wrong; on line {}, after insert at line {}",
787                i,
788                at
789            );
790        }
791    }
792}
793
794pub(crate) struct Line {
795    index: usize,
796    segs: Vec<SegmentIdx>,
797}
798
799pub(crate) fn line_length(out: &MakeSegsState, lines: &Lines, line_i: LineIdx) -> usize {
800    let mut max_len = 0;
801    let mut len = 0;
802    for seg_i in &lines.owned_lines.get(line_i.0).unwrap().segs {
803        let seg = out.segs.get(seg_i.0).unwrap();
804        match &seg.content {
805            SegmentContent::Text(t) => {
806                let mut first = true;
807                for line in t.split('\n') {
808                    if !first {
809                        max_len = max_len.max(len);
810                        len = line.chars().count();
811                    } else {
812                        len += line.chars().count();
813                        first = false;
814                    }
815                }
816            },
817            SegmentContent::Break(b, _) => {
818                if out.nodes.get(seg.node.0).unwrap().split {
819                    len += out.config.indent_spaces * b.get().0;
820                }
821            },
822            SegmentContent::Whitespace(_) => { },
823            SegmentContent::RawTextAdjustIndent(t) => {
824                let prefix_len = t.find('"').map(|i| i + 1).unwrap_or(0);
825                let start_len = len;
826                let mut first = true;
827                let mut local_max = 0;
828                for line in t.split('\n') {
829                    if !first {
830                        len = start_len + prefix_len + line.chars().count();
831                        local_max = local_max.max(len);
832                    } else {
833                        len += line.chars().count();
834                        local_max = local_max.max(len);
835                        first = false;
836                    }
837                }
838                len = local_max;
839            },
840        };
841    }
842    max_len.max(len)
843}
844
845#[derive(Clone, Copy)]
846pub(crate) struct LineIdx(usize);
847
848struct Lines {
849    lines: Vec<LineIdx>,
850    owned_lines: Vec<Line>,
851}
852
853pub struct MakeSegsState {
854    config: FormatConfig,
855    // MakeSegsState should be cloned at each call level with flags overwritable, but
856    // it's a huge amount of work and only applies to this field for now. So instead
857    // manage externally (for now).
858    /// Positive if processing within a macro, used to control macro tweaks.
859    macro_depth: Rc<Cell<usize>>,
860    nodes: Vec<SplitGroup>,
861    segs: Vec<Segment>,
862    pub(crate) warnings: Vec<Error>,
863    whitespaces: BTreeMap<HashLineColumn, (usize, Vec<Whitespace>)>,
864}
865
866#[derive(PartialEq, Debug)]
867pub(crate) enum MarginGroup {
868    Attr,
869    BlockDef,
870    Import,
871    None,
872}
873
874pub(crate) fn new_sg(out: &mut MakeSegsState) -> SplitGroupBuilder {
875    let idx = SplitGroupIdx(out.nodes.len());
876    out.nodes.push(SplitGroup {
877        split: false,
878        segments: vec![],
879        children: vec![],
880    });
881    SplitGroupBuilder {
882        node: idx,
883        segs: vec![],
884        children: vec![],
885        initial_split: false,
886        reverse_children: false,
887    }
888}
889
890pub(crate) fn new_sg_lit(
891    out: &mut MakeSegsState,
892    start: Option<(&Alignment, Vec<LineColumn>)>,
893    text: impl ToString,
894) -> SplitGroupIdx {
895    let mut sg = new_sg(out);
896    if let Some((base_indent, starts)) = start {
897        for loc in starts {
898            append_whitespace(out, base_indent, &mut sg, loc);
899        }
900    }
901    sg.seg(out, text.to_string());
902    sg.build(out)
903}
904
905fn render_indent(config: &FormatConfig, current_indent: IndentLevel) -> String {
906    match config.indent_unit {
907        IndentUnit::Spaces => return " ".repeat(config.indent_spaces * current_indent.0),
908        IndentUnit::Tabs => return "\t".repeat(current_indent.0),
909    }
910}
911
912pub(crate) fn run_external_formatter(command: &[String], content: &str) -> Result<String, loga::Error> {
913    let mut tmp_file: Option<NamedTempFile> = None;
914    let args = command[1..].iter().map(|a| -> Result<String, loga::Error> {
915        if a == "{}" {
916            let f = match tmp_file {
917                Some(ref f) => f,
918                None => {
919                    let mut f = NamedTempFile::new().context("Failed to create external formatter temp file")?;
920                    f.write_all(content.as_bytes()).context("Failed to write external formatter temp file")?;
921                    f.flush().context("Failed to flush external formatter temp file")?;
922                    tmp_file.insert(f)
923                },
924            };
925            return Ok(f.path().to_string_lossy().to_string());
926        } else {
927            return Ok(a.clone());
928        }
929    }).collect::<Result<Vec<_>, _>>()?;
930    if let Some(ref tmp_file) = tmp_file {
931        let status =
932            Command::new(&command[0])
933                .args(&args)
934                .status()
935                .context_with("Failed to run external formatter", ea!(cmd = command[0].as_str()))?;
936        if !status.success() {
937            return Err(loga::err_with("External formatter exited with error", ea!(cmd = &command[0])));
938        }
939        return fs::read_to_string(tmp_file.path()).context("Failed to read external formatter output");
940    } else {
941        let mut child =
942            Command::new(&command[0])
943                .args(&args)
944                .stdin(Stdio::piped())
945                .stdout(Stdio::piped())
946                .spawn()
947                .context_with("Failed to spawn external formatter", ea!(cmd = command[0].as_str()))?;
948        child
949            .stdin
950            .take()
951            .unwrap()
952            .write_all(content.as_bytes())
953            .context("Failed to write to external formatter stdin")?;
954        let output = child.wait_with_output().context("Failed to get external formatter output")?;
955        if !output.status.success() {
956            return Err(loga::err_with("External formatter exited with error", ea!(cmd = &command[0])));
957        }
958        return String::from_utf8(output.stdout)
959            .map_err(loga::err)
960            .context("External formatter output is not valid UTF-8");
961    }
962}
963
964pub(crate) struct Segment {
965    pub(crate) content: SegmentContent,
966    pub(crate) line: Option<SegmentLine>,
967    pub(crate) mode: SegmentMode,
968    pub(crate) node: SplitGroupIdx,
969}
970
971#[derive(Debug)]
972pub(crate) enum SegmentContent {
973    Break(Alignment, bool),
974    RawTextAdjustIndent(String),
975    Text(String),
976    Whitespace((Alignment, Vec<Whitespace>)),
977}
978
979#[derive(Clone, Copy)]
980pub struct SegmentIdx(usize);
981
982pub(crate) struct SegmentLine {
983    pub(crate) line: LineIdx,
984    pub(crate) seg_index: usize,
985}
986
987#[derive(Debug, Clone, Copy)]
988pub(crate) enum SegmentMode {
989    All,
990    Split,
991    Unsplit,
992}
993
994pub(crate) fn split_group(out: &mut MakeSegsState, lines: &mut Lines, sg_i: SplitGroupIdx) {
995    let sg = out.nodes.get_mut(sg_i.0).unwrap();
996    sg.split = true;
997    for seg_i in &sg.segments.clone() {
998        let res = {
999            let seg = out.segs.get(seg_i.0).unwrap();
1000            match (&seg.mode, &seg.content) {
1001                (SegmentMode::Split, SegmentContent::Break(_, _)) => {
1002                    let seg_line = seg.line.as_ref().unwrap();
1003                    Some((seg_line.line, seg_line.seg_index))
1004                },
1005                _ => None,
1006            }
1007        };
1008        if let Some((line_i, off)) = res {
1009            split_line_at(out, lines, line_i, off, None);
1010        };
1011    }
1012}
1013
1014pub(crate) fn split_line_at(
1015    out: &mut MakeSegsState,
1016    lines: &mut Lines,
1017    line_idx: LineIdx,
1018    off: usize,
1019    inject_start: Option<SegmentIdx>,
1020) {
1021    let line = lines.owned_lines.get_mut(line_idx.0).unwrap();
1022    let mut new_segs = vec![];
1023    if let Some(s) = inject_start {
1024        new_segs.push(s);
1025    }
1026    new_segs.extend(line.segs.split_off(off));
1027    {
1028        let seg_i = new_segs.get(0).unwrap();
1029        let seg = out.segs.get(seg_i.0).unwrap();
1030        match &seg.content {
1031            SegmentContent::Break(a, activate) => {
1032                if *activate {
1033                    a.activate();
1034                }
1035            },
1036            SegmentContent::Whitespace((a, _)) => {
1037                a.activate();
1038            },
1039            _ => { },
1040        };
1041    }
1042    let insert_at = line.index + 1;
1043    insert_line(out, lines, insert_at, new_segs);
1044}
1045
1046pub struct SplitGroup {
1047    pub(crate) children: Vec<SplitGroupIdx>,
1048    pub(crate) segments: Vec<SegmentIdx>,
1049    pub(crate) split: bool,
1050}
1051
1052pub(crate) struct SplitGroupBuilder {
1053    children: Vec<SplitGroupIdx>,
1054    initial_split: bool,
1055    pub(crate) node: SplitGroupIdx,
1056    reverse_children: bool,
1057    segs: Vec<SegmentIdx>,
1058}
1059
1060impl SplitGroupBuilder {
1061    pub(crate) fn add(&mut self, out: &mut MakeSegsState, seg: Segment) {
1062        let idx = SegmentIdx(out.segs.len());
1063        out.segs.push(seg);
1064        self.segs.push(idx);
1065    }
1066
1067    pub(crate) fn build(self, out: &mut MakeSegsState) -> SplitGroupIdx {
1068        let sg = out.nodes.get_mut(self.node.0).unwrap();
1069        sg.split = self.initial_split;
1070        sg.children = self.children;
1071        if self.reverse_children {
1072            sg.children.reverse();
1073        }
1074        sg.segments = self.segs;
1075        for seg in &sg.segments {
1076            out.segs.get_mut(seg.0).unwrap().node = self.node;
1077        }
1078        self.node
1079    }
1080
1081    pub(crate) fn child(&mut self, child: SplitGroupIdx) {
1082        self.children.push(child);
1083    }
1084
1085    pub(crate) fn initial_split(&mut self) {
1086        self.initial_split = true;
1087    }
1088
1089    pub(crate) fn reverse_children(&mut self) {
1090        self.reverse_children = true;
1091    }
1092
1093    pub(crate) fn seg(&mut self, out: &mut MakeSegsState, text: impl ToString) {
1094        self.add(out, Segment {
1095            node: self.node,
1096            line: None,
1097            mode: SegmentMode::All,
1098            content: SegmentContent::Text(text.to_string()),
1099        });
1100    }
1101
1102    pub(crate) fn seg_split(&mut self, out: &mut MakeSegsState, text: impl ToString) {
1103        self.add(out, Segment {
1104            node: self.node,
1105            line: None,
1106            mode: SegmentMode::Split,
1107            content: SegmentContent::Text(text.to_string()),
1108        });
1109    }
1110
1111    pub(crate) fn seg_unsplit(&mut self, out: &mut MakeSegsState, text: impl ToString) {
1112        self.add(out, Segment {
1113            node: self.node,
1114            line: None,
1115            mode: SegmentMode::Unsplit,
1116            content: SegmentContent::Text(text.to_string()),
1117        });
1118    }
1119
1120    pub(crate) fn split(&mut self, out: &mut MakeSegsState, alignment: Alignment, activate: bool) {
1121        self.add(out, Segment {
1122            node: self.node,
1123            line: None,
1124            mode: SegmentMode::Split,
1125            content: SegmentContent::Break(alignment, activate),
1126        });
1127    }
1128
1129    pub(crate) fn split_always(&mut self, out: &mut MakeSegsState, alignment: Alignment, activate: bool) {
1130        self.add(out, Segment {
1131            node: self.node,
1132            line: None,
1133            mode: SegmentMode::All,
1134            content: SegmentContent::Break(alignment, activate),
1135        });
1136    }
1137
1138    pub(crate) fn split_if(&mut self, out: &mut MakeSegsState, alignment: Alignment, always: bool, activate: bool) {
1139        self.add(out, Segment {
1140            node: self.node,
1141            line: None,
1142            mode: if always {
1143                SegmentMode::All
1144            } else {
1145                SegmentMode::Split
1146            },
1147            content: SegmentContent::Break(alignment, activate),
1148        });
1149    }
1150}
1151
1152#[derive(Clone, Copy)]
1153pub struct SplitGroupIdx(usize);
1154
1155#[derive(Debug, Clone)]
1156pub struct Whitespace {
1157    // The loc of the AST node this whitespace is associated with. Special loc (0, 1)
1158    // == end of file.
1159    pub loc: LineColumn,
1160    pub mode: WhitespaceMode,
1161}
1162
1163#[derive(Debug, Clone)]
1164pub enum WhitespaceMode {
1165    BlankLines(usize),
1166    Comment(Comment),
1167}