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