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 || self.lifted.is_some() || self.resizing.is_some()
69    }
70
71    /// Follow a dragged pointer, wherever in the window it is.
72    pub(super) fn drag_to(&mut self, position: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
73        // A lifted block follows the pointer.
74        if let Some((from, _)) = self.lifted {
75            if let Some(to) = self.layouts.block_at(position) {
76                self.lifted = Some((from, to));
77                cx.notify();
78            }
79            return;
80        }
81        // An image being resized follows the pointer the same way — the
82        // document holds nothing until the handle is released.
83        if let Some((ix, _)) = self.resizing {
84            if let Some(width) = self.dragged_width(ix, position.x) {
85                self.resizing = Some((ix, Some(width)));
86                cx.notify();
87            }
88            return;
89        }
90        if self.dragging
91            && let Some(hit) = self.layouts.hit(position)
92        {
93            self.selection = self.selection.extend_to(hit).clamp(&self.doc);
94            cx.notify();
95        }
96    }
97
98    pub(super) fn pressed(
99        &mut self,
100        position: gpui::Point<gpui::Pixels>,
101        click_count: usize,
102        modifiers: gpui::Modifiers,
103        window: &mut Window,
104        cx: &mut Context<Self>,
105    ) {
106        ui::popover::close_popup(self, cx, |this| &mut this.block_menu);
107        ui::popover::close_popup(self, cx, |this| &mut this.language_menu);
108        self.pasted = None;
109        self.focus_handle.clone().focus(window, cx);
110        // Ahead of the hit test, and returning without one: the
111        // box is a control, and a caret dropped into the row
112        // behind it would move the caret on every check.
113        if let Some(ix) = self.checkbox_at(position) {
114            self.toggle_task(ix, cx);
115            return;
116        }
117        if self.tail_click(position, cx) {
118            return;
119        }
120        let Some(hit) = self.layouts.hit(position) else {
121            return cx.notify();
122        };
123        self.selection = match click_count {
124            // Shift extends from wherever the anchor already is,
125            // which is what makes click-then-shift-click a range.
126            _ if modifiers.shift => self.selection.extend_to(hit),
127            1 => Selection::at(hit),
128            2 => Selection::new(hit.word_left(&self.doc), hit.word_right(&self.doc)),
129            _ => Selection::new(hit.home(), hit.end(&self.doc)),
130        }
131        .clamp(&self.doc);
132        self.dragging = click_count == 1 && !modifiers.shift;
133        self.history.interrupt();
134        self.caret_moved();
135        // Only the editor sees the press, so only the editor can
136        // say which anchor it landed on.
137        if let Some(id) = self.anchor_at(position) {
138            cx.emit(EditorEvent::AnchorActivated(id));
139        }
140        cx.notify();
141    }
142
143    /// A click past the end of the document. Without this the document has no
144    /// end: the click snaps back into the block above it, and what gets typed
145    /// lands inside the code the reader was trying to escape.
146    pub(super) fn tail_click(
147        &mut self,
148        at: gpui::Point<gpui::Pixels>,
149        cx: &mut Context<Self>,
150    ) -> bool {
151        let Some(last) = self.doc.blocks.len().checked_sub(1) else {
152            return false;
153        };
154        let Some(bounds) = self.layouts.block_bounds(last) else {
155            return false;
156        };
157        at.y > bounds.origin.y + bounds.size.height && self.append_tail(cx)
158    }
159}