diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e211496343..3c434a7455 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -18791,6 +18791,11 @@ impl Editor { cx.emit(EditorEvent::BufferEdited); cx.emit(SearchEvent::MatchesInvalidated); if *singleton_buffer_edited { + if let Some(buffer) = multibuffer.read(cx).as_singleton() { + if buffer.read(cx).file().is_none() { + cx.emit(EditorEvent::TitleChanged); + } + } if let Some(project) = &self.project { #[allow(clippy::mutable_key_type)] let languages_affected = multibuffer.update(cx, |multibuffer, cx| { diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index c7f22149d9..53b3b53de8 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -2600,13 +2600,27 @@ impl MultiBuffer { return title.into(); } - if let Some(buffer) = self.as_singleton() { - if let Some(file) = buffer.read(cx).file() { - return file.file_name(cx).to_string_lossy(); - } - } + self.as_singleton() + .and_then(|buffer| { + let buffer = buffer.read(cx); - "untitled".into() + if let Some(file) = buffer.file() { + return Some(file.file_name(cx).to_string_lossy()); + } + + let title = buffer + .snapshot() + .chars() + .skip_while(|ch| ch.is_whitespace()) + .take_while(|&ch| ch != '\n') + .take(40) + .collect::() + .trim_end() + .to_string(); + + (!title.is_empty()).then(|| title.into()) + }) + .unwrap_or("untitled".into()) } pub fn set_title(&mut self, title: String, cx: &mut Context) { diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index 435bfd56ba..65ea1189cb 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -3651,3 +3651,59 @@ fn assert_line_indents(snapshot: &MultiBufferSnapshot) { "reversed_line_indents({max_row})" ); } + +#[gpui::test] +fn test_new_empty_buffer_uses_untitled_title(cx: &mut App) { + let buffer = cx.new(|cx| Buffer::local("", cx)); + let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + assert_eq!(multibuffer.read(cx).title(cx), "untitled"); +} + +#[gpui::test] +fn test_new_empty_buffer_uses_untitled_title_when_only_contains_whitespace(cx: &mut App) { + let buffer = cx.new(|cx| Buffer::local("\n ", cx)); + let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + assert_eq!(multibuffer.read(cx).title(cx), "untitled"); +} + +#[gpui::test] +fn test_new_empty_buffer_takes_first_line_for_title(cx: &mut App) { + let buffer = cx.new(|cx| Buffer::local("Hello World\nSecond line", cx)); + let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + assert_eq!(multibuffer.read(cx).title(cx), "Hello World"); +} + +#[gpui::test] +fn test_new_empty_buffer_takes_trimmed_first_line_for_title(cx: &mut App) { + let buffer = cx.new(|cx| Buffer::local("\nHello, World ", cx)); + let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + assert_eq!(multibuffer.read(cx).title(cx), "Hello, World"); +} + +#[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 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_buffers_title_can_be_set(cx: &mut App) { + let buffer = cx.new(|cx| Buffer::local("Hello World", cx)); + let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + assert_eq!(multibuffer.read(cx).title(cx), "Hello World"); + + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.set_title("Hey".into(), cx) + }); + assert_eq!(multibuffer.read(cx).title(cx), "Hey"); +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0c284661aa..22a03ea9bc 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -3027,7 +3027,7 @@ mod tests { }); cx.read(|cx| { assert!(editor.is_dirty(cx)); - assert_eq!(editor.read(cx).title(cx), "untitled"); + assert_eq!(editor.read(cx).title(cx), "hi"); }); // When the save completes, the buffer's title is updated and the language is assigned based