]> git.lizzy.rs Git - rust.git/blobdiff - src/imports.rs
Remove BlockIndentStyle::Inherit
[rust.git] / src / imports.rs
index d9eb7b772a3b2ca3a9ad332168d2d70c7b9226de..cafba0bb2c5aa6ab18b9e73ef7c9816803584603 100644 (file)
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use Shape;
+use utils;
+use syntax::codemap::{self, BytePos, Span};
+use codemap::SpanUtils;
+use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, definitive_tactic};
+use types::{rewrite_path, PathContext};
+use rewrite::{Rewrite, RewriteContext};
 use visitor::FmtVisitor;
-use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
-use utils::{span_after, format_visibility};
+use std::cmp::{self, Ordering};
 
-use syntax::ast;
-use syntax::parse::token;
-use syntax::print::pprust;
-use syntax::codemap::Span;
+use syntax::{ast, ptr};
 
-// TODO (some day) remove unused imports, expand globs, compress many single imports into a list import
+fn path_of(a: &ast::ViewPath_) -> &ast::Path {
+    match *a {
+        ast::ViewPath_::ViewPathSimple(_, ref p) => p,
+        ast::ViewPath_::ViewPathGlob(ref p) => p,
+        ast::ViewPath_::ViewPathList(ref p, _) => p,
+    }
+}
+
+fn compare_path_segments(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
+    a.identifier
+        .name
+        .as_str()
+        .cmp(&b.identifier.name.as_str())
+}
+
+fn compare_paths(a: &ast::Path, b: &ast::Path) -> Ordering {
+    for segment in a.segments.iter().zip(b.segments.iter()) {
+        let ord = compare_path_segments(segment.0, segment.1);
+        if ord != Ordering::Equal {
+            return ord;
+        }
+    }
+    a.segments.len().cmp(&b.segments.len())
+}
 
-fn rewrite_single_use_list(path_str: String, vpi: ast::PathListItem, vis: &str) -> String {
-    if let ast::PathListItem_::PathListIdent{ name, .. } = vpi.node {
-        let name_str = token::get_ident(name).to_string();
-        if path_str.len() == 0 {
-            format!("{}use {};", vis, name_str)
+fn compare_path_list_items(a: &ast::PathListItem, b: &ast::PathListItem) -> Ordering {
+    let a_name_str = &*a.node
+                           .name
+                           .name
+                           .as_str();
+    let b_name_str = &*b.node
+                           .name
+                           .name
+                           .as_str();
+    let name_ordering = if a_name_str == "self" {
+        if b_name_str == "self" {
+            Ordering::Equal
         } else {
-            format!("{}use {}::{};", vis, path_str, name_str)
+            Ordering::Less
         }
     } else {
-        if path_str.len() != 0 {
-            format!("{}use {};", vis, path_str)
+        if b_name_str == "self" {
+            Ordering::Greater
         } else {
-            // This catches the import: use {self}, which is a compiler error, so we just
-            // leave it alone.
-            format!("{}use {{self}};", vis)
+            a_name_str.cmp(&b_name_str)
         }
+    };
+    if name_ordering == Ordering::Equal {
+        match a.node.rename {
+            Some(a_rename) => {
+                match b.node.rename {
+                    Some(b_rename) => a_rename.name.as_str().cmp(&b_rename.name.as_str()),
+                    None => Ordering::Greater,
+                }
+            }
+            None => Ordering::Less,
+        }
+    } else {
+        name_ordering
     }
 }
 
-impl<'a> FmtVisitor<'a> {
-    // Basically just pretty prints a multi-item import.
-    // Returns None when the import can be removed.
-    pub fn rewrite_use_list(&self,
-                            block_indent: usize,
-                            one_line_budget: usize, // excluding indentation
-                            multi_line_budget: usize,
-                            path: &ast::Path,
-                            path_list: &[ast::PathListItem],
-                            visibility: ast::Visibility,
-                            span: Span)
+fn compare_path_list_item_lists(a_items: &Vec<ast::PathListItem>,
+                                b_items: &Vec<ast::PathListItem>)
+                                -> Ordering {
+    let mut a = a_items.clone();
+    let mut b = b_items.clone();
+    a.sort_by(|a, b| compare_path_list_items(a, b));
+    b.sort_by(|a, b| compare_path_list_items(a, b));
+    for comparison_pair in a.iter().zip(b.iter()) {
+        let ord = compare_path_list_items(comparison_pair.0, comparison_pair.1);
+        if ord != Ordering::Equal {
+            return ord;
+        }
+    }
+    a.len().cmp(&b.len())
+}
+
+fn compare_view_path_types(a: &ast::ViewPath_, b: &ast::ViewPath_) -> Ordering {
+    use syntax::ast::ViewPath_::*;
+    match (a, b) {
+        (&ViewPathSimple(..), &ViewPathSimple(..)) => Ordering::Equal,
+        (&ViewPathSimple(..), _) => Ordering::Less,
+        (&ViewPathGlob(_), &ViewPathSimple(..)) => Ordering::Greater,
+        (&ViewPathGlob(_), &ViewPathGlob(_)) => Ordering::Equal,
+        (&ViewPathGlob(_), &ViewPathList(..)) => Ordering::Less,
+        (&ViewPathList(_, ref a_items), &ViewPathList(_, ref b_items)) => {
+            compare_path_list_item_lists(a_items, b_items)
+        }
+        (&ViewPathList(..), _) => Ordering::Greater,
+    }
+}
+
+fn compare_view_paths(a: &ast::ViewPath_, b: &ast::ViewPath_) -> Ordering {
+    match compare_paths(path_of(a), path_of(b)) {
+        Ordering::Equal => compare_view_path_types(a, b),
+        cmp => cmp,
+    }
+}
+
+fn compare_use_items(a: &ast::Item, b: &ast::Item) -> Option<Ordering> {
+    match (&a.node, &b.node) {
+        (&ast::ItemKind::Use(ref a_vp), &ast::ItemKind::Use(ref b_vp)) => {
+            Some(compare_view_paths(&a_vp.node, &b_vp.node))
+        }
+        _ => None,
+    }
+}
+
+// TODO (some day) remove unused imports, expand globs, compress many single
+// imports into a list import.
+
+fn rewrite_view_path_prefix(path: &ast::Path,
+                            context: &RewriteContext,
+                            shape: Shape)
                             -> Option<String> {
-        let path_str = pprust::path_to_string(path);
-        let vis = format_visibility(visibility);
+    let path_str = if path.segments
+           .last()
+           .unwrap()
+           .identifier
+           .to_string() == "self" && path.segments.len() > 1 {
+        let path = &ast::Path {
+                        span: path.span.clone(),
+                        segments: path.segments[..path.segments.len() - 1].to_owned(),
+                    };
+        try_opt!(rewrite_path(context, PathContext::Import, None, path, shape))
+    } else {
+        try_opt!(rewrite_path(context, PathContext::Import, None, path, shape))
+    };
+    Some(path_str)
+}
 
-        match path_list.len() {
-            0 => return None,
-            1 => return Some(rewrite_single_use_list(path_str, path_list[0], vis)),
-            _ => ()
+impl Rewrite for ast::ViewPath {
+    // Returns an empty string when the ViewPath is empty (like foo::bar::{})
+    fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+        match self.node {
+            ast::ViewPath_::ViewPathList(_, ref path_list) if path_list.is_empty() => {
+                Some(String::new())
+            }
+            ast::ViewPath_::ViewPathList(ref path, ref path_list) => {
+                rewrite_use_list(shape, path, path_list, self.span, context)
+            }
+            ast::ViewPath_::ViewPathGlob(ref path) => {
+                // 4 = "::*".len()
+                let prefix_shape = try_opt!(shape.sub_width(3));
+                let path_str = try_opt!(rewrite_view_path_prefix(path, context, prefix_shape));
+                Some(format!("{}::*", path_str))
+            }
+            ast::ViewPath_::ViewPathSimple(ident, ref path) => {
+                let ident_str = ident.to_string();
+                // 4 = " as ".len()
+                let prefix_shape = try_opt!(shape.sub_width(ident_str.len() + 4));
+                let path_str = try_opt!(rewrite_view_path_prefix(path, context, prefix_shape));
+
+                Some(if path.segments
+                            .last()
+                            .unwrap()
+                            .identifier == ident {
+                         path_str
+                     } else {
+                         format!("{} as {}", path_str, ident_str)
+                     })
+            }
         }
+    }
+}
+
+impl<'a> FmtVisitor<'a> {
+    pub fn format_imports(&mut self, use_items: &[ptr::P<ast::Item>]) {
+        // Find the location immediately before the first use item in the run. This must not lie
+        // before the current `self.last_pos`
+        let pos_before_first_use_item = use_items.first()
+            .map(|p_i| {
+                cmp::max(self.last_pos,
+                         p_i.attrs
+                             .iter()
+                             .map(|attr| attr.span.lo)
+                             .min()
+                             .unwrap_or(p_i.span.lo))
+            })
+            .unwrap_or(self.last_pos);
+        // Construct a list of pairs, each containing a `use` item and the start of span before
+        // that `use` item.
+        let mut last_pos_of_prev_use_item = pos_before_first_use_item;
+        let mut ordered_use_items = use_items.iter()
+            .map(|p_i| {
+                     let new_item = (&*p_i, last_pos_of_prev_use_item);
+                     last_pos_of_prev_use_item = p_i.span.hi;
+                     new_item
+                 })
+            .collect::<Vec<_>>();
+        let pos_after_last_use_item = last_pos_of_prev_use_item;
+        // Order the imports by view-path & other import path properties
+        ordered_use_items.sort_by(|a, b| compare_use_items(a.0, b.0).unwrap());
+        // First, output the span before the first import
+        let prev_span_str = self.snippet(codemap::mk_sp(self.last_pos, pos_before_first_use_item));
+        // Look for purely trailing space at the start of the prefix snippet before a linefeed, or
+        // a prefix that's entirely horizontal whitespace.
+        let prefix_span_start = match prev_span_str.find('\n') {
+            Some(offset) if prev_span_str[..offset].trim().is_empty() => {
+                self.last_pos + BytePos(offset as u32)
+            }
+            None if prev_span_str.trim().is_empty() => pos_before_first_use_item,
+            _ => self.last_pos,
+        };
+        // Look for indent (the line part preceding the use is all whitespace) and excise that
+        // from the prefix
+        let span_end = match prev_span_str.rfind('\n') {
+            Some(offset) if prev_span_str[offset..].trim().is_empty() => {
+                self.last_pos + BytePos(offset as u32)
+            }
+            _ => pos_before_first_use_item,
+        };
 
-        // 2 = ::
-        let path_separation_w = if path_str.len() > 0 { 2 } else { 0 };
-        // 5 = "use " + {
-        let indent = path_str.len() + 5 + path_separation_w + vis.len();
-
-        // 2 = } + ;
-        let used_width = indent + 2;
-
-        // Break as early as possible when we've blown our budget.
-        let remaining_line_budget = one_line_budget.checked_sub(used_width).unwrap_or(0);
-        let remaining_multi_budget = multi_line_budget.checked_sub(used_width).unwrap_or(0);
-
-        let fmt = ListFormatting { tactic: ListTactic::Mixed,
-                                   separator: ",",
-                                   trailing_separator: SeparatorTactic::Never,
-                                   indent: block_indent + indent,
-                                   h_width: remaining_line_budget,
-                                   v_width: remaining_multi_budget,
-                                   ends_with_newline: true, };
-
-        let mut items = itemize_list(self.codemap,
-                                     vec![ListItem::from_str("")], /* Dummy value, explanation
-                                                                    * below */
-                                     path_list.iter(),
-                                     ",",
-                                     "}",
-                                     |vpi| vpi.span.lo,
-                                     |vpi| vpi.span.hi,
-                                     |vpi| match vpi.node {
-                                         ast::PathListItem_::PathListIdent{ name, .. } => {
-                                             token::get_ident(name).to_string()
-                                         }
-                                         ast::PathListItem_::PathListMod{ .. } => {
-                                             "self".to_owned()
-                                         }
-                                     },
-                                     span_after(span, "{", self.codemap),
-                                     span.hi);
-
-        // We prefixed the item list with a dummy value so that we can
-        // potentially move "self" to the front of the vector without touching
-        // the rest of the items.
-        // FIXME: Make more efficient by using a linked list? That would
-        // require changes to the signatures of itemize_list and write_list.
-        let has_self = move_self_to_front(&mut items);
-        let first_index = if has_self { 0 } else { 1 };
-
-        if self.config.reorder_imports {
-            items.tail_mut().sort_by(|a, b| a.item.cmp(&b.item));
+        self.last_pos = prefix_span_start;
+        self.format_missing(span_end);
+        for ordered in ordered_use_items {
+            // Fake out the formatter by setting `self.last_pos` to the appropriate location before
+            // each item before visiting it.
+            self.last_pos = ordered.1;
+            self.visit_item(ordered.0);
         }
+        self.last_pos = pos_after_last_use_item;
+    }
 
-        let list = write_list(&items[first_index..], &fmt);
+    pub fn format_import(&mut self, vis: &ast::Visibility, vp: &ast::ViewPath, span: Span) {
+        let vis = utils::format_visibility(vis);
+        let mut offset = self.block_indent;
+        offset.alignment += vis.len() + "use ".len();
+        // 1 = ";"
+        match vp.rewrite(&self.get_context(),
+                         Shape::legacy(self.config.max_width - offset.width() - 1, offset)) {
+            Some(ref s) if s.is_empty() => {
+                // Format up to last newline
+                let prev_span = codemap::mk_sp(self.last_pos, source!(self, span).lo);
+                let span_end = match self.snippet(prev_span).rfind('\n') {
+                    Some(offset) => self.last_pos + BytePos(offset as u32),
+                    None => source!(self, span).lo,
+                };
+                self.format_missing(span_end);
+                self.last_pos = source!(self, span).hi;
+            }
+            Some(ref s) => {
+                let s = format!("{}use {};", vis, s);
+                self.format_missing_with_indent(source!(self, span).lo);
+                self.buffer.push_str(&s);
+                self.last_pos = source!(self, span).hi;
+            }
+            None => {
+                self.format_missing_with_indent(source!(self, span).lo);
+                self.format_missing(source!(self, span).hi);
+            }
+        }
+    }
+}
 
-        Some(if path_str.len() == 0 {
-            format!("{}use {{{}}};", vis, list)
+fn rewrite_single_use_list(path_str: String, vpi: &ast::PathListItem) -> String {
+    let mut item_str = vpi.node.name.to_string();
+    if item_str == "self" {
+        item_str = "".to_owned();
+    }
+    let path_item_str = if path_str.is_empty() {
+        if item_str.is_empty() {
+            "self".to_owned()
         } else {
-            format!("{}use {}::{{{}}};", vis, path_str, list)
-        })
+            item_str
+        }
+    } else if item_str.is_empty() {
+        path_str
+    } else {
+        format!("{}::{}", path_str, item_str)
+    };
+    append_alias(path_item_str, vpi)
+}
+
+fn rewrite_path_item(vpi: &&ast::PathListItem) -> Option<String> {
+    Some(append_alias(vpi.node.name.to_string(), vpi))
+}
+
+fn append_alias(path_item_str: String, vpi: &ast::PathListItem) -> String {
+    match vpi.node.rename {
+        Some(rename) => format!("{} as {}", path_item_str, rename),
+        None => path_item_str,
+    }
+}
+
+// Pretty prints a multi-item import.
+// Assumes that path_list.len() > 0.
+pub fn rewrite_use_list(shape: Shape,
+                        path: &ast::Path,
+                        path_list: &[ast::PathListItem],
+                        span: Span,
+                        context: &RewriteContext)
+                        -> Option<String> {
+    // Returns a different option to distinguish `::foo` and `foo`
+    let path_str = try_opt!(rewrite_path(context, PathContext::Import, None, path, shape));
+
+    match path_list.len() {
+        0 => unreachable!(),
+        1 => return Some(rewrite_single_use_list(path_str, &path_list[0])),
+        _ => (),
+    }
+
+    // 2 = {}
+    let remaining_width = shape.width.checked_sub(path_str.len() + 2).unwrap_or(0);
+
+    let mut items = {
+        // Dummy value, see explanation below.
+        let mut items = vec![ListItem::from_str("")];
+        let iter = itemize_list(context.codemap,
+                                path_list.iter(),
+                                "}",
+                                |vpi| vpi.span.lo,
+                                |vpi| vpi.span.hi,
+                                rewrite_path_item,
+                                context.codemap.span_after(span, "{"),
+                                span.hi);
+        items.extend(iter);
+        items
+    };
+
+    // We prefixed the item list with a dummy value so that we can
+    // potentially move "self" to the front of the vector without touching
+    // the rest of the items.
+    let has_self = move_self_to_front(&mut items);
+    let first_index = if has_self { 0 } else { 1 };
+
+    if context.config.reorder_imported_names {
+        items[1..].sort_by(|a, b| a.item.cmp(&b.item));
     }
+
+    let colons_offset = if path_str.is_empty() { 0 } else { 2 };
+
+    let tactic = definitive_tactic(&items[first_index..],
+                                   ::lists::ListTactic::Mixed,
+                                   remaining_width);
+    let fmt = ListFormatting {
+        tactic: tactic,
+        separator: ",",
+        trailing_separator: SeparatorTactic::Never,
+        // FIXME This is too conservative, and will not use all width
+        // available
+        // (loose 1 column (";"))
+        shape: Shape::legacy(remaining_width,
+                             shape.indent + path_str.len() + 1 + colons_offset),
+        ends_with_newline: false,
+        config: context.config,
+    };
+    let list_str = try_opt!(write_list(&items[first_index..], &fmt));
+
+    Some(if path_str.is_empty() {
+             format!("{{{}}}", list_str)
+         } else {
+             format!("{}::{{{}}}", path_str, list_str)
+         })
 }
 
 // Returns true when self item was found.
 fn move_self_to_front(items: &mut Vec<ListItem>) -> bool {
-    match items.iter().position(|item| item.item == "self") {
+    match items.iter().position(|item| item.item.as_ref().map(|x| &x[..]) == Some("self")) {
         Some(pos) => {
             items[0] = items.remove(pos);
             true
-        },
-        None => false
+        }
+        None => false,
     }
 }