agent: Cleanup `message_editor` (#28614)

Bennet Bo Fenner created

This PR splits up the rendering of the message editor into multiple
functions. Previously we had a single `h_flex()...` expression which
spanned across 550 lines, `cargo fmt` stopped working.

Release Notes:

- N/A

Change summary

crates/agent/src/message_editor.rs | 959 ++++++++++++++++---------------
1 file changed, 480 insertions(+), 479 deletions(-)

Detailed changes

crates/agent/src/message_editor.rs 🔗

@@ -1,6 +1,8 @@
+use std::collections::BTreeMap;
 use std::sync::Arc;
 
 use crate::assistant_model_selector::ModelType;
+use buffer_diff::BufferDiff;
 use collections::HashSet;
 use editor::actions::MoveUp;
 use editor::{
@@ -336,21 +338,23 @@ impl MessageEditor {
             diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));
         }
     }
-}
-
-impl Focusable for MessageEditor {
-    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
-        self.editor.focus_handle(cx)
-    }
-}
 
-impl Render for MessageEditor {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let font_size = TextSize::Small.rems(cx);
-        let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
+    fn render_editor(
+        &self,
+        font_size: Rems,
+        line_height: Pixels,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Div {
+        let thread = self.thread.read(cx);
 
+        let editor_bg_color = cx.theme().colors().editor_background;
+        let is_generating = thread.is_generating();
         let focus_handle = self.editor.focus_handle(cx);
 
+        let is_model_selected = self.is_model_selected(cx);
+        let is_editor_empty = self.is_editor_empty(cx);
+
         let is_editor_expanded = self.editor_is_expanded;
         let expand_icon = if is_editor_expanded {
             IconName::Minimize
@@ -358,21 +362,470 @@ impl Render for MessageEditor {
             IconName::Maximize
         };
 
-        let thread = self.thread.read(cx);
-        let is_generating = thread.is_generating();
-        let total_token_usage = thread.total_token_usage(cx);
-        let is_model_selected = self.is_model_selected(cx);
-        let is_editor_empty = self.is_editor_empty(cx);
-        let is_edit_changes_expanded = self.edits_expanded;
+        v_flex()
+            .key_context("MessageEditor")
+            .on_action(cx.listener(Self::chat))
+            .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
+                this.profile_selector
+                    .read(cx)
+                    .menu_handle()
+                    .toggle(window, cx);
+            }))
+            .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
+                this.model_selector
+                    .update(cx, |model_selector, cx| model_selector.toggle(window, cx));
+            }))
+            .on_action(cx.listener(Self::toggle_context_picker))
+            .on_action(cx.listener(Self::remove_all_context))
+            .on_action(cx.listener(Self::move_up))
+            .on_action(cx.listener(Self::toggle_chat_mode))
+            .on_action(cx.listener(Self::expand_message_editor))
+            .gap_2()
+            .p_2()
+            .bg(editor_bg_color)
+            .border_t_1()
+            .border_color(cx.theme().colors().border)
+            .child(
+                h_flex()
+                    .items_start()
+                    .justify_between()
+                    .child(self.context_strip.clone())
+                    .child(
+                        IconButton::new("toggle-height", expand_icon)
+                            .icon_size(IconSize::XSmall)
+                            .icon_color(Color::Muted)
+                            .tooltip({
+                                let focus_handle = focus_handle.clone();
+                                move |window, cx| {
+                                    let expand_label = if is_editor_expanded {
+                                        "Minimize Message Editor".to_string()
+                                    } else {
+                                        "Expand Message Editor".to_string()
+                                    };
 
-        let action_log = self.thread.read(cx).action_log();
-        let changed_buffers = action_log.read(cx).changed_buffers(cx);
-        let changed_buffers_count = changed_buffers.len();
+                                    Tooltip::for_action_in(
+                                        expand_label,
+                                        &ExpandMessageEditor,
+                                        &focus_handle,
+                                        window,
+                                        cx,
+                                    )
+                                }
+                            })
+                            .on_click(cx.listener(|_, _, window, cx| {
+                                window.dispatch_action(Box::new(ExpandMessageEditor), cx);
+                            })),
+                    ),
+            )
+            .child(
+                v_flex()
+                    .size_full()
+                    .gap_4()
+                    .when(is_editor_expanded, |this| {
+                        this.h(vh(0.8, window)).justify_between()
+                    })
+                    .child(div().when(is_editor_expanded, |this| this.h_full()).child({
+                        let settings = ThemeSettings::get_global(cx);
+
+                        let text_style = TextStyle {
+                            color: cx.theme().colors().text,
+                            font_family: settings.buffer_font.family.clone(),
+                            font_fallbacks: settings.buffer_font.fallbacks.clone(),
+                            font_features: settings.buffer_font.features.clone(),
+                            font_size: font_size.into(),
+                            line_height: line_height.into(),
+                            ..Default::default()
+                        };
+
+                        EditorElement::new(
+                            &self.editor,
+                            EditorStyle {
+                                background: editor_bg_color,
+                                local_player: cx.theme().players().local(),
+                                text: text_style,
+                                syntax: cx.theme().syntax().clone(),
+                                ..Default::default()
+                            },
+                        )
+                        .into_any()
+                    }))
+                    .child(
+                        h_flex()
+                            .flex_none()
+                            .justify_between()
+                            .child(h_flex().gap_2().child(self.profile_selector.clone()))
+                            .child(h_flex().gap_1().child(self.model_selector.clone()).map({
+                                let focus_handle = focus_handle.clone();
+                                move |parent| {
+                                    if is_generating {
+                                        parent.child(
+                                            IconButton::new(
+                                                "stop-generation",
+                                                IconName::StopFilled,
+                                            )
+                                            .icon_color(Color::Error)
+                                            .style(ButtonStyle::Tinted(ui::TintColor::Error))
+                                            .tooltip(move |window, cx| {
+                                                Tooltip::for_action(
+                                                    "Stop Generation",
+                                                    &editor::actions::Cancel,
+                                                    window,
+                                                    cx,
+                                                )
+                                            })
+                                            .on_click({
+                                                let focus_handle = focus_handle.clone();
+                                                move |_event, window, cx| {
+                                                    focus_handle.dispatch_action(
+                                                        &editor::actions::Cancel,
+                                                        window,
+                                                        cx,
+                                                    );
+                                                }
+                                            })
+                                            .with_animation(
+                                                "pulsating-label",
+                                                Animation::new(Duration::from_secs(2))
+                                                    .repeat()
+                                                    .with_easing(pulsating_between(0.4, 1.0)),
+                                                |icon_button, delta| icon_button.alpha(delta),
+                                            ),
+                                        )
+                                    } else {
+                                        parent.child(
+                                            IconButton::new("send-message", IconName::Send)
+                                                .icon_color(Color::Accent)
+                                                .style(ButtonStyle::Filled)
+                                                .disabled(
+                                                    is_editor_empty
+                                                        || !is_model_selected
+                                                        || self.waiting_for_summaries_to_send,
+                                                )
+                                                .on_click({
+                                                    let focus_handle = focus_handle.clone();
+                                                    move |_event, window, cx| {
+                                                        focus_handle
+                                                            .dispatch_action(&Chat, window, cx);
+                                                    }
+                                                })
+                                                .when(
+                                                    !is_editor_empty && is_model_selected,
+                                                    |button| {
+                                                        button.tooltip(move |window, cx| {
+                                                            Tooltip::for_action(
+                                                                "Send", &Chat, window, cx,
+                                                            )
+                                                        })
+                                                    },
+                                                )
+                                                .when(is_editor_empty, |button| {
+                                                    button.tooltip(Tooltip::text(
+                                                        "Type a message to submit",
+                                                    ))
+                                                })
+                                                .when(!is_model_selected, |button| {
+                                                    button.tooltip(Tooltip::text(
+                                                        "Select a model to continue",
+                                                    ))
+                                                }),
+                                        )
+                                    }
+                                }
+                            })),
+                    ),
+            )
+    }
+
+    fn render_changed_buffers(
+        &self,
+        changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Div {
+        let focus_handle = self.editor.focus_handle(cx);
 
         let editor_bg_color = cx.theme().colors().editor_background;
         let border_color = cx.theme().colors().border;
         let active_color = cx.theme().colors().element_selected;
         let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
+        let is_edit_changes_expanded = self.edits_expanded;
+
+        v_flex()
+            .mx_2()
+            .bg(bg_edit_files_disclosure)
+            .border_1()
+            .border_b_0()
+            .border_color(border_color)
+            .rounded_t_md()
+            .shadow(smallvec::smallvec![gpui::BoxShadow {
+                color: gpui::black().opacity(0.15),
+                offset: point(px(1.), px(-1.)),
+                blur_radius: px(3.),
+                spread_radius: px(0.),
+            }])
+            .child(
+                h_flex()
+                    .id("edits-container")
+                    .cursor_pointer()
+                    .p_1p5()
+                    .justify_between()
+                    .when(is_edit_changes_expanded, |this| {
+                        this.border_b_1().border_color(border_color)
+                    })
+                    .on_click(
+                        cx.listener(|this, _, window, cx| this.handle_review_click(window, cx)),
+                    )
+                    .child(
+                        h_flex()
+                            .gap_1()
+                            .child(
+                                Disclosure::new("edits-disclosure", is_edit_changes_expanded)
+                                    .on_click(cx.listener(|this, _ev, _window, cx| {
+                                        this.edits_expanded = !this.edits_expanded;
+                                        cx.notify();
+                                    })),
+                            )
+                            .child(
+                                Label::new("Edits")
+                                    .size(LabelSize::Small)
+                                    .color(Color::Muted),
+                            )
+                            .child(Label::new("•").size(LabelSize::XSmall).color(Color::Muted))
+                            .child(
+                                Label::new(format!(
+                                    "{} {}",
+                                    changed_buffers.len(),
+                                    if changed_buffers.len() == 1 {
+                                        "file"
+                                    } else {
+                                        "files"
+                                    }
+                                ))
+                                .size(LabelSize::Small)
+                                .color(Color::Muted),
+                            ),
+                    )
+                    .child(
+                        Button::new("review", "Review Changes")
+                            .label_size(LabelSize::Small)
+                            .key_binding(
+                                KeyBinding::for_action_in(
+                                    &OpenAgentDiff,
+                                    &focus_handle,
+                                    window,
+                                    cx,
+                                )
+                                .map(|kb| kb.size(rems_from_px(12.))),
+                            )
+                            .on_click(cx.listener(|this, _, window, cx| {
+                                this.handle_review_click(window, cx)
+                            })),
+                    ),
+            )
+            .when(is_edit_changes_expanded, |parent| {
+                parent.child(
+                    v_flex().children(changed_buffers.into_iter().enumerate().flat_map(
+                        |(index, (buffer, _diff))| {
+                            let file = buffer.read(cx).file()?;
+                            let path = file.path();
+
+                            let parent_label = path.parent().and_then(|parent| {
+                                let parent_str = parent.to_string_lossy();
+
+                                if parent_str.is_empty() {
+                                    None
+                                } else {
+                                    Some(
+                                        Label::new(format!(
+                                            "/{}{}",
+                                            parent_str,
+                                            std::path::MAIN_SEPARATOR_STR
+                                        ))
+                                        .color(Color::Muted)
+                                        .size(LabelSize::XSmall)
+                                        .buffer_font(cx),
+                                    )
+                                }
+                            });
+
+                            let name_label = path.file_name().map(|name| {
+                                Label::new(name.to_string_lossy().to_string())
+                                    .size(LabelSize::XSmall)
+                                    .buffer_font(cx)
+                            });
+
+                            let file_icon = FileIcons::get_icon(&path, cx)
+                                .map(Icon::from_path)
+                                .map(|icon| icon.color(Color::Muted).size(IconSize::Small))
+                                .unwrap_or_else(|| {
+                                    Icon::new(IconName::File)
+                                        .color(Color::Muted)
+                                        .size(IconSize::Small)
+                                });
+
+                            let hover_color = cx
+                                .theme()
+                                .colors()
+                                .element_background
+                                .blend(cx.theme().colors().editor_foreground.opacity(0.025));
+
+                            let overlay_gradient = linear_gradient(
+                                90.,
+                                linear_color_stop(editor_bg_color, 1.),
+                                linear_color_stop(editor_bg_color.opacity(0.2), 0.),
+                            );
+
+                            let overlay_gradient_hover = linear_gradient(
+                                90.,
+                                linear_color_stop(hover_color, 1.),
+                                linear_color_stop(hover_color.opacity(0.2), 0.),
+                            );
+
+                            let element = h_flex()
+                                .group("edited-code")
+                                .id(("file-container", index))
+                                .cursor_pointer()
+                                .relative()
+                                .py_1()
+                                .pl_2()
+                                .pr_1()
+                                .gap_2()
+                                .justify_between()
+                                .bg(cx.theme().colors().editor_background)
+                                .hover(|style| style.bg(hover_color))
+                                .when(index + 1 < changed_buffers.len(), |parent| {
+                                    parent.border_color(border_color).border_b_1()
+                                })
+                                .child(
+                                    h_flex()
+                                        .id("file-name")
+                                        .pr_8()
+                                        .gap_1p5()
+                                        .max_w_full()
+                                        .overflow_x_scroll()
+                                        .child(file_icon)
+                                        .child(
+                                            h_flex()
+                                                .gap_0p5()
+                                                .children(name_label)
+                                                .children(parent_label),
+                                        ) // TODO: show lines changed
+                                        .child(Label::new("+").color(Color::Created))
+                                        .child(Label::new("-").color(Color::Deleted)),
+                                )
+                                .child(
+                                    div().visible_on_hover("edited-code").child(
+                                        Button::new("review", "Review")
+                                            .label_size(LabelSize::Small)
+                                            .on_click({
+                                                let buffer = buffer.clone();
+                                                cx.listener(move |this, _, window, cx| {
+                                                    this.handle_file_click(
+                                                        buffer.clone(),
+                                                        window,
+                                                        cx,
+                                                    );
+                                                })
+                                            }),
+                                    ),
+                                )
+                                .child(
+                                    div()
+                                        .id("gradient-overlay")
+                                        .absolute()
+                                        .h_5_6()
+                                        .w_12()
+                                        .bottom_0()
+                                        .right(px(52.))
+                                        .bg(overlay_gradient)
+                                        .group_hover("edited-code", |style| {
+                                            style.bg(overlay_gradient_hover)
+                                        }),
+                                )
+                                .on_click({
+                                    let buffer = buffer.clone();
+                                    cx.listener(move |this, _, window, cx| {
+                                        this.handle_file_click(buffer.clone(), window, cx);
+                                    })
+                                });
+
+                            Some(element)
+                        },
+                    )),
+                )
+            })
+    }
+
+    fn render_reaching_token_limit(&self, line_height: Pixels, cx: &mut Context<Self>) -> Div {
+        h_flex()
+            .p_2()
+            .gap_2()
+            .flex_wrap()
+            .justify_between()
+            .bg(cx.theme().status().warning_background.opacity(0.1))
+            .border_t_1()
+            .border_color(cx.theme().colors().border)
+            .child(
+                h_flex()
+                    .gap_2()
+                    .items_start()
+                    .child(
+                        h_flex()
+                            .h(line_height)
+                            .justify_center()
+                            .child(
+                                Icon::new(IconName::Warning)
+                                    .color(Color::Warning)
+                                    .size(IconSize::XSmall),
+                            ),
+                    )
+                    .child(
+                        v_flex()
+                            .mr_auto()
+                            .child(Label::new("Thread reaching the token limit soon").size(LabelSize::Small))
+                            .child(
+                                Label::new(
+                                    "Start a new thread from a summary to continue the conversation.",
+                                )
+                                .size(LabelSize::Small)
+                                .color(Color::Muted),
+                            ),
+                    ),
+            )
+            .child(
+                Button::new("new-thread", "Start New Thread")
+                    .on_click(cx.listener(|this, _, window, cx| {
+                        let from_thread_id = Some(this.thread.read(cx).id().clone());
+
+                        window.dispatch_action(Box::new(NewThread {
+                            from_thread_id
+                        }), cx);
+                    }))
+                    .icon(IconName::Plus)
+                    .icon_position(IconPosition::Start)
+                    .icon_size(IconSize::Small)
+                    .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+                    .label_size(LabelSize::Small),
+            )
+    }
+}
+
+impl Focusable for MessageEditor {
+    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
+        self.editor.focus_handle(cx)
+    }
+}
+
+impl Render for MessageEditor {
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let thread = self.thread.read(cx);
+        let total_token_usage = thread.total_token_usage(cx);
+
+        let action_log = self.thread.read(cx).action_log();
+        let changed_buffers = action_log.read(cx).changed_buffers(cx);
+
+        let font_size = TextSize::Small.rems(cx);
+        let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
 
         v_flex()
             .size_full()
@@ -383,7 +836,7 @@ impl Render for MessageEditor {
                             .flex_none()
                             .px_2()
                             .py_2()
-                            .bg(editor_bg_color)
+                            .bg(cx.theme().colors().editor_background)
                             .border_1()
                             .border_color(cx.theme().colors().border_variant)
                             .rounded_lg()
@@ -411,465 +864,13 @@ impl Render for MessageEditor {
                     ),
                 )
             })
-            .when(changed_buffers_count > 0, |parent| {
-                parent.child(
-                    v_flex()
-                        .mx_2()
-                        .bg(bg_edit_files_disclosure)
-                        .border_1()
-                        .border_b_0()
-                        .border_color(border_color)
-                        .rounded_t_md()
-                        .shadow(smallvec::smallvec![gpui::BoxShadow {
-                            color: gpui::black().opacity(0.15),
-                            offset: point(px(1.), px(-1.)),
-                            blur_radius: px(3.),
-                            spread_radius: px(0.),
-                        }])
-                        .child(
-                            h_flex()
-                                .id("edits-container")
-                                .cursor_pointer()
-                                .p_1p5()
-                                .justify_between()
-                                .when(is_edit_changes_expanded, |this| {
-                                    this.border_b_1().border_color(border_color)
-                                })
-                                .on_click(cx.listener(|this, _, window, cx| {
-                                    this.handle_review_click(window, cx)
-                                }))
-                                .child(
-                                    h_flex()
-                                        .gap_1()
-                                        .child(
-                                            Disclosure::new(
-                                                "edits-disclosure",
-                                                is_edit_changes_expanded,
-                                            )
-                                            .on_click(
-                                                cx.listener(|this, _ev, _window, cx| {
-                                                    this.edits_expanded = !this.edits_expanded;
-                                                    cx.notify();
-                                                }),
-                                            ),
-                                        )
-                                        .child(
-                                            Label::new("Edits")
-                                                .size(LabelSize::Small)
-                                                .color(Color::Muted),
-                                        )
-                                        .child(
-                                            Label::new("•")
-                                                .size(LabelSize::XSmall)
-                                                .color(Color::Muted),
-                                        )
-                                        .child(
-                                            Label::new(format!(
-                                                "{} {}",
-                                                changed_buffers_count,
-                                                if changed_buffers_count == 1 {
-                                                    "file"
-                                                } else {
-                                                    "files"
-                                                }
-                                            ))
-                                            .size(LabelSize::Small)
-                                            .color(Color::Muted),
-                                        ),
-                                )
-                                .child(
-                                    Button::new("review", "Review Changes")
-                                        .label_size(LabelSize::Small)
-                                        .key_binding(
-                                            KeyBinding::for_action_in(
-                                                &OpenAgentDiff,
-                                                &focus_handle,
-                                                window,
-                                                cx,
-                                            )
-                                            .map(|kb| kb.size(rems_from_px(12.))),
-                                        )
-                                        .on_click(cx.listener(|this, _, window, cx| {
-                                            this.handle_review_click(window, cx)
-                                        })),
-                                ),
-                        )
-                        .when(is_edit_changes_expanded, |parent| {
-                            parent.child(
-                                v_flex().children(
-                                    changed_buffers.into_iter().enumerate().flat_map(
-                                        |(index, (buffer, _diff))| {
-                                            let file = buffer.read(cx).file()?;
-                                            let path = file.path();
-
-                                            let parent_label = path.parent().and_then(|parent| {
-                                                let parent_str = parent.to_string_lossy();
-
-                                                if parent_str.is_empty() {
-                                                    None
-                                                } else {
-                                                    Some(
-                                                        Label::new(format!(
-                                                            "/{}{}",
-                                                            parent_str,
-                                                            std::path::MAIN_SEPARATOR_STR
-                                                        ))
-                                                        .color(Color::Muted)
-                                                        .size(LabelSize::XSmall)
-                                                        .buffer_font(cx),
-                                                    )
-                                                }
-                                            });
-
-                                            let name_label = path.file_name().map(|name| {
-                                                Label::new(name.to_string_lossy().to_string())
-                                                    .size(LabelSize::XSmall)
-                                                    .buffer_font(cx)
-                                            });
-
-                                            let file_icon = FileIcons::get_icon(&path, cx)
-                                                .map(Icon::from_path)
-                                                .map(|icon| {
-                                                    icon.color(Color::Muted).size(IconSize::Small)
-                                                })
-                                                .unwrap_or_else(|| {
-                                                    Icon::new(IconName::File)
-                                                        .color(Color::Muted)
-                                                        .size(IconSize::Small)
-                                                });
-
-                                            let hover_color = cx.theme()
-                                                .colors()
-                                                .element_background
-                                                .blend(cx.theme().colors().editor_foreground.opacity(0.025));
-
-                                            let overlay_gradient = linear_gradient(
-                                                90.,
-                                                linear_color_stop(
-                                                    editor_bg_color,
-                                                    1.,
-                                                ),
-                                                linear_color_stop(
-                                                    editor_bg_color
-                                                        .opacity(0.2),
-                                                    0.,
-                                                ),
-                                            );
-
-                                            let overlay_gradient_hover = linear_gradient(
-                                                90.,
-                                                linear_color_stop(
-                                                    hover_color,
-                                                    1.,
-                                                ),
-                                                linear_color_stop(
-                                                    hover_color
-                                                        .opacity(0.2),
-                                                    0.,
-                                                ),
-                                            );
-
-                                            let element = h_flex()
-                                                .group("edited-code")
-                                                .id(("file-container", index))
-                                                .cursor_pointer()
-                                                .relative()
-                                                .py_1()
-                                                .pl_2()
-                                                .pr_1()
-                                                .gap_2()
-                                                .justify_between()
-                                                .bg(cx.theme().colors().editor_background)
-                                                .hover(|style| style.bg(hover_color))
-                                                .when(index + 1 < changed_buffers_count, |parent| {
-                                                    parent.border_color(border_color).border_b_1()
-                                                })
-                                                .child(
-                                                    h_flex()
-                                                        .id("file-name")
-                                                        .pr_8()
-                                                        .gap_1p5()
-                                                        .max_w_full()
-                                                        .overflow_x_scroll()
-                                                        .child(file_icon)
-                                                        .child(
-                                                            h_flex()
-                                                                .gap_0p5()
-                                                                .children(name_label)
-                                                                .children(parent_label)
-                                                        ) // TODO: show lines changed
-                                                        .child(
-                                                            Label::new("+")
-                                                                .color(Color::Created),
-                                                        )
-                                                        .child(
-                                                            Label::new("-")
-                                                                .color(Color::Deleted),
-                                                        ),
-                                                )
-                                                .child(
-                                                    div().visible_on_hover("edited-code").child(
-                                                        Button::new("review", "Review")
-                                                            .label_size(LabelSize::Small)
-                                                            .on_click({
-                                                                let buffer = buffer.clone();
-                                                                cx.listener(move |this, _, window, cx| {
-                                                                    this.handle_file_click(buffer.clone(), window, cx);
-                                                                })
-                                                            })
-                                                    )
-                                                )
-                                                .child(
-                                                    div()
-                                                        .id("gradient-overlay")
-                                                        .absolute()
-                                                        .h_5_6()
-                                                        .w_12()
-                                                        .bottom_0()
-                                                        .right(px(52.))
-                                                        .bg(overlay_gradient)
-                                                        .group_hover("edited-code", |style| style.bg(overlay_gradient_hover))
-                                                    ,
-                                                )
-                                                .on_click({
-                                                    let buffer = buffer.clone();
-                                                    cx.listener(move |this, _, window, cx| {
-                                                        this.handle_file_click(buffer.clone(), window, cx);
-                                                    })
-                                                });
-
-                                            Some(element)
-                                        },
-                                    ),
-                                ),
-                            )
-                        }),
-                )
+            .when(changed_buffers.len() > 0, |parent| {
+                parent.child(self.render_changed_buffers(&changed_buffers, window, cx))
             })
-            .child(
-                v_flex()
-                    .key_context("MessageEditor")
-                    .on_action(cx.listener(Self::chat))
-                    .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
-                        this.profile_selector
-                            .read(cx)
-                            .menu_handle()
-                            .toggle(window, cx);
-                    }))
-                    .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
-                        this.model_selector
-                            .update(cx, |model_selector, cx| model_selector.toggle(window, cx));
-                    }))
-                    .on_action(cx.listener(Self::toggle_context_picker))
-                    .on_action(cx.listener(Self::remove_all_context))
-                    .on_action(cx.listener(Self::move_up))
-                    .on_action(cx.listener(Self::toggle_chat_mode))
-                    .on_action(cx.listener(Self::expand_message_editor))
-                    .gap_2()
-                    .p_2()
-                    .bg(editor_bg_color)
-                    .border_t_1()
-                    .border_color(cx.theme().colors().border)
-                    .child(
-                        h_flex()
-                            .items_start()
-                            .justify_between()
-                            .child(self.context_strip.clone())
-                            .child(
-                                IconButton::new("toggle-height", expand_icon)
-                                    .icon_size(IconSize::XSmall)
-                                    .icon_color(Color::Muted)
-                                    .tooltip({
-                                        let focus_handle = focus_handle.clone();
-                                        move |window, cx| {
-                                        let expand_label = if is_editor_expanded {
-                                            "Minimize Message Editor".to_string()
-                                        } else {
-                                            "Expand Message Editor".to_string()
-                                        };
-
-                                        Tooltip::for_action_in(
-                                            expand_label,
-                                            &ExpandMessageEditor,
-                                            &focus_handle,
-                                            window,
-                                            cx,
-                                        )
-                                    }})
-                                    .on_click(cx.listener(|_, _, window, cx| {
-                                        window.dispatch_action(Box::new(ExpandMessageEditor), cx);
-                                    }))
-                            )
-                    )
-                    .child(
-                        v_flex()
-                            .size_full()
-                            .gap_4()
-                            .when(is_editor_expanded, |this| this.h(vh(0.8, window)).justify_between())
-                            .child(div().when(is_editor_expanded, |this| this.h_full()).child({
-                                    let settings = ThemeSettings::get_global(cx);
-
-                                    let text_style = TextStyle {
-                                        color: cx.theme().colors().text,
-                                        font_family: settings.buffer_font.family.clone(),
-                                        font_fallbacks: settings.buffer_font.fallbacks.clone(),
-                                        font_features: settings.buffer_font.features.clone(),
-                                        font_size: font_size.into(),
-                                        line_height: line_height.into(),
-                                        ..Default::default()
-                                    };
-
-                                    EditorElement::new(
-                                        &self.editor,
-                                        EditorStyle {
-                                            background: editor_bg_color,
-                                            local_player: cx.theme().players().local(),
-                                            text: text_style,
-                                            syntax: cx.theme().syntax().clone(),
-                                            ..Default::default()
-                                        },
-                                    ).into_any()
-                            }))
-                            .child(
-                                h_flex()
-                                    .flex_none()
-                                    .justify_between()
-                                    .child(h_flex().gap_2().child(self.profile_selector.clone()))
-                                    .child(
-                                        h_flex().gap_1()
-                                            .child(self.model_selector.clone())
-                                            .map({
-                                                let focus_handle = focus_handle.clone();
-                                                move |parent| {
-                                                if is_generating {
-                                                    parent.child(
-                                                        IconButton::new("stop-generation", IconName::StopFilled)
-                                                            .icon_color(Color::Error)
-                                                            .style(ButtonStyle::Tinted(ui::TintColor::Error))
-                                                            .tooltip(move |window, cx| {
-                                                                Tooltip::for_action(
-                                                                    "Stop Generation",
-                                                                    &editor::actions::Cancel,
-                                                                    window,
-                                                                    cx,
-                                                                )
-                                                            })
-                                                            .on_click({
-                                                                let focus_handle = focus_handle.clone();
-                                                                move |_event, window, cx| {
-                                                                    focus_handle.dispatch_action(
-                                                                        &editor::actions::Cancel,
-                                                                        window,
-                                                                        cx,
-                                                                    );
-                                                                }
-                                                            })
-                                                            .with_animation(
-                                                                "pulsating-label",
-                                                                Animation::new(Duration::from_secs(2))
-                                                                    .repeat()
-                                                                    .with_easing(pulsating_between(0.4, 1.0)),
-                                                                |icon_button, delta| icon_button.alpha(delta),
-                                                            ),
-                                                    )
-                                                } else {
-                                                    parent.child(
-                                                        IconButton::new("send-message", IconName::Send)
-                                                            .icon_color(Color::Accent)
-                                                            .style(ButtonStyle::Filled)
-                                                            .disabled(
-                                                                is_editor_empty
-                                                                    || !is_model_selected
-                                                                    || self.waiting_for_summaries_to_send
-                                                            )
-                                                            .on_click({
-                                                                let focus_handle = focus_handle.clone();
-                                                                move |_event, window, cx| {
-                                                                    focus_handle.dispatch_action(&Chat, window, cx);
-                                                                }
-                                                            })
-                                                            .when(!is_editor_empty && is_model_selected, |button| {
-                                                                button.tooltip(move |window, cx| {
-                                                                    Tooltip::for_action(
-                                                                        "Send",
-                                                                        &Chat,
-                                                                        window,
-                                                                        cx,
-                                                                    )
-                                                                })
-                                                            })
-                                                            .when(is_editor_empty, |button| {
-                                                                button.tooltip(Tooltip::text(
-                                                                    "Type a message to submit",
-                                                                ))
-                                                            })
-                                                            .when(!is_model_selected, |button| {
-                                                                button.tooltip(Tooltip::text(
-                                                                    "Select a model to continue",
-                                                                ))
-                                                            })
-                                                    )
-                                                }
-                                                }
-                                            }
-                                        )
-                                    ),
-                            ),
-                    )
+            .child(self.render_editor(font_size, line_height, window, cx))
+            .when(
+                total_token_usage.ratio != TokenUsageRatio::Normal,
+                |parent| parent.child(self.render_reaching_token_limit(line_height, cx)),
             )
-            .when(total_token_usage.ratio != TokenUsageRatio::Normal, |parent| {
-                parent.child(
-                    h_flex()
-                        .p_2()
-                        .gap_2()
-                        .flex_wrap()
-                        .justify_between()
-                        .bg(cx.theme().status().warning_background.opacity(0.1))
-                        .border_t_1()
-                        .border_color(cx.theme().colors().border)
-                        .child(
-                            h_flex()
-                                .gap_2()
-                                .items_start()
-                                .child(
-                                    h_flex()
-                                        .h(line_height)
-                                        .justify_center()
-                                        .child(
-                                            Icon::new(IconName::Warning)
-                                                .color(Color::Warning)
-                                                .size(IconSize::XSmall),
-                                        ),
-                                )
-                                .child(
-                                    v_flex()
-                                        .mr_auto()
-                                        .child(Label::new("Thread reaching the token limit soon").size(LabelSize::Small))
-                                        .child(
-                                            Label::new(
-                                                "Start a new thread from a summary to continue the conversation.",
-                                            )
-                                            .size(LabelSize::Small)
-                                            .color(Color::Muted),
-                                        ),
-                                ),
-                        )
-                        .child(
-                            Button::new("new-thread", "Start New Thread")
-                                .on_click(cx.listener(|this, _, window, cx| {
-                                    let from_thread_id = Some(this.thread.read(cx).id().clone());
-
-                                    window.dispatch_action(Box::new(NewThread {
-                                        from_thread_id
-                                    }), cx);
-                                }))
-                                .icon(IconName::Plus)
-                                .icon_position(IconPosition::Start)
-                                .icon_size(IconSize::Small)
-                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
-                                .label_size(LabelSize::Small),
-                        ),
-                )
-            })
     }
 }