diff --git a/crates/editor2/src/display_map/wrap_map.rs b/crates/editor2/src/display_map/wrap_map.rs index 408142ae07..5aeecbae97 100644 --- a/crates/editor2/src/display_map/wrap_map.rs +++ b/crates/editor2/src/display_map/wrap_map.rs @@ -162,7 +162,7 @@ impl WrapMap { { let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); - let edits = new_snapshot + edits = new_snapshot .update( tab_snapshot, &[TabEdit { diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 99d8a4bb93..6ef843dfd1 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -1734,21 +1734,11 @@ impl Editor { // Self::new(EditorMode::Full, buffer, None, field_editor_style, cx) // } - // pub fn auto_height( - // max_lines: usize, - // field_editor_style: Option>, - // cx: &mut ViewContext, - // ) -> Self { - // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); - // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); - // Self::new( - // EditorMode::AutoHeight { max_lines }, - // buffer, - // None, - // field_editor_style, - // cx, - // ) - // } + pub fn auto_height(max_lines: usize, cx: &mut ViewContext) -> Self { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new())); + let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx) + } pub fn for_buffer( buffer: Model, @@ -8374,6 +8364,18 @@ impl Editor { cx.notify(); } + pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext) { + let rem_size = cx.rem_size(); + self.display_map.update(cx, |map, cx| { + map.set_font( + style.text.font(), + style.text.font_size.to_pixels(rem_size), + cx, + ) + }); + self.style = Some(style); + } + pub fn set_wrap_width(&self, width: Option, cx: &mut AppContext) -> bool { self.display_map .update(cx, |map, cx| map.set_wrap_width(width, cx)) @@ -9397,7 +9399,7 @@ impl Render for Editor { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let settings = ThemeSettings::get_global(cx); let text_style = match self.mode { - EditorMode::SingleLine => TextStyle { + EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle { color: cx.theme().colors().text, font_family: settings.ui_font.family.clone(), font_features: settings.ui_font.features, @@ -9410,8 +9412,6 @@ impl Render for Editor { white_space: WhiteSpace::Normal, }, - EditorMode::AutoHeight { max_lines } => todo!(), - EditorMode::Full => TextStyle { color: cx.theme().colors().text, font_family: settings.buffer_font.family.clone(), diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 24402c7e37..928d50224e 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -20,9 +20,9 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, - BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, - ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement, + div, point, px, relative, size, transparent_black, Action, AnyElement, AsyncWindowContext, + AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, + ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement, IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, @@ -1662,11 +1662,6 @@ impl EditorElement { cx: &mut WindowContext, ) -> LayoutState { self.editor.update(cx, |editor, cx| { - // let mut size = constraint.max; - // if size.x.is_infinite() { - // unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); - // } - let snapshot = editor.snapshot(cx); let style = self.style.clone(); @@ -1702,6 +1697,7 @@ impl EditorElement { }; editor.gutter_width = gutter_width; + let text_width = bounds.size.width - gutter_width; let overscroll = size(em_width, px(0.)); let snapshot = { @@ -1728,25 +1724,6 @@ impl EditorElement { .collect::>(); let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; - // todo!("this should happen during layout") - let editor_mode = snapshot.mode; - if let EditorMode::AutoHeight { max_lines } = editor_mode { - todo!() - // size.set_y( - // scroll_height - // .min(constraint.max_along(Axis::Vertical)) - // .max(constraint.min_along(Axis::Vertical)) - // .max(line_height) - // .min(line_height * max_lines as f32), - // ) - } else if let EditorMode::SingleLine = editor_mode { - bounds.size.height = line_height.min(bounds.size.height); - } - // todo!() - // else if size.y.is_infinite() { - // // size.set_y(scroll_height); - // } - // let gutter_size = size(gutter_width, bounds.size.height); let text_size = size(text_width, bounds.size.height); @@ -2064,7 +2041,7 @@ impl EditorElement { .unwrap(); LayoutState { - mode: editor_mode, + mode: snapshot.mode, position_map: Arc::new(PositionMap { size: bounds.size, scroll_position: point( @@ -2617,19 +2594,44 @@ impl Element for EditorElement { cx: &mut gpui::WindowContext, ) -> (gpui::LayoutId, Self::State) { self.editor.update(cx, |editor, cx| { - editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this. + editor.set_style(self.style.clone(), cx); - let rem_size = cx.rem_size(); - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = match editor.mode { + let layout_id = match editor.mode { EditorMode::SingleLine => { - self.style.text.line_height_in_pixels(cx.rem_size()).into() + let rem_size = cx.rem_size(); + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); + cx.request_layout(&style, None) + } + EditorMode::AutoHeight { max_lines } => { + let editor_handle = cx.view().clone(); + let max_line_number_width = + self.max_line_number_width(&editor.snapshot(cx), cx); + cx.request_measured_layout( + Style::default(), + move |known_dimensions, available_space, cx| { + editor_handle + .update(cx, |editor, cx| { + compute_auto_height_layout( + editor, + max_lines, + max_line_number_width, + known_dimensions, + cx, + ) + }) + .unwrap_or_default() + }, + ) + } + EditorMode::Full => { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) } - EditorMode::AutoHeight { .. } => todo!(), - EditorMode::Full => relative(1.).into(), }; - let layout_id = cx.request_layout(&style, None); (layout_id, ()) }) @@ -2698,604 +2700,6 @@ impl IntoElement for EditorElement { } } -// impl EditorElement { -// type LayoutState = LayoutState; -// type PaintState = (); - -// fn layout( -// &mut self, -// constraint: SizeConstraint, -// editor: &mut Editor, -// cx: &mut ViewContext, -// ) -> (gpui::Point, Self::LayoutState) { -// let mut size = constraint.max; -// if size.x.is_infinite() { -// unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); -// } - -// let snapshot = editor.snapshot(cx); -// let style = self.style.clone(); - -// let line_height = (style.text.font_size * style.line_height_scalar).round(); - -// let gutter_padding; -// let gutter_width; -// let gutter_margin; -// if snapshot.show_gutter { -// let em_width = style.text.em_width(cx.font_cache()); -// gutter_padding = (em_width * style.gutter_padding_factor).round(); -// gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; -// gutter_margin = -style.text.descent(cx.font_cache()); -// } else { -// gutter_padding = 0.0; -// gutter_width = 0.0; -// gutter_margin = 0.0; -// }; - -// let text_width = size.x - gutter_width; -// let em_width = style.text.em_width(cx.font_cache()); -// let em_advance = style.text.em_advance(cx.font_cache()); -// let overscroll = point(em_width, 0.); -// let snapshot = { -// editor.set_visible_line_count(size.y / line_height, cx); - -// let editor_width = text_width - gutter_margin - overscroll.x - em_width; -// let wrap_width = match editor.soft_wrap_mode(cx) { -// SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, -// SoftWrap::EditorWidth => editor_width, -// SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), -// }; - -// if editor.set_wrap_width(Some(wrap_width), cx) { -// editor.snapshot(cx) -// } else { -// snapshot -// } -// }; - -// let wrap_guides = editor -// .wrap_guides(cx) -// .iter() -// .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) -// .collect(); - -// let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height; -// if let EditorMode::AutoHeight { max_lines } = snapshot.mode { -// size.set_y( -// scroll_height -// .min(constraint.max_along(Axis::Vertical)) -// .max(constraint.min_along(Axis::Vertical)) -// .max(line_height) -// .min(line_height * max_lines as f32), -// ) -// } else if let EditorMode::SingleLine = snapshot.mode { -// size.set_y(line_height.max(constraint.min_along(Axis::Vertical))) -// } else if size.y.is_infinite() { -// size.set_y(scroll_height); -// } -// let gutter_size = point(gutter_width, size.y); -// let text_size = point(text_width, size.y); - -// let autoscroll_horizontally = editor.autoscroll_vertically(size.y, line_height, cx); -// let mut snapshot = editor.snapshot(cx); - -// let scroll_position = snapshot.scroll_position(); -// // The scroll position is a fractional point, the whole number of which represents -// // the top of the window in terms of display rows. -// let start_row = scroll_position.y as u32; -// let height_in_lines = size.y / line_height; -// let max_row = snapshot.max_point().row(); - -// // Add 1 to ensure selections bleed off screen -// let end_row = 1 + cmp::min( -// (scroll_position.y + height_in_lines).ceil() as u32, -// max_row, -// ); - -// let start_anchor = if start_row == 0 { -// Anchor::min() -// } else { -// snapshot -// .buffer_snapshot -// .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) -// }; -// let end_anchor = if end_row > max_row { -// Anchor::max -// } else { -// snapshot -// .buffer_snapshot -// .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) -// }; - -// let mut selections: Vec<(SelectionStyle, Vec)> = Vec::new(); -// let mut active_rows = BTreeMap::new(); -// let mut fold_ranges = Vec::new(); -// let is_singleton = editor.is_singleton(cx); - -// let highlighted_rows = editor.highlighted_rows(); -// let theme = theme::current(cx); -// let highlighted_ranges = editor.background_highlights_in_range( -// start_anchor..end_anchor, -// &snapshot.display_snapshot, -// theme.as_ref(), -// ); - -// fold_ranges.extend( -// snapshot -// .folds_in_range(start_anchor..end_anchor) -// .map(|anchor| { -// let start = anchor.start.to_point(&snapshot.buffer_snapshot); -// ( -// start.row, -// start.to_display_point(&snapshot.display_snapshot) -// ..anchor.end.to_display_point(&snapshot), -// ) -// }), -// ); - -// let mut newest_selection_head = None; - -// if editor.show_local_selections { -// let mut local_selections: Vec> = editor -// .selections -// .disjoint_in_range(start_anchor..end_anchor, cx); -// local_selections.extend(editor.selections.pending(cx)); -// let mut layouts = Vec::new(); -// let newest = editor.selections.newest(cx); -// for selection in local_selections.drain(..) { -// let is_empty = selection.start == selection.end; -// let is_newest = selection == newest; - -// let layout = SelectionLayout::new( -// selection, -// editor.selections.line_mode, -// editor.cursor_shape, -// &snapshot.display_snapshot, -// is_newest, -// true, -// ); -// if is_newest { -// newest_selection_head = Some(layout.head); -// } - -// for row in cmp::max(layout.active_rows.start, start_row) -// ..=cmp::min(layout.active_rows.end, end_row) -// { -// let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); -// *contains_non_empty_selection |= !is_empty; -// } -// layouts.push(layout); -// } - -// selections.push((style.selection, layouts)); -// } - -// if let Some(collaboration_hub) = &editor.collaboration_hub { -// // When following someone, render the local selections in their color. -// if let Some(leader_id) = editor.leader_peer_id { -// if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { -// if let Some(participant_index) = collaboration_hub -// .user_participant_indices(cx) -// .get(&collaborator.user_id) -// { -// if let Some((local_selection_style, _)) = selections.first_mut() { -// *local_selection_style = -// style.selection_style_for_room_participant(participant_index.0); -// } -// } -// } -// } - -// let mut remote_selections = HashMap::default(); -// for selection in snapshot.remote_selections_in_range( -// &(start_anchor..end_anchor), -// collaboration_hub.as_ref(), -// cx, -// ) { -// let selection_style = if let Some(participant_index) = selection.participant_index { -// style.selection_style_for_room_participant(participant_index.0) -// } else { -// style.absent_selection -// }; - -// // Don't re-render the leader's selections, since the local selections -// // match theirs. -// if Some(selection.peer_id) == editor.leader_peer_id { -// continue; -// } - -// remote_selections -// .entry(selection.replica_id) -// .or_insert((selection_style, Vec::new())) -// .1 -// .push(SelectionLayout::new( -// selection.selection, -// selection.line_mode, -// selection.cursor_shape, -// &snapshot.display_snapshot, -// false, -// false, -// )); -// } - -// selections.extend(remote_selections.into_values()); -// } - -// let scrollbar_settings = &settings::get::(cx).scrollbar; -// let show_scrollbars = match scrollbar_settings.show { -// ShowScrollbar::Auto => { -// // Git -// (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) -// || -// // Selections -// (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty) -// // Scrollmanager -// || editor.scroll_manager.scrollbars_visible() -// } -// ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), -// ShowScrollbar::Always => true, -// ShowScrollbar::Never => false, -// }; - -// let fold_ranges: Vec<(BufferRow, Range, Color)> = fold_ranges -// .into_iter() -// .map(|(id, fold)| { -// let color = self -// .style -// .folds -// .ellipses -// .background -// .style_for(&mut cx.mouse_state::(id as usize)) -// .color; - -// (id, fold, color) -// }) -// .collect(); - -// let head_for_relative = newest_selection_head.unwrap_or_else(|| { -// let newest = editor.selections.newest::(cx); -// SelectionLayout::new( -// newest, -// editor.selections.line_mode, -// editor.cursor_shape, -// &snapshot.display_snapshot, -// true, -// true, -// ) -// .head -// }); - -// let (line_number_layouts, fold_statuses) = self.layout_line_numbers( -// start_row..end_row, -// &active_rows, -// head_for_relative, -// is_singleton, -// &snapshot, -// cx, -// ); - -// let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); - -// let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); - -// let mut max_visible_line_width = 0.0; -// let line_layouts = -// self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx); -// for line_with_invisibles in &line_layouts { -// if line_with_invisibles.line.width() > max_visible_line_width { -// max_visible_line_width = line_with_invisibles.line.width(); -// } -// } - -// let style = self.style.clone(); -// let longest_line_width = layout_line( -// snapshot.longest_row(), -// &snapshot, -// &style, -// cx.text_layout_cache(), -// ) -// .width(); -// let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x; -// let em_width = style.text.em_width(cx.font_cache()); -// let (scroll_width, blocks) = self.layout_blocks( -// start_row..end_row, -// &snapshot, -// size.x, -// scroll_width, -// gutter_padding, -// gutter_width, -// em_width, -// gutter_width + gutter_margin, -// line_height, -// &style, -// &line_layouts, -// editor, -// cx, -// ); - -// let scroll_max = point( -// ((scroll_width - text_size.x) / em_width).max(0.0), -// max_row as f32, -// ); - -// let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); - -// let autoscrolled = if autoscroll_horizontally { -// editor.autoscroll_horizontally( -// start_row, -// text_size.x, -// scroll_width, -// em_width, -// &line_layouts, -// cx, -// ) -// } else { -// false -// }; - -// if clamped || autoscrolled { -// snapshot = editor.snapshot(cx); -// } - -// let style = editor.style(cx); - -// let mut context_menu = None; -// let mut code_actions_indicator = None; -// if let Some(newest_selection_head) = newest_selection_head { -// if (start_row..end_row).contains(&newest_selection_head.row()) { -// if editor.context_menu_visible() { -// context_menu = -// editor.render_context_menu(newest_selection_head, style.clone(), cx); -// } - -// let active = matches!( -// editor.context_menu.read().as_ref(), -// Some(crate::ContextMenu::CodeActions(_)) -// ); - -// code_actions_indicator = editor -// .render_code_actions_indicator(&style, active, cx) -// .map(|indicator| (newest_selection_head.row(), indicator)); -// } -// } - -// let visible_rows = start_row..start_row + line_layouts.len() as u32; -// let mut hover = editor.hover_state.render( -// &snapshot, -// &style, -// visible_rows, -// editor.workspace.as_ref().map(|(w, _)| w.clone()), -// cx, -// ); -// let mode = editor.mode; - -// let mut fold_indicators = editor.render_fold_indicators( -// fold_statuses, -// &style, -// editor.gutter_hovered, -// line_height, -// gutter_margin, -// cx, -// ); - -// if let Some((_, context_menu)) = context_menu.as_mut() { -// context_menu.layout( -// SizeConstraint { -// min: gpui::Point::::zero(), -// max: point( -// cx.window_size().x * 0.7, -// (12. * line_height).min((size.y - line_height) / 2.), -// ), -// }, -// editor, -// cx, -// ); -// } - -// if let Some((_, indicator)) = code_actions_indicator.as_mut() { -// indicator.layout( -// SizeConstraint::strict_along( -// Axis::Vertical, -// line_height * style.code_actions.vertical_scale, -// ), -// editor, -// cx, -// ); -// } - -// for fold_indicator in fold_indicators.iter_mut() { -// if let Some(indicator) = fold_indicator.as_mut() { -// indicator.layout( -// SizeConstraint::strict_along( -// Axis::Vertical, -// line_height * style.code_actions.vertical_scale, -// ), -// editor, -// cx, -// ); -// } -// } - -// if let Some((_, hover_popovers)) = hover.as_mut() { -// for hover_popover in hover_popovers.iter_mut() { -// hover_popover.layout( -// SizeConstraint { -// min: gpui::Point::::zero(), -// max: point( -// (120. * em_width) // Default size -// .min(size.x / 2.) // Shrink to half of the editor width -// .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters -// (16. * line_height) // Default size -// .min(size.y / 2.) // Shrink to half of the editor height -// .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines -// ), -// }, -// editor, -// cx, -// ); -// } -// } - -// let invisible_symbol_font_size = self.style.text.font_size / 2.0; -// let invisible_symbol_style = RunStyle { -// color: self.style.whitespace, -// font_id: self.style.text.font_id, -// underline: Default::default(), -// }; - -// ( -// size, -// LayoutState { -// mode, -// position_map: Arc::new(PositionMap { -// size, -// scroll_max, -// line_layouts, -// line_height, -// em_width, -// em_advance, -// snapshot, -// }), -// visible_display_row_range: start_row..end_row, -// wrap_guides, -// gutter_size, -// gutter_padding, -// text_size, -// scrollbar_row_range, -// show_scrollbars, -// is_singleton, -// max_row, -// gutter_margin, -// active_rows, -// highlighted_rows, -// highlighted_ranges, -// fold_ranges, -// line_number_layouts, -// display_hunks, -// blocks, -// selections, -// context_menu, -// code_actions_indicator, -// fold_indicators, -// tab_invisible: cx.text_layout_cache().layout_str( -// "→", -// invisible_symbol_font_size, -// &[("→".len(), invisible_symbol_style)], -// ), -// space_invisible: cx.text_layout_cache().layout_str( -// "•", -// invisible_symbol_font_size, -// &[("•".len(), invisible_symbol_style)], -// ), -// hover_popovers: hover, -// }, -// ) -// } - -// fn paint( -// &mut self, -// bounds: Bounds, -// visible_bounds: Bounds, -// layout: &mut Self::LayoutState, -// editor: &mut Editor, -// cx: &mut ViewContext, -// ) -> Self::PaintState { -// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); -// cx.scene().push_layer(Some(visible_bounds)); - -// let gutter_bounds = Bounds::::new(bounds.origin, layout.gutter_size); -// let text_bounds = Bounds::::new( -// bounds.origin + point(layout.gutter_size.x, 0.0), -// layout.text_size, -// ); - -// Self::attach_mouse_handlers( -// &layout.position_map, -// layout.hover_popovers.is_some(), -// visible_bounds, -// text_bounds, -// gutter_bounds, -// bounds, -// cx, -// ); - -// self.paint_background(gutter_bounds, text_bounds, layout, cx); -// if layout.gutter_size.x > 0. { -// self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx); -// } -// self.paint_text(text_bounds, visible_bounds, layout, editor, cx); - -// cx.scene().push_layer(Some(bounds)); -// if !layout.blocks.is_empty { -// self.paint_blocks(bounds, visible_bounds, layout, editor, cx); -// } -// self.paint_scrollbar(bounds, layout, &editor, cx); -// cx.scene().pop_layer(); -// cx.scene().pop_layer(); -// } - -// fn rect_for_text_range( -// &self, -// range_utf16: Range, -// bounds: Bounds, -// _: Bounds, -// layout: &Self::LayoutState, -// _: &Self::PaintState, -// _: &Editor, -// _: &ViewContext, -// ) -> Option> { -// let text_bounds = Bounds::::new( -// bounds.origin + point(layout.gutter_size.x, 0.0), -// layout.text_size, -// ); -// let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.); -// let scroll_position = layout.position_map.snapshot.scroll_position(); -// let start_row = scroll_position.y as u32; -// let scroll_top = scroll_position.y * layout.position_map.line_height; -// let scroll_left = scroll_position.x * layout.position_map.em_width; - -// let range_start = OffsetUtf16(range_utf16.start) -// .to_display_point(&layout.position_map.snapshot.display_snapshot); -// if range_start.row() < start_row { -// return None; -// } - -// let line = &layout -// .position_map -// .line_layouts -// .get((range_start.row() - start_row) as usize)? -// .line; -// let range_start_x = line.x_for_index(range_start.column() as usize); -// let range_start_y = range_start.row() as f32 * layout.position_map.line_height; -// Some(Bounds::::new( -// content_origin -// + point( -// range_start_x, -// range_start_y + layout.position_map.line_height, -// ) -// - point(scroll_left, scroll_top), -// point( -// layout.position_map.em_width, -// layout.position_map.line_height, -// ), -// )) -// } - -// fn debug( -// &self, -// bounds: Bounds, -// _: &Self::LayoutState, -// _: &Self::PaintState, -// _: &Editor, -// _: &ViewContext, -// ) -> json::Value { -// json!({ -// "type": "BufferElement", -// "bounds": bounds.to_json() -// }) -// } -// } - type BufferRow = u32; pub struct LayoutState { @@ -4134,3 +3538,59 @@ pub fn register_action( } }) } + +fn compute_auto_height_layout( + editor: &mut Editor, + max_lines: usize, + max_line_number_width: Pixels, + known_dimensions: Size>, + cx: &mut ViewContext, +) -> Option> { + let mut width = known_dimensions.width?; + if let Some(height) = known_dimensions.height { + return Some(size(width, height)); + } + + let style = editor.style.as_ref().unwrap(); + let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_size = style.text.font_size.to_pixels(cx.rem_size()); + let line_height = style.text.line_height_in_pixels(cx.rem_size()); + let em_width = cx + .text_system() + .typographic_bounds(font_id, font_size, 'm') + .unwrap() + .size + .width; + + let mut snapshot = editor.snapshot(cx); + let gutter_padding; + let gutter_width; + let gutter_margin; + if snapshot.show_gutter { + let descent = cx.text_system().descent(font_id, font_size).unwrap(); + let gutter_padding_factor = 3.5; + gutter_padding = (em_width * gutter_padding_factor).round(); + gutter_width = max_line_number_width + gutter_padding * 2.0; + gutter_margin = -descent; + } else { + gutter_padding = Pixels::ZERO; + gutter_width = Pixels::ZERO; + gutter_margin = Pixels::ZERO; + }; + + editor.gutter_width = gutter_width; + let text_width = width - gutter_width; + let overscroll = size(em_width, px(0.)); + + let editor_width = text_width - gutter_margin - overscroll.width - em_width; + if editor.set_wrap_width(Some(editor_width), cx) { + snapshot = editor.snapshot(cx); + } + + let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; + let height = scroll_height + .max(line_height) + .min(line_height * max_lines as f32); + + Some(size(width, height)) +} diff --git a/crates/editor2/src/selections_collection.rs b/crates/editor2/src/selections_collection.rs index 6542ace5fb..8d71916210 100644 --- a/crates/editor2/src/selections_collection.rs +++ b/crates/editor2/src/selections_collection.rs @@ -315,14 +315,11 @@ impl SelectionsCollection { let line = display_map.layout_row(row, &text_layout_details); - dbg!("****START COL****"); let start_col = line.closest_index_for_x(positions.start) as u32; if start_col < line_len || (is_empty && positions.start == line.width) { let start = DisplayPoint::new(row, start_col); - dbg!("****END COL****"); let end_col = line.closest_index_for_x(positions.end) as u32; let end = DisplayPoint::new(row, end_col); - dbg!(start_col, end_col); Some(Selection { id: post_inc(&mut self.next_selection_id), diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index ea00c5a635..9938b94edb 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -1256,7 +1256,7 @@ mod tests { // // TODO: without closing, the opened items do not propagate their history changes for some reason // it does work in real app though, only tests do not propagate. - workspace.update(cx, |_, cx| dbg!(cx.focused())); + workspace.update(cx, |_, cx| cx.focused()); let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await; assert!( diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index d57daca062..490d0b61a1 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -144,7 +144,6 @@ impl TextState { runs: Option>, cx: &mut WindowContext, ) -> LayoutId { - let text_system = cx.text_system().clone(); let text_style = cx.text_style(); let font_size = text_style.font_size.to_pixels(cx.rem_size()); let line_height = text_style @@ -152,18 +151,16 @@ impl TextState { .to_pixels(font_size.into(), cx.rem_size()); let text = SharedString::from(text); - let rem_size = cx.rem_size(); - let runs = if let Some(runs) = runs { runs } else { vec![text_style.to_run(text.len())] }; - let layout_id = cx.request_measured_layout(Default::default(), rem_size, { + let layout_id = cx.request_measured_layout(Default::default(), { let element_state = self.clone(); - move |known_dimensions, available_space| { + move |known_dimensions, available_space, cx| { let wrap_width = if text_style.white_space == WhiteSpace::Normal { known_dimensions.width.or(match available_space.width { crate::AvailableSpace::Definite(x) => Some(x), @@ -181,7 +178,8 @@ impl TextState { } } - let Some(lines) = text_system + let Some(lines) = cx + .text_system() .shape_text( &text, font_size, &runs, wrap_width, // Wrap if we know the width. ) diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 2d5a46f3d9..d8f4cc6804 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -109,7 +109,6 @@ impl Element for UniformList { cx: &mut WindowContext, ) -> (LayoutId, Self::State) { let max_items = self.item_count; - let rem_size = cx.rem_size(); let item_size = state .as_ref() .map(|s| s.item_size) @@ -120,9 +119,7 @@ impl Element for UniformList { .layout(state.map(|s| s.interactive), cx, |style, cx| { cx.request_measured_layout( style, - rem_size, - move |known_dimensions: Size>, - available_space: Size| { + move |known_dimensions, available_space, _cx| { let desired_height = item_size.height * max_items; let width = known_dimensions diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 81a057055a..2bceb1bc13 100644 --- a/crates/gpui2/src/taffy.rs +++ b/crates/gpui2/src/taffy.rs @@ -1,4 +1,7 @@ -use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style}; +use crate::{ + AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, + WindowContext, +}; use collections::{HashMap, HashSet}; use smallvec::SmallVec; use std::fmt::Debug; @@ -9,13 +12,21 @@ use taffy::{ Taffy, }; -type Measureable = dyn Fn(Size>, Size) -> Size + Send + Sync; - pub struct TaffyLayoutEngine { - taffy: Taffy>, + taffy: Taffy, children_to_parents: HashMap, absolute_layout_bounds: HashMap>, computed_layouts: HashSet, + nodes_to_measure: HashMap< + LayoutId, + Box< + dyn FnMut( + Size>, + Size, + &mut WindowContext, + ) -> Size, + >, + >, } static EXPECT_MESSAGE: &'static str = @@ -28,6 +39,7 @@ impl TaffyLayoutEngine { children_to_parents: HashMap::default(), absolute_layout_bounds: HashMap::default(), computed_layouts: HashSet::default(), + nodes_to_measure: HashMap::default(), } } @@ -36,6 +48,7 @@ impl TaffyLayoutEngine { self.children_to_parents.clear(); self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); + self.nodes_to_measure.clear(); } pub fn request_layout( @@ -65,18 +78,18 @@ impl TaffyLayoutEngine { &mut self, style: Style, rem_size: Pixels, - measure: impl Fn(Size>, Size) -> Size - + Send - + Sync + measure: impl FnMut(Size>, Size, &mut WindowContext) -> Size + 'static, ) -> LayoutId { let style = style.to_taffy(rem_size); - let measurable = Box::new(measure); - self.taffy - .new_leaf_with_context(style, measurable) + let layout_id = self + .taffy + .new_leaf_with_context(style, ()) .expect(EXPECT_MESSAGE) - .into() + .into(); + self.nodes_to_measure.insert(layout_id, Box::new(measure)); + layout_id } // Used to understand performance @@ -126,7 +139,12 @@ impl TaffyLayoutEngine { Ok(edges) } - pub fn compute_layout(&mut self, id: LayoutId, available_space: Size) { + pub fn compute_layout( + &mut self, + id: LayoutId, + available_space: Size, + cx: &mut WindowContext, + ) { // Leaving this here until we have a better instrumentation approach. // println!("Laying out {} children", self.count_all_children(id)?); // println!("Max layout depth: {}", self.max_depth(0, id)?); @@ -159,8 +177,8 @@ impl TaffyLayoutEngine { .compute_layout_with_measure( id.into(), available_space.into(), - |known_dimensions, available_space, _node_id, context| { - let Some(measure) = context else { + |known_dimensions, available_space, node_id, _context| { + let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else { return taffy::geometry::Size::default(); }; @@ -169,10 +187,11 @@ impl TaffyLayoutEngine { height: known_dimensions.height.map(Pixels), }; - measure(known_dimensions, available_space.into()).into() + measure(known_dimensions, available_space.into(), cx).into() }, ) .expect(EXPECT_MESSAGE); + // println!("compute_layout took {:?}", started_at.elapsed()); } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 08fe56808a..280c52df2a 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -209,9 +209,7 @@ impl AnyView { ) { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, rendered_element) = (self.layout)(self, cx); - cx.window - .layout_engine - .compute_layout(layout_id, available_space); + cx.compute_layout(layout_id, available_space); (self.paint)(self, rendered_element, cx); }) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 228a95d054..5724f1e070 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -209,7 +209,7 @@ pub struct Window { sprite_atlas: Arc, rem_size: Pixels, viewport_size: Size, - pub(crate) layout_engine: TaffyLayoutEngine, + layout_engine: Option, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, pub(crate) previous_frame: Frame, @@ -327,7 +327,7 @@ impl Window { sprite_atlas, rem_size: px(16.), viewport_size: content_size, - layout_engine: TaffyLayoutEngine::new(), + layout_engine: Some(TaffyLayoutEngine::new()), root_view: None, element_id_stack: GlobalElementId::default(), previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), @@ -606,9 +606,11 @@ impl<'a> WindowContext<'a> { self.app.layout_id_buffer.extend(children.into_iter()); let rem_size = self.rem_size(); - self.window - .layout_engine - .request_layout(style, rem_size, &self.app.layout_id_buffer) + self.window.layout_engine.as_mut().unwrap().request_layout( + style, + rem_size, + &self.app.layout_id_buffer, + ) } /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children, @@ -618,22 +620,25 @@ impl<'a> WindowContext<'a> { /// The given closure is invoked at layout time with the known dimensions and available space and /// returns a `Size`. pub fn request_measured_layout< - F: Fn(Size>, Size) -> Size + Send + Sync + 'static, + F: FnMut(Size>, Size, &mut WindowContext) -> Size + + 'static, >( &mut self, style: Style, - rem_size: Pixels, measure: F, ) -> LayoutId { + let rem_size = self.rem_size(); self.window .layout_engine + .as_mut() + .unwrap() .request_measured_layout(style, rem_size, measure) } pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { - self.window - .layout_engine - .compute_layout(layout_id, available_space) + let mut layout_engine = self.window.layout_engine.take().unwrap(); + layout_engine.compute_layout(layout_id, available_space, self); + self.window.layout_engine = Some(layout_engine); } /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not @@ -643,6 +648,8 @@ impl<'a> WindowContext<'a> { let mut bounds = self .window .layout_engine + .as_mut() + .unwrap() .layout_bounds(layout_id) .map(Into::into); bounds.origin += self.element_offset(); @@ -1193,7 +1200,7 @@ impl<'a> WindowContext<'a> { self.text_system().start_frame(); let window = &mut *self.window; - window.layout_engine.clear(); + window.layout_engine.as_mut().unwrap().clear(); mem::swap(&mut window.previous_frame, &mut window.current_frame); let frame = &mut window.current_frame; diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index 0eaf3d126c..2d63d1d491 100644 --- a/crates/storybook2/src/stories.rs +++ b/crates/storybook2/src/stories.rs @@ -1,3 +1,4 @@ +mod auto_height_editor; mod focus; mod kitchen_sink; mod picker; @@ -5,6 +6,7 @@ mod scroll; mod text; mod z_index; +pub use auto_height_editor::*; pub use focus::*; pub use kitchen_sink::*; pub use picker::*; diff --git a/crates/storybook2/src/stories/auto_height_editor.rs b/crates/storybook2/src/stories/auto_height_editor.rs new file mode 100644 index 0000000000..2f3089a4e6 --- /dev/null +++ b/crates/storybook2/src/stories/auto_height_editor.rs @@ -0,0 +1,34 @@ +use editor::Editor; +use gpui::{ + div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext, + WindowContext, +}; + +pub struct AutoHeightEditorStory { + editor: View, +} + +impl AutoHeightEditorStory { + pub fn new(cx: &mut WindowContext) -> View { + cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]); + cx.build_view(|cx| Self { + editor: cx.build_view(|cx| { + let mut editor = Editor::auto_height(3, cx); + editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor + }), + }) + } +} + +impl Render for AutoHeightEditorStory { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + div() + .size_full() + .bg(white()) + .text_sm() + .child(div().w_32().bg(gpui::black()).child(self.editor.clone())) + } +} diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 994b31c336..4fe76ce878 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -12,6 +12,7 @@ use ui::prelude::*; #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] #[strum(serialize_all = "snake_case")] pub enum ComponentStory { + AutoHeightEditor, Avatar, Button, Checkbox, @@ -34,6 +35,7 @@ pub enum ComponentStory { impl ComponentStory { pub fn story(&self, cx: &mut WindowContext) -> AnyView { match self { + Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(), Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(), Self::Button => cx.build_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(), diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index cefd56c93c..2088b6f4be 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -80,7 +80,6 @@ impl Render for Toolbar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - //dbg!(&self.items.len()); v_stack() .border_b() .border_color(cx.theme().colors().border_variant)