Coalesce consecutive spaces in new buffer tab titles (#32363)
Joseph T. Lyons
created 6 months ago
VS Code has a behavior where it coalesces consecutive spaces in new
buffer tab titles, which I quite like. This presents the content better
and allows more meaningful content to be displayed, as consecutive
spaces don't count towards the 40 character limit.
VS Code
<img width="1013" alt="SCR-20250608-uelt"
src="https://github.com/user-attachments/assets/71a1fd4b-a506-4eab-b6a4-66096a12f1ad"
/>
Zed
<img width="1136" alt="SCR-20250608-ueif"
src="https://github.com/user-attachments/assets/f40fc3c9-0f0f-471d-93ed-be9568fbe778"
/>
Release Notes:
- N/A
Change summary
crates/editor/src/editor.rs | 2
crates/multi_buffer/src/multi_buffer.rs | 67 +++++++++++++++-----
crates/multi_buffer/src/multi_buffer_tests.rs | 18 ++++-
3 files changed, 64 insertions(+), 23 deletions(-)
Detailed changes
@@ -18858,7 +18858,7 @@ impl Editor {
cx.emit(EditorEvent::BufferEdited);
cx.emit(SearchEvent::MatchesInvalidated);
if *singleton_buffer_edited {
- if let Some(buffer) = multibuffer.read(cx).as_singleton() {
+ if let Some(buffer) = edited_buffer {
if buffer.read(cx).file().is_none() {
cx.emit(EditorEvent::TitleChanged);
}
@@ -2600,27 +2600,58 @@ impl MultiBuffer {
return title.into();
}
- self.as_singleton()
- .and_then(|buffer| {
- let buffer = buffer.read(cx);
+ if let Some(buffer) = self.as_singleton() {
+ let buffer = buffer.read(cx);
+
+ if let Some(file) = buffer.file() {
+ return file.file_name(cx).to_string_lossy();
+ }
+
+ if let Some(title) = self.buffer_based_title(buffer) {
+ return title;
+ }
+ };
+
+ "untitled".into()
+ }
+
+ fn buffer_based_title(&self, buffer: &Buffer) -> Option<Cow<str>> {
+ let mut is_leading_whitespace = true;
+ let mut count = 0;
+ let mut prev_was_space = false;
+ let mut title = String::new();
+
+ for ch in buffer.snapshot().chars() {
+ if is_leading_whitespace && ch.is_whitespace() {
+ continue;
+ }
- if let Some(file) = buffer.file() {
- return Some(file.file_name(cx).to_string_lossy());
+ is_leading_whitespace = false;
+
+ if ch == '\n' || count >= 40 {
+ break;
+ }
+
+ if ch.is_whitespace() {
+ if !prev_was_space {
+ title.push(' ');
+ count += 1;
+ prev_was_space = true;
}
+ } else {
+ title.push(ch);
+ count += 1;
+ prev_was_space = false;
+ }
+ }
- let title = buffer
- .snapshot()
- .chars()
- .skip_while(|ch| ch.is_whitespace())
- .take_while(|&ch| ch != '\n')
- .take(40)
- .collect::<String>()
- .trim_end()
- .to_string();
-
- (!title.is_empty()).then(|| title.into())
- })
- .unwrap_or("untitled".into())
+ let title = title.trim_end().to_string();
+
+ if !title.is_empty() {
+ return Some(title.into());
+ }
+
+ None
}
pub fn set_title(&mut self, title: String, cx: &mut Context<Self>) {
@@ -3686,10 +3686,20 @@ fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) {
#[gpui::test]
fn test_new_empty_buffer_uses_truncated_first_line_for_title(cx: &mut App) {
- let title_after = ["a", "b", "c", "d"]
- .map(|letter| letter.repeat(10))
- .join("");
- let title = format!("{}{}", title_after, "e".repeat(10));
+ let title = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
+ let title_after = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd";
+ let buffer = cx.new(|cx| Buffer::local(title, cx));
+ let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
+
+ assert_eq!(multibuffer.read(cx).title(cx), title_after);
+}
+
+#[gpui::test]
+fn test_new_empty_buffer_uses_truncated_first_line_for_title_after_merging_adjacent_spaces(
+ cx: &mut App,
+) {
+ let title = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddddeeeeeeeeee";
+ let title_after = "aaaaaaaaaabbbbbbbbbb ccccccccccddddddddd";
let buffer = cx.new(|cx| Buffer::local(title, cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));