Fix non-ascii message bar text width calculation

When formatting text for display in the message bar, Alacritty was using
the byte length of the text instead of the glyph count. This lead to
unnecessary blank space at the end of lines due to overestimation of
their length.

There also were no extra spaces inserted after fullwidth characters,
leading to Alacritty giving them only a single cell of space. In line
with the rest of Alacritty's rendering, a wide char spacer whitespace is
now inserted in the message bar after glyphs which should occupy two
cells.

Fixes #4250.

Co-authored-by: Christian Duerr <contact@christianduerr.com>
This commit is contained in:
Alessandro Menezes 2020-10-03 23:17:36 -04:00 committed by GitHub
parent f5813899c3
commit 44a6dba0af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 12 deletions

View File

@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- IME window position with fullwidth characters in the search bar
- Selection expanding over 2 characters when scrolled in history with fullwidth characters in use
- Selection scrolling not starting when mouse is over the message bar
- Incorrect text width calculation in message bar when the message contains multibyte characters
## 0.5.0

View File

@ -1,6 +1,7 @@
use std::collections::VecDeque;
use alacritty_terminal::term::SizeInfo;
use unicode_width::UnicodeWidthChar;
pub const CLOSE_BUTTON_TEXT: &str = "[X]";
const CLOSE_BUTTON_PADDING: usize = 1;
@ -37,34 +38,49 @@ impl Message {
let total_lines =
(size_info.height() - 2. * size_info.padding_y()) / size_info.cell_height();
let max_lines = (total_lines as usize).saturating_sub(MIN_FREE_LINES);
let button_len = CLOSE_BUTTON_TEXT.len();
let button_len = CLOSE_BUTTON_TEXT.chars().count();
// Split line to fit the screen.
let mut lines = Vec::new();
let mut line = String::new();
let mut line_len = 0;
for c in self.text.trim().chars() {
if c == '\n'
|| line.len() == num_cols
|| line_len == num_cols
// Keep space in first line for button.
|| (lines.is_empty()
&& num_cols >= button_len
&& line.len() == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING))
&& line_len == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING))
{
let is_whitespace = c.is_whitespace();
// Attempt to wrap on word boundaries.
if let (Some(index), true) = (line.rfind(char::is_whitespace), c != '\n') {
let mut new_line = String::new();
if let Some(index) = line.rfind(char::is_whitespace).filter(|_| !is_whitespace) {
let split = line.split_off(index + 1);
line.pop();
lines.push(Self::pad_text(line, num_cols));
line = split
} else {
lines.push(Self::pad_text(line, num_cols));
line = String::new();
new_line = split;
}
lines.push(Self::pad_text(line, num_cols));
line = new_line;
line_len = line.chars().count();
// Do not append whitespace at EOL.
if is_whitespace {
continue;
}
}
if c != '\n' {
line.push(c);
line.push(c);
// Reserve extra column for fullwidth characters.
let width = c.width().unwrap_or(0);
if width == 2 {
line.push(' ');
}
line_len += width
}
lines.push(Self::pad_text(line, num_cols));
@ -110,7 +126,7 @@ impl Message {
/// Right-pad text to fit a specific number of columns.
#[inline]
fn pad_text(mut text: String, num_cols: usize) -> String {
let padding_len = num_cols.saturating_sub(text.len());
let padding_len = num_cols.saturating_sub(text.chars().count());
text.extend(vec![' '; padding_len]);
text
}
@ -340,6 +356,34 @@ mod tests {
]);
}
#[test]
fn wrap_with_unicode() {
let input = "ab\nc 👩d fgh";
let mut message_buffer = MessageBuffer::new();
message_buffer.push(Message::new(input.into(), MessageType::Error));
let size = SizeInfo::new(7., 10., 1., 1., 0., 0., false);
let lines = message_buffer.message().unwrap().text(&size);
assert_eq!(lines, vec![
String::from("ab [X]"),
String::from("c 👩 d "),
String::from("fgh ")
]);
}
#[test]
fn strip_whitespace_at_linebreak() {
let input = "\n0 1 2 3";
let mut message_buffer = MessageBuffer::new();
message_buffer.push(Message::new(input.into(), MessageType::Error));
let size = SizeInfo::new(3., 10., 1., 1., 0., 0., false);
let lines = message_buffer.message().unwrap().text(&size);
assert_eq!(lines, vec![String::from("[X]"), String::from("0 1"), String::from("2 3"),]);
}
#[test]
fn remove_duplicates() {
let mut message_buffer = MessageBuffer::new();