Mikayla MakiKick off agent v2 (#44190)75c71a911 months agoHistory
267 lines · 8.5 KB · Rust
RawDownload
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};use gpui::{    Action, AppContext as _, Entity, EventEmitter, Focusable, NoAction, Subscription, WeakEntity,};use multi_buffer::{MultiBuffer, MultiBufferFilterMode};use project::Project;use ui::{    App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,    Styled as _, Window, div,};use workspace::{    ActivePaneDecorator, Item, ItemHandle, Pane, PaneGroup, SplitDirection, Workspace,}; use crate::{Editor, EditorEvent}; struct SplitDiffFeatureFlag; impl FeatureFlag for SplitDiffFeatureFlag {    const NAME: &'static str = "split-diff";     fn enabled_for_staff() -> bool {        true    }} #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]struct SplitDiff; #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]struct UnsplitDiff; pub struct SplittableEditor {    primary_editor: Entity<Editor>,    secondary: Option<SecondaryEditor>,    panes: PaneGroup,    workspace: WeakEntity<Workspace>,    _subscriptions: Vec<Subscription>,} struct SecondaryEditor {    editor: Entity<Editor>,    pane: Entity<Pane>,    has_latest_selection: bool,    _subscriptions: Vec<Subscription>,} impl SplittableEditor {    pub fn primary_editor(&self) -> &Entity<Editor> {        &self.primary_editor    }     pub fn last_selected_editor(&self) -> &Entity<Editor> {        if let Some(secondary) = &self.secondary            && secondary.has_latest_selection        {            &secondary.editor        } else {            &self.primary_editor        }    }     pub fn new_unsplit(        buffer: Entity<MultiBuffer>,        project: Entity<Project>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let primary_editor =            cx.new(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), window, cx));        let pane = cx.new(|cx| {            let mut pane = Pane::new(                workspace.downgrade(),                project,                Default::default(),                None,                NoAction.boxed_clone(),                true,                window,                cx,            );            pane.set_should_display_tab_bar(|_, _| false);            pane.add_item(primary_editor.boxed_clone(), true, true, None, window, cx);            pane        });        let panes = PaneGroup::new(pane);        // TODO(split-diff) we might want to tag editor events with whether they came from primary/secondary        let subscriptions =            vec![                cx.subscribe(&primary_editor, |this, _, event: &EditorEvent, cx| {                    if let EditorEvent::SelectionsChanged { .. } = event                        && let Some(secondary) = &mut this.secondary                    {                        secondary.has_latest_selection = false;                    }                    cx.emit(event.clone())                }),            ];         window.defer(cx, {            let workspace = workspace.downgrade();            let primary_editor = primary_editor.downgrade();            move |window, cx| {                workspace                    .update(cx, |workspace, cx| {                        primary_editor.update(cx, |editor, cx| {                            editor.added_to_workspace(workspace, window, cx);                        })                    })                    .ok();            }        });        Self {            primary_editor,            secondary: None,            panes,            workspace: workspace.downgrade(),            _subscriptions: subscriptions,        }    }     fn split(&mut self, _: &SplitDiff, window: &mut Window, cx: &mut Context<Self>) {        if !cx.has_flag::<SplitDiffFeatureFlag>() {            return;        }        if self.secondary.is_some() {            return;        }        let Some(workspace) = self.workspace.upgrade() else {            return;        };        let project = workspace.read(cx).project().clone();        let follower = self.primary_editor.update(cx, |primary, cx| {            primary.buffer().update(cx, |buffer, cx| {                let follower = buffer.get_or_create_follower(cx);                buffer.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));                follower            })        });        follower.update(cx, |follower, _| {            follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));        });        let secondary_editor = workspace.update(cx, |workspace, cx| {            cx.new(|cx| {                let mut editor = Editor::for_multibuffer(follower, Some(project), window, cx);                // TODO(split-diff) this should be at the multibuffer level                editor.set_use_base_text_line_numbers(true, cx);                editor.added_to_workspace(workspace, window, cx);                editor            })        });        let secondary_pane = cx.new(|cx| {            let mut pane = Pane::new(                workspace.downgrade(),                workspace.read(cx).project().clone(),                Default::default(),                None,                NoAction.boxed_clone(),                true,                window,                cx,            );            pane.set_should_display_tab_bar(|_, _| false);            pane.add_item(                ItemHandle::boxed_clone(&secondary_editor),                false,                false,                None,                window,                cx,            );            pane        });         let subscriptions =            vec![                cx.subscribe(&secondary_editor, |this, _, event: &EditorEvent, cx| {                    if let EditorEvent::SelectionsChanged { .. } = event                        && let Some(secondary) = &mut this.secondary                    {                        secondary.has_latest_selection = true;                    }                    cx.emit(event.clone())                }),            ];        self.secondary = Some(SecondaryEditor {            editor: secondary_editor,            pane: secondary_pane.clone(),            has_latest_selection: false,            _subscriptions: subscriptions,        });        let primary_pane = self.panes.first_pane();        self.panes            .split(&primary_pane, &secondary_pane, SplitDirection::Left, cx)            .unwrap();        cx.notify();    }     fn unsplit(&mut self, _: &UnsplitDiff, _: &mut Window, cx: &mut Context<Self>) {        let Some(secondary) = self.secondary.take() else {            return;        };        self.panes.remove(&secondary.pane, cx).unwrap();        self.primary_editor.update(cx, |primary, cx| {            primary.buffer().update(cx, |buffer, _| {                buffer.set_filter_mode(None);            });        });        cx.notify();    }     pub fn added_to_workspace(        &mut self,        workspace: &mut Workspace,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.workspace = workspace.weak_handle();        self.primary_editor.update(cx, |primary_editor, cx| {            primary_editor.added_to_workspace(workspace, window, cx);        });        if let Some(secondary) = &self.secondary {            secondary.editor.update(cx, |secondary_editor, cx| {                secondary_editor.added_to_workspace(workspace, window, cx);            });        }    }} impl EventEmitter<EditorEvent> for SplittableEditor {}impl Focusable for SplittableEditor {    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {        self.primary_editor.read(cx).focus_handle(cx)    }} impl Render for SplittableEditor {    fn render(        &mut self,        window: &mut ui::Window,        cx: &mut ui::Context<Self>,    ) -> impl ui::IntoElement {        let inner = if self.secondary.is_none() {            self.primary_editor.clone().into_any_element()        } else if let Some(active) = self.panes.panes().into_iter().next() {            self.panes                .render(                    None,                    &ActivePaneDecorator::new(active, &self.workspace),                    window,                    cx,                )                .into_any_element()        } else {            div().into_any_element()        };        div()            .id("splittable-editor")            .on_action(cx.listener(Self::split))            .on_action(cx.listener(Self::unsplit))            .size_full()            .child(inner)    }}