]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
:arrow_up: rust-analyzer
[rust.git] / src / tools / rust-analyzer / crates / ide-db / src / syntax_helpers / insert_whitespace_into_node.rs
1 //! Utilities for formatting macro expanded nodes until we get a proper formatter.
2 use syntax::{
3     ast::make,
4     ted::{self, Position},
5     NodeOrToken,
6     SyntaxKind::{self, *},
7     SyntaxNode, SyntaxToken, WalkEvent, T,
8 };
9
10 // FIXME: It would also be cool to share logic here and in the mbe tests,
11 // which are pretty unreadable at the moment.
12 /// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
13 pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
14     let mut indent = 0;
15     let mut last: Option<SyntaxKind> = None;
16     let mut mods = Vec::new();
17     let syn = syn.clone_subtree().clone_for_update();
18
19     let before = Position::before;
20     let after = Position::after;
21
22     let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| {
23         (pos(token.clone()), make::tokens::whitespace(&" ".repeat(2 * indent)))
24     };
25     let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| {
26         (pos(token.clone()), make::tokens::single_space())
27     };
28     let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| {
29         (pos(token.clone()), make::tokens::single_newline())
30     };
31
32     for event in syn.preorder_with_tokens() {
33         let token = match event {
34             WalkEvent::Enter(NodeOrToken::Token(token)) => token,
35             WalkEvent::Leave(NodeOrToken::Node(node))
36                 if matches!(
37                     node.kind(),
38                     ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL | MACRO_RULES
39                 ) =>
40             {
41                 if indent > 0 {
42                     mods.push((
43                         Position::after(node.clone()),
44                         make::tokens::whitespace(&" ".repeat(2 * indent)),
45                     ));
46                 }
47                 if node.parent().is_some() {
48                     mods.push((Position::after(node), make::tokens::single_newline()));
49                 }
50                 continue;
51             }
52             _ => continue,
53         };
54         let tok = &token;
55
56         let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
57             tok.next_token().map(|it| f(it.kind())).unwrap_or(default)
58         };
59         let is_last =
60             |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
61
62         match tok.kind() {
63             k if is_text(k) && is_next(|it| !it.is_punct() || it == UNDERSCORE, false) => {
64                 mods.push(do_ws(after, tok));
65             }
66             L_CURLY if is_next(|it| it != R_CURLY, true) => {
67                 indent += 1;
68                 if is_last(is_text, false) {
69                     mods.push(do_ws(before, tok));
70                 }
71
72                 mods.push(do_indent(after, tok, indent));
73                 mods.push(do_nl(after, tok));
74             }
75             R_CURLY if is_last(|it| it != L_CURLY, true) => {
76                 indent = indent.saturating_sub(1);
77
78                 if indent > 0 {
79                     mods.push(do_indent(before, tok, indent));
80                 }
81                 mods.push(do_nl(before, tok));
82             }
83             R_CURLY => {
84                 if indent > 0 {
85                     mods.push(do_indent(after, tok, indent));
86                 }
87                 mods.push(do_nl(after, tok));
88             }
89             LIFETIME_IDENT if is_next(is_text, true) => {
90                 mods.push(do_ws(after, tok));
91             }
92             MUT_KW if is_next(|it| it == SELF_KW, false) => {
93                 mods.push(do_ws(after, tok));
94             }
95             AS_KW | DYN_KW | IMPL_KW | CONST_KW => {
96                 mods.push(do_ws(after, tok));
97             }
98             T![;] => {
99                 if indent > 0 {
100                     mods.push(do_indent(after, tok, indent));
101                 }
102                 mods.push(do_nl(after, tok));
103             }
104             T![=] if is_next(|it| it == T![>], false) => {
105                 // FIXME: this branch is for `=>` in macro_rules!, which is currently parsed as
106                 // two separate symbols.
107                 mods.push(do_ws(before, tok));
108                 mods.push(do_ws(after, &tok.next_token().unwrap()));
109             }
110             T![->] | T![=] | T![=>] => {
111                 mods.push(do_ws(before, tok));
112                 mods.push(do_ws(after, tok));
113             }
114             T![!] if is_last(|it| it == MACRO_RULES_KW, false) && is_next(is_text, false) => {
115                 mods.push(do_ws(after, tok));
116             }
117             _ => (),
118         }
119
120         last = Some(tok.kind());
121     }
122
123     for (pos, insert) in mods {
124         ted::insert(pos, insert);
125     }
126
127     if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) {
128         ted::remove(it);
129     }
130
131     syn
132 }
133
134 fn is_text(k: SyntaxKind) -> bool {
135     k.is_keyword() || k.is_literal() || k == IDENT || k == UNDERSCORE
136 }