@@ -8,15 +8,23 @@ use crate::{
use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
- black, point, px, relative, size, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style,
- TextRun, TextStyle, TextSystem, ViewContext, WindowContext,
+ black, hsla, point, px, relative, size, transparent_black, AnyElement, BorrowWindow, Bounds,
+ ContentMask, Corners, Edges, Element, Hsla, Line, Pixels, Size, Style, TextRun, TextStyle,
+ TextSystem, ViewContext, WindowContext,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
use multi_buffer::Anchor;
use settings::Settings;
use smallvec::SmallVec;
-use std::{borrow::Cow, cmp, fmt::Write, iter, ops::Range, sync::Arc};
+use std::{
+ borrow::Cow,
+ cmp::{self, Ordering},
+ fmt::Write,
+ iter,
+ ops::Range,
+ sync::Arc,
+};
use sum_tree::Bias;
use theme::{ActiveTheme, PlayerColor};
use workspace::item::Item;
@@ -368,7 +376,7 @@ impl EditorElement {
// let mut scroll_delta = gpui::Point<Pixels>::zero();
// let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
- // let top = text_bounds.origin_y() + vertical_margin;
+ // let top = text_bounds.origin.y + vertical_margin;
// let bottom = text_bounds.lower_left().y() - vertical_margin;
// if position.y() < top {
// scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
@@ -378,7 +386,7 @@ impl EditorElement {
// }
// let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
- // let left = text_bounds.origin_x() + horizontal_margin;
+ // let left = text_bounds.origin.x + horizontal_margin;
// let right = text_bounds.upper_right().x() - horizontal_margin;
// if position.x() < left {
// scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
@@ -476,122 +484,129 @@ impl EditorElement {
// position_map.snapshot.ongoing_scroll.filter(&mut delta)
// } else {
// //Not trackpad
- // delta *= vec2f(max_glyph_width, line_height);
+ // delta *= point(max_glyph_width, line_height);
// None //Resets ongoing scroll
// };
// let scroll_position = position_map.snapshot.scroll_position();
// let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
// let y = (scroll_position.y() * line_height - delta.y()) / line_height;
- // let scroll_position = vec2f(x, y).clamp(gpui::Point<Pixels>::zero(), position_map.scroll_max);
+ // let scroll_position = point(x, y).clamp(gpui::Point<Pixels>::zero(), position_map.scroll_max);
// editor.scroll(scroll_position, axis, cx);
// true
// }
- // fn paint_background(
- // &self,
- // gutter_bounds: Bounds<Pixels>,
- // text_bounds: Bounds<Pixels>,
- // layout: &LayoutState,
- // cx: &mut ViewContext<Editor>,
- // ) {
- // let bounds = gutter_bounds.union_rect(text_bounds);
- // let scroll_top =
- // layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
- // cx.scene().push_quad(Quad {
- // bounds: gutter_bounds,
- // background: Some(self.style.gutter_background),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: Default::default(),
- // });
- // cx.scene().push_quad(Quad {
- // bounds: text_bounds,
- // background: Some(self.style.background),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: Default::default(),
- // });
+ fn paint_background(
+ &self,
+ gutter_bounds: Bounds<Pixels>,
+ text_bounds: Bounds<Pixels>,
+ layout: &LayoutState,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ let bounds = gutter_bounds.union(&text_bounds);
+ let scroll_top =
+ layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
+ let gutter_bg = cx.theme().colors().editor_gutter;
+ cx.paint_quad(
+ gutter_bounds,
+ Corners::default(),
+ gutter_bg,
+ Edges::default(),
+ transparent_black(),
+ );
+ cx.paint_quad(
+ text_bounds,
+ Corners::default(),
+ self.style.background,
+ Edges::default(),
+ transparent_black(),
+ );
- // if let EditorMode::Full = layout.mode {
- // let mut active_rows = layout.active_rows.iter().peekable();
- // while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
- // let mut end_row = *start_row;
- // while active_rows.peek().map_or(false, |r| {
- // *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
- // }) {
- // active_rows.next().unwrap();
- // end_row += 1;
- // }
+ if let EditorMode::Full = layout.mode {
+ let mut active_rows = layout.active_rows.iter().peekable();
+ while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
+ let mut end_row = *start_row;
+ while active_rows.peek().map_or(false, |r| {
+ *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
+ }) {
+ active_rows.next().unwrap();
+ end_row += 1;
+ }
- // if !contains_non_empty_selection {
- // let origin = vec2f(
- // bounds.origin_x(),
- // bounds.origin_y() + (layout.position_map.line_height * *start_row as f32)
- // - scroll_top,
- // );
- // let size = vec2f(
- // bounds.width(),
- // layout.position_map.line_height * (end_row - start_row + 1) as f32,
- // );
- // cx.scene().push_quad(Quad {
- // bounds: Bounds<Pixels>::new(origin, size),
- // background: Some(self.style.active_line_background),
- // border: Border::default().into(),
- // corner_radii: Default::default(),
- // });
- // }
- // }
+ if !contains_non_empty_selection {
+ let origin = point(
+ bounds.origin.x,
+ bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
+ - scroll_top,
+ );
+ let size = size(
+ bounds.size.width,
+ layout.position_map.line_height * (end_row - start_row + 1) as f32,
+ );
+ let active_line_bg = cx.theme().colors().editor_active_line;
+ cx.paint_quad(
+ Bounds { origin, size },
+ Corners::default(),
+ active_line_bg,
+ Edges::default(),
+ transparent_black(),
+ );
+ }
+ }
- // if let Some(highlighted_rows) = &layout.highlighted_rows {
- // let origin = vec2f(
- // bounds.origin_x(),
- // bounds.origin_y()
- // + (layout.position_map.line_height * highlighted_rows.start as f32)
- // - scroll_top,
- // );
- // let size = vec2f(
- // bounds.width(),
- // layout.position_map.line_height * highlighted_rows.len() as f32,
- // );
- // cx.scene().push_quad(Quad {
- // bounds: Bounds<Pixels>::new(origin, size),
- // background: Some(self.style.highlighted_line_background),
- // border: Border::default().into(),
- // corner_radii: Default::default(),
- // });
- // }
+ if let Some(highlighted_rows) = &layout.highlighted_rows {
+ let origin = point(
+ bounds.origin.x,
+ bounds.origin.y
+ + (layout.position_map.line_height * highlighted_rows.start as f32)
+ - scroll_top,
+ );
+ let size = size(
+ bounds.size.width,
+ layout.position_map.line_height * highlighted_rows.len() as f32,
+ );
+ let highlighted_line_bg = cx.theme().colors().editor_highlighted_line;
+ cx.paint_quad(
+ Bounds { origin, size },
+ Corners::default(),
+ highlighted_line_bg,
+ Edges::default(),
+ transparent_black(),
+ );
+ }
- // let scroll_left =
- // layout.position_map.snapshot.scroll_position().x() * layout.position_map.em_width;
+ let scroll_left =
+ layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
- // for (wrap_position, active) in layout.wrap_guides.iter() {
- // let x =
- // (text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.)
- // - scroll_left;
+ for (wrap_position, active) in layout.wrap_guides.iter() {
+ let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
+ - scroll_left;
- // if x < text_bounds.origin_x()
- // || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
- // {
- // continue;
- // }
+ if x < text_bounds.origin.x
+ || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
+ {
+ continue;
+ }
- // let color = if *active {
- // self.style.active_wrap_guide
- // } else {
- // self.style.wrap_guide
- // };
- // cx.scene().push_quad(Quad {
- // bounds: Bounds<Pixels>::new(
- // vec2f(x, text_bounds.origin_y()),
- // vec2f(1., text_bounds.height()),
- // ),
- // background: Some(color),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: Default::default(),
- // });
- // }
- // }
- // }
+ let color = if *active {
+ cx.theme().colors().editor_active_wrap_guide
+ } else {
+ cx.theme().colors().editor_wrap_guide
+ };
+ cx.paint_quad(
+ Bounds {
+ origin: point(x, text_bounds.origin.y),
+ size: size(px(1.), text_bounds.size.height),
+ },
+ Corners::default(),
+ color,
+ Edges::default(),
+ transparent_black(),
+ );
+ }
+ }
+ }
// fn paint_gutter(
// &mut self,
@@ -618,7 +633,7 @@ impl EditorElement {
// for (ix, line) in layout.line_number_layouts.iter().enumerate() {
// if let Some(line) = line {
// let line_origin = bounds.origin()
- // + vec2f(
+ // + point(
// bounds.width() - line.width() - layout.gutter_padding,
// ix as f32 * line_height - (scroll_top % line_height),
// );
@@ -629,11 +644,11 @@ impl EditorElement {
// for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
// if let Some(indicator) = fold_indicator.as_mut() {
- // let position = vec2f(
+ // let position = point(
// bounds.width() - layout.gutter_padding,
// ix as f32 * line_height - (scroll_top % line_height),
// );
- // let centering_offset = vec2f(
+ // let centering_offset = point(
// (layout.gutter_padding + layout.gutter_margin - indicator.size().x()) / 2.,
// (line_height - indicator.size().y()) / 2.,
// );
@@ -649,7 +664,7 @@ impl EditorElement {
// let mut y = *row as f32 * line_height - scroll_top;
// x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
// y += (line_height - indicator.size().y()) / 2.;
- // indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx);
+ // indicator.paint(bounds.origin() + point(x, y), visible_bounds, editor, cx);
// }
// }
@@ -668,11 +683,11 @@ impl EditorElement {
// let end_y = start_y + line_height;
// let width = diff_style.removed_width_em * line_height;
- // let highlight_origin = bounds.origin() + vec2f(-width, start_y);
- // let highlight_size = vec2f(width * 2., end_y - start_y);
+ // let highlight_origin = bounds.origin() + point(-width, start_y);
+ // let highlight_size = point(width * 2., end_y - start_y);
// let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds: highlight_bounds,
// background: Some(diff_style.modified),
// border: Border::new(0., Color::transparent_black()).into(),
@@ -701,11 +716,11 @@ impl EditorElement {
// let end_y = start_y + line_height;
// let width = diff_style.removed_width_em * line_height;
- // let highlight_origin = bounds.origin() + vec2f(-width, start_y);
- // let highlight_size = vec2f(width * 2., end_y - start_y);
+ // let highlight_origin = bounds.origin() + point(-width, start_y);
+ // let highlight_size = point(width * 2., end_y - start_y);
// let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds: highlight_bounds,
// background: Some(diff_style.deleted),
// border: Border::new(0., Color::transparent_black()).into(),
@@ -723,11 +738,11 @@ impl EditorElement {
// let end_y = end_row as f32 * line_height - scroll_top;
// let width = diff_style.width_em * line_height;
- // let highlight_origin = bounds.origin() + vec2f(-width, start_y);
- // let highlight_size = vec2f(width * 2., end_y - start_y);
+ // let highlight_origin = bounds.origin() + point(-width, start_y);
+ // let highlight_size = point(width * 2., end_y - start_y);
// let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds: highlight_bounds,
// background: Some(color),
// border: Border::new(0., Color::transparent_black()).into(),
@@ -750,7 +765,7 @@ impl EditorElement {
// let scroll_top = scroll_position.y() * layout.position_map.line_height;
// let max_glyph_width = layout.position_map.em_width;
// let scroll_left = scroll_position.x() * max_glyph_width;
- // let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
+ // let content_origin = bounds.origin() + point(layout.gutter_margin, 0.);
// let line_end_overshoot = 0.15 * layout.position_map.line_height;
// let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
@@ -899,15 +914,15 @@ impl EditorElement {
// let y = cursor_position.row() as f32 * layout.position_map.line_height
// - scroll_top;
// if selection.is_newest {
- // editor.pixel_position_of_newest_cursor = Some(vec2f(
- // bounds.origin_x() + x + block_width / 2.,
- // bounds.origin_y() + y + layout.position_map.line_height / 2.,
+ // editor.pixel_position_of_newest_cursor = Some(point(
+ // bounds.origin.x + x + block_width / 2.,
+ // bounds.origin.y + y + layout.position_map.line_height / 2.,
// ));
// }
// cursors.push(Cursor {
// color: selection_style.cursor,
// block_width,
- // origin: vec2f(x, y),
+ // origin: point(x, y),
// line_height: layout.position_map.line_height,
// shape: selection.cursor_shape,
// block_text,
@@ -947,7 +962,7 @@ impl EditorElement {
// &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
// let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
// let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
- // let mut list_origin = content_origin + vec2f(x, y);
+ // let mut list_origin = content_origin + point(x, y);
// let list_width = context_menu.size().x();
// let list_height = context_menu.size().y();
@@ -963,7 +978,7 @@ impl EditorElement {
// context_menu.paint(
// list_origin,
- // Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+ // Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
// editor,
// cx,
// );
@@ -988,14 +1003,14 @@ impl EditorElement {
// // Compute Hovered Point
// let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
// let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
- // let hovered_point = content_origin + vec2f(x, y);
+ // let hovered_point = content_origin + point(x, y);
// if hovered_point.y() - height_to_reserve > 0.0 {
// // There is enough space above. Render popovers above the hovered point
// let mut current_y = hovered_point.y();
// for hover_popover in hover_popovers {
// let size = hover_popover.size();
- // let mut popover_origin = vec2f(hovered_point.x(), current_y - size.y());
+ // let mut popover_origin = point(hovered_point.x(), current_y - size.y());
// let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
// if x_out_of_bounds < 0.0 {
@@ -1004,7 +1019,7 @@ impl EditorElement {
// hover_popover.paint(
// popover_origin,
- // Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+ // Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
// editor,
// cx,
// );
@@ -1016,7 +1031,7 @@ impl EditorElement {
// let mut current_y = hovered_point.y() + layout.position_map.line_height;
// for hover_popover in hover_popovers {
// let size = hover_popover.size();
- // let mut popover_origin = vec2f(hovered_point.x(), current_y);
+ // let mut popover_origin = point(hovered_point.x(), current_y);
// let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
// if x_out_of_bounds < 0.0 {
@@ -1025,7 +1040,7 @@ impl EditorElement {
// hover_popover.paint(
// popover_origin,
- // Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+ // Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
// editor,
// cx,
// );
@@ -1040,9 +1055,9 @@ impl EditorElement {
// cx.scene().pop_layer();
// }
- // fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> f32 {
- // bounds.max_x() - self.style.theme.scrollbar.width
- // }
+ fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
+ bounds.upper_right().x - self.style.scrollbar_width
+ }
// fn paint_scrollbar(
// &mut self,
@@ -1082,11 +1097,11 @@ impl EditorElement {
// let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
// let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
- // let track_bounds = Bounds<Pixels>::from_points(vec2f(left, top), vec2f(right, bottom));
- // let thumb_bounds = Bounds<Pixels>::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
+ // let track_bounds = Bounds<Pixels>::from_points(point(left, top), point(right, bottom));
+ // let thumb_bounds = Bounds<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
// if layout.show_scrollbars {
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds: track_bounds,
// border: style.track.border.into(),
// background: style.track.background_color,
@@ -1114,9 +1129,9 @@ impl EditorElement {
// if end_y - start_y < 1. {
// end_y = start_y + 1.;
// }
- // let bounds = Bounds<Pixels>::from_points(vec2f(left, start_y), vec2f(right, end_y));
+ // let bounds = Bounds<Pixels>::from_points(point(left, start_y), point(right, end_y));
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds,
// background: Some(color),
// border: border.into(),
@@ -1158,7 +1173,7 @@ impl EditorElement {
// if end_y - start_y < 1. {
// end_y = start_y + 1.;
// }
- // let bounds = Bounds<Pixels>::from_points(vec2f(left, start_y), vec2f(right, end_y));
+ // let bounds = Bounds<Pixels>::from_points(point(left, start_y), point(right, end_y));
// let color = match hunk.status() {
// DiffHunkStatus::Added => diff_style.inserted,
@@ -1176,7 +1191,7 @@ impl EditorElement {
// left: true,
// };
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds,
// background: Some(color),
// border: border.into(),
@@ -1185,7 +1200,7 @@ impl EditorElement {
// }
// }
- // cx.scene().push_quad(Quad {
+ // cx.paint_quad(Quad {
// bounds: thumb_bounds,
// border: style.thumb.border.into(),
// background: style.thumb.background_color,
@@ -1316,12 +1331,12 @@ impl EditorElement {
// for block in &mut layout.blocks {
// let mut origin = bounds.origin()
- // + vec2f(
+ // + point(
// 0.,
// block.row as f32 * layout.position_map.line_height - scroll_top,
// );
// if !matches!(block.style, BlockStyle::Sticky) {
- // origin += vec2f(-scroll_left, 0.);
+ // origin += point(-scroll_left, 0.);
// }
// block.element.paint(origin, visible_bounds, editor, cx);
// }
@@ -1561,920 +1576,1005 @@ impl EditorElement {
}
}
- // #[allow(clippy::too_many_arguments)]
- // fn layout_blocks(
- // &mut self,
- // rows: Range<u32>,
- // snapshot: &EditorSnapshot,
- // editor_width: f32,
- // scroll_width: f32,
- // gutter_padding: f32,
- // gutter_width: f32,
- // em_width: f32,
- // text_x: f32,
- // line_height: f32,
- // style: &EditorStyle,
- // line_layouts: &[LineWithInvisibles],
- // editor: &mut Editor,
- // cx: &mut ViewContext<Editor>,
- // ) -> (f32, Vec<BlockLayout>) {
- // let mut block_id = 0;
- // let scroll_x = snapshot.scroll_anchor.offset.x();
- // let (fixed_blocks, non_fixed_blocks) = snapshot
- // .blocks_in_range(rows.clone())
- // .partition::<Vec<_>, _>(|(_, block)| match block {
- // TransformBlock::ExcerptHeader { .. } => false,
- // TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
- // });
- // let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
- // let mut element = match block {
- // TransformBlock::Custom(block) => {
- // let align_to = block
- // .position()
- // .to_point(&snapshot.buffer_snapshot)
- // .to_display_point(snapshot);
- // let anchor_x = text_x
- // + if rows.contains(&align_to.row()) {
- // line_layouts[(align_to.row() - rows.start) as usize]
- // .line
- // .x_for_index(align_to.column() as usize)
- // } else {
- // layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
- // .x_for_index(align_to.column() as usize)
- // };
+ fn compute_layout(
+ &mut self,
+ editor: &mut Editor,
+ cx: &mut ViewContext<'_, Editor>,
+ bounds: Bounds<Pixels>,
+ ) -> LayoutState {
+ // let mut size = constraint.max;
+ // if size.x().is_infinite() {
+ // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
+ // }
- // block.render(&mut BlockContext {
- // view_context: cx,
- // anchor_x,
- // gutter_padding,
- // line_height,
- // scroll_x,
- // gutter_width,
- // em_width,
- // block_id,
- // })
- // }
- // TransformBlock::ExcerptHeader {
- // id,
- // buffer,
- // range,
- // starts_new_buffer,
- // ..
- // } => {
- // let tooltip_style = theme::current(cx).tooltip.clone();
- // let include_root = editor
- // .project
- // .as_ref()
- // .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
- // .unwrap_or_default();
- // let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
- // let jump_path = ProjectPath {
- // worktree_id: file.worktree_id(cx),
- // path: file.path.clone(),
- // };
- // let jump_anchor = range
- // .primary
- // .as_ref()
- // .map_or(range.context.start, |primary| primary.start);
- // let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
+ let snapshot = editor.snapshot(cx);
+ let style = self.style.clone();
+ let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+ let font_size = style.text.font_size * cx.rem_size();
+ let line_height = (font_size * style.line_height_scalar).round();
+ let em_width = cx
+ .text_system()
+ .typographic_bounds(font_id, font_size, 'm')
+ .unwrap()
+ .size
+ .width;
+ let em_advance = cx
+ .text_system()
+ .advance(font_id, font_size, 'm')
+ .unwrap()
+ .width;
- // enum JumpIcon {}
- // MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
- // let style = style.jump_icon.style_for(state);
- // Svg::new("icons/arrow_up_right.svg")
- // .with_color(style.color)
- // .constrained()
- // .with_width(style.icon_width)
- // .aligned()
- // .contained()
- // .with_style(style.container)
- // .constrained()
- // .with_width(style.button_width)
- // .with_height(style.button_width)
- // })
- // .with_cursor_style(CursorStyle::PointingHand)
- // .on_click(MouseButton::Left, move |_, editor, cx| {
- // if let Some(workspace) = editor
- // .workspace
- // .as_ref()
- // .and_then(|(workspace, _)| workspace.upgrade(cx))
- // {
- // workspace.update(cx, |workspace, cx| {
- // Editor::jump(
- // workspace,
- // jump_path.clone(),
- // jump_position,
- // jump_anchor,
- // cx,
- // );
- // });
- // }
- // })
- // .with_tooltip::<JumpIcon>(
- // (*id).into(),
- // "Jump to Buffer".to_string(),
- // Some(Box::new(crate::OpenExcerpts)),
- // tooltip_style.clone(),
- // cx,
- // )
- // .aligned()
- // .flex_float()
- // });
+ let gutter_padding;
+ let gutter_width;
+ let gutter_margin;
+ if snapshot.show_gutter {
+ let descent = cx.text_system().descent(font_id, font_size).unwrap();
- // if *starts_new_buffer {
- // let editor_font_size = style.text.font_size;
- // let style = &style.diagnostic_path_header;
- // let font_size = (style.text_scale_factor * editor_font_size).round();
+ let gutter_padding_factor = 3.5;
+ gutter_padding = (em_width * gutter_padding_factor).round();
+ gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
+ gutter_margin = -descent;
+ } else {
+ gutter_padding = px(0.0);
+ gutter_width = px(0.0);
+ gutter_margin = px(0.0);
+ };
- // let path = buffer.resolve_file_path(cx, include_root);
- // let mut filename = None;
- // let mut parent_path = None;
- // // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
- // if let Some(path) = path {
- // filename = path.file_name().map(|f| f.to_string_lossy().to_string());
- // parent_path =
- // path.parent().map(|p| p.to_string_lossy().to_string() + "/");
- // }
+ let text_width = bounds.size.width - gutter_width;
+ let overscroll = size(em_width, px(0.));
+ let snapshot = {
+ editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
- // Flex::row()
- // .with_child(
- // Label::new(
- // filename.unwrap_or_else(|| "untitled".to_string()),
- // style.filename.text.clone().with_font_size(font_size),
- // )
- // .contained()
- // .with_style(style.filename.container)
- // .aligned(),
- // )
- // .with_children(parent_path.map(|path| {
- // Label::new(path, style.path.text.clone().with_font_size(font_size))
- // .contained()
- // .with_style(style.path.container)
- // .aligned()
- // }))
- // .with_children(jump_icon)
- // .contained()
- // .with_style(style.container)
- // .with_padding_left(gutter_padding)
- // .with_padding_right(gutter_padding)
- // .expanded()
- // .into_any_named("path header block")
- // } else {
- // let text_style = style.text.clone();
- // Flex::row()
- // .with_child(Label::new("⋯", text_style))
- // .with_children(jump_icon)
- // .contained()
- // .with_padding_left(gutter_padding)
- // .with_padding_right(gutter_padding)
- // .expanded()
- // .into_any_named("collapsed context")
- // }
- // }
- // };
+ let editor_width = text_width - gutter_margin - overscroll.width - 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),
+ };
- // element.layout(
- // SizeConstraint {
- // min: gpui::Point<Pixels>::zero(),
- // max: vec2f(width, block.height() as f32 * line_height),
- // },
- // editor,
- // cx,
- // );
- // element
- // };
+ if editor.set_wrap_width(Some(wrap_width), cx) {
+ editor.snapshot(cx)
+ } else {
+ snapshot
+ }
+ };
- // let mut fixed_block_max_width = 0f32;
- // let mut blocks = Vec::new();
- // for (row, block) in fixed_blocks {
- // let element = render_block(block, f32::INFINITY, block_id);
- // block_id += 1;
- // fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
- // blocks.push(BlockLayout {
- // row,
- // element,
- // style: BlockStyle::Fixed,
- // });
- // }
- // for (row, block) in non_fixed_blocks {
- // let style = match block {
- // TransformBlock::Custom(block) => block.style(),
- // TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
- // };
- // let width = match style {
- // BlockStyle::Sticky => editor_width,
- // BlockStyle::Flex => editor_width
- // .max(fixed_block_max_width)
- // .max(gutter_width + scroll_width),
- // BlockStyle::Fixed => unreachable!(),
- // };
- // let element = render_block(block, width, block_id);
- // block_id += 1;
- // blocks.push(BlockLayout {
- // row,
- // element,
- // style,
- // });
- // }
- // (
- // scroll_width.max(fixed_block_max_width - gutter_width),
- // blocks,
- // )
- // }
-}
+ let wrap_guides = editor
+ .wrap_guides(cx)
+ .iter()
+ .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
+ .collect::<SmallVec<[_; 2]>>();
-#[derive(Debug)]
-pub struct LineWithInvisibles {
- pub line: Line,
- invisibles: Vec<Invisible>,
-}
+ 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 {
+ todo!()
+ // size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
+ }
+ // 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);
-impl LineWithInvisibles {
- fn from_chunks<'a>(
- chunks: impl Iterator<Item = HighlightedChunk<'a>>,
- text_style: &TextStyle,
- max_line_len: usize,
- max_line_count: usize,
- line_number_layouts: &[Option<Line>],
- editor_mode: EditorMode,
- cx: &WindowContext,
- ) -> Vec<Self> {
- let mut layouts = Vec::with_capacity(max_line_count);
- let mut line = String::new();
- let mut invisibles = Vec::new();
- let mut styles = Vec::new();
- let mut non_whitespace_added = false;
- let mut row = 0;
- let mut line_exceeded_max_len = false;
- let font_size = text_style.font_size * cx.rem_size();
+ let autoscroll_horizontally =
+ editor.autoscroll_vertically(bounds.size.height, line_height, cx);
+ let mut snapshot = editor.snapshot(cx);
- for highlighted_chunk in chunks.chain([HighlightedChunk {
- chunk: "\n",
- style: None,
- is_tab: false,
- }]) {
- for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
- if ix > 0 {
- let layout = cx
- .text_system()
- .layout_text(&line, font_size, &styles, None);
- layouts.push(Self {
- line: layout.unwrap().pop().unwrap(),
- invisibles: invisibles.drain(..).collect(),
- });
+ 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 = f32::from(bounds.size.height / line_height);
+ let max_row = snapshot.max_point().row();
- line.clear();
- styles.clear();
- row += 1;
- line_exceeded_max_len = false;
- non_whitespace_added = false;
- if row == max_line_count {
- return layouts;
- }
- }
+ // 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);
- if !line_chunk.is_empty() && !line_exceeded_max_len {
- let text_style = if let Some(style) = highlighted_chunk.style {
- text_style
- .clone()
- .highlight(style)
- .map(Cow::Owned)
- .unwrap_or_else(|_| Cow::Borrowed(text_style))
- } else {
- Cow::Borrowed(text_style)
- };
+ 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))
+ };
- if line.len() + line_chunk.len() > max_line_len {
- let mut chunk_len = max_line_len - line.len();
- while !line_chunk.is_char_boundary(chunk_len) {
- chunk_len -= 1;
- }
- line_chunk = &line_chunk[..chunk_len];
- line_exceeded_max_len = true;
- }
+ let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
+ let mut active_rows = BTreeMap::new();
+ let mut fold_ranges = Vec::new();
+ let is_singleton = editor.is_singleton(cx);
- styles.push(TextRun {
- len: line_chunk.len(),
- font: text_style.font(),
- color: text_style.color,
- underline: text_style.underline,
- });
+ let highlighted_rows = editor.highlighted_rows();
+ let highlighted_ranges = editor.background_highlights_in_range(
+ start_anchor..end_anchor,
+ &snapshot.display_snapshot,
+ cx.theme().colors(),
+ );
- if editor_mode == EditorMode::Full {
- // Line wrap pads its contents with fake whitespaces,
- // avoid printing them
- let inside_wrapped_string = line_number_layouts
- .get(row)
- .and_then(|layout| layout.as_ref())
- .is_none();
- if highlighted_chunk.is_tab {
- if non_whitespace_added || !inside_wrapped_string {
- invisibles.push(Invisible::Tab {
- line_start_offset: line.len(),
- });
- }
- } else {
- invisibles.extend(
- line_chunk
- .chars()
- .enumerate()
- .filter(|(_, line_char)| {
- let is_whitespace = line_char.is_whitespace();
- non_whitespace_added |= !is_whitespace;
- is_whitespace
- && (non_whitespace_added || !inside_wrapped_string)
- })
- .map(|(whitespace_index, _)| Invisible::Whitespace {
- line_offset: line.len() + whitespace_index,
- }),
- )
- }
- }
+ 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),
+ )
+ }),
+ );
- line.push_str(line_chunk);
- }
- }
- }
+ let mut newest_selection_head = None;
- layouts
- }
+ if editor.show_local_selections {
+ let mut local_selections: Vec<Selection<Point>> = 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;
- fn draw(
- &self,
- layout: &LayoutState,
- row: u32,
- scroll_top: Pixels,
- content_origin: gpui::Point<Pixels>,
- scroll_left: Pixels,
- visible_text_bounds: Bounds<Pixels>,
- whitespace_setting: ShowWhitespaceSetting,
- selection_ranges: &[Range<DisplayPoint>],
- cx: &mut ViewContext<Editor>,
- ) {
- let line_height = layout.position_map.line_height;
- let line_y = line_height * row as f32 - scroll_top;
-
- self.line.paint(
- content_origin + gpui::point(-scroll_left, line_y),
- line_height,
- cx,
- );
+ 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);
+ }
- self.draw_invisibles(
- &selection_ranges,
- layout,
- content_origin,
- scroll_left,
- line_y,
- row,
- line_height,
- whitespace_setting,
- cx,
- );
- }
+ 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);
+ }
- fn draw_invisibles(
- &self,
- selection_ranges: &[Range<DisplayPoint>],
- layout: &LayoutState,
- content_origin: gpui::Point<Pixels>,
- scroll_left: Pixels,
- line_y: Pixels,
- row: u32,
- line_height: Pixels,
- whitespace_setting: ShowWhitespaceSetting,
- cx: &mut ViewContext<Editor>,
- ) {
- let allowed_invisibles_regions = match whitespace_setting {
- ShowWhitespaceSetting::None => return,
- ShowWhitespaceSetting::Selection => Some(selection_ranges),
- ShowWhitespaceSetting::All => None,
- };
+ selections.push((style.local_player, layouts));
+ }
- for invisible in &self.invisibles {
- let (&token_offset, invisible_symbol) = match invisible {
- Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
- Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
- };
+ 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 = cx
+ .theme()
+ .players()
+ .color_for_participant(participant_index.0);
+ }
+ }
+ }
+ }
- let x_offset = self.line.x_for_index(token_offset);
- let invisible_offset = (layout.position_map.em_width - invisible_symbol.width())
- .max(Pixels::from(0.0))
- / 2.0;
- let origin =
- content_origin + gpui::point(-scroll_left + x_offset + invisible_offset, line_y);
+ 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 {
+ cx.theme()
+ .players()
+ .color_for_participant(participant_index.0)
+ } else {
+ cx.theme().players().absent()
+ };
- if let Some(allowed_regions) = allowed_invisibles_regions {
- let invisible_point = DisplayPoint::new(row, token_offset as u32);
- if !allowed_regions
- .iter()
- .any(|region| region.start <= invisible_point && invisible_point < region.end)
- {
+ // 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,
+ ));
}
- invisible_symbol.paint(origin, line_height, cx);
+
+ selections.extend(remote_selections.into_values());
}
- }
-}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum Invisible {
- Tab { line_start_offset: usize },
- Whitespace { line_offset: usize },
-}
+ let scrollbar_settings = EditorSettings::get_global(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,
+ };
-impl Element<Editor> for EditorElement {
- type ElementState = ();
+ let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
+ .into_iter()
+ .map(|(id, fold)| {
+ todo!("folds!")
+ // let color = self
+ // .style
+ // .folds
+ // .ellipses
+ // .background
+ // .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
+ // .color;
- fn id(&self) -> Option<gpui::ElementId> {
- None
- }
+ // (id, fold, color)
+ })
+ .collect();
- fn initialize(
- &mut self,
- view_state: &mut Editor,
- element_state: Option<Self::ElementState>,
- cx: &mut gpui::ViewContext<Editor>,
- ) -> Self::ElementState {
- ()
- }
+ let head_for_relative = newest_selection_head.unwrap_or_else(|| {
+ let newest = editor.selections.newest::<Point>(cx);
+ SelectionLayout::new(
+ newest,
+ editor.selections.line_mode,
+ editor.cursor_shape,
+ &snapshot.display_snapshot,
+ true,
+ true,
+ )
+ .head
+ });
- fn layout(
- &mut self,
- view_state: &mut Editor,
- element_state: &mut Self::ElementState,
- cx: &mut gpui::ViewContext<Editor>,
- ) -> gpui::LayoutId {
- let rem_size = cx.rem_size();
- let mut style = Style::default();
- style.size.width = relative(1.).into();
- style.size.height = relative(1.).into();
- cx.request_layout(&style, None)
- }
+ let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
+ start_row..end_row,
+ &active_rows,
+ head_for_relative,
+ is_singleton,
+ &snapshot,
+ cx,
+ );
- fn paint(
- &mut self,
- bounds: Bounds<gpui::Pixels>,
- editor: &mut Editor,
- element_state: &mut Self::ElementState,
- cx: &mut gpui::ViewContext<Editor>,
- ) {
- // let mut size = constraint.max;
- // if size.x().is_infinite() {
- // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
- // }
+ let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
- let snapshot = editor.snapshot(cx);
- let style = self.style.clone();
- let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
- let font_size = style.text.font_size * cx.rem_size();
- let line_height = (font_size * style.line_height_scalar).round();
- let em_width = cx
- .text_system()
- .typographic_bounds(font_id, font_size, 'm')
- .unwrap()
- .size
- .width;
- let em_advance = cx
- .text_system()
- .advance(font_id, font_size, 'm')
- .unwrap()
- .width;
+ let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
- 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 mut max_visible_line_width = Pixels::ZERO;
+ 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 gutter_padding_factor = 3.5;
- gutter_padding = (em_width * gutter_padding_factor).round();
- gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
- gutter_margin = -descent;
- } else {
- gutter_padding = px(0.0);
- gutter_width = px(0.0);
- gutter_margin = px(0.0);
- };
+ let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
+ .unwrap()
+ .width();
+ let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
+ // todo!("blocks")
+ // 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 text_width = bounds.size.width - gutter_width;
- let overscroll = size(em_width, px(0.));
- let snapshot = {
- editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
+ let scroll_max = point(
+ f32::from((scroll_width - text_size.width) / em_width).max(0.0),
+ max_row as f32,
+ );
- let editor_width = text_width - gutter_margin - overscroll.width - 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),
- };
+ let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
- if editor.set_wrap_width(Some(wrap_width), cx) {
- editor.snapshot(cx)
- } else {
- snapshot
- }
+ let autoscrolled = if autoscroll_horizontally {
+ editor.autoscroll_horizontally(
+ start_row,
+ text_size.width,
+ scroll_width,
+ em_width,
+ &line_layouts,
+ cx,
+ )
+ } else {
+ false
};
- let wrap_guides = editor
- .wrap_guides(cx)
- .iter()
- .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
- .collect::<SmallVec<[_; 2]>>();
-
- let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
- // todo!("this should happen during layout")
- if let EditorMode::AutoHeight { max_lines } = snapshot.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 = snapshot.mode {
- todo!()
- // size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
+ if clamped || autoscrolled {
+ snapshot = editor.snapshot(cx);
}
- // 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);
-
- let autoscroll_horizontally =
- editor.autoscroll_vertically(bounds.size.height, 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 = f32::from(bounds.size.height / line_height);
- let max_row = snapshot.max_point().row();
+ // todo!("context menu")
+ // 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);
+ // }
- // 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 active = matches!(
+ // editor.context_menu.read().as_ref(),
+ // Some(crate::ContextMenu::CodeActions(_))
+ // );
- 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))
- };
+ // code_actions_indicator = editor
+ // .render_code_actions_indicator(&style, active, cx)
+ // .map(|indicator| (newest_selection_head.row(), indicator));
+ // }
+ // }
- let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
- let mut active_rows = BTreeMap::new();
- let mut fold_ranges = Vec::new();
- let is_singleton = editor.is_singleton(cx);
+ let visible_rows = start_row..start_row + line_layouts.len() as u32;
+ // todo!("hover")
+ // 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 highlighted_rows = editor.highlighted_rows();
- let highlighted_ranges = editor.background_highlights_in_range(
- start_anchor..end_anchor,
- &snapshot.display_snapshot,
- cx.theme().colors(),
- );
+ // todo!("fold_indicators")
+ // let mut fold_indicators = editor.render_fold_indicators(
+ // fold_statuses,
+ // &style,
+ // editor.gutter_hovered,
+ // line_height,
+ // gutter_margin,
+ // cx,
+ // );
- 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),
- )
- }),
- );
+ // todo!("context_menu")
+ // if let Some((_, context_menu)) = context_menu.as_mut() {
+ // context_menu.layout(
+ // SizeConstraint {
+ // min: gpui::Point<Pixels>::zero(),
+ // max: point(
+ // cx.window_size().x() * 0.7,
+ // (12. * line_height).min((size.y() - line_height) / 2.),
+ // ),
+ // },
+ // editor,
+ // cx,
+ // );
+ // }
- let mut newest_selection_head = None;
+ // todo!("code actions")
+ // 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,
+ // );
+ // }
- if editor.show_local_selections {
- let mut local_selections: Vec<Selection<Point>> = 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;
+ // todo!("fold indicators")
+ // 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,
+ // );
+ // }
+ // }
- 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);
- }
+ // todo!("hover popovers")
+ // if let Some((_, hover_popovers)) = hover.as_mut() {
+ // for hover_popover in hover_popovers.iter_mut() {
+ // hover_popover.layout(
+ // SizeConstraint {
+ // min: gpui::Point<Pixels>::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,
+ // );
+ // }
+ // }
- 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;
+ let invisible_symbol_font_size = font_size / 2.;
+ let tab_invisible = cx
+ .text_system()
+ .layout_text(
+ "→",
+ invisible_symbol_font_size,
+ &[TextRun {
+ len: "→".len(),
+ font: self.style.text.font(),
+ color: cx.theme().colors().editor_invisible,
+ underline: None,
+ }],
+ None,
+ )
+ .unwrap()
+ .pop()
+ .unwrap();
+ let space_invisible = cx
+ .text_system()
+ .layout_text(
+ "•",
+ invisible_symbol_font_size,
+ &[TextRun {
+ len: "•".len(),
+ font: self.style.text.font(),
+ color: cx.theme().colors().editor_invisible,
+ underline: None,
+ }],
+ None,
+ )
+ .unwrap()
+ .pop()
+ .unwrap();
+
+ LayoutState {
+ mode: editor_mode,
+ position_map: Arc::new(PositionMap {
+ size: bounds.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,
+ space_invisible,
+ // hover_popovers: hover,
+ }
+ }
+
+ // #[allow(clippy::too_many_arguments)]
+ // fn layout_blocks(
+ // &mut self,
+ // rows: Range<u32>,
+ // snapshot: &EditorSnapshot,
+ // editor_width: f32,
+ // scroll_width: f32,
+ // gutter_padding: f32,
+ // gutter_width: f32,
+ // em_width: f32,
+ // text_x: f32,
+ // line_height: f32,
+ // style: &EditorStyle,
+ // line_layouts: &[LineWithInvisibles],
+ // editor: &mut Editor,
+ // cx: &mut ViewContext<Editor>,
+ // ) -> (f32, Vec<BlockLayout>) {
+ // let mut block_id = 0;
+ // let scroll_x = snapshot.scroll_anchor.offset.x();
+ // let (fixed_blocks, non_fixed_blocks) = snapshot
+ // .blocks_in_range(rows.clone())
+ // .partition::<Vec<_>, _>(|(_, block)| match block {
+ // TransformBlock::ExcerptHeader { .. } => false,
+ // TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
+ // });
+ // let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
+ // let mut element = match block {
+ // TransformBlock::Custom(block) => {
+ // let align_to = block
+ // .position()
+ // .to_point(&snapshot.buffer_snapshot)
+ // .to_display_point(snapshot);
+ // let anchor_x = text_x
+ // + if rows.contains(&align_to.row()) {
+ // line_layouts[(align_to.row() - rows.start) as usize]
+ // .line
+ // .x_for_index(align_to.column() as usize)
+ // } else {
+ // layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
+ // .x_for_index(align_to.column() as usize)
+ // };
+
+ // block.render(&mut BlockContext {
+ // view_context: cx,
+ // anchor_x,
+ // gutter_padding,
+ // line_height,
+ // scroll_x,
+ // gutter_width,
+ // em_width,
+ // block_id,
+ // })
+ // }
+ // TransformBlock::ExcerptHeader {
+ // id,
+ // buffer,
+ // range,
+ // starts_new_buffer,
+ // ..
+ // } => {
+ // let tooltip_style = theme::current(cx).tooltip.clone();
+ // let include_root = editor
+ // .project
+ // .as_ref()
+ // .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
+ // .unwrap_or_default();
+ // let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
+ // let jump_path = ProjectPath {
+ // worktree_id: file.worktree_id(cx),
+ // path: file.path.clone(),
+ // };
+ // let jump_anchor = range
+ // .primary
+ // .as_ref()
+ // .map_or(range.context.start, |primary| primary.start);
+ // let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
+
+ // enum JumpIcon {}
+ // MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
+ // let style = style.jump_icon.style_for(state);
+ // Svg::new("icons/arrow_up_right.svg")
+ // .with_color(style.color)
+ // .constrained()
+ // .with_width(style.icon_width)
+ // .aligned()
+ // .contained()
+ // .with_style(style.container)
+ // .constrained()
+ // .with_width(style.button_width)
+ // .with_height(style.button_width)
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .on_click(MouseButton::Left, move |_, editor, cx| {
+ // if let Some(workspace) = editor
+ // .workspace
+ // .as_ref()
+ // .and_then(|(workspace, _)| workspace.upgrade(cx))
+ // {
+ // workspace.update(cx, |workspace, cx| {
+ // Editor::jump(
+ // workspace,
+ // jump_path.clone(),
+ // jump_position,
+ // jump_anchor,
+ // cx,
+ // );
+ // });
+ // }
+ // })
+ // .with_tooltip::<JumpIcon>(
+ // (*id).into(),
+ // "Jump to Buffer".to_string(),
+ // Some(Box::new(crate::OpenExcerpts)),
+ // tooltip_style.clone(),
+ // cx,
+ // )
+ // .aligned()
+ // .flex_float()
+ // });
+
+ // if *starts_new_buffer {
+ // let editor_font_size = style.text.font_size;
+ // let style = &style.diagnostic_path_header;
+ // let font_size = (style.text_scale_factor * editor_font_size).round();
+
+ // let path = buffer.resolve_file_path(cx, include_root);
+ // let mut filename = None;
+ // let mut parent_path = None;
+ // // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
+ // if let Some(path) = path {
+ // filename = path.file_name().map(|f| f.to_string_lossy().to_string());
+ // parent_path =
+ // path.parent().map(|p| p.to_string_lossy().to_string() + "/");
+ // }
+
+ // Flex::row()
+ // .with_child(
+ // Label::new(
+ // filename.unwrap_or_else(|| "untitled".to_string()),
+ // style.filename.text.clone().with_font_size(font_size),
+ // )
+ // .contained()
+ // .with_style(style.filename.container)
+ // .aligned(),
+ // )
+ // .with_children(parent_path.map(|path| {
+ // Label::new(path, style.path.text.clone().with_font_size(font_size))
+ // .contained()
+ // .with_style(style.path.container)
+ // .aligned()
+ // }))
+ // .with_children(jump_icon)
+ // .contained()
+ // .with_style(style.container)
+ // .with_padding_left(gutter_padding)
+ // .with_padding_right(gutter_padding)
+ // .expanded()
+ // .into_any_named("path header block")
+ // } else {
+ // let text_style = style.text.clone();
+ // Flex::row()
+ // .with_child(Label::new("⋯", text_style))
+ // .with_children(jump_icon)
+ // .contained()
+ // .with_padding_left(gutter_padding)
+ // .with_padding_right(gutter_padding)
+ // .expanded()
+ // .into_any_named("collapsed context")
+ // }
+ // }
+ // };
+
+ // element.layout(
+ // SizeConstraint {
+ // min: gpui::Point<Pixels>::zero(),
+ // max: point(width, block.height() as f32 * line_height),
+ // },
+ // editor,
+ // cx,
+ // );
+ // element
+ // };
+
+ // let mut fixed_block_max_width = 0f32;
+ // let mut blocks = Vec::new();
+ // for (row, block) in fixed_blocks {
+ // let element = render_block(block, f32::INFINITY, block_id);
+ // block_id += 1;
+ // fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
+ // blocks.push(BlockLayout {
+ // row,
+ // element,
+ // style: BlockStyle::Fixed,
+ // });
+ // }
+ // for (row, block) in non_fixed_blocks {
+ // let style = match block {
+ // TransformBlock::Custom(block) => block.style(),
+ // TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
+ // };
+ // let width = match style {
+ // BlockStyle::Sticky => editor_width,
+ // BlockStyle::Flex => editor_width
+ // .max(fixed_block_max_width)
+ // .max(gutter_width + scroll_width),
+ // BlockStyle::Fixed => unreachable!(),
+ // };
+ // let element = render_block(block, width, block_id);
+ // block_id += 1;
+ // blocks.push(BlockLayout {
+ // row,
+ // element,
+ // style,
+ // });
+ // }
+ // (
+ // scroll_width.max(fixed_block_max_width - gutter_width),
+ // blocks,
+ // )
+ // }
+}
+
+#[derive(Debug)]
+pub struct LineWithInvisibles {
+ pub line: Line,
+ invisibles: Vec<Invisible>,
+}
+
+impl LineWithInvisibles {
+ fn from_chunks<'a>(
+ chunks: impl Iterator<Item = HighlightedChunk<'a>>,
+ text_style: &TextStyle,
+ max_line_len: usize,
+ max_line_count: usize,
+ line_number_layouts: &[Option<Line>],
+ editor_mode: EditorMode,
+ cx: &WindowContext,
+ ) -> Vec<Self> {
+ let mut layouts = Vec::with_capacity(max_line_count);
+ let mut line = String::new();
+ let mut invisibles = Vec::new();
+ let mut styles = Vec::new();
+ let mut non_whitespace_added = false;
+ let mut row = 0;
+ let mut line_exceeded_max_len = false;
+ let font_size = text_style.font_size * cx.rem_size();
+
+ for highlighted_chunk in chunks.chain([HighlightedChunk {
+ chunk: "\n",
+ style: None,
+ is_tab: false,
+ }]) {
+ for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
+ if ix > 0 {
+ let layout = cx
+ .text_system()
+ .layout_text(&line, font_size, &styles, None);
+ layouts.push(Self {
+ line: layout.unwrap().pop().unwrap(),
+ invisibles: invisibles.drain(..).collect(),
+ });
+
+ line.clear();
+ styles.clear();
+ row += 1;
+ line_exceeded_max_len = false;
+ non_whitespace_added = false;
+ if row == max_line_count {
+ return layouts;
+ }
}
- layouts.push(layout);
- }
- selections.push((style.local_player, layouts));
- }
+ if !line_chunk.is_empty() && !line_exceeded_max_len {
+ let text_style = if let Some(style) = highlighted_chunk.style {
+ text_style
+ .clone()
+ .highlight(style)
+ .map(Cow::Owned)
+ .unwrap_or_else(|_| Cow::Borrowed(text_style))
+ } else {
+ Cow::Borrowed(text_style)
+ };
- 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 = cx
- .theme()
- .players()
- .color_for_participant(participant_index.0);
+ if line.len() + line_chunk.len() > max_line_len {
+ let mut chunk_len = max_line_len - line.len();
+ while !line_chunk.is_char_boundary(chunk_len) {
+ chunk_len -= 1;
}
+ line_chunk = &line_chunk[..chunk_len];
+ line_exceeded_max_len = true;
}
- }
- }
- 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 {
- cx.theme()
- .players()
- .color_for_participant(participant_index.0)
- } else {
- cx.theme().players().absent()
- };
+ styles.push(TextRun {
+ len: line_chunk.len(),
+ font: text_style.font(),
+ color: text_style.color,
+ underline: text_style.underline,
+ });
- // Don't re-render the leader's selections, since the local selections
- // match theirs.
- if Some(selection.peer_id) == editor.leader_peer_id {
- continue;
- }
+ if editor_mode == EditorMode::Full {
+ // Line wrap pads its contents with fake whitespaces,
+ // avoid printing them
+ let inside_wrapped_string = line_number_layouts
+ .get(row)
+ .and_then(|layout| layout.as_ref())
+ .is_none();
+ if highlighted_chunk.is_tab {
+ if non_whitespace_added || !inside_wrapped_string {
+ invisibles.push(Invisible::Tab {
+ line_start_offset: line.len(),
+ });
+ }
+ } else {
+ invisibles.extend(
+ line_chunk
+ .chars()
+ .enumerate()
+ .filter(|(_, line_char)| {
+ let is_whitespace = line_char.is_whitespace();
+ non_whitespace_added |= !is_whitespace;
+ is_whitespace
+ && (non_whitespace_added || !inside_wrapped_string)
+ })
+ .map(|(whitespace_index, _)| Invisible::Whitespace {
+ line_offset: line.len() + whitespace_index,
+ }),
+ )
+ }
+ }
- 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,
- ));
+ line.push_str(line_chunk);
+ }
}
-
- selections.extend(remote_selections.into_values());
}
- let scrollbar_settings = EditorSettings::get_global(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<DisplayPoint>, Hsla)> = fold_ranges
- .into_iter()
- .map(|(id, fold)| {
- todo!("folds!")
- // let color = self
- // .style
- // .folds
- // .ellipses
- // .background
- // .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
- // .color;
-
- // (id, fold, color)
- })
- .collect();
+ layouts
+ }
- let head_for_relative = newest_selection_head.unwrap_or_else(|| {
- let newest = editor.selections.newest::<Point>(cx);
- SelectionLayout::new(
- newest,
- editor.selections.line_mode,
- editor.cursor_shape,
- &snapshot.display_snapshot,
- true,
- true,
- )
- .head
- });
+ fn draw(
+ &self,
+ layout: &LayoutState,
+ row: u32,
+ scroll_top: Pixels,
+ content_origin: gpui::Point<Pixels>,
+ scroll_left: Pixels,
+ visible_text_bounds: Bounds<Pixels>,
+ whitespace_setting: ShowWhitespaceSetting,
+ selection_ranges: &[Range<DisplayPoint>],
+ cx: &mut ViewContext<Editor>,
+ ) {
+ let line_height = layout.position_map.line_height;
+ let line_y = line_height * row as f32 - scroll_top;
- let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
- start_row..end_row,
- &active_rows,
- head_for_relative,
- is_singleton,
- &snapshot,
+ self.line.paint(
+ content_origin + gpui::point(-scroll_left, line_y),
+ line_height,
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 = Pixels::ZERO;
- 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 longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
- .unwrap()
- .width();
- let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
- // todo!("blocks")
- // 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(
- f32::from((scroll_width - text_size.width) / em_width).max(0.0),
- max_row as f32,
+ self.draw_invisibles(
+ &selection_ranges,
+ layout,
+ content_origin,
+ scroll_left,
+ line_y,
+ row,
+ line_height,
+ whitespace_setting,
+ cx,
);
+ }
- let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
- let autoscrolled = if autoscroll_horizontally {
- editor.autoscroll_horizontally(
- start_row,
- text_size.width,
- scroll_width,
- em_width,
- &line_layouts,
- cx,
- )
- } else {
- false
+ fn draw_invisibles(
+ &self,
+ selection_ranges: &[Range<DisplayPoint>],
+ layout: &LayoutState,
+ content_origin: gpui::Point<Pixels>,
+ scroll_left: Pixels,
+ line_y: Pixels,
+ row: u32,
+ line_height: Pixels,
+ whitespace_setting: ShowWhitespaceSetting,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ let allowed_invisibles_regions = match whitespace_setting {
+ ShowWhitespaceSetting::None => return,
+ ShowWhitespaceSetting::Selection => Some(selection_ranges),
+ ShowWhitespaceSetting::All => None,
};
- if clamped || autoscrolled {
- snapshot = editor.snapshot(cx);
- }
-
- // todo!("context menu")
- // 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);
- // }
+ for invisible in &self.invisibles {
+ let (&token_offset, invisible_symbol) = match invisible {
+ Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
+ Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
+ };
- // let active = matches!(
- // editor.context_menu.read().as_ref(),
- // Some(crate::ContextMenu::CodeActions(_))
- // );
+ let x_offset = self.line.x_for_index(token_offset);
+ let invisible_offset = (layout.position_map.em_width - invisible_symbol.width())
+ .max(Pixels::from(0.0))
+ / 2.0;
+ let origin =
+ content_origin + gpui::point(-scroll_left + x_offset + invisible_offset, line_y);
- // code_actions_indicator = editor
- // .render_code_actions_indicator(&style, active, cx)
- // .map(|indicator| (newest_selection_head.row(), indicator));
- // }
- // }
+ if let Some(allowed_regions) = allowed_invisibles_regions {
+ let invisible_point = DisplayPoint::new(row, token_offset as u32);
+ if !allowed_regions
+ .iter()
+ .any(|region| region.start <= invisible_point && invisible_point < region.end)
+ {
+ continue;
+ }
+ }
+ invisible_symbol.paint(origin, line_height, cx);
+ }
+ }
+}
- let visible_rows = start_row..start_row + line_layouts.len() as u32;
- // todo!("hover")
- // let mut hover = editor.hover_state.render(
- // &snapshot,
- // &style,
- // visible_rows,
- // editor.workspace.as_ref().map(|(w, _)| w.clone()),
- // cx,
- // );
- // let mode = editor.mode;
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum Invisible {
+ Tab { line_start_offset: usize },
+ Whitespace { line_offset: usize },
+}
- // todo!("fold_indicators")
- // let mut fold_indicators = editor.render_fold_indicators(
- // fold_statuses,
- // &style,
- // editor.gutter_hovered,
- // line_height,
- // gutter_margin,
- // cx,
- // );
+impl Element<Editor> for EditorElement {
+ type ElementState = ();
- // todo!("context_menu")
- // if let Some((_, context_menu)) = context_menu.as_mut() {
- // context_menu.layout(
- // SizeConstraint {
- // min: gpui::Point<Pixels>::zero(),
- // max: vec2f(
- // cx.window_size().x() * 0.7,
- // (12. * line_height).min((size.y() - line_height) / 2.),
- // ),
- // },
- // editor,
- // cx,
- // );
- // }
+ fn id(&self) -> Option<gpui::ElementId> {
+ None
+ }
- // todo!("code actions")
- // 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,
- // );
- // }
+ fn initialize(
+ &mut self,
+ view_state: &mut Editor,
+ element_state: Option<Self::ElementState>,
+ cx: &mut gpui::ViewContext<Editor>,
+ ) -> Self::ElementState {
+ ()
+ }
- // todo!("fold indicators")
- // 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,
- // );
- // }
- // }
+ fn layout(
+ &mut self,
+ view_state: &mut Editor,
+ element_state: &mut Self::ElementState,
+ cx: &mut gpui::ViewContext<Editor>,
+ ) -> gpui::LayoutId {
+ let rem_size = cx.rem_size();
+ let mut style = Style::default();
+ style.size.width = relative(1.).into();
+ style.size.height = relative(1.).into();
+ cx.request_layout(&style, None)
+ }
- // todo!("hover popovers")
- // if let Some((_, hover_popovers)) = hover.as_mut() {
- // for hover_popover in hover_popovers.iter_mut() {
- // hover_popover.layout(
- // SizeConstraint {
- // min: gpui::Point<Pixels>::zero(),
- // max: vec2f(
- // (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,
- // );
- // }
- // }
+ fn paint(
+ &mut self,
+ bounds: Bounds<gpui::Pixels>,
+ editor: &mut Editor,
+ element_state: &mut Self::ElementState,
+ cx: &mut gpui::ViewContext<Editor>,
+ ) {
+ let layout = self.compute_layout(editor, cx, bounds);
+ cx.with_content_mask(ContentMask { bounds }, |cx| {
+ let gutter_bounds = Bounds {
+ origin: bounds.origin,
+ size: layout.gutter_size,
+ };
+ let text_bounds = Bounds {
+ origin: gutter_bounds.upper_right(),
+ size: layout.text_size,
+ };
- // 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(),
- // };
- //
+ self.paint_background(gutter_bounds, text_bounds, &layout, cx);
+ });
}
}