652 lines · 21.1 KB · Rust
RawDownload
use std::cmp; use collections::{HashMap, HashSet};use gpui::{    AbsoluteLength, AnyElement, App, AvailableSpace, Bounds, Context, DragMoveEvent, Element,    Entity, GlobalElementId, Hsla, InspectorElementId, IntoElement, LayoutId, Length,    ParentElement, Pixels, StatefulInteractiveElement, Styled, TextStyleRefinement, Window, div,    linear_color_stop, linear_gradient, point, px, size,};use multi_buffer::{Anchor, ExcerptId};use settings::Settings;use smallvec::smallvec;use text::BufferId;use theme::ActiveTheme;use ui::scrollbars::ShowScrollbar;use ui::{h_flex, prelude::*, v_flex}; use gpui::ContentMask; use crate::{    DisplayRow, Editor, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,    MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, RowExt, StickyHeaderExcerpt,    display_map::Block,    element::{EditorElement, SplitSide, header_jump_data, render_buffer_header},    scroll::ScrollOffset,    split::SplittableEditor,}; const RESIZE_HANDLE_WIDTH: f32 = 8.0; #[derive(Debug, Clone)]struct DraggedSplitHandle; pub struct SplitEditorState {    left_ratio: f32,    visible_left_ratio: f32,    cached_width: Pixels,} impl SplitEditorState {    pub fn new(_cx: &mut App) -> Self {        Self {            left_ratio: 0.5,            visible_left_ratio: 0.5,            cached_width: px(0.),        }    }     #[allow(clippy::misnamed_getters)]    pub fn left_ratio(&self) -> f32 {        self.visible_left_ratio    }     pub fn right_ratio(&self) -> f32 {        1.0 - self.visible_left_ratio    }     fn on_drag_move(        &mut self,        drag_event: &DragMoveEvent<DraggedSplitHandle>,        _window: &mut Window,        _cx: &mut Context<Self>,    ) {        let drag_position = drag_event.event.position;        let bounds = drag_event.bounds;        let bounds_width = bounds.right() - bounds.left();         if bounds_width > px(0.) {            self.cached_width = bounds_width;        }         let min_ratio = 0.1;        let max_ratio = 0.9;         let new_ratio = (drag_position.x - bounds.left()) / bounds_width;        self.visible_left_ratio = new_ratio.clamp(min_ratio, max_ratio);    }     fn commit_ratio(&mut self) {        self.left_ratio = self.visible_left_ratio;    }     fn on_double_click(&mut self) {        self.left_ratio = 0.5;        self.visible_left_ratio = 0.5;    }} #[derive(IntoElement)]pub struct SplitEditorView {    splittable_editor: Entity<SplittableEditor>,    style: EditorStyle,    split_state: Entity<SplitEditorState>,} impl SplitEditorView {    pub fn new(        splittable_editor: Entity<SplittableEditor>,        style: EditorStyle,        split_state: Entity<SplitEditorState>,    ) -> Self {        Self {            splittable_editor,            style,            split_state,        }    }} fn render_resize_handle(    state: &Entity<SplitEditorState>,    separator_color: Hsla,    _window: &mut Window,    _cx: &mut App,) -> AnyElement {    let state_for_click = state.clone();     div()        .id("split-resize-container")        .relative()        .h_full()        .flex_shrink_0()        .w(px(1.))        .bg(separator_color)        .child(            div()                .id("split-resize-handle")                .absolute()                .left(px(-RESIZE_HANDLE_WIDTH / 2.0))                .w(px(RESIZE_HANDLE_WIDTH))                .h_full()                .cursor_col_resize()                .block_mouse_except_scroll()                .on_click(move |event, _, cx| {                    if event.click_count() >= 2 {                        state_for_click.update(cx, |state, _| {                            state.on_double_click();                        });                    }                    cx.stop_propagation();                })                .on_drag(DraggedSplitHandle, |_, _, _, cx| cx.new(|_| gpui::Empty)),        )        .into_any_element()} impl RenderOnce for SplitEditorView {    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {        let splittable_editor = self.splittable_editor.read(cx);         assert!(            splittable_editor.lhs_editor().is_some(),            "`SplitEditorView` requires `SplittableEditor` to be in split mode"        );         let lhs_editor = splittable_editor.lhs_editor().unwrap().clone();        let rhs_editor = splittable_editor.rhs_editor().clone();         let mut lhs = EditorElement::new(&lhs_editor, self.style.clone());        let mut rhs = EditorElement::new(&rhs_editor, self.style.clone());         lhs.set_split_side(SplitSide::Left);        rhs.set_split_side(SplitSide::Right);         let left_ratio = self.split_state.read(cx).left_ratio();        let right_ratio = self.split_state.read(cx).right_ratio();         let separator_color = cx.theme().colors().border_variant;         let resize_handle = render_resize_handle(&self.split_state, separator_color, window, cx);         let state_for_drag = self.split_state.downgrade();        let state_for_drop = self.split_state.downgrade();         let buffer_headers = SplitBufferHeadersElement::new(rhs_editor.clone(), self.style.clone());         let lhs_editor_for_order = lhs_editor;        let rhs_editor_for_order = rhs_editor;         div()            .id("split-editor-view-container")            .size_full()            .relative()            .child(                h_flex()                    .with_dynamic_prepaint_order(move |_window, cx| {                        let lhs_needs = lhs_editor_for_order.read(cx).has_autoscroll_request();                        let rhs_needs = rhs_editor_for_order.read(cx).has_autoscroll_request();                        match (lhs_needs, rhs_needs) {                            (false, true) => smallvec![2, 1, 0],                            _ => smallvec![0, 1, 2],                        }                    })                    .id("split-editor-view")                    .size_full()                    .on_drag_move::<DraggedSplitHandle>(move |event, window, cx| {                        state_for_drag                            .update(cx, |state, cx| {                                state.on_drag_move(event, window, cx);                            })                            .ok();                    })                    .on_drop::<DraggedSplitHandle>(move |_, _, cx| {                        state_for_drop                            .update(cx, |state, _| {                                state.commit_ratio();                            })                            .ok();                    })                    .child(                        div()                            .id("split-editor-left")                            .flex_shrink()                            .min_w_0()                            .h_full()                            .flex_basis(DefiniteLength::Fraction(left_ratio))                            .overflow_hidden()                            .child(lhs),                    )                    .child(resize_handle)                    .child(                        div()                            .id("split-editor-right")                            .flex_shrink()                            .min_w_0()                            .h_full()                            .flex_basis(DefiniteLength::Fraction(right_ratio))                            .overflow_hidden()                            .child(rhs),                    ),            )            .child(buffer_headers)    }} struct SplitBufferHeadersElement {    editor: Entity<Editor>,    style: EditorStyle,} impl SplitBufferHeadersElement {    fn new(editor: Entity<Editor>, style: EditorStyle) -> Self {        Self { editor, style }    }} struct BufferHeaderLayout {    element: AnyElement,} struct SplitBufferHeadersPrepaintState {    sticky_header: Option<AnyElement>,    non_sticky_headers: Vec<BufferHeaderLayout>,} impl IntoElement for SplitBufferHeadersElement {    type Element = Self;     fn into_element(self) -> Self::Element {        self    }} impl Element for SplitBufferHeadersElement {    type RequestLayoutState = ();    type PrepaintState = SplitBufferHeadersPrepaintState;     fn id(&self) -> Option<gpui::ElementId> {        Some("split-buffer-headers".into())    }     fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {        None    }     fn request_layout(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        window: &mut Window,        _cx: &mut App,    ) -> (LayoutId, Self::RequestLayoutState) {        let mut style = gpui::Style::default();        style.position = gpui::Position::Absolute;        style.inset.top = DefiniteLength::Fraction(0.0).into();        style.inset.left = DefiniteLength::Fraction(0.0).into();        style.size.width = Length::Definite(DefiniteLength::Fraction(1.0));        style.size.height = Length::Definite(DefiniteLength::Fraction(1.0));        let layout_id = window.request_layout(style, [], _cx);        (layout_id, ())    }     fn prepaint(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        window: &mut Window,        cx: &mut App,    ) -> Self::PrepaintState {        if bounds.size.width <= px(0.) || bounds.size.height <= px(0.) {            return SplitBufferHeadersPrepaintState {                sticky_header: None,                non_sticky_headers: Vec::new(),            };        }         let rem_size = self.rem_size();        let text_style = TextStyleRefinement {            font_size: Some(self.style.text.font_size),            line_height: Some(self.style.text.line_height),            ..Default::default()        };         window.with_rem_size(rem_size, |window| {            window.with_text_style(Some(text_style), |window| {                Self::prepaint_inner(self, bounds, window, cx)            })        })    }     fn paint(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        prepaint: &mut Self::PrepaintState,        window: &mut Window,        cx: &mut App,    ) {        let rem_size = self.rem_size();        let text_style = TextStyleRefinement {            font_size: Some(self.style.text.font_size),            line_height: Some(self.style.text.line_height),            ..Default::default()        };         window.with_rem_size(rem_size, |window| {            window.with_text_style(Some(text_style), |window| {                window.with_content_mask(Some(ContentMask { bounds }), |window| {                    for header_layout in &mut prepaint.non_sticky_headers {                        header_layout.element.paint(window, cx);                    }                     if let Some(mut sticky_header) = prepaint.sticky_header.take() {                        sticky_header.paint(window, cx);                    }                });            });        });    }} impl SplitBufferHeadersElement {    fn rem_size(&self) -> Option<Pixels> {        match self.style.text.font_size {            AbsoluteLength::Pixels(pixels) => {                let rem_size_scale = {                    let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;                    let default_font_size_delta = 1. - default_font_size_scale;                    1. + default_font_size_delta                };                 Some(pixels * rem_size_scale)            }            AbsoluteLength::Rems(rems) => Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into())),        }    }     fn prepaint_inner(        &mut self,        bounds: Bounds<Pixels>,        window: &mut Window,        cx: &mut App,    ) -> SplitBufferHeadersPrepaintState {        let line_height = window.line_height();         let snapshot = self            .editor            .update(cx, |editor, cx| editor.snapshot(window, cx));        let scroll_position = snapshot.scroll_position();         // Compute right margin to avoid overlapping the scrollbar        let settings = EditorSettings::get_global(cx);        let scrollbars_shown = settings.scrollbar.show != ShowScrollbar::Never;        let vertical_scrollbar_width = (scrollbars_shown            && settings.scrollbar.axes.vertical            && self.editor.read(cx).show_scrollbars.vertical)            .then_some(EditorElement::SCROLLBAR_WIDTH)            .unwrap_or_default();        let available_width = bounds.size.width - vertical_scrollbar_width;         let visible_height_in_lines = bounds.size.height / line_height;        let max_row = snapshot.max_point().row();        let start_row = cmp::min(DisplayRow(scroll_position.y.floor() as u32), max_row);        let end_row = cmp::min(            (scroll_position.y + visible_height_in_lines as f64).ceil() as u32,            max_row.next_row().0,        );        let end_row = DisplayRow(end_row);         let (selected_buffer_ids, latest_selection_anchors) =            self.compute_selection_info(&snapshot, cx);         let sticky_header = if snapshot.buffer_snapshot().show_headers() {            snapshot                .sticky_header_excerpt(scroll_position.y)                .map(|sticky_excerpt| {                    self.build_sticky_header(                        sticky_excerpt,                        &snapshot,                        scroll_position,                        bounds,                        available_width,                        line_height,                        &selected_buffer_ids,                        &latest_selection_anchors,                        start_row,                        end_row,                        window,                        cx,                    )                })        } else {            None        };         let sticky_header_excerpt_id = snapshot            .sticky_header_excerpt(scroll_position.y)            .map(|e| e.excerpt.id);         let non_sticky_headers = self.build_non_sticky_headers(            &snapshot,            scroll_position,            bounds,            available_width,            line_height,            start_row,            end_row,            &selected_buffer_ids,            &latest_selection_anchors,            sticky_header_excerpt_id,            window,            cx,        );         SplitBufferHeadersPrepaintState {            sticky_header,            non_sticky_headers,        }    }     fn compute_selection_info(        &self,        snapshot: &EditorSnapshot,        cx: &App,    ) -> (HashSet<BufferId>, HashMap<BufferId, Anchor>) {        let editor = self.editor.read(cx);        let all_selections = editor            .selections            .all::<crate::Point>(&snapshot.display_snapshot);        let all_anchor_selections = editor.selections.all_anchors(&snapshot.display_snapshot);         let mut selected_buffer_ids = HashSet::default();        for selection in &all_selections {            for buffer_id in snapshot                .buffer_snapshot()                .buffer_ids_for_range(selection.range())            {                selected_buffer_ids.insert(buffer_id);            }        }         let mut anchors_by_buffer: HashMap<BufferId, (usize, Anchor)> = HashMap::default();        for selection in all_anchor_selections.iter() {            let head = selection.head();            if let Some(buffer_id) = head.text_anchor.buffer_id {                anchors_by_buffer                    .entry(buffer_id)                    .and_modify(|(latest_id, latest_anchor)| {                        if selection.id > *latest_id {                            *latest_id = selection.id;                            *latest_anchor = head;                        }                    })                    .or_insert((selection.id, head));            }        }        let latest_selection_anchors = anchors_by_buffer            .into_iter()            .map(|(buffer_id, (_, anchor))| (buffer_id, anchor))            .collect();         (selected_buffer_ids, latest_selection_anchors)    }     fn build_sticky_header(        &self,

Lines 1–500 of 652.