use std::rc::Rc;
use crate::{AnyElement, App, Entity, Pixels, Point, SharedString, Window, point, px, size};
use super::super::layout::LastLayout;
use super::state::EditorState;
pub(crate) const GUTTER_WIDTH: Pixels = crate::px(20.);
pub type GutterMarkerBuilder = Rc<dyn Fn(usize, &mut Window, &mut App) -> Option<AnyElement>>;
#[derive(Clone)]
pub(crate) struct GutterMarkerEntry {
pub(super) id: SharedString,
pub(super) enabled: bool,
pub(super) build: GutterMarkerBuilder,
}
#[derive(Default)]
pub(crate) struct GutterMarkersLayout {
pub(crate) width: Pixels,
items: Vec<AnyElement>,
}
impl EditorState {
pub fn add_gutter_provider(
&self,
id: impl Into<SharedString>,
builder: impl Fn(usize, &mut Window, &mut App) -> Option<AnyElement> + 'static,
cx: &mut App,
) {
let entry = GutterMarkerEntry {
id: id.into(),
enabled: true,
build: Rc::new(builder),
};
let _ = self.input.update(cx, |state, cx| {
if let Some(slot) = state.gutter_markers.iter_mut().find(|e| e.id == entry.id) {
*slot = entry;
} else {
state.gutter_markers.push(entry);
}
cx.notify();
});
}
pub fn remove_gutter_provider(&self, id: &str, cx: &mut App) {
let _ = self.input.update(cx, |state, cx| {
let before = state.gutter_markers.len();
state.gutter_markers.retain(|e| e.id.as_ref() != id);
if state.gutter_markers.len() != before {
cx.notify();
}
});
}
pub fn set_gutter_provider_enabled(&self, id: &str, enabled: bool, cx: &mut App) {
let _ = self.input.update(cx, |state, cx| {
if let Some(slot) = state
.gutter_markers
.iter_mut()
.find(|e| e.id.as_ref() == id)
{
slot.enabled = enabled;
cx.notify();
}
});
}
pub fn set_gutter_column_enabled(&self, enabled: bool, cx: &mut App) {
let _ = self.input.update(cx, |state, cx| {
state.gutter_column_enabled = enabled;
cx.notify();
});
}
pub fn gutter_column_enabled(&self, cx: &App) -> bool {
self.input
.read_with(cx, |state, _| state.gutter_column_enabled)
}
pub fn gutter_providers(&self, cx: &App) -> Vec<(SharedString, bool)> {
self.input.read_with(cx, |state, _| {
state
.gutter_markers
.iter()
.map(|e| (e.id.clone(), e.enabled))
.collect()
})
}
}
pub(crate) fn gutter_column_width(state: &super::super::InputState) -> Pixels {
if state.gutter_column_enabled && state.gutter_markers.iter().any(|e| e.enabled) {
GUTTER_WIDTH
} else {
px(0.)
}
}
pub(crate) fn layout_gutter_markers(
state: &Entity<super::super::InputState>,
origin: Point<Pixels>,
last_layout: &LastLayout,
line_height: Pixels,
window: &mut Window,
cx: &mut App,
) -> GutterMarkersLayout {
let builders = state.read_with(cx, |state, _| {
state
.gutter_markers
.iter()
.filter(|e| e.enabled)
.map(|e| e.build.clone())
.collect::<Vec<GutterMarkerBuilder>>()
});
if builders.is_empty() {
return GutterMarkersLayout::default();
}
let mut items = Vec::new();
let mut y = last_layout.visible_top;
for (line_layout, &buffer_row) in last_layout
.lines
.iter()
.zip(last_layout.visible_buffer_lines.iter())
{
for build in &builders {
if let Some(mut element) = build(buffer_row, window, cx) {
let cell_origin = point(origin.x, origin.y + y);
element.prepaint_as_root(
cell_origin,
size(GUTTER_WIDTH, line_height).into(),
window,
cx,
);
items.push(element);
}
}
y += line_layout.size(line_height).height;
}
GutterMarkersLayout {
width: GUTTER_WIDTH,
items,
}
}
pub(crate) fn paint_gutter_markers(
layout: &mut GutterMarkersLayout,
window: &mut Window,
cx: &mut App,
) {
if layout.width == px(0.) {
return;
}
for element in layout.items.iter_mut() {
element.paint(window, cx);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AppContext as _, Context, Entity, Render};
struct Probe {
state: Entity<EditorState>,
}
impl Render for Probe {
fn render(
&mut self,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> impl crate::IntoElement {
crate::div()
}
}
fn provider_count(editor: &Entity<EditorState>, cx: &mut crate::VisualTestContext) -> usize {
editor.read_with(cx, |state, cx| {
state
.input()
.read_with(cx, |input, _| input.gutter_markers.len())
})
}
#[rgpui::test]
fn provider_registry_roundtrip(cx: &mut crate::TestAppContext) {
let (probe, cx) = cx.add_window_view(|window, cx| {
let editor = cx.new(|cx| EditorState::new(window, cx, "fn main() {}\n"));
Probe { state: editor }
});
let editor = probe.read_with(cx, |probe, _| probe.state.clone());
assert!(!editor.read_with(cx, |state, cx| state.gutter_column_enabled(cx)));
cx.update(|_, cx| {
editor.update(cx, |state, cx| {
state.add_gutter_provider("run", |_, _, _| None, cx);
state.add_gutter_provider("bp", |_, _, _| None, cx);
state.add_gutter_provider("run", |_, _, _| None, cx);
});
});
assert_eq!(provider_count(&editor, cx), 2);
cx.update(|_, cx| {
editor.update(cx, |state, cx| {
state.set_gutter_provider_enabled("bp", false, cx);
});
});
let enabled: Vec<bool> = editor.read_with(cx, |state, cx| {
state.input().read_with(cx, |input, _| {
input.gutter_markers.iter().map(|e| e.enabled).collect()
})
});
assert!(enabled.contains(&true) && enabled.contains(&false));
cx.update(|_, cx| {
editor.update(cx, |state, cx| {
state.set_gutter_column_enabled(true, cx);
});
});
assert!(editor.read_with(cx, |state, cx| state.gutter_column_enabled(cx)));
cx.update(|_, cx| {
editor.update(cx, |state, cx| {
state.remove_gutter_provider("bp", cx);
state.remove_gutter_provider("nonexistent", cx);
});
});
assert_eq!(provider_count(&editor, cx), 1);
}
}