editor: Wrap placeholder if text overflows (#37919)

Bennet Bo Fenner and Agus Zubiaga created

This fixes an issue where long placeholders would be cut off, e.g. in a
Claude Code thread:

<img width="387" height="115" alt="image"
src="https://github.com/user-attachments/assets/831a54aa-cf2b-4d87-af86-e368a5936f6b"
/>

Now:

<img width="354" height="115" alt="image"
src="https://github.com/user-attachments/assets/e5df5e05-0869-4db2-8dee-38611263191c"
/>


Most of the changes in this PR are caused by us requiring `&mut Window`
in `set_placeholder_text`.

Release Notes:

- Fixed an issue where placeholders inside editors would not wrap

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>

Change summary

crates/agent_ui/src/acp/message_editor.rs                        |  4 
crates/agent_ui/src/acp/thread_history.rs                        |  2 
crates/agent_ui/src/acp/thread_view.rs                           |  3 
crates/agent_ui/src/active_thread.rs                             |  1 
crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs |  2 
crates/agent_ui/src/inline_prompt_editor.rs                      |  6 
crates/agent_ui/src/message_editor.rs                            |  2 
crates/agent_ui/src/thread_history.rs                            |  2 
crates/collab_ui/src/collab_panel.rs                             |  2 
crates/debugger_ui/src/new_process_modal.rs                      |  4 
crates/debugger_ui/src/session/running/breakpoint_list.rs        |  2 
crates/debugger_ui/src/session/running/console.rs                |  2 
crates/debugger_ui/src/session/running/memory_view.rs            |  4 
crates/editor/src/editor.rs                                      | 63 +
crates/editor/src/element.rs                                     | 13 
crates/extensions_ui/src/extensions_ui.rs                        |  2 
crates/git_ui/src/commit_modal.rs                                |  2 
crates/git_ui/src/git_panel.rs                                   | 35 
crates/git_ui/src/git_ui.rs                                      |  2 
crates/go_to_line/src/go_to_line.rs                              | 19 
crates/keymap_editor/src/keymap_editor.rs                        |  4 
crates/language_models/src/provider/anthropic.rs                 |  2 
crates/language_models/src/provider/bedrock.rs                   |  8 
crates/language_models/src/provider/deepseek.rs                  |  2 
crates/language_models/src/provider/google.rs                    |  2 
crates/language_models/src/provider/mistral.rs                   |  2 
crates/language_models/src/provider/open_router.rs               |  7 
crates/outline_panel/src/outline_panel.rs                        |  2 
crates/picker/src/head.rs                                        |  2 
crates/picker/src/picker.rs                                      |  2 
crates/recent_projects/src/remote_servers.rs                     |  7 
crates/rules_library/src/rules_library.rs                        |  2 
crates/search/src/buffer_search.rs                               | 14 
crates/search/src/project_search.rs                              |  8 
crates/ui_input/src/ui_input.rs                                  |  2 
crates/zeta/src/rate_completion_modal.rs                         |  2 
36 files changed, 143 insertions(+), 97 deletions(-)

Detailed changes

crates/agent_ui/src/acp/message_editor.rs ๐Ÿ”—

@@ -91,7 +91,7 @@ impl MessageEditor {
         prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
         available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
         agent_name: SharedString,
-        placeholder: impl Into<Arc<str>>,
+        placeholder: &str,
         mode: EditorMode,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -117,7 +117,7 @@ impl MessageEditor {
             let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 
             let mut editor = Editor::new(mode, buffer, None, window, cx);
-            editor.set_placeholder_text(placeholder, cx);
+            editor.set_placeholder_text(placeholder, window, cx);
             editor.set_show_indent_guides(false, cx);
             editor.set_soft_wrap();
             editor.set_use_modal_editing(true);

crates/agent_ui/src/acp/thread_history.rs ๐Ÿ”—

@@ -70,7 +70,7 @@ impl AcpThreadHistory {
     ) -> Self {
         let search_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Search threads...", cx);
+            editor.set_placeholder_text("Search threads...", window, cx);
             editor
         });
 

crates/agent_ui/src/acp/thread_view.rs ๐Ÿ”—

@@ -250,6 +250,7 @@ impl ThreadFeedbackState {
             );
             editor.set_placeholder_text(
                 "What went wrong? Share your feedback so we can improve.",
+                window,
                 cx,
             );
             editor
@@ -355,7 +356,7 @@ impl AcpThreadView {
                 prompt_capabilities.clone(),
                 available_commands.clone(),
                 agent.name(),
-                placeholder,
+                &placeholder,
                 editor::EditorMode::AutoHeight {
                     min_lines: MIN_EDITOR_LINES,
                     max_lines: Some(MAX_EDITOR_LINES),

crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs ๐Ÿ”—

@@ -156,7 +156,7 @@ impl ManageProfilesModal {
     ) {
         let name_editor = cx.new(|cx| Editor::single_line(window, cx));
         name_editor.update(cx, |editor, cx| {
-            editor.set_placeholder_text("Profile name", cx);
+            editor.set_placeholder_text("Profile name", window, cx);
         });
 
         self.mode = Mode::NewProfile(NewProfileMode {

crates/agent_ui/src/inline_prompt_editor.rs ๐Ÿ”—

@@ -229,7 +229,7 @@ impl<T: 'static> PromptEditor<T> {
         self.editor = cx.new(|cx| {
             let mut editor = Editor::auto_height(1, Self::MAX_LINES as usize, window, cx);
             editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
-            editor.set_placeholder_text("Add a promptโ€ฆ", cx);
+            editor.set_placeholder_text("Add a promptโ€ฆ", window, cx);
             editor.set_text(prompt, window, cx);
             insert_message_creases(
                 &mut editor,
@@ -782,7 +782,7 @@ impl PromptEditor<BufferCodegen> {
             // always show the cursor (even when it isn't focused) because
             // typing in one will make what you typed appear in all of them.
             editor.set_show_cursor_when_unfocused(true, cx);
-            editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
+            editor.set_placeholder_text(&Self::placeholder_text(&mode, window, cx), window, cx);
             editor.register_addon(ContextCreasesAddon::new());
             editor.set_context_menu_options(ContextMenuOptions {
                 min_entries_visible: 12,
@@ -949,7 +949,7 @@ impl PromptEditor<TerminalCodegen> {
                 cx,
             );
             editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
-            editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
+            editor.set_placeholder_text(&Self::placeholder_text(&mode, window, cx), window, cx);
             editor.set_context_menu_options(ContextMenuOptions {
                 min_entries_visible: 12,
                 max_entries_visible: 12,

crates/agent_ui/src/message_editor.rs ๐Ÿ”—

@@ -124,7 +124,7 @@ pub(crate) fn create_editor(
             window,
             cx,
         );
-        editor.set_placeholder_text("Message the agent โ€“ @ to include context", cx);
+        editor.set_placeholder_text("Message the agent โ€“ @ to include context", window, cx);
         editor.disable_word_completions();
         editor.set_show_indent_guides(false, cx);
         editor.set_soft_wrap();

crates/agent_ui/src/thread_history.rs ๐Ÿ”—

@@ -73,7 +73,7 @@ impl ThreadHistory {
     ) -> Self {
         let search_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Search threads...", cx);
+            editor.set_placeholder_text("Search threads...", window, cx);
             editor
         });
 

crates/collab_ui/src/collab_panel.rs ๐Ÿ”—

@@ -280,7 +280,7 @@ impl CollabPanel {
         cx.new(|cx| {
             let filter_editor = cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text("Filter...", cx);
+                editor.set_placeholder_text("Filter...", window, cx);
                 editor
             });
 

crates/debugger_ui/src/new_process_modal.rs ๐Ÿ”—

@@ -803,12 +803,12 @@ impl ConfigureMode {
     pub(super) fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
         let program = cx.new(|cx| Editor::single_line(window, cx));
         program.update(cx, |this, cx| {
-            this.set_placeholder_text("ENV=Zed ~/bin/program --option", cx);
+            this.set_placeholder_text("ENV=Zed ~/bin/program --option", window, cx);
         });
 
         let cwd = cx.new(|cx| Editor::single_line(window, cx));
         cwd.update(cx, |this, cx| {
-            this.set_placeholder_text("Ex: $ZED_WORKTREE_ROOT", cx);
+            this.set_placeholder_text("Ex: $ZED_WORKTREE_ROOT", window, cx);
         });
 
         cx.new(|_| Self {

crates/debugger_ui/src/session/running/breakpoint_list.rs ๐Ÿ”—

@@ -219,7 +219,7 @@ impl BreakpointList {
         });
 
         self.input.update(cx, |this, cx| {
-            this.set_placeholder_text(placeholder, cx);
+            this.set_placeholder_text(placeholder, window, cx);
             this.set_read_only(is_exception_breakpoint);
             this.set_text(active_value.as_deref().unwrap_or(""), window, cx);
         });

crates/debugger_ui/src/session/running/console.rs ๐Ÿ”—

@@ -83,7 +83,7 @@ impl Console {
         let this = cx.weak_entity();
         let query_bar = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Evaluate an expression", cx);
+            editor.set_placeholder_text("Evaluate an expression", window, cx);
             editor.set_use_autoclose(false);
             editor.set_show_gutter(false, cx);
             editor.set_show_wrap_guides(false, cx);

crates/debugger_ui/src/session/running/memory_view.rs ๐Ÿ”—

@@ -428,14 +428,14 @@ impl MemoryView {
         if !self.is_writing_memory {
             self.query_editor.update(cx, |this, cx| {
                 this.clear(window, cx);
-                this.set_placeholder_text("Write to Selected Memory Range", cx);
+                this.set_placeholder_text("Write to Selected Memory Range", window, cx);
             });
             self.is_writing_memory = true;
             self.query_editor.focus_handle(cx).focus(window);
         } else {
             self.query_editor.update(cx, |this, cx| {
                 this.clear(window, cx);
-                this.set_placeholder_text("Go to Memory Address / Expression", cx);
+                this.set_placeholder_text("Go to Memory Address / Expression", window, cx);
             });
             self.is_writing_memory = false;
         }

crates/editor/src/editor.rs ๐Ÿ”—

@@ -1008,6 +1008,7 @@ pub struct Editor {
     /// Map of how text in the buffer should be displayed.
     /// Handles soft wraps, folds, fake inlay text insertions, etc.
     pub display_map: Entity<DisplayMap>,
+    placeholder_display_map: Option<Entity<DisplayMap>>,
     pub selections: SelectionsCollection,
     pub scroll_manager: ScrollManager,
     /// When inline assist editors are linked, they all render cursors because
@@ -1057,7 +1058,6 @@ pub struct Editor {
     show_breakpoints: Option<bool>,
     show_wrap_guides: Option<bool>,
     show_indent_guides: Option<bool>,
-    placeholder_text: Option<Arc<str>>,
     highlight_order: usize,
     highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
     background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
@@ -1209,7 +1209,7 @@ pub struct EditorSnapshot {
     show_breakpoints: Option<bool>,
     git_blame_gutter_max_author_length: Option<usize>,
     pub display_snapshot: DisplaySnapshot,
-    pub placeholder_text: Option<Arc<str>>,
+    pub placeholder_display_snapshot: Option<DisplaySnapshot>,
     is_focused: bool,
     scroll_anchor: ScrollAnchor,
     ongoing_scroll: OngoingScroll,
@@ -2066,6 +2066,7 @@ impl Editor {
             last_focused_descendant: None,
             buffer: buffer.clone(),
             display_map: display_map.clone(),
+            placeholder_display_map: None,
             selections,
             scroll_manager: ScrollManager::new(cx),
             columnar_selection_state: None,
@@ -2109,7 +2110,6 @@ impl Editor {
             show_breakpoints: None,
             show_wrap_guides: None,
             show_indent_guides,
-            placeholder_text: None,
             highlight_order: 0,
             highlighted_rows: HashMap::default(),
             background_highlights: HashMap::default(),
@@ -2728,9 +2728,12 @@ impl Editor {
             show_breakpoints: self.show_breakpoints,
             git_blame_gutter_max_author_length,
             display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
+            placeholder_display_snapshot: self
+                .placeholder_display_map
+                .as_ref()
+                .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
             scroll_anchor: self.scroll_manager.anchor(),
             ongoing_scroll: self.scroll_manager.ongoing_scroll(),
-            placeholder_text: self.placeholder_text.clone(),
             is_focused: self.focus_handle.is_focused(window),
             current_line_highlight: self
                 .current_line_highlight
@@ -2826,20 +2829,37 @@ impl Editor {
         self.refresh_edit_prediction(false, false, window, cx);
     }
 
-    pub fn placeholder_text(&self) -> Option<&str> {
-        self.placeholder_text.as_deref()
+    pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
+        self.placeholder_display_map
+            .as_ref()
+            .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
     }
 
     pub fn set_placeholder_text(
         &mut self,
-        placeholder_text: impl Into<Arc<str>>,
+        placeholder_text: &str,
+        window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        let placeholder_text = Some(placeholder_text.into());
-        if self.placeholder_text != placeholder_text {
-            self.placeholder_text = placeholder_text;
-            cx.notify();
-        }
+        let multibuffer = cx
+            .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
+
+        let style = window.text_style();
+
+        self.placeholder_display_map = Some(cx.new(|cx| {
+            DisplayMap::new(
+                multibuffer,
+                style.font(),
+                style.font_size.to_pixels(window.rem_size()),
+                None,
+                FILE_HEADER_HEIGHT,
+                MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
+                Default::default(),
+                DiagnosticSeverity::Off,
+                cx,
+            )
+        }));
+        cx.notify();
     }
 
     pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
@@ -18895,8 +18915,16 @@ impl Editor {
     // Called by the element. This method is not designed to be called outside of the editor
     // element's layout code because it does not notify when rewrapping is computed synchronously.
     pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
-        self.display_map
-            .update(cx, |map, cx| map.set_wrap_width(width, cx))
+        if self.is_empty(cx) {
+            self.placeholder_display_map
+                .as_ref()
+                .map_or(false, |display_map| {
+                    display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
+                })
+        } else {
+            self.display_map
+                .update(cx, |map, cx| map.set_wrap_width(width, cx))
+        }
     }
 
     pub fn set_soft_wrap(&mut self) {
@@ -23011,8 +23039,10 @@ impl EditorSnapshot {
         self.is_focused
     }
 
-    pub fn placeholder_text(&self) -> Option<&Arc<str>> {
-        self.placeholder_text.as_ref()
+    pub fn placeholder_text(&self) -> Option<String> {
+        self.placeholder_display_snapshot
+            .as_ref()
+            .map(|display_map| display_map.text())
     }
 
     pub fn scroll_position(&self) -> gpui::Point<f32> {
@@ -24003,6 +24033,7 @@ impl BreakpointPromptEditor {
                     BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
                     BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
                 },
+                window,
                 cx,
             );
 

crates/editor/src/element.rs ๐Ÿ”—

@@ -3453,12 +3453,15 @@ impl EditorElement {
 
             let placeholder_lines = placeholder_text
                 .as_ref()
-                .map_or("", AsRef::as_ref)
-                .split('\n')
+                .map_or(Vec::new(), |text| text.split('\n').collect::<Vec<_>>());
+
+            let placeholder_line_count = placeholder_lines.len();
+
+            placeholder_lines
+                .into_iter()
                 .skip(rows.start.0 as usize)
                 .chain(iter::repeat(""))
-                .take(rows.len());
-            placeholder_lines
+                .take(cmp::max(rows.len(), placeholder_line_count))
                 .map(move |line| {
                     let run = TextRun {
                         len: line.len(),
@@ -10743,7 +10746,7 @@ mod tests {
         let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
         window
             .update(cx, |editor, window, cx| {
-                editor.set_placeholder_text("hello", cx);
+                editor.set_placeholder_text("hello", window, cx);
                 editor.insert_blocks(
                     [BlockProperties {
                         style: BlockStyle::Fixed,

crates/extensions_ui/src/extensions_ui.rs ๐Ÿ”—

@@ -327,7 +327,7 @@ impl ExtensionsPage {
 
             let query_editor = cx.new(|cx| {
                 let mut input = Editor::single_line(window, cx);
-                input.set_placeholder_text("Search extensions...", cx);
+                input.set_placeholder_text("Search extensions...", window, cx);
                 if let Some(id) = focus_extension_id {
                     input.set_text(format!("id:{id}"), window, cx);
                 }

crates/git_ui/src/commit_modal.rs ๐Ÿ”—

@@ -198,7 +198,7 @@ impl CommitModal {
             && commit_message.is_empty()
         {
             commit_editor.update(cx, |editor, cx| {
-                editor.set_placeholder_text(suggested_commit_message, cx);
+                editor.set_placeholder_text(&suggested_commit_message, window, cx);
             });
         }
 

crates/git_ui/src/git_panel.rs ๐Ÿ”—

@@ -420,7 +420,7 @@ pub(crate) fn commit_message_editor(
     commit_editor.set_show_wrap_guides(false, cx);
     commit_editor.set_show_indent_guides(false, cx);
     let placeholder = placeholder.unwrap_or("Enter commit message".into());
-    commit_editor.set_placeholder_text(placeholder, cx);
+    commit_editor.set_placeholder_text(&placeholder, window, cx);
     commit_editor
 }
 
@@ -445,10 +445,10 @@ impl GitPanel {
             .detach();
 
             let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
-            cx.observe_global::<SettingsStore>(move |this, cx| {
+            cx.observe_global_in::<SettingsStore>(window, move |this, window, cx| {
                 let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
                 if is_sort_by_path != was_sort_by_path {
-                    this.update_visible_entries(cx);
+                    this.update_visible_entries(window, cx);
                 }
                 was_sort_by_path = is_sort_by_path
             })
@@ -1053,7 +1053,7 @@ impl GitPanel {
             let filename = path.path.file_name()?.to_string_lossy();
 
             if !entry.status.is_created() {
-                self.perform_checkout(vec![entry.clone()], cx);
+                self.perform_checkout(vec![entry.clone()], window, cx);
             } else {
                 let prompt = prompt(&format!("Trash {}?", filename), None, window, cx);
                 cx.spawn_in(window, async move |_, cx| {
@@ -1082,7 +1082,12 @@ impl GitPanel {
         });
     }
 
-    fn perform_checkout(&mut self, entries: Vec<GitStatusEntry>, cx: &mut Context<Self>) {
+    fn perform_checkout(
+        &mut self,
+        entries: Vec<GitStatusEntry>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
         let workspace = self.workspace.clone();
         let Some(active_repository) = self.active_repository.clone() else {
             return;
@@ -1095,7 +1100,7 @@ impl GitPanel {
             entries: entries.clone(),
             finished: false,
         });
-        self.update_visible_entries(cx);
+        self.update_visible_entries(window, cx);
         let task = cx.spawn(async move |_, cx| {
             let tasks: Vec<_> = workspace.update(cx, |workspace, cx| {
                 workspace.project().update(cx, |project, cx| {
@@ -1142,16 +1147,16 @@ impl GitPanel {
             Ok(())
         });
 
-        cx.spawn(async move |this, cx| {
+        cx.spawn_in(window, async move |this, cx| {
             let result = task.await;
 
-            this.update(cx, |this, cx| {
+            this.update_in(cx, |this, window, cx| {
                 for pending in this.pending.iter_mut() {
                     if pending.op_id == op_id {
                         pending.finished = true;
                         if result.is_err() {
                             pending.target_status = TargetStatus::Unchanged;
-                            this.update_visible_entries(cx);
+                            this.update_visible_entries(window, cx);
                         }
                         break;
                     }
@@ -1207,10 +1212,10 @@ impl GitPanel {
             window,
             cx,
         );
-        cx.spawn(async move |this, cx| {
+        cx.spawn_in(window, async move |this, cx| {
             if let Ok(RestoreCancel::RestoreTrackedFiles) = prompt.await {
-                this.update(cx, |this, cx| {
-                    this.perform_checkout(entries, cx);
+                this.update_in(cx, |this, window, cx| {
+                    this.perform_checkout(entries, window, cx);
                 })
                 .ok();
             }
@@ -2642,7 +2647,7 @@ impl GitPanel {
                         if clear_pending {
                             git_panel.clear_pending();
                         }
-                        git_panel.update_visible_entries(cx);
+                        git_panel.update_visible_entries(window, cx);
                         git_panel.update_scrollbar_properties(window, cx);
                     })
                     .ok();
@@ -2695,7 +2700,7 @@ impl GitPanel {
         self.pending.retain(|v| !v.finished)
     }
 
-    fn update_visible_entries(&mut self, cx: &mut Context<Self>) {
+    fn update_visible_entries(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         let bulk_staging = self.bulk_staging.take();
         let last_staged_path_prev_index = bulk_staging
             .as_ref()
@@ -2870,7 +2875,7 @@ impl GitPanel {
         let placeholder_text = suggested_commit_message.unwrap_or("Enter commit message".into());
 
         self.commit_editor.update(cx, |editor, cx| {
-            editor.set_placeholder_text(Arc::from(placeholder_text), cx)
+            editor.set_placeholder_text(&placeholder_text, window, cx)
         });
 
         cx.notify();

crates/git_ui/src/git_ui.rs ๐Ÿ”—

@@ -637,7 +637,7 @@ impl GitCloneModal {
     pub fn show(panel: Entity<GitPanel>, window: &mut Window, cx: &mut Context<Self>) -> Self {
         let repo_input = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Enter repository URLโ€ฆ", cx);
+            editor.set_placeholder_text("Enter repository URLโ€ฆ", window, cx);
             editor
         });
         let focus_handle = repo_input.focus_handle(cx);

crates/go_to_line/src/go_to_line.rs ๐Ÿ”—

@@ -103,17 +103,20 @@ impl GoToLine {
                             return;
                         };
                         editor.update(cx, |editor, cx| {
-                            if let Some(placeholder_text) = editor.placeholder_text()
+                            if let Some(placeholder_text) = editor.placeholder_text(cx)
                                 && editor.text(cx).is_empty()
                             {
-                                let placeholder_text = placeholder_text.to_string();
                                 editor.set_text(placeholder_text, window, cx);
                             }
                         });
                     }
                 })
                 .detach();
-            editor.set_placeholder_text(format!("{line}{FILE_ROW_COLUMN_DELIMITER}{column}"), cx);
+            editor.set_placeholder_text(
+                &format!("{line}{FILE_ROW_COLUMN_DELIMITER}{column}"),
+                window,
+                cx,
+            );
             editor
         });
         let line_editor_change = cx.subscribe_in(&line_editor, window, Self::on_line_editor_event);
@@ -691,11 +694,11 @@ mod tests {
         let go_to_line_view = open_go_to_line_view(workspace, cx);
         go_to_line_view.update(cx, |go_to_line_view, cx| {
             assert_eq!(
-                go_to_line_view
-                    .line_editor
-                    .read(cx)
-                    .placeholder_text()
-                    .expect("No placeholder text"),
+                go_to_line_view.line_editor.update(cx, |line_editor, cx| {
+                    line_editor
+                        .placeholder_text(cx)
+                        .expect("No placeholder text")
+                }),
                 format!(
                     "{}:{}",
                     expected_placeholder.line, expected_placeholder.character

crates/keymap_editor/src/keymap_editor.rs ๐Ÿ”—

@@ -442,7 +442,7 @@ impl KeymapEditor {
 
         let filter_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Filter action namesโ€ฆ", cx);
+            editor.set_placeholder_text("Filter action namesโ€ฆ", window, cx);
             editor
         });
 
@@ -2804,7 +2804,7 @@ impl ActionArgumentsEditor {
             editor.set_text(arguments, window, cx);
         } else {
             // TODO: default value from schema?
-            editor.set_placeholder_text("Action Arguments", cx);
+            editor.set_placeholder_text("Action Arguments", window, cx);
         }
     }
 

crates/language_models/src/provider/anthropic.rs ๐Ÿ”—

@@ -969,7 +969,7 @@ impl ConfigurationView {
         Self {
             api_key_editor: cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text(Self::PLACEHOLDER_TEXT, cx);
+                editor.set_placeholder_text(Self::PLACEHOLDER_TEXT, window, cx);
                 editor
             }),
             state,

crates/language_models/src/provider/bedrock.rs ๐Ÿ”—

@@ -1053,22 +1053,22 @@ impl ConfigurationView {
         Self {
             access_key_id_editor: cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text(Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT, cx);
+                editor.set_placeholder_text(Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT, window, cx);
                 editor
             }),
             secret_access_key_editor: cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text(Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT, cx);
+                editor.set_placeholder_text(Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT, window, cx);
                 editor
             }),
             session_token_editor: cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text(Self::PLACEHOLDER_SESSION_TOKEN_TEXT, cx);
+                editor.set_placeholder_text(Self::PLACEHOLDER_SESSION_TOKEN_TEXT, window, cx);
                 editor
             }),
             region_editor: cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text(Self::PLACEHOLDER_REGION, cx);
+                editor.set_placeholder_text(Self::PLACEHOLDER_REGION, window, cx);
                 editor
             }),
             state,

crates/language_models/src/provider/deepseek.rs ๐Ÿ”—

@@ -575,7 +575,7 @@ impl ConfigurationView {
     fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
         let api_key_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("sk-00000000000000000000000000000000", cx);
+            editor.set_placeholder_text("sk-00000000000000000000000000000000", window, cx);
             editor
         });
 

crates/language_models/src/provider/google.rs ๐Ÿ”—

@@ -842,7 +842,7 @@ impl ConfigurationView {
         Self {
             api_key_editor: cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text("AIzaSy...", cx);
+                editor.set_placeholder_text("AIzaSy...", window, cx);
                 editor
             }),
             target_agent,

crates/language_models/src/provider/mistral.rs ๐Ÿ”—

@@ -744,7 +744,7 @@ impl ConfigurationView {
     fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
         let api_key_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("0aBCDEFGhIjKLmNOpqrSTUVwxyzabCDE1f2", cx);
+            editor.set_placeholder_text("0aBCDEFGhIjKLmNOpqrSTUVwxyzabCDE1f2", window, cx);
             editor
         });
 

crates/language_models/src/provider/open_router.rs ๐Ÿ”—

@@ -787,8 +787,11 @@ impl ConfigurationView {
     fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
         let api_key_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor
-                .set_placeholder_text("sk_or_000000000000000000000000000000000000000000000000", cx);
+            editor.set_placeholder_text(
+                "sk_or_000000000000000000000000000000000000000000000000",
+                window,
+                cx,
+            );
             editor
         });
 

crates/outline_panel/src/outline_panel.rs ๐Ÿ”—

@@ -737,7 +737,7 @@ impl OutlinePanel {
         cx.new(|cx| {
             let filter_editor = cx.new(|cx| {
                 let mut editor = Editor::single_line(window, cx);
-                editor.set_placeholder_text("Filter...", cx);
+                editor.set_placeholder_text("Filter...", window, cx);
                 editor
             });
             let filter_update_subscription = cx.subscribe_in(

crates/picker/src/head.rs ๐Ÿ”—

@@ -23,7 +23,7 @@ impl Head {
     ) -> Self {
         let editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text(placeholder_text, cx);
+            editor.set_placeholder_text(placeholder_text.as_ref(), window, cx);
             editor
         });
         cx.subscribe_in(&editor, window, edit_handler).detach();

crates/picker/src/picker.rs ๐Ÿ”—

@@ -615,7 +615,7 @@ impl<D: PickerDelegate> Picker<D> {
             Head::Editor(editor) => {
                 let placeholder = self.delegate.placeholder_text(window, cx);
                 editor.update(cx, |editor, cx| {
-                    editor.set_placeholder_text(placeholder, cx);
+                    editor.set_placeholder_text(placeholder.as_ref(), window, cx);
                     cx.notify();
                 });
             }

crates/recent_projects/src/remote_servers.rs ๐Ÿ”—

@@ -104,7 +104,7 @@ impl EditNicknameState {
             .and_then(|state| state.nickname)
             .filter(|text| !text.is_empty());
         this.editor.update(cx, |this, cx| {
-            this.set_placeholder_text("Add a nickname for this server", cx);
+            this.set_placeholder_text("Add a nickname for this server", window, cx);
             if let Some(starting_text) = starting_text {
                 this.set_text(starting_text, window, cx);
             }
@@ -1038,13 +1038,14 @@ impl RemoteServerProjects {
     fn render_create_remote_server(
         &self,
         state: &CreateRemoteServer,
+        window: &mut Window,
         cx: &mut Context<Self>,
     ) -> impl IntoElement {
         let ssh_prompt = state.ssh_prompt.clone();
 
         state.address_editor.update(cx, |editor, cx| {
             if editor.text(cx).is_empty() {
-                editor.set_placeholder_text("ssh user@example -p 2222", cx);
+                editor.set_placeholder_text("ssh user@example -p 2222", window, cx);
             }
         });
 
@@ -1731,7 +1732,7 @@ impl Render for RemoteServerProjects {
                     .into_any_element(),
                 Mode::ProjectPicker(element) => element.clone().into_any_element(),
                 Mode::CreateRemoteServer(state) => self
-                    .render_create_remote_server(state, cx)
+                    .render_create_remote_server(state, window, cx)
                     .into_any_element(),
                 Mode::EditNickname(state) => self
                     .render_edit_nickname(state, window, cx)

crates/rules_library/src/rules_library.rs ๐Ÿ”—

@@ -612,7 +612,7 @@ impl RulesLibrary {
                     Ok(rule) => {
                         let title_editor = cx.new(|cx| {
                             let mut editor = Editor::single_line(window, cx);
-                            editor.set_placeholder_text("Untitled", cx);
+                            editor.set_placeholder_text("Untitled", window, cx);
                             editor.set_text(rule_metadata.title.unwrap_or_default(), window, cx);
                             if prompt_id.is_built_in() {
                                 editor.set_read_only(true);

crates/search/src/buffer_search.rs ๐Ÿ”—

@@ -154,16 +154,14 @@ impl Render for BufferSearchBar {
             find_in_results,
         } = self.supported_options(cx);
 
-        if self.query_editor.update(cx, |query_editor, _cx| {
-            query_editor.placeholder_text().is_none()
-        }) {
-            self.query_editor.update(cx, |editor, cx| {
-                editor.set_placeholder_text("Searchโ€ฆ", cx);
-            });
-        }
+        self.query_editor.update(cx, |query_editor, cx| {
+            if query_editor.placeholder_text(cx).is_none() {
+                query_editor.set_placeholder_text("Searchโ€ฆ", window, cx);
+            }
+        });
 
         self.replacement_editor.update(cx, |editor, cx| {
-            editor.set_placeholder_text("Replace withโ€ฆ", cx);
+            editor.set_placeholder_text("Replace withโ€ฆ", window, cx);
         });
 
         let mut color_override = None;

crates/search/src/project_search.rs ๐Ÿ”—

@@ -766,7 +766,7 @@ impl ProjectSearchView {
 
         let query_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Search all filesโ€ฆ", cx);
+            editor.set_placeholder_text("Search all filesโ€ฆ", window, cx);
             editor.set_text(query_text, window, cx);
             editor
         });
@@ -789,7 +789,7 @@ impl ProjectSearchView {
         );
         let replacement_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Replace in projectโ€ฆ", cx);
+            editor.set_placeholder_text("Replace in projectโ€ฆ", window, cx);
             if let Some(text) = replacement_text {
                 editor.set_text(text, window, cx);
             }
@@ -815,7 +815,7 @@ impl ProjectSearchView {
 
         let included_files_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Include: crates/**/*.toml", cx);
+            editor.set_placeholder_text("Include: crates/**/*.toml", window, cx);
 
             editor
         });
@@ -828,7 +828,7 @@ impl ProjectSearchView {
 
         let excluded_files_editor = cx.new(|cx| {
             let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Exclude: vendor/*, *.lock", cx);
+            editor.set_placeholder_text("Exclude: vendor/*, *.lock", window, cx);
 
             editor
         });

crates/ui_input/src/ui_input.rs ๐Ÿ”—

@@ -55,7 +55,7 @@ impl SingleLineInput {
 
         let editor = cx.new(|cx| {
             let mut input = Editor::single_line(window, cx);
-            input.set_placeholder_text(placeholder_text.clone(), cx);
+            input.set_placeholder_text(&placeholder_text, window, cx);
             input
         });
 

crates/zeta/src/rate_completion_modal.rs ๐Ÿ”—

@@ -291,7 +291,7 @@ impl RateCompletionModal {
                 editor.set_show_wrap_guides(false, cx);
                 editor.set_show_indent_guides(false, cx);
                 editor.set_show_edit_predictions(Some(false), window, cx);
-                editor.set_placeholder_text("Add your feedbackโ€ฆ", cx);
+                editor.set_placeholder_text("Add your feedbackโ€ฆ", window, cx);
                 if focus {
                     cx.focus_self(window);
                 }