about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-08-30 06:49:15 +0000
committerbors <bors@rust-lang.org>2019-08-30 06:49:15 +0000
commit19a38de68a8de14fe49e96b315db026bd57b9696 (patch)
tree6719abd3ec46dbff6b320d65b2cbe451cd8a2300
parentc7d4df0fcfc3ed3ee98a430e451c6776e90d65ae (diff)
parentaaf4dc35e33eea8b658b82a307b81e63e8b214f4 (diff)
downloadrust-19a38de68a8de14fe49e96b315db026bd57b9696.tar.gz
rust-19a38de68a8de14fe49e96b315db026bd57b9696.zip
Auto merge of #63402 - estebank:strip-margin, r=oli-obk
Strip code to the left and right in diagnostics for long lines

Fix #62999.
-rw-r--r--Cargo.lock12
-rw-r--r--src/librustc/session/config.rs2
-rw-r--r--src/librustc/session/mod.rs6
-rw-r--r--src/librustc_driver/lib.rs12
-rw-r--r--src/librustc_errors/Cargo.toml1
-rw-r--r--src/librustc_errors/emitter.rs462
-rw-r--r--src/librustc_errors/lib.rs2
-rw-r--r--src/librustdoc/core.rs1
-rw-r--r--src/librustdoc/test.rs2
-rw-r--r--src/libsyntax/json.rs2
-rw-r--r--src/libsyntax/parse/lexer/tests.rs78
-rw-r--r--src/libsyntax/tests.rs13
-rw-r--r--src/test/ui/inline-asm-bad-operand.stderr4
-rw-r--r--src/test/ui/lint/lint-stability-deprecated.stderr36
-rw-r--r--src/test/ui/regions/regions-name-undeclared.stderr8
-rw-r--r--src/test/ui/terminal-width/non-whitespace-trimming-2.rs6
-rw-r--r--src/test/ui/terminal-width/non-whitespace-trimming-2.stderr12
-rw-r--r--src/test/ui/terminal-width/non-whitespace-trimming-unicode.rs6
-rw-r--r--src/test/ui/terminal-width/non-whitespace-trimming-unicode.stderr12
-rw-r--r--src/test/ui/terminal-width/non-whitespace-trimming.rs6
-rw-r--r--src/test/ui/terminal-width/non-whitespace-trimming.stderr12
-rw-r--r--src/test/ui/terminal-width/whitespace-trimming-2.rs8
-rw-r--r--src/test/ui/terminal-width/whitespace-trimming-2.stderr14
-rw-r--r--src/test/ui/terminal-width/whitespace-trimming.rs6
-rw-r--r--src/test/ui/terminal-width/whitespace-trimming.stderr12
-rw-r--r--src/tools/tidy/src/deps.rs1
26 files changed, 580 insertions, 156 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1b294b22026..ae641d6ae32 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3232,6 +3232,7 @@ dependencies = [
  "rustc_data_structures",
  "serialize",
  "syntax_pos",
+ "term_size",
  "termcolor",
  "unicode-width",
 ]
@@ -4108,6 +4109,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "term_size"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
+dependencies = [
+ "kernel32-sys",
+ "libc",
+ "winapi 0.2.8",
+]
+
+[[package]]
 name = "termcolor"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 9ecdff25d26..740d9db7edc 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -1292,6 +1292,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
         "show macro backtraces even for non-local macros"),
     teach: bool = (false, parse_bool, [TRACKED],
         "show extended diagnostic help"),
+    terminal_width: Option<usize> = (None, parse_opt_uint, [UNTRACKED],
+        "set the current terminal width"),
     continue_parse_after_error: bool = (false, parse_bool, [TRACKED],
         "attempt to recover from parse errors (experimental)"),
     dep_tasks: bool = (false, parse_bool, [UNTRACKED],
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 61dac678912..f01883d9634 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -1055,6 +1055,7 @@ fn default_emitter(
                         Some(source_map.clone()),
                         short,
                         sopts.debugging_opts.teach,
+                        sopts.debugging_opts.terminal_width,
                     ),
                     Some(dst) => EmitterWriter::new(
                         dst,
@@ -1062,6 +1063,7 @@ fn default_emitter(
                         short,
                         false, // no teach messages when writing to a buffer
                         false, // no colors when writing to a buffer
+                        None,  // no terminal width
                     ),
                 };
                 Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing))
@@ -1375,7 +1377,7 @@ pub fn early_error(output: config::ErrorOutputType, msg: &str) -> ! {
     let emitter: Box<dyn Emitter + sync::Send> = match output {
         config::ErrorOutputType::HumanReadable(kind) => {
             let (short, color_config) = kind.unzip();
-            Box::new(EmitterWriter::stderr(color_config, None, short, false))
+            Box::new(EmitterWriter::stderr(color_config, None, short, false, None))
         }
         config::ErrorOutputType::Json { pretty, json_rendered } =>
             Box::new(JsonEmitter::basic(pretty, json_rendered)),
@@ -1389,7 +1391,7 @@ pub fn early_warn(output: config::ErrorOutputType, msg: &str) {
     let emitter: Box<dyn Emitter + sync::Send> = match output {
         config::ErrorOutputType::HumanReadable(kind) => {
             let (short, color_config) = kind.unzip();
-            Box::new(EmitterWriter::stderr(color_config, None, short, false))
+            Box::new(EmitterWriter::stderr(color_config, None, short, false, None))
         }
         config::ErrorOutputType::Json { pretty, json_rendered } =>
             Box::new(JsonEmitter::basic(pretty, json_rendered)),
diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs
index e7712ae115f..e3ea92dc8ab 100644
--- a/src/librustc_driver/lib.rs
+++ b/src/librustc_driver/lib.rs
@@ -1156,11 +1156,13 @@ pub fn report_ices_to_stderr_if_any<F: FnOnce() -> R, R>(f: F) -> Result<R, Erro
             // Thread panicked without emitting a fatal diagnostic
             eprintln!("");
 
-            let emitter =
-                Box::new(errors::emitter::EmitterWriter::stderr(errors::ColorConfig::Auto,
-                                                                None,
-                                                                false,
-                                                                false));
+            let emitter = Box::new(errors::emitter::EmitterWriter::stderr(
+                errors::ColorConfig::Auto,
+                None,
+                false,
+                false,
+                None,
+            ));
             let handler = errors::Handler::with_emitter(true, None, emitter);
 
             // a .span_bug or .bug call has already printed what
diff --git a/src/librustc_errors/Cargo.toml b/src/librustc_errors/Cargo.toml
index 32f121f18f6..1541845bb55 100644
--- a/src/librustc_errors/Cargo.toml
+++ b/src/librustc_errors/Cargo.toml
@@ -18,3 +18,4 @@ unicode-width = "0.1.4"
 atty = "0.2"
 termcolor = "1.0"
 annotate-snippets = "0.6.1"
+term_size = "0.3.1"
diff --git a/src/librustc_errors/emitter.rs b/src/librustc_errors/emitter.rs
index 361b5cd9357..77d373e7a8c 100644
--- a/src/librustc_errors/emitter.rs
+++ b/src/librustc_errors/emitter.rs
@@ -24,7 +24,7 @@ use rustc_data_structures::sync::Lrc;
 use std::borrow::Cow;
 use std::io::prelude::*;
 use std::io;
-use std::cmp::{min, Reverse};
+use std::cmp::{min, max, Reverse};
 use std::path::Path;
 use termcolor::{StandardStream, ColorChoice, ColorSpec, BufferWriter, Ansi};
 use termcolor::{WriteColor, Color, Buffer};
@@ -51,9 +51,127 @@ impl HumanReadableErrorType {
         dst: Box<dyn Write + Send>,
         source_map: Option<Lrc<SourceMapperDyn>>,
         teach: bool,
+        terminal_width: Option<usize>,
     ) -> EmitterWriter {
         let (short, color_config) = self.unzip();
-        EmitterWriter::new(dst, source_map, short, teach, color_config.suggests_using_colors())
+        let color = color_config.suggests_using_colors();
+        EmitterWriter::new(dst, source_map, short, teach, color, terminal_width)
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+struct Margin {
+    /// The available whitespace in the left that can be consumed when centering.
+    pub whitespace_left: usize,
+    /// The column of the beginning of left-most span.
+    pub span_left: usize,
+    /// The column of the end of right-most span.
+    pub span_right: usize,
+    /// The beginning of the line to be displayed.
+    pub computed_left: usize,
+    /// The end of the line to be displayed.
+    pub computed_right: usize,
+    /// The current width of the terminal. 140 by default and in tests.
+    pub column_width: usize,
+    /// The end column of a span label, including the span. Doesn't account for labels not in the
+    /// same line as the span.
+    pub label_right: usize,
+}
+
+impl Margin {
+    fn new(
+        whitespace_left: usize,
+        span_left: usize,
+        span_right: usize,
+        label_right: usize,
+        column_width: usize,
+        max_line_len: usize,
+    ) -> Self {
+        // The 6 is padding to give a bit of room for `...` when displaying:
+        // ```
+        // error: message
+        //   --> file.rs:16:58
+        //    |
+        // 16 | ... fn foo(self) -> Self::Bar {
+        //    |                     ^^^^^^^^^
+        // ```
+
+        let mut m = Margin {
+            whitespace_left: if whitespace_left >= 6 { whitespace_left - 6 } else { 0 },
+            span_left: if span_left >= 6 { span_left - 6 } else { 0 },
+            span_right: span_right + 6,
+            computed_left: 0,
+            computed_right: 0,
+            column_width,
+            label_right: label_right + 6,
+        };
+        m.compute(max_line_len);
+        m
+    }
+
+    fn was_cut_left(&self) -> bool {
+        self.computed_left > 0
+    }
+
+    fn was_cut_right(&self, line_len: usize) -> bool {
+        let right = if self.computed_right == self.span_right ||
+            self.computed_right == self.label_right
+        {
+            // Account for the "..." padding given above. Otherwise we end up with code lines that
+            // do fit but end in "..." as if they were trimmed.
+            self.computed_right - 6
+        } else {
+            self.computed_right
+        };
+        right < line_len && line_len > self.computed_left + self.column_width
+    }
+
+    fn compute(&mut self, max_line_len: usize) {
+        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
+        self.computed_left = if self.whitespace_left > 20 {
+            self.whitespace_left - 16 // We want some padding.
+        } else {
+            0
+        };
+        // We want to show as much as possible, max_line_len is the right-most boundary for the
+        // relevant code.
+        self.computed_right = max(max_line_len, self.computed_left);
+
+        if self.computed_right - self.computed_left > self.column_width {
+            // Trimming only whitespace isn't enough, let's get craftier.
+            if self.label_right - self.whitespace_left <= self.column_width {
+                // Attempt to fit the code window only trimming whitespace.
+                self.computed_left = self.whitespace_left;
+                self.computed_right = self.computed_left + self.column_width;
+            } else if self.label_right - self.span_left <= self.column_width {
+                // Attempt to fit the code window considering only the spans and labels.
+                let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
+                self.computed_left = self.span_left - padding_left;
+                self.computed_right = self.computed_left + self.column_width;
+            } else if self.span_right - self.span_left <= self.column_width {
+                // Attempt to fit the code window considering the spans and labels plus padding.
+                let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
+                self.computed_left = self.span_left - padding_left;
+                self.computed_right = self.computed_left + self.column_width;
+            } else { // Mostly give up but still don't show the full line.
+                self.computed_left = self.span_left;
+                self.computed_right = self.span_right;
+            }
+        }
+    }
+
+    fn left(&self, line_len: usize) -> usize {
+        min(self.computed_left, line_len)
+    }
+
+    fn right(&self, line_len: usize) -> usize {
+        if max(line_len, self.computed_left) - self.computed_left <= self.column_width {
+            line_len
+        } else if self.computed_right > line_len {
+            line_len
+        } else {
+            self.computed_right
+        }
     }
 }
 
@@ -180,6 +298,7 @@ pub struct EmitterWriter {
     short_message: bool,
     teach: bool,
     ui_testing: bool,
+    terminal_width: Option<usize>,
 }
 
 #[derive(Debug)]
@@ -190,11 +309,13 @@ pub struct FileWithAnnotatedLines {
 }
 
 impl EmitterWriter {
-    pub fn stderr(color_config: ColorConfig,
-                  source_map: Option<Lrc<SourceMapperDyn>>,
-                  short_message: bool,
-                  teach: bool)
-                  -> EmitterWriter {
+    pub fn stderr(
+        color_config: ColorConfig,
+        source_map: Option<Lrc<SourceMapperDyn>>,
+        short_message: bool,
+        teach: bool,
+        terminal_width: Option<usize>,
+    ) -> EmitterWriter {
         let dst = Destination::from_stderr(color_config);
         EmitterWriter {
             dst,
@@ -202,6 +323,7 @@ impl EmitterWriter {
             short_message,
             teach,
             ui_testing: false,
+            terminal_width,
         }
     }
 
@@ -211,6 +333,7 @@ impl EmitterWriter {
         short_message: bool,
         teach: bool,
         colored: bool,
+        terminal_width: Option<usize>,
     ) -> EmitterWriter {
         EmitterWriter {
             dst: Raw(dst, colored),
@@ -218,6 +341,7 @@ impl EmitterWriter {
             short_message,
             teach,
             ui_testing: false,
+            terminal_width,
         }
     }
 
@@ -234,12 +358,70 @@ impl EmitterWriter {
         }
     }
 
-    fn render_source_line(&self,
-                          buffer: &mut StyledBuffer,
-                          file: Lrc<SourceFile>,
-                          line: &Line,
-                          width_offset: usize,
-                          code_offset: usize) -> Vec<(usize, Style)> {
+    fn draw_line(
+        &self,
+        buffer: &mut StyledBuffer,
+        source_string: &str,
+        line_index: usize,
+        line_offset: usize,
+        width_offset: usize,
+        code_offset: usize,
+        margin: Margin,
+    ) {
+        let line_len = source_string.len();
+        // Create the source line we will highlight.
+        let left = margin.left(line_len);
+        let right = margin.right(line_len);
+        // On long lines, we strip the source line, accounting for unicode.
+        let mut taken = 0;
+        let code: String = source_string.chars().skip(left).take_while(|ch| {
+            // Make sure that the trimming on the right will fall within the terminal width.
+            // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
+            // For now, just accept that sometimes the code line will be longer than desired.
+            let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
+            if taken + next > right - left {
+                return false;
+            }
+            taken += next;
+            true
+        }).collect();
+        buffer.puts(line_offset, code_offset, &code, Style::Quotation);
+        if margin.was_cut_left() {
+            // We have stripped some code/whitespace from the beginning, make it clear.
+            buffer.puts(line_offset, code_offset, "...", Style::LineNumber);
+        }
+        if margin.was_cut_right(line_len) {
+            // We have stripped some code after the right-most span end, make it clear we did so.
+            buffer.puts(line_offset, code_offset + taken - 3, "...", Style::LineNumber);
+        }
+        buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber);
+
+        draw_col_separator(buffer, line_offset, width_offset - 2);
+    }
+
+    fn render_source_line(
+        &self,
+        buffer: &mut StyledBuffer,
+        file: Lrc<SourceFile>,
+        line: &Line,
+        width_offset: usize,
+        code_offset: usize,
+        margin: Margin,
+    ) -> Vec<(usize, Style)> {
+        // Draw:
+        //
+        //   LL | ... code ...
+        //      |     ^^-^ span label
+        //      |       |
+        //      |       secondary span label
+        //
+        //   ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it
+        //   |  | |   |
+        //   |  | |   actual code found in your source code and the spans we use to mark it
+        //   |  | when there's too much wasted space to the left, trim it
+        //   |  vertical divider between the column number and the code
+        //   column number
+
         if line.line_index == 0 {
             return Vec::new();
         }
@@ -251,14 +433,21 @@ impl EmitterWriter {
 
         let line_offset = buffer.num_lines();
 
-        // First create the source line we will highlight.
-        buffer.puts(line_offset, code_offset, &source_string, Style::Quotation);
-        buffer.puts(line_offset,
-                    0,
-                    &self.maybe_anonymized(line.line_index),
-                    Style::LineNumber);
+        let left = margin.left(source_string.len()); // Left trim
+        // Account for unicode characters of width !=0 that were removed.
+        let left = source_string.chars().take(left).fold(0, |acc, ch| {
+            acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)
+        });
 
-        draw_col_separator(buffer, line_offset, width_offset - 2);
+        self.draw_line(
+            buffer,
+            &source_string,
+            line.line_index,
+            line_offset,
+            width_offset,
+            code_offset,
+            margin,
+        );
 
         // Special case when there's only one annotation involved, it is the start of a multiline
         // span and there's no text at the beginning of the code line. Instead of doing the whole
@@ -279,18 +468,13 @@ impl EmitterWriter {
         if line.annotations.len() == 1 {
             if let Some(ref ann) = line.annotations.get(0) {
                 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
-                    if source_string.chars()
-                                    .take(ann.start_col)
-                                    .all(|c| c.is_whitespace()) {
+                    if source_string.chars().take(ann.start_col).all(|c| c.is_whitespace()) {
                         let style = if ann.is_primary {
                             Style::UnderlinePrimary
                         } else {
                             Style::UnderlineSecondary
                         };
-                        buffer.putc(line_offset,
-                                    width_offset + depth - 1,
-                                    '/',
-                                    style);
+                        buffer.putc(line_offset, width_offset + depth - 1, '/', style);
                         return vec![(depth, style)];
                     }
                 }
@@ -511,19 +695,23 @@ impl EmitterWriter {
             match annotation.annotation_type {
                 AnnotationType::MultilineStart(depth) |
                 AnnotationType::MultilineEnd(depth) => {
-                    draw_range(buffer,
-                               '_',
-                               line_offset + pos,
-                               width_offset + depth,
-                               code_offset + annotation.start_col,
-                               style);
+                    draw_range(
+                        buffer,
+                        '_',
+                        line_offset + pos,
+                        width_offset + depth,
+                        code_offset + annotation.start_col - left,
+                        style,
+                    );
                 }
                 _ if self.teach => {
-                    buffer.set_style_range(line_offset,
-                                           code_offset + annotation.start_col,
-                                           code_offset + annotation.end_col,
-                                           style,
-                                           annotation.is_primary);
+                    buffer.set_style_range(
+                        line_offset,
+                        code_offset + annotation.start_col - left,
+                        code_offset + annotation.end_col - left,
+                        style,
+                        annotation.is_primary,
+                    );
                 }
                 _ => {}
             }
@@ -551,7 +739,7 @@ impl EmitterWriter {
             if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
                 for p in line_offset + 1..=line_offset + pos {
                     buffer.putc(p,
-                                code_offset + annotation.start_col,
+                                code_offset + annotation.start_col - margin.computed_left,
                                 '|',
                                 style);
                 }
@@ -595,15 +783,20 @@ impl EmitterWriter {
                 Style::LabelSecondary
             };
             let (pos, col) = if pos == 0 {
-                (pos + 1, annotation.end_col + 1)
+                (pos + 1, if annotation.end_col + 1 > left {
+                    annotation.end_col + 1 - left
+                } else {
+                    0
+                })
             } else {
-                (pos + 2, annotation.start_col)
+                (pos + 2, if annotation.start_col > left {
+                    annotation.start_col - left
+                } else {
+                    0
+                })
             };
             if let Some(ref label) = annotation.label {
-                buffer.puts(line_offset + pos,
-                            code_offset + col,
-                            &label,
-                            style);
+                buffer.puts(line_offset + pos, code_offset + col, &label, style);
             }
         }
 
@@ -638,10 +831,16 @@ impl EmitterWriter {
                 ('-', Style::UnderlineSecondary)
             };
             for p in annotation.start_col..annotation.end_col {
-                buffer.putc(line_offset + 1,
-                            code_offset + p,
-                            underline,
-                            style);
+                buffer.putc(
+                    line_offset + 1,
+                    if code_offset + p > left {
+                        code_offset + p - left
+                    } else {
+                        0
+                    },
+                    underline,
+                    style,
+                );
             }
         }
         annotations_position.iter().filter_map(|&(_, annotation)| {
@@ -979,22 +1178,30 @@ impl EmitterWriter {
                     let buffer_msg_line_offset = buffer.num_lines();
 
                     buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber);
-                    buffer.append(buffer_msg_line_offset,
-                                  &format!("{}:{}:{}",
-                                           loc.file.name,
-                                           sm.doctest_offset_line(&loc.file.name, loc.line),
-                                           loc.col.0 + 1),
-                                  Style::LineAndColumn);
+                    buffer.append(
+                        buffer_msg_line_offset,
+                        &format!(
+                            "{}:{}:{}",
+                            loc.file.name,
+                            sm.doctest_offset_line(&loc.file.name, loc.line),
+                            loc.col.0 + 1,
+                        ),
+                        Style::LineAndColumn,
+                    );
                     for _ in 0..max_line_num_len {
                         buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
                     }
                 } else {
-                    buffer.prepend(0,
-                                   &format!("{}:{}:{}: ",
-                                            loc.file.name,
-                                            sm.doctest_offset_line(&loc.file.name, loc.line),
-                                            loc.col.0 + 1),
-                                   Style::LineAndColumn);
+                    buffer.prepend(
+                        0,
+                        &format!(
+                            "{}:{}:{}: ",
+                            loc.file.name,
+                            sm.doctest_offset_line(&loc.file.name, loc.line),
+                            loc.col.0 + 1,
+                        ),
+                        Style::LineAndColumn,
+                    );
                 }
             } else if !self.short_message {
                 // remember where we are in the output buffer for easy reference
@@ -1037,22 +1244,94 @@ impl EmitterWriter {
                 // Contains the vertical lines' positions for active multiline annotations
                 let mut multilines = FxHashMap::default();
 
+                // Get the left-side margin to remove it
+                let mut whitespace_margin = std::usize::MAX;
+                for line_idx in 0..annotated_file.lines.len() {
+                    let file = annotated_file.file.clone();
+                    let line = &annotated_file.lines[line_idx];
+                    if let Some(source_string) = file.get_line(line.line_index - 1) {
+                        let leading_whitespace = source_string
+                            .chars()
+                            .take_while(|c| c.is_whitespace())
+                            .count();
+                        if source_string.chars().any(|c| !c.is_whitespace()) {
+                            whitespace_margin = min(
+                                whitespace_margin,
+                                leading_whitespace,
+                            );
+                        }
+                    }
+                }
+                if whitespace_margin == std::usize::MAX {
+                    whitespace_margin = 0;
+                }
+
+                // Left-most column any visible span points at.
+                let mut span_left_margin = std::usize::MAX;
+                for line in &annotated_file.lines {
+                    for ann in &line.annotations {
+                        span_left_margin = min(span_left_margin, ann.start_col);
+                        span_left_margin = min(span_left_margin, ann.end_col);
+                    }
+                }
+                if span_left_margin == std::usize::MAX {
+                    span_left_margin = 0;
+                }
+
+                // Right-most column any visible span points at.
+                let mut span_right_margin = 0;
+                let mut label_right_margin = 0;
+                let mut max_line_len = 0;
+                for line in &annotated_file.lines {
+                    max_line_len = max(max_line_len, annotated_file.file
+                        .get_line(line.line_index - 1)
+                        .map(|s| s.len())
+                        .unwrap_or(0));
+                    for ann in &line.annotations {
+                        span_right_margin = max(span_right_margin, ann.start_col);
+                        span_right_margin = max(span_right_margin, ann.end_col);
+                        // FIXME: account for labels not in the same line
+                        let label_right = ann.label.as_ref().map(|l| l.len() + 1).unwrap_or(0);
+                        label_right_margin = max(label_right_margin, ann.end_col + label_right);
+                    }
+                }
+
+                let width_offset = 3 + max_line_num_len;
+                let code_offset = if annotated_file.multiline_depth == 0 {
+                    width_offset
+                } else {
+                    width_offset + annotated_file.multiline_depth + 1
+                };
+
+                let column_width = if let Some(width) = self.terminal_width {
+                    width
+                } else if self.ui_testing {
+                    140
+                } else {
+                    term_size::dimensions().map(|(w, _)| w - code_offset).unwrap_or(140)
+                };
+
+                let margin = Margin::new(
+                    whitespace_margin,
+                    span_left_margin,
+                    span_right_margin,
+                    label_right_margin,
+                    column_width,
+                    max_line_len,
+                );
+
                 // Next, output the annotate source for this file
                 for line_idx in 0..annotated_file.lines.len() {
                     let previous_buffer_line = buffer.num_lines();
 
-                    let width_offset = 3 + max_line_num_len;
-                    let code_offset = if annotated_file.multiline_depth == 0 {
-                        width_offset
-                    } else {
-                        width_offset + annotated_file.multiline_depth + 1
-                    };
-
-                    let depths = self.render_source_line(&mut buffer,
-                                                         annotated_file.file.clone(),
-                                                         &annotated_file.lines[line_idx],
-                                                         width_offset,
-                                                         code_offset);
+                    let depths = self.render_source_line(
+                        &mut buffer,
+                        annotated_file.file.clone(),
+                        &annotated_file.lines[line_idx],
+                        width_offset,
+                        code_offset,
+                        margin,
+                    );
 
                     let mut to_add = FxHashMap::default();
 
@@ -1099,25 +1378,24 @@ impl EmitterWriter {
 
                             let last_buffer_line_num = buffer.num_lines();
 
-                            buffer.puts(last_buffer_line_num,
-                                        0,
-                                        &self.maybe_anonymized(annotated_file.lines[line_idx + 1]
-                                                                             .line_index - 1),
-                                        Style::LineNumber);
-                            draw_col_separator(&mut buffer,
-                                               last_buffer_line_num,
-                                               1 + max_line_num_len);
-                            buffer.puts(last_buffer_line_num,
-                                        code_offset,
-                                        &unannotated_line,
-                                        Style::Quotation);
+                            self.draw_line(
+                                &mut buffer,
+                                &unannotated_line,
+                                annotated_file.lines[line_idx + 1].line_index - 1,
+                                last_buffer_line_num,
+                                width_offset,
+                                code_offset,
+                                margin,
+                            );
 
                             for (depth, style) in &multilines {
-                                draw_multiline_line(&mut buffer,
-                                                    last_buffer_line_num,
-                                                    width_offset,
-                                                    *depth,
-                                                    *style);
+                                draw_multiline_line(
+                                    &mut buffer,
+                                    last_buffer_line_num,
+                                    width_offset,
+                                    *depth,
+                                    *style,
+                                );
                             }
                         }
                     }
@@ -1207,7 +1485,7 @@ impl EmitterWriter {
                         // ...or trailing spaces. Account for substitutions containing unicode
                         // characters.
                         let sub_len = part.snippet.trim().chars().fold(0, |acc, ch| {
-                            acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
+                            acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)
                         });
 
                         let underline_start = (span_start_pos + start) as isize + offset;
@@ -1230,7 +1508,7 @@ impl EmitterWriter {
 
                         // length of the code after substitution
                         let full_sub_len = part.snippet.chars().fold(0, |acc, ch| {
-                            acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) as isize
+                            acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1) as isize
                         });
 
                         // length of the code to be substituted
diff --git a/src/librustc_errors/lib.rs b/src/librustc_errors/lib.rs
index 4018a667bf2..6585633e00a 100644
--- a/src/librustc_errors/lib.rs
+++ b/src/librustc_errors/lib.rs
@@ -383,7 +383,7 @@ impl Handler {
                                       cm: Option<Lrc<SourceMapperDyn>>,
                                       flags: HandlerFlags)
                                       -> Handler {
-        let emitter = Box::new(EmitterWriter::stderr(color_config, cm, false, false));
+        let emitter = Box::new(EmitterWriter::stderr(color_config, cm, false, false, None));
         Handler::with_emitter_and_flags(emitter, flags)
     }
 
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 24305cd517b..9cfcad42719 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -193,6 +193,7 @@ pub fn new_handler(error_format: ErrorOutputType,
                     source_map.map(|cm| cm as _),
                     short,
                     sessopts.debugging_opts.teach,
+                    sessopts.debugging_opts.terminal_width,
                 ).ui_testing(ui_testing)
             )
         },
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index cb6ae1c2bd2..c3447655062 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -433,7 +433,7 @@ pub fn make_test(s: &str,
         // Any errors in parsing should also appear when the doctest is compiled for real, so just
         // send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
         let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-        let emitter = EmitterWriter::new(box io::sink(), None, false, false, false);
+        let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None);
         // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
         let handler = Handler::with_emitter(false, None, box emitter);
         let sess = ParseSess::with_span_handler(handler, cm);
diff --git a/src/libsyntax/json.rs b/src/libsyntax/json.rs
index 83c9c692bd3..ada46f7bc5a 100644
--- a/src/libsyntax/json.rs
+++ b/src/libsyntax/json.rs
@@ -219,7 +219,7 @@ impl Diagnostic {
         }
         let buf = BufWriter::default();
         let output = buf.clone();
-        je.json_rendered.new_emitter(Box::new(buf), Some(je.sm.clone()), false)
+        je.json_rendered.new_emitter(Box::new(buf), Some(je.sm.clone()), false, None)
             .ui_testing(je.ui_testing).emit_diagnostic(db);
         let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
         let output = String::from_utf8(output).unwrap();
diff --git a/src/libsyntax/parse/lexer/tests.rs b/src/libsyntax/parse/lexer/tests.rs
index a915aa42fd1..652ae95c853 100644
--- a/src/libsyntax/parse/lexer/tests.rs
+++ b/src/libsyntax/parse/lexer/tests.rs
@@ -10,7 +10,14 @@ use errors::{Handler, emitter::EmitterWriter};
 use syntax_pos::{BytePos, Span};
 
 fn mk_sess(sm: Lrc<SourceMap>) -> ParseSess {
-    let emitter = EmitterWriter::new(Box::new(io::sink()), Some(sm.clone()), false, false, false);
+    let emitter = EmitterWriter::new(
+        Box::new(io::sink()),
+        Some(sm.clone()),
+        false,
+        false,
+        false,
+        None,
+    );
     ParseSess::with_span_handler(Handler::with_emitter(true, None, Box::new(emitter)), sm)
 }
 
@@ -28,10 +35,11 @@ fn t1() {
     with_default_globals(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let sh = mk_sess(sm.clone());
-        let mut string_reader = setup(&sm,
-                                    &sh,
-                                    "/* my source file */ fn main() { println!(\"zebra\"); }\n"
-                                        .to_string());
+        let mut string_reader = setup(
+            &sm,
+            &sh,
+            "/* my source file */ fn main() { println!(\"zebra\"); }\n".to_string(),
+        );
         assert_eq!(string_reader.next_token(), token::Comment);
         assert_eq!(string_reader.next_token(), token::Whitespace);
         let tok1 = string_reader.next_token();
@@ -127,8 +135,10 @@ fn character_a() {
     with_default_globals(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let sh = mk_sess(sm.clone());
-        assert_eq!(setup(&sm, &sh, "'a'".to_string()).next_token(),
-                    mk_lit(token::Char, "a", None));
+        assert_eq!(
+            setup(&sm, &sh, "'a'".to_string()).next_token(),
+            mk_lit(token::Char, "a", None),
+        );
     })
 }
 
@@ -137,8 +147,10 @@ fn character_space() {
     with_default_globals(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let sh = mk_sess(sm.clone());
-        assert_eq!(setup(&sm, &sh, "' '".to_string()).next_token(),
-                    mk_lit(token::Char, " ", None));
+        assert_eq!(
+            setup(&sm, &sh, "' '".to_string()).next_token(),
+            mk_lit(token::Char, " ", None),
+        );
     })
 }
 
@@ -147,8 +159,10 @@ fn character_escaped() {
     with_default_globals(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let sh = mk_sess(sm.clone());
-        assert_eq!(setup(&sm, &sh, "'\\n'".to_string()).next_token(),
-                    mk_lit(token::Char, "\\n", None));
+        assert_eq!(
+            setup(&sm, &sh, "'\\n'".to_string()).next_token(),
+            mk_lit(token::Char, "\\n", None),
+        );
     })
 }
 
@@ -157,8 +171,10 @@ fn lifetime_name() {
     with_default_globals(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let sh = mk_sess(sm.clone());
-        assert_eq!(setup(&sm, &sh, "'abc".to_string()).next_token(),
-                    token::Lifetime(Symbol::intern("'abc")));
+        assert_eq!(
+            setup(&sm, &sh, "'abc".to_string()).next_token(),
+            token::Lifetime(Symbol::intern("'abc")),
+        );
     })
 }
 
@@ -167,8 +183,10 @@ fn raw_string() {
     with_default_globals(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let sh = mk_sess(sm.clone());
-        assert_eq!(setup(&sm, &sh, "r###\"\"#a\\b\x00c\"\"###".to_string()).next_token(),
-                    mk_lit(token::StrRaw(3), "\"#a\\b\x00c\"", None));
+        assert_eq!(
+            setup(&sm, &sh, "r###\"\"#a\\b\x00c\"\"###".to_string()).next_token(),
+            mk_lit(token::StrRaw(3), "\"#a\\b\x00c\"", None),
+        );
     })
 }
 
@@ -179,11 +197,15 @@ fn literal_suffixes() {
         let sh = mk_sess(sm.clone());
         macro_rules! test {
             ($input: expr, $tok_type: ident, $tok_contents: expr) => {{
-                assert_eq!(setup(&sm, &sh, format!("{}suffix", $input)).next_token(),
-                            mk_lit(token::$tok_type, $tok_contents, Some("suffix")));
+                assert_eq!(
+                    setup(&sm, &sh, format!("{}suffix", $input)).next_token(),
+                    mk_lit(token::$tok_type, $tok_contents, Some("suffix")),
+                );
                 // with a whitespace separator:
-                assert_eq!(setup(&sm, &sh, format!("{} suffix", $input)).next_token(),
-                            mk_lit(token::$tok_type, $tok_contents, None));
+                assert_eq!(
+                    setup(&sm, &sh, format!("{} suffix", $input)).next_token(),
+                    mk_lit(token::$tok_type, $tok_contents, None),
+                );
             }}
         }
 
@@ -197,12 +219,18 @@ fn literal_suffixes() {
         test!("1.0", Float, "1.0");
         test!("1.0e10", Float, "1.0e10");
 
-        assert_eq!(setup(&sm, &sh, "2us".to_string()).next_token(),
-                    mk_lit(token::Integer, "2", Some("us")));
-        assert_eq!(setup(&sm, &sh, "r###\"raw\"###suffix".to_string()).next_token(),
-                    mk_lit(token::StrRaw(3), "raw", Some("suffix")));
-        assert_eq!(setup(&sm, &sh, "br###\"raw\"###suffix".to_string()).next_token(),
-                    mk_lit(token::ByteStrRaw(3), "raw", Some("suffix")));
+        assert_eq!(
+            setup(&sm, &sh, "2us".to_string()).next_token(),
+            mk_lit(token::Integer, "2", Some("us")),
+        );
+        assert_eq!(
+            setup(&sm, &sh, "r###\"raw\"###suffix".to_string()).next_token(),
+            mk_lit(token::StrRaw(3), "raw", Some("suffix")),
+        );
+        assert_eq!(
+            setup(&sm, &sh, "br###\"raw\"###suffix".to_string()).next_token(),
+            mk_lit(token::ByteStrRaw(3), "raw", Some("suffix")),
+        );
     })
 }
 
diff --git a/src/libsyntax/tests.rs b/src/libsyntax/tests.rs
index 4c0e1e3704d..c472212bc20 100644
--- a/src/libsyntax/tests.rs
+++ b/src/libsyntax/tests.rs
@@ -144,11 +144,14 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
             println!("text: {:?}", source_map.span_to_snippet(span));
         }
 
-        let emitter = EmitterWriter::new(Box::new(Shared { data: output.clone() }),
-                                        Some(source_map.clone()),
-                                        false,
-                                        false,
-                                        false);
+        let emitter = EmitterWriter::new(
+            Box::new(Shared { data: output.clone() }),
+            Some(source_map.clone()),
+            false,
+            false,
+            false,
+            None,
+        );
         let handler = Handler::with_emitter(true, None, Box::new(emitter));
         handler.span_err(msp, "foo");
 
diff --git a/src/test/ui/inline-asm-bad-operand.stderr b/src/test/ui/inline-asm-bad-operand.stderr
index 4554da7b798..55523bad6c5 100644
--- a/src/test/ui/inline-asm-bad-operand.stderr
+++ b/src/test/ui/inline-asm-bad-operand.stderr
@@ -37,8 +37,8 @@ LL |         asm!("mov sp, $0"::"r"(addr),
 error[E0669]: invalid value for constraint in inline assembly
   --> $DIR/inline-asm-bad-operand.rs:56:32
    |
-LL |                            "r"("hello e0669"));
-   |                                ^^^^^^^^^^^^^
+LL | ...                   "r"("hello e0669"));
+   |                           ^^^^^^^^^^^^^
 
 error: aborting due to 7 previous errors
 
diff --git a/src/test/ui/lint/lint-stability-deprecated.stderr b/src/test/ui/lint/lint-stability-deprecated.stderr
index 8132a66df8a..62380135b33 100644
--- a/src/test/ui/lint/lint-stability-deprecated.stderr
+++ b/src/test/ui/lint/lint-stability-deprecated.stderr
@@ -67,14 +67,14 @@ LL |         deprecated_unstable_text();
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:57:9
    |
-LL |         Trait::trait_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   Trait::trait_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:59:9
    |
-LL |         <Foo as Trait>::trait_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   <Foo as Trait>::trait_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::DeprecatedStruct': text
   --> $DIR/lint-stability-deprecated.rs:106:17
@@ -181,14 +181,14 @@ LL |         <Foo as Trait>::trait_deprecated_unstable(&foo);
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:155:9
    |
-LL |         Trait::trait_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   Trait::trait_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:157:9
    |
-LL |         <Foo as Trait>::trait_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   <Foo as Trait>::trait_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::DeprecatedTrait': text
   --> $DIR/lint-stability-deprecated.rs:185:10
@@ -421,20 +421,20 @@ LL |         <Foo>::trait_deprecated_unstable(&foo);
 warning: use of deprecated item 'lint_stability::MethodTester::method_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:53:13
    |
-LL |         foo.method_deprecated_unstable_text();
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   foo.method_deprecated_unstable_text();
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::MethodTester::method_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:54:9
    |
-LL |         Foo::method_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   Foo::method_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::MethodTester::method_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:55:9
    |
-LL |         <Foo>::method_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   <Foo>::method_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:56:13
@@ -445,8 +445,8 @@ LL |         foo.trait_deprecated_unstable_text();
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:58:9
    |
-LL |         <Foo>::trait_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   <Foo>::trait_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::DeprecatedStruct::i': text
   --> $DIR/lint-stability-deprecated.rs:107:13
@@ -505,8 +505,8 @@ LL |         foo.trait_deprecated_unstable_text();
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated_unstable_text': text
   --> $DIR/lint-stability-deprecated.rs:156:9
    |
-LL |         <Foo>::trait_deprecated_unstable_text(&foo);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | ...   <Foo>::trait_deprecated_unstable_text(&foo);
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of deprecated item 'lint_stability::Trait::trait_deprecated': text
   --> $DIR/lint-stability-deprecated.rs:173:13
diff --git a/src/test/ui/regions/regions-name-undeclared.stderr b/src/test/ui/regions/regions-name-undeclared.stderr
index 4840d751f7f..5f6a48a35f3 100644
--- a/src/test/ui/regions/regions-name-undeclared.stderr
+++ b/src/test/ui/regions/regions-name-undeclared.stderr
@@ -49,14 +49,14 @@ LL | fn fn_types(a: &'a isize,
 error[E0261]: use of undeclared lifetime name `'b`
   --> $DIR/regions-name-undeclared.rs:42:36
    |
-LL |                                   &'b isize,
-   |                                    ^^ undeclared lifetime
+LL | ...                   &'b isize,
+   |                        ^^ undeclared lifetime
 
 error[E0261]: use of undeclared lifetime name `'b`
   --> $DIR/regions-name-undeclared.rs:45:36
    |
-LL |                                   &'b isize)>,
-   |                                    ^^ undeclared lifetime
+LL | ...                   &'b isize)>,
+   |                        ^^ undeclared lifetime
 
 error[E0261]: use of undeclared lifetime name `'a`
   --> $DIR/regions-name-undeclared.rs:46:17
diff --git a/src/test/ui/terminal-width/non-whitespace-trimming-2.rs b/src/test/ui/terminal-width/non-whitespace-trimming-2.rs
new file mode 100644
index 00000000000..abd9e189a75
--- /dev/null
+++ b/src/test/ui/terminal-width/non-whitespace-trimming-2.rs
@@ -0,0 +1,6 @@
+// ignore-tidy-linelength
+
+fn main() {
+    let _: usize = 0; let _: usize = 1; let _: usize = 2; let _: usize = 3; let _: usize = 4; let _: usize = 5; let _: usize = 6; let _: usize = 7; let _: usize = 8; let _: usize = 9; let _: usize = 10; let _: usize = 11; let _: usize = 12; let _: usize = 13; let _: usize = 14; let _: usize = 15; let _: () = 42; let _: usize = 0; let _: usize = 1; let _: usize = 2; let _: usize = 3; let _: usize = 4; let _: usize = 5; let _: usize = 6; let _: usize = 7; let _: usize = 8; let _: usize = 9; let _: usize = 10; let _: usize = 11; let _: usize = 12; let _: usize = 13; let _: usize = 14; let _: usize = 15;
+//~^ ERROR mismatched types
+}
diff --git a/src/test/ui/terminal-width/non-whitespace-trimming-2.stderr b/src/test/ui/terminal-width/non-whitespace-trimming-2.stderr
new file mode 100644
index 00000000000..bf1699f5cab
--- /dev/null
+++ b/src/test/ui/terminal-width/non-whitespace-trimming-2.stderr
@@ -0,0 +1,12 @@
+error[E0308]: mismatched types
+  --> $DIR/non-whitespace-trimming-2.rs:4:311
+   |
+LL | ...; let _: usize = 14; let _: usize = 15; let _: () = 42; let _: usize = 0; let _: usize = 1; let _: usize = 2; let _: usize = 3; let _:...
+   |                                                        ^^ expected (), found integer
+   |
+   = note: expected type `()`
+              found type `{integer}`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/terminal-width/non-whitespace-trimming-unicode.rs b/src/test/ui/terminal-width/non-whitespace-trimming-unicode.rs
new file mode 100644
index 00000000000..8d4d1b16279
--- /dev/null
+++ b/src/test/ui/terminal-width/non-whitespace-trimming-unicode.rs
@@ -0,0 +1,6 @@
+// ignore-tidy-linelength
+
+fn main() {
+    let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀🦀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42;  let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀🦀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4";
+//~^ ERROR mismatched types
+}
diff --git a/src/test/ui/terminal-width/non-whitespace-trimming-unicode.stderr b/src/test/ui/terminal-width/non-whitespace-trimming-unicode.stderr
new file mode 100644
index 00000000000..b56b1948d9e
--- /dev/null
+++ b/src/test/ui/terminal-width/non-whitespace-trimming-unicode.stderr
@@ -0,0 +1,12 @@
+error[E0308]: mismatched types
+  --> $DIR/non-whitespace-trimming-unicode.rs:4:415
+   |
+LL | ...♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42;  let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓  ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆...
+   |                                               ^^ expected (), found integer
+   |
+   = note: expected type `()`
+              found type `{integer}`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/terminal-width/non-whitespace-trimming.rs b/src/test/ui/terminal-width/non-whitespace-trimming.rs
new file mode 100644
index 00000000000..f6c8d345c65
--- /dev/null
+++ b/src/test/ui/terminal-width/non-whitespace-trimming.rs
@@ -0,0 +1,6 @@
+// ignore-tidy-linelength
+
+fn main() {
+    let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();
+//~^ ERROR mismatched types
+}
diff --git a/src/test/ui/terminal-width/non-whitespace-trimming.stderr b/src/test/ui/terminal-width/non-whitespace-trimming.stderr
new file mode 100644
index 00000000000..622713eb5f6
--- /dev/null
+++ b/src/test/ui/terminal-width/non-whitespace-trimming.stderr
@@ -0,0 +1,12 @@
+error[E0308]: mismatched types
+  --> $DIR/non-whitespace-trimming.rs:4:241
+   |
+LL | ...) = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ()...
+   |                                                        ^^ expected (), found integer
+   |
+   = note: expected type `()`
+              found type `{integer}`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/terminal-width/whitespace-trimming-2.rs b/src/test/ui/terminal-width/whitespace-trimming-2.rs
new file mode 100644
index 00000000000..c68f678aab3
--- /dev/null
+++ b/src/test/ui/terminal-width/whitespace-trimming-2.rs
@@ -0,0 +1,8 @@
+// ignore-tidy-linelength
+
+fn foo() -> usize {
+                                                                                                                                                                                          ()
+//~^ ERROR mismatched types
+}
+
+fn main() {}
diff --git a/src/test/ui/terminal-width/whitespace-trimming-2.stderr b/src/test/ui/terminal-width/whitespace-trimming-2.stderr
new file mode 100644
index 00000000000..38df5a9e9a0
--- /dev/null
+++ b/src/test/ui/terminal-width/whitespace-trimming-2.stderr
@@ -0,0 +1,14 @@
+error[E0308]: mismatched types
+  --> $DIR/whitespace-trimming-2.rs:4:187
+   |
+LL | ...-> usize {
+   |       ----- expected `usize` because of return type
+LL | ...                                                                                                                                                                                 ()
+   |                                                                                                                                                                                     ^^ expected usize, found ()
+   |
+   = note: expected type `usize`
+              found type `()`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/terminal-width/whitespace-trimming.rs b/src/test/ui/terminal-width/whitespace-trimming.rs
new file mode 100644
index 00000000000..f747bcf17e0
--- /dev/null
+++ b/src/test/ui/terminal-width/whitespace-trimming.rs
@@ -0,0 +1,6 @@
+// ignore-tidy-linelength
+
+fn main() {
+                                                                                                                                                                                    let _: () = 42;
+//~^ ERROR mismatched types
+}
diff --git a/src/test/ui/terminal-width/whitespace-trimming.stderr b/src/test/ui/terminal-width/whitespace-trimming.stderr
new file mode 100644
index 00000000000..45a804b9f6a
--- /dev/null
+++ b/src/test/ui/terminal-width/whitespace-trimming.stderr
@@ -0,0 +1,12 @@
+error[E0308]: mismatched types
+  --> $DIR/whitespace-trimming.rs:4:193
+   |
+LL | ...                   let _: () = 42;
+   |                                   ^^ expected (), found integer
+   |
+   = note: expected type `()`
+              found type `{integer}`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index e07a07234c7..a564b991c40 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -162,6 +162,7 @@ const WHITELIST: &[Crate<'_>] = &[
     Crate("termcolor"),
     Crate("terminon"),
     Crate("termion"),
+    Crate("term_size"),
     Crate("thread_local"),
     Crate("ucd-util"),
     Crate("unicode-width"),