Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22909 from AayushMainali-Github/fix/22844-unicode-progress-report
fix: make analysis-stats progress bar Unicode-safe
Chayim Refael Friedman 2 weeks ago
parent aaa7af5 · parent e5197b0 · commit 0a032f2
-rw-r--r--crates/rust-analyzer/src/cli/progress_report.rs105
1 files changed, 78 insertions, 27 deletions
diff --git a/crates/rust-analyzer/src/cli/progress_report.rs b/crates/rust-analyzer/src/cli/progress_report.rs
index 028311388c..aff2c0d6ff 100644
--- a/crates/rust-analyzer/src/cli/progress_report.rs
+++ b/crates/rust-analyzer/src/cli/progress_report.rs
@@ -65,31 +65,7 @@ impl<'a> ProgressReport<'a> {
}
fn update_text(&mut self, text: &str) {
- // Get length of common portion
- let mut common_prefix_length = 0;
- let common_length = usize::min(self.text.len(), text.len());
-
- while common_prefix_length < common_length
- && text.chars().nth(common_prefix_length).unwrap()
- == self.text.chars().nth(common_prefix_length).unwrap()
- {
- common_prefix_length += 1;
- }
-
- // Backtrack to the first differing character
- let mut output = String::new();
- output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length);
- // Output new suffix, using chars() iter to ensure unicode compatibility
- output.extend(text.chars().skip(common_prefix_length));
-
- // If the new text is shorter than the old one: delete overlapping characters
- if let Some(overlap_count) = self.text.len().checked_sub(text.len())
- && overlap_count > 0
- {
- output += &" ".repeat(overlap_count);
- output += &"\x08".repeat(overlap_count);
- }
-
+ let output = render_text_update(&self.text, text);
let _ = io::stdout().write(output.as_bytes());
let _ = io::stdout().flush();
text.clone_into(&mut self.text);
@@ -105,11 +81,86 @@ impl<'a> ProgressReport<'a> {
}
// Fill all last text to space and return the cursor
- let spaces = " ".repeat(self.text.len());
- let backspaces = "\x08".repeat(self.text.len());
+ let len = self.text.chars().count();
+ let spaces = " ".repeat(len);
+ let backspaces = "\x08".repeat(len);
print!("{backspaces}{spaces}{backspaces}");
let _ = io::stdout().flush();
self.text = String::new();
}
}
+
+fn render_text_update(old: &str, new: &str) -> String {
+ let old_len = old.chars().count();
+ let new_len = new.chars().count();
+
+ // Get length of common portion
+ let mut common_prefix_length = 0;
+ let common_length = usize::min(old_len, new_len);
+
+ while common_prefix_length < common_length
+ && new.chars().nth(common_prefix_length).unwrap()
+ == old.chars().nth(common_prefix_length).unwrap()
+ {
+ common_prefix_length += 1;
+ }
+
+ // Backtrack to the first differing character
+ let mut output = String::new();
+ output += &'\x08'.to_string().repeat(old_len - common_prefix_length);
+ // Output new suffix, using chars() iter to ensure unicode compatibility
+ output.extend(new.chars().skip(common_prefix_length));
+
+ // If the new text is shorter than the old one: delete overlapping characters
+ if let Some(overlap_count) = old_len.checked_sub(new_len)
+ && overlap_count > 0
+ {
+ output += &" ".repeat(overlap_count);
+ output += &"\x08".repeat(overlap_count);
+ }
+
+ output
+}
+
+#[cfg(test)]
+mod tests {
+ use super::render_text_update;
+
+ #[test]
+ fn ascii_prefix_reuse() {
+ let old = "1/7 14% processing: foo";
+ let new = "1/7 28% processing: bar";
+ let update = render_text_update(old, new);
+
+ let common = "1/7 ";
+ let backspaces = old.chars().count() - common.chars().count();
+ let expected = format!("{}{}", "\x08".repeat(backspaces), "28% processing: bar");
+ assert_eq!(update, expected);
+ }
+
+ #[test]
+ fn unicode_identifiers_do_not_panic() {
+ // Regression test for rust-lang/rust-analyzer#22844: previous code
+ // compared byte lengths with char indices, so `chars().nth(...).unwrap()`
+ // panicked on non-ASCII.
+ let old = "1/7 14% processing: f::消息";
+ let new = "2/7 28% processing: f::消息内容";
+ let update = render_text_update(old, new);
+
+ let backspaces = old.chars().count();
+ let expected = format!("{}{new}", "\x08".repeat(backspaces));
+ assert_eq!(update, expected);
+ }
+
+ #[test]
+ fn shorter_unicode_message_clears_overlap() {
+ let old = "processing: 消息内容";
+ let new = "processing: 消息";
+ let update = render_text_update(old, new);
+
+ // Drop the last two chars, then blank/backspace the leftover width.
+ let expected = "\x08\x08 \x08\x08";
+ assert_eq!(update, expected);
+ }
+}