1use super::*;
4
5const EDGE_SCROLL_TICK: Duration = Duration::from_millis(16);
8const EDGE_SCROLL_MAX: f32 = 48.0;
9
10impl Editor {
11 pub(super) fn checkbox_at(&self, at: gpui::Point<gpui::Pixels>) -> Option<usize> {
16 let ix = self.layouts.block_at(at)?;
17 self.layouts
18 .checkbox_bounds(ix)
19 .is_some_and(|bounds| bounds.contains(&at))
20 .then_some(ix)
21 }
22
23 pub(super) fn append_tail(&mut self, cx: &mut Context<Self>) -> bool {
28 let Some(last) = self.doc.blocks.len().checked_sub(1) else {
29 return false;
30 };
31 if !self.blocks() {
32 return false;
33 }
34 if self.doc.blocks[last].parts().last() == Some(&Part::Body) {
35 return false;
36 }
37 self.edit(EditKind::Structure, cx, |this| {
38 this.doc
39 .edit()
40 .blocks
41 .push(markdown::Block::new(BlockKind::Paragraph(Text::default())));
42 let ix = this.doc.blocks.len() - 1;
43 this.selection = Selection::at(Cursor::new(ix, Part::Body, 0).clamp(&this.doc));
44 vec![]
45 });
46 true
47 }
48
49 pub fn press(
57 &mut self,
58 position: gpui::Point<gpui::Pixels>,
59 click_count: usize,
60 modifiers: gpui::Modifiers,
61 window: &mut Window,
62 cx: &mut Context<Self>,
63 ) {
64 if window.default_prevented() {
65 return;
66 }
67 window.prevent_default();
68 self.pressed(position, click_count, modifiers, window, cx);
69 }
70
71 pub(super) fn in_drag(&self) -> bool {
74 self.dragging.is_some()
75 || self.lifted.is_some()
76 || self.resizing.is_some()
77 || self.sizing.is_some()
78 || self.table_drag.is_some()
79 }
80
81 pub(super) fn drag_to(&mut self, position: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
84 self.drag_at = Some(position);
85 if self.edge_scroll.is_none() && self.edge_step() != 0.0 {
86 self.edge_scroll = Some(cx.spawn(async move |this, cx| {
87 loop {
88 cx.background_executor().timer(EDGE_SCROLL_TICK).await;
89 let more = this.update(cx, |this, cx| this.edge_scroll_tick(cx));
90 if !more.unwrap_or(false) {
91 return;
92 }
93 }
94 }));
95 }
96 self.follow(position, cx);
97 }
98
99 fn edge_step(&self) -> f32 {
102 let (Some(scroll), Some(at)) = (&self.scroll, self.drag_at) else {
103 return 0.0;
104 };
105 if self.dragging.is_none() && self.lifted.is_none() {
106 return 0.0;
107 }
108 let view = scroll.bounds();
109 let past = if at.y < view.top() {
110 f32::from(at.y - view.top())
111 } else if at.y > view.bottom() {
112 f32::from(at.y - view.bottom())
113 } else {
114 return 0.0;
115 };
116 (past / 2.0).clamp(-EDGE_SCROLL_MAX, EDGE_SCROLL_MAX)
117 }
118
119 fn edge_scroll_tick(&mut self, cx: &mut Context<Self>) -> bool {
122 let step = self.edge_step();
123 let (Some(scroll), Some(at)) = (self.scroll.clone(), self.drag_at) else {
124 return false;
125 };
126 if step == 0.0 {
127 self.edge_scroll = None;
128 return false;
129 }
130 let offset = scroll.offset();
131 let y = (offset.y - gpui::px(step)).clamp(-scroll.max_offset().y, gpui::px(0.0));
132 scroll.set_offset(gpui::point(offset.x, y));
133 self.follow(at, cx);
134 cx.notify();
135 true
136 }
137
138 fn follow(&mut self, position: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
140 if self.table_drag.is_some() {
141 return self.drag_table_to(position, cx);
142 }
143 if let Some((from, _)) = self.lifted {
145 if let Some(to) = self.layouts.block_at(position) {
146 self.lifted = Some((from, to));
147 cx.notify();
148 }
149 return;
150 }
151 if self.drag_height(position.y, cx) {
152 return;
153 }
154 if let Some((ix, _)) = self.resizing {
157 if let Some(width) = self.dragged_width(ix, position.x) {
158 self.resizing = Some((ix, Some(width)));
159 cx.notify();
160 }
161 return;
162 }
163 if let Some((unit, pressed)) = self.dragging.clone()
164 && let Some((hit, _)) = self.layouts.hit(position)
165 {
166 let (anchor, head) = ui::input::drag_selection(pressed, hit.span(unit, &self.doc));
167 self.selection = Selection::new(anchor, head).clamp(&self.doc);
168 cx.notify();
169 }
170 }
171
172 pub(super) fn pressed(
173 &mut self,
174 position: gpui::Point<gpui::Pixels>,
175 click_count: usize,
176 modifiers: gpui::Modifiers,
177 window: &mut Window,
178 cx: &mut Context<Self>,
179 ) {
180 ui::popover::close_popup(self, cx, |this| &mut this.block_menu);
181 ui::popover::close_popup(self, cx, |this| &mut this.language_menu);
182 ui::popover::close_popup(self, cx, |this| &mut this.table_menu);
183 ui::popover::close_popup(self, cx, |this| &mut this.text_menu);
184 ui::popover::close_popup(self, cx, |this| &mut this.image_menu);
185 self.pasted = None;
186 self.focus_handle.clone().focus(window, cx);
187 if let Some(ix) = self.checkbox_at(position) {
191 self.toggle_task(ix, cx);
192 return;
193 }
194 if self.tail_click(position, cx) {
195 return;
196 }
197 let Some((hit, affinity)) = self.layouts.hit(position) else {
198 return cx.notify();
199 };
200 let unit = ui::input::Granularity::of_clicks(click_count);
201 self.selection = match modifiers.shift {
204 true => self.selection.extend_to(hit).with_affinity(affinity),
205 false => {
206 let span = hit.span(unit, &self.doc);
207 let affinity = match span.end == hit {
208 true => affinity,
209 false => Affinity::Downstream,
210 };
211 Selection::new(span.start, span.end).with_affinity(affinity)
212 }
213 }
214 .clamp(&self.doc);
215 self.dragging = (!modifiers.shift).then(|| {
216 let (start, end) = self.selection.ordered();
217 (unit, start..end)
218 });
219 self.run.interrupt();
220 self.caret_moved();
221 if let Some(id) = self.anchor_at(position) {
224 cx.emit(EditorEvent::AnchorActivated(id));
225 }
226 cx.notify();
227 }
228
229 pub(super) fn right_pressed(
232 &mut self,
233 position: gpui::Point<gpui::Pixels>,
234 window: &mut Window,
235 cx: &mut Context<Self>,
236 ) {
237 ui::popover::close_popup(self, cx, |this| &mut this.block_menu);
238 ui::popover::close_popup(self, cx, |this| &mut this.language_menu);
239 if let Some(target) = self.image_target_at(position) {
240 self.focus_handle.clone().focus(window, cx);
241 self.image_menu.open(Dropped::new(target, position));
242 return cx.notify();
243 }
244 if let Some((hit, affinity)) = self.layouts.hit(position) {
245 let (start, end) = self.selection.ordered();
246 if self.selection.is_collapsed() || hit < start || hit > end {
247 self.selection = Selection::at(hit).with_affinity(affinity).clamp(&self.doc);
248 self.run.interrupt();
249 self.caret_moved();
250 }
251 }
252 self.focus_handle.clone().focus(window, cx);
253 let items = ui::menu::edit_items(
254 !self.selection.is_collapsed(),
255 cx.read_from_clipboard().is_some(),
256 [&Cut, &Copy, &Paste, &SelectAll],
257 window,
258 );
259 self.text_menu.open(Dropped::new(items, position));
260 cx.notify();
261 }
262
263 pub(super) fn tail_click(
267 &mut self,
268 at: gpui::Point<gpui::Pixels>,
269 cx: &mut Context<Self>,
270 ) -> bool {
271 let Some(last) = self.doc.blocks.len().checked_sub(1) else {
272 return false;
273 };
274 let Some(bounds) = self.layouts.block_bounds(last) else {
275 return false;
276 };
277 at.y > bounds.origin.y + bounds.size.height && self.append_tail(cx)
278 }
279}