]> git.lizzy.rs Git - rust.git/blob - src/expr.rs
Move vertical mode override to write_list
[rust.git] / src / expr.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use visitor::FmtVisitor;
12 use utils::*;
13 use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
14
15 use syntax::{ast, ptr};
16 use syntax::codemap::{Pos, Span};
17 use syntax::parse::token;
18 use syntax::print::pprust;
19
20 use MIN_STRING;
21
22 impl<'a> FmtVisitor<'a> {
23     // TODO NEEDS TESTS
24     fn rewrite_string_lit(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
25         // FIXME I bet this stomps unicode escapes in the source string
26
27         // Check if there is anything to fix: we always try to fixup multi-line
28         // strings, or if the string is too long for the line.
29         let l_loc = self.codemap.lookup_char_pos(span.lo);
30         let r_loc = self.codemap.lookup_char_pos(span.hi);
31         if l_loc.line == r_loc.line && r_loc.col.to_usize() <= config!(max_width) {
32             return self.snippet(span);
33         }
34
35         // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
36
37         let s = s.escape_default();
38
39         let offset = offset + 1;
40         let indent = make_indent(offset);
41         let indent = &indent;
42
43         let max_chars = width - 1;
44
45         let mut cur_start = 0;
46         let mut result = String::new();
47         result.push('"');
48         loop {
49             let mut cur_end = cur_start + max_chars;
50
51             if cur_end >= s.len() {
52                 result.push_str(&s[cur_start..]);
53                 break;
54             }
55
56             // Make sure we're on a char boundary.
57             cur_end = next_char(&s, cur_end);
58
59             // Push cur_end left until we reach whitespace
60             while !s.char_at(cur_end-1).is_whitespace() {
61                 cur_end = prev_char(&s, cur_end);
62
63                 if cur_end - cur_start < MIN_STRING {
64                     // We can't break at whitespace, fall back to splitting
65                     // anywhere that doesn't break an escape sequence
66                     cur_end = next_char(&s, cur_start + max_chars);
67                     while s.char_at(cur_end) == '\\' {
68                         cur_end = prev_char(&s, cur_end);
69                     }
70                 }
71             }
72             // Make sure there is no whitespace to the right of the break.
73             while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
74                 cur_end = next_char(&s, cur_end+1);
75             }
76             result.push_str(&s[cur_start..cur_end]);
77             result.push_str("\\\n");
78             result.push_str(indent);
79
80             cur_start = cur_end;
81         }
82         result.push('"');
83
84         result
85     }
86
87     fn rewrite_call(&mut self,
88                     callee: &ast::Expr,
89                     args: &[ptr::P<ast::Expr>],
90                     width: usize,
91                     offset: usize)
92         -> String
93     {
94         debug!("rewrite_call, width: {}, offset: {}", width, offset);
95
96         // TODO using byte lens instead of char lens (and probably all over the place too)
97         let callee_str = self.rewrite_expr(callee, width, offset);
98         debug!("rewrite_call, callee_str: `{}`", callee_str);
99         // 2 is for parens.
100         let remaining_width = width - callee_str.len() - 2;
101         let offset = callee_str.len() + 1 + offset;
102         let arg_count = args.len();
103
104         let args_str = if arg_count > 0 {
105             let args: Vec<_> = args.iter().map(|e| (self.rewrite_expr(e,
106                                                                       remaining_width,
107                                                                       offset), String::new())).collect();
108             let fmt = ListFormatting {
109                 tactic: ListTactic::HorizontalVertical,
110                 separator: ",",
111                 trailing_separator: SeparatorTactic::Never,
112                 indent: offset,
113                 h_width: remaining_width,
114                 v_width: remaining_width,
115             };
116             write_list(&args, &fmt)
117         } else {
118             String::new()
119         };
120
121         format!("{}({})", callee_str, args_str)
122     }
123
124     fn rewrite_paren(&mut self, subexpr: &ast::Expr, width: usize, offset: usize) -> String {
125         debug!("rewrite_paren, width: {}, offset: {}", width, offset);
126         // 1 is for opening paren, 2 is for opening+closing, we want to keep the closing
127         // paren on the same line as the subexpr
128         let subexpr_str = self.rewrite_expr(subexpr, width-2, offset+1);
129         debug!("rewrite_paren, subexpr_str: `{}`", subexpr_str);
130         format!("({})", subexpr_str)
131     }
132
133     fn rewrite_struct_lit(&mut self,
134                           path: &ast::Path,
135                           fields: &[ast::Field],
136                           base: Option<&ast::Expr>,
137                           width: usize,
138                           offset: usize)
139         -> String
140     {
141         debug!("rewrite_struct_lit: width {}, offset {}", width, offset);
142         assert!(fields.len() > 0 || base.is_some());
143
144         let path_str = pprust::path_to_string(path);
145         // Foo { a: Foo } - indent is +3, width is -5.
146         let indent = offset + path_str.len() + 3;
147         let budget = width - (path_str.len() + 5);
148
149         let mut field_strs: Vec<_> =
150             fields.iter().map(|f| self.rewrite_field(f, budget, indent)).collect();
151         if let Some(expr) = base {
152             // Another 2 on the width/indent for the ..
153             field_strs.push(format!("..{}", self.rewrite_expr(expr, budget - 2, indent + 2)))
154         }
155
156         // FIXME comments
157         let field_strs: Vec<_> = field_strs.into_iter().map(|s| (s, String::new())).collect();
158         let fmt = ListFormatting {
159             tactic: ListTactic::HorizontalVertical,
160             separator: ",",
161             trailing_separator: if base.is_some() {
162                     SeparatorTactic::Never
163                 } else {
164                     config!(struct_lit_trailing_comma)
165                 },
166             indent: indent,
167             h_width: budget,
168             v_width: budget,
169         };
170         let fields_str = write_list(&field_strs, &fmt);
171         format!("{} {{ {} }}", path_str, fields_str)
172
173         // FIXME if the usual multi-line layout is too wide, we should fall back to
174         // Foo {
175         //     a: ...,
176         // }
177     }
178
179     fn rewrite_field(&mut self, field: &ast::Field, width: usize, offset: usize) -> String {
180         let name = &token::get_ident(field.ident.node);
181         let overhead = name.len() + 2;
182         let expr = self.rewrite_expr(&field.expr, width - overhead, offset + overhead);
183         format!("{}: {}", name, expr)
184     }
185
186     fn rewrite_tuple_lit(&mut self, items: &[ptr::P<ast::Expr>], width: usize, offset: usize)
187         -> String {
188         // opening paren
189         let indent = offset + 1;
190         // In case of length 1, need a trailing comma
191         if items.len() == 1 {
192             return format!("({},)", self.rewrite_expr(&*items[0], width - 3, indent));
193         }
194         // Only last line has width-1 as budget, other may take max_width
195         let item_strs: Vec<_> =
196             items.iter()
197                  .enumerate()
198                  .map(|(i, item)| self.rewrite_expr(
199                     item,
200                     // last line : given width (minus "("+")"), other lines : max_width
201                     // (minus "("+","))
202                     if i == items.len() - 1 { width - 2 } else { config!(max_width) - indent - 2 },
203                     indent))
204                  .collect();
205         let tactics = if item_strs.iter().any(|s| s.contains('\n')) {
206             ListTactic::Vertical
207         } else {
208             ListTactic::HorizontalVertical
209         };
210         // FIXME handle comments
211         let item_strs: Vec<_> = item_strs.into_iter().map(|s| (s, String::new())).collect();
212         let fmt = ListFormatting {
213             tactic: tactics,
214             separator: ",",
215             trailing_separator: SeparatorTactic::Never,
216             indent: indent,
217             h_width: width - 2,
218             v_width: width - 2,
219         };
220         let item_str = write_list(&item_strs, &fmt);
221         format!("({})", item_str)
222     }
223
224
225     pub fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
226         match expr.node {
227             ast::Expr_::ExprLit(ref l) => {
228                 match l.node {
229                     ast::Lit_::LitStr(ref is, _) => {
230                         let result = self.rewrite_string_lit(&is, l.span, width, offset);
231                         debug!("string lit: `{}`", result);
232                         return result;
233                     }
234                     _ => {}
235                 }
236             }
237             ast::Expr_::ExprCall(ref callee, ref args) => {
238                 return self.rewrite_call(callee, args, width, offset);
239             }
240             ast::Expr_::ExprParen(ref subexpr) => {
241                 return self.rewrite_paren(subexpr, width, offset);
242             }
243             ast::Expr_::ExprStruct(ref path, ref fields, ref base) => {
244                 return self.rewrite_struct_lit(path,
245                                                fields,
246                                                base.as_ref().map(|e| &**e),
247                                                width,
248                                                offset);
249             }
250             ast::Expr_::ExprTup(ref items) => {
251                 return self.rewrite_tuple_lit(items, width, offset);
252             }
253             _ => {}
254         }
255
256         self.snippet(expr.span)
257     }
258 }