Skip to main content

editor/editor/
pointer.rs

1//! Presses, drags and clicks below the last block.
2
3use super::*;
4
5impl Editor {
6    /// The task block whose checkbox `at` landed in, in window coordinates.
7    ///
8    /// The box that painted rather than the marker column: a press in the
9    /// gutter beside it is a press on the row, and still places a caret.
10    pub(super) fn checkbox_at(&self, at: gpui::Point<gpui::Pixels>) -> Option<usize> {
11        let ix = self.layouts.block_at(at)?;
12        self.layouts
13            .checkbox_bounds(ix)
14            .is_some_and(|bounds| bounds.contains(&at))
15            .then_some(ix)
16    }
17
18    /// The paragraph a document ending in a fence, a table, a rule or an image
19    /// has no other way to grow: a fence swallows Enter, a cell and a caption
20    /// have nowhere to put one, and a rule holds no caret at all. `false` when
21    /// the last block ends in a body, which can carry on by itself.
22    pub(super) fn append_tail(&mut self, cx: &mut Context<Self>) -> bool {
23        let Some(last) = self.doc.blocks.len().checked_sub(1) else {
24            return false;
25        };
26        if !self.blocks() {
27            return false;
28        }
29        if self.doc.blocks[last].parts().last() == Some(&Part::Body) {
30            return false;
31        }
32        self.edit(EditKind::Structure, cx, |this| {
33            this.doc
34                .blocks
35                .push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
36            let ix = this.doc.blocks.len() - 1;
37            this.selection = Selection::at(Cursor::new(ix, Part::Body, 0).clamp(&this.doc));
38            vec![]
39        });
40        true
41    }
42
43    /// Press at `position` as if on the document: menus close, the editor
44    /// takes focus, and the caret goes to the nearest place a caret can be —
45    /// below the last block, above the first, or beside a line.
46    ///
47    /// For a host whose own frame around the editor should behave as the page. A
48    /// press the editor's box already took is marked with
49    /// [`Window::prevent_default`], and this ignores one so marked.
50    pub fn press(
51        &mut self,
52        position: gpui::Point<gpui::Pixels>,
53        click_count: usize,
54        modifiers: gpui::Modifiers,
55        window: &mut Window,
56        cx: &mut Context<Self>,
57    ) {
58        if window.default_prevented() {
59            return;
60        }
61        window.prevent_default();
62        self.pressed(position, click_count, modifiers, window, cx);
63    }
64
65    /// Whether a press is being dragged: a selection, a lifted block or an
66    /// image resize.
67    pub(super) fn in_drag(&self) -> bool {
68        self.dragging.is_some()
69            || self.lifted.is_some()
70            || self.resizing.is_some()
71            || self.table_drag.is_some()
72    }
73
74    /// Follow a dragged pointer, wherever in the window it is.
75    pub(super) fn drag_to(&mut self, position: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
76        if self.table_drag.is_some() {
77            return self.drag_table_to(position, cx);
78        }
79        // A lifted block follows the pointer.
80        if let Some((from, _)) = self.lifted {
81            if let Some(to) = self.layouts.block_at(position) {
82                self.lifted = Some((from, to));
83                cx.notify();
84            }
85            return;
86        }
87        // An image being resized follows the pointer the same way — the
88        // document holds nothing until the handle is released.
89        if let Some((ix, _)) = self.resizing {
90            if let Some(width) = self.dragged_width(ix, position.x) {
91                self.resizing = Some((ix, Some(width)));
92                cx.notify();
93            }
94            return;
95        }
96        if let Some((unit, pressed)) = self.dragging.clone()
97            && let Some(hit) = self.layouts.hit(position)
98        {
99            let (anchor, head) = ui::input::drag_selection(pressed, hit.span(unit, &self.doc));
100            self.selection = Selection::new(anchor, head).clamp(&self.doc);
101            cx.notify();
102        }
103    }
104
105    pub(super) fn pressed(
106        &mut self,
107        position: gpui::Point<gpui::Pixels>,
108        click_count: usize,
109        modifiers: gpui::Modifiers,
110        window: &mut Window,
111        cx: &mut Context<Self>,
112    ) {
113        ui::popover::close_popup(self, cx, |this| &mut this.block_menu);
114        ui::popover::close_popup(self, cx, |this| &mut this.language_menu);
115        ui::popover::close_popup(self, cx, |this| &mut this.table_menu);
116        self.pasted = None;
117        self.focus_handle.clone().focus(window, cx);
118        // Ahead of the hit test, and returning without one: the
119        // box is a control, and a caret dropped into the row
120        // behind it would move the caret on every check.
121        if let Some(ix) = self.checkbox_at(position) {
122            self.toggle_task(ix, cx);
123            return;
124        }
125        if self.tail_click(position, cx) {
126            return;
127        }
128        let Some(hit) = self.layouts.hit(position) else {
129            return cx.notify();
130        };
131        let unit = ui::input::Granularity::of_clicks(click_count);
132        // Shift extends from wherever the anchor already is, which is what
133        // makes click-then-shift-click a range.
134        self.selection = match modifiers.shift {
135            true => self.selection.extend_to(hit),
136            false => {
137                let span = hit.span(unit, &self.doc);
138                Selection::new(span.start, span.end)
139            }
140        }
141        .clamp(&self.doc);
142        self.dragging = (!modifiers.shift).then(|| {
143            let (start, end) = self.selection.ordered();
144            (unit, start..end)
145        });
146        self.history.interrupt();
147        self.caret_moved();
148        // Only the editor sees the press, so only the editor can
149        // say which anchor it landed on.
150        if let Some(id) = self.anchor_at(position) {
151            cx.emit(EditorEvent::AnchorActivated(id));
152        }
153        cx.notify();
154    }
155
156    /// A click past the end of the document. Without this the document has no
157    /// end: the click snaps back into the block above it, and what gets typed
158    /// lands inside the code the reader was trying to escape.
159    pub(super) fn tail_click(
160        &mut self,
161        at: gpui::Point<gpui::Pixels>,
162        cx: &mut Context<Self>,
163    ) -> bool {
164        let Some(last) = self.doc.blocks.len().checked_sub(1) else {
165            return false;
166        };
167        let Some(bounds) = self.layouts.block_bounds(last) else {
168            return false;
169        };
170        at.y > bounds.origin.y + bounds.size.height && self.append_tail(cx)
171    }
172}