use crate::{BlockLayouts, Cursor, Doc, Editing, Selection, render_with};
use gpui::{
AnyElement, Context, CursorStyle, ElementId, MouseButton, MouseDownEvent, MouseMoveEvent,
Window, div, prelude::*,
};
use std::rc::Rc;
pub enum Pointer {
Down(Cursor),
Move(Cursor),
Up,
}
#[expect(
clippy::too_many_arguments,
reason = "a document, its selection, and a gesture"
)]
pub fn render<V: 'static>(
id: impl Into<ElementId>,
doc: &Doc,
layouts: &BlockLayouts,
selection: Option<Selection>,
dragging: bool,
window: &mut Window,
cx: &mut Context<V>,
on_pointer: impl Fn(&mut V, Pointer, &mut Context<V>) + 'static,
) -> AnyElement {
let on_pointer = Rc::new(on_pointer);
let (down, moved, up, off) = (
on_pointer.clone(),
on_pointer.clone(),
on_pointer.clone(),
on_pointer,
);
let (at_down, at_move) = (layouts.clone(), layouts.clone());
div()
.id(id)
.cursor(CursorStyle::IBeam)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |view, event: &MouseDownEvent, _, cx| {
if let Some(cursor) = at_down.hit(event.position) {
down(view, Pointer::Down(cursor), cx);
}
}),
)
.on_mouse_move(cx.listener(move |view, event: &MouseMoveEvent, _, cx| {
if dragging && let Some(cursor) = at_move.hit(event.position) {
moved(view, Pointer::Move(cursor), cx);
}
}))
.on_mouse_up(
MouseButton::Left,
cx.listener(move |view, _, _, cx| up(view, Pointer::Up, cx)),
)
.on_mouse_up_out(
MouseButton::Left,
cx.listener(move |view, _, _, cx| off(view, Pointer::Up, cx)),
)
.child(render_with(
doc,
Editing {
selection,
caret_on: false,
layouts: Some(layouts),
..Editing::default()
},
window,
cx,
))
.into_any_element()
}
pub fn copied(doc: &Doc, selection: Selection) -> String {
doc.spans(selection)
.into_iter()
.filter_map(|(at, range)| {
let text = &doc.blocks.get(at.block)?.text_at(at.part)?.text;
text.get(range).map(str::to_owned)
})
.collect::<Vec<_>>()
.join("\n")
}
gpui::actions!(selectable, [Copy]);
struct Bindings;
impl gpui::Global for Bindings {}
pub fn surface(
focus: &gpui::FocusHandle,
doc: &Doc,
selection: Option<Selection>,
cx: &mut gpui::App,
) -> gpui::Div {
if !cx.has_global::<Bindings>() {
let chord = if cfg!(target_os = "macos") {
"cmd-c"
} else {
"ctrl-c"
};
cx.bind_keys([gpui::KeyBinding::new(chord, Copy, Some("SelectableText"))]);
cx.set_global(Bindings);
}
let text = selection
.map(|selection| copied(doc, selection))
.unwrap_or_default();
let focus = focus.clone();
div()
.key_context("SelectableText")
.track_focus(&focus)
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
window.focus(&focus, cx)
})
.on_action(move |_: &Copy, _, cx| {
if !text.is_empty() {
cx.write_to_clipboard(gpui::ClipboardItem::new_string(text.clone()));
}
})
}