1use std::{cell::Cell, collections::HashMap, ops::Range, rc::Rc};
12
13use gpui::prelude::FluentBuilder as _;
14use gpui::{
15 AnyElement, App, Bounds, CursorStyle, DefiniteLength, DispatchPhase, Edges, Element, ElementId,
16 ElementInputHandler, Entity, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InspectorElementId,
17 InteractiveElement, IntoElement, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
18 MouseUpEvent, ParentElement, Pixels, Refineable, RenderOnce, Role, StatefulInteractiveElement,
19 Style, StyleRefinement, Styled, TextStyle, Window, canvas, div, fill, point, px, size,
20};
21
22use super::{cull::Measured, id::BlockId, projection::Snapshot, state::*, style::BlockEditorStyle};
23use gpui_kit::base::StyledExt as _;
24use gpui_kit::base::text::editor_host::{
25 BlockNode, FlowPosition, RenderContext, collect_leaf_geometry,
26};
27
28const LIST_CHUNK: usize = 32;
30
31#[derive(IntoElement)]
35pub struct BlockEditor {
36 state: Entity<BlockEditorState>,
37 style: StyleRefinement,
38}
39
40impl BlockEditor {
41 pub fn new(state: &Entity<BlockEditorState>) -> Self {
42 Self {
43 state: state.clone(),
44 style: StyleRefinement::default(),
45 }
46 }
47}
48
49impl Styled for BlockEditor {
52 fn style(&mut self) -> &mut StyleRefinement {
53 &mut self.style
54 }
55}
56
57pub(crate) fn block_role(node: &BlockNode) -> Option<(Role, Option<usize>)> {
61 Some(match node {
62 BlockNode::Paragraph(_) => (Role::Paragraph, None),
63 BlockNode::Heading { level, .. } => (Role::Heading, Some(*level as usize)),
64 BlockNode::Blockquote { .. } => (Role::Blockquote, None),
65 BlockNode::List { .. } => (Role::List, None),
66 BlockNode::CodeBlock(_) => (Role::Code, None),
67 BlockNode::Table(_) => (Role::Table, None),
68 BlockNode::HorizontalRule { .. } => (Role::Splitter, None),
69 BlockNode::Custom { .. } => (Role::Group, None),
70 BlockNode::ListItem { .. } => (Role::ListItem, None),
71 BlockNode::Root { .. }
72 | BlockNode::Break { .. }
73 | BlockNode::Definition { .. }
74 | BlockNode::Unknown => return None,
75 _ => return None,
76 })
77}
78
79impl RenderOnce for BlockEditor {
80 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
81 let theme = gpui_kit::base::Theme::global(cx);
82 let render_cx = RenderContext::from_app(cx).on_link_click(|_, _, _, _| {});
85 let text_style = render_cx.style().clone();
86 let mut style = self.state.read(cx).style.resolved(&theme.tokens);
87 if style.selection_is_default() {
88 style = style.with_selection(text_style.selection());
89 }
90 let caret_was_visible = {
93 let state = self.state.read(cx);
94 state
95 .scroll_handle
96 .as_ref()
97 .zip(state.model.caret())
98 .is_some_and(|(handle, pos)| {
99 state
100 .frame
101 .borrow()
102 .caret_bounds(pos, style.caret_width())
103 .is_some_and(|caret| handle.bounds().contains(&caret.center()))
104 })
105 };
106 let (focus, snapshot, backgrounds, extensions, keep, list_items) =
107 self.state.update(cx, |state, _| {
108 let snapshot = state.snapshot();
109 let mut backgrounds: HashMap<u64, Vec<(Range<usize>, Hsla)>> = HashMap::new();
110 for (block, range) in state.selection_ranges() {
111 let range = match state.model.doc.get(block).map(|e| &e.content) {
113 Some(super::document::BlockContent::Atom(_)) => {
114 0..state.model.doc.leaf_len(block).unwrap_or(range.end)
115 }
116 _ => range,
117 };
118 backgrounds
119 .entry(block.as_u64())
120 .or_default()
121 .push((range, style.selection()));
122 }
123 (
124 state.focus_handle.clone(),
125 snapshot,
126 backgrounds,
127 state.model.doc.extensions.clone(),
128 state.pinned_blocks(),
129 (!state.cull.borrow().index.is_current(state.model.revision())).then(|| {
131 let doc = &state.model.doc;
132 doc.children(doc.root)
133 .iter()
134 .filter(|id| {
135 matches!(
136 doc.kind(**id),
137 Some(super::kind::BlockKindData::List { .. })
138 ) && doc.children(**id).len() > LIST_CHUNK
139 })
140 .map(|id| (*id, Rc::<[BlockId]>::from(doc.children(*id))))
141 .collect::<HashMap<_, _>>()
142 }),
143 )
144 });
145
146 let mut typography = window.text_style();
149 typography.refine(&self.style.text);
150 let node_cx = render_cx
151 .with_markdown_extensions(extensions)
152 .with_body_font_size(typography.font_size.to_pixels(window.rem_size()));
153 let (cull, revision) = {
154 let state = self.state.read(cx);
155 (state.cull.clone(), state.model.revision())
156 };
157 let mut children: Vec<AnyElement> = Vec::new();
158 {
159 let mut cull_state = cull.borrow_mut();
160 cull_state.measured.clear();
161 let window_span = cull_state.visible.map(|(top, bottom)| {
162 let margin = ((bottom - top) * 2.).max(px(600.));
165 (top - margin, bottom + margin)
166 });
167 let cull_state = &mut *cull_state;
168 if let Some(list_items) = list_items {
169 let nodes: Vec<&BlockNode> = snapshot
174 .blocks
175 .iter()
176 .flat_map(|b| b.nodes.iter())
177 .collect();
178 let mut units = Vec::with_capacity(nodes.len());
179 let mut node_ix = 0;
180 for (block_ix, block) in snapshot.blocks.iter().enumerate() {
181 for (node_in_block, node) in block.nodes.iter().enumerate() {
182 let flow = FlowPosition::at(&nodes, node_ix, false);
183 let unit = |id, items| super::cull::UnitDesc {
184 id,
185 block: block_ix,
186 node: node_in_block,
187 node_ix,
188 items,
189 flow,
190 };
191 match (node, list_items.get(&block.id)) {
192 (BlockNode::List { children, .. }, Some(ids))
193 if ids.len() == children.len() =>
194 {
195 for start in (0..ids.len()).step_by(LIST_CHUNK) {
196 let range = start..(start + LIST_CHUNK).min(ids.len());
197 units.push(unit(ids[start], Some((range, ids.clone()))));
198 }
199 }
200 _ => units.push(unit(block.id, None)),
201 }
202 node_ix += 1;
203 }
204 }
205 cull_state.index.rebuild(
206 revision,
207 units.iter().map(|u| {
208 let covered: &[BlockId] = match &u.items {
209 Some((range, ids)) => &ids[range.clone()],
210 None => &[],
211 };
212 (u.id, covered)
213 }),
214 &cull_state.heights,
215 );
216 cull_state.units = units;
217 cull_state.node_count = nodes.len();
218 }
219 let units = &cull_state.units;
220 let count = cull_state.node_count;
221 let index = &cull_state.index;
222 let n = units.len();
223 let (first, last) = match window_span {
226 None => (0, n),
227 Some((top, bottom)) => {
228 let first = index.find(top.max(px(0.))).min(n);
229 (first, (index.find(bottom) + 1).min(n).max(first))
230 }
231 };
232 let mut wanted: Vec<usize> = keep
233 .iter()
234 .filter_map(|id| index.unit_of.get(id).copied())
235 .filter(|ix| *ix < first || *ix >= last)
236 .collect();
237 wanted.extend(first..last);
238 wanted.sort_unstable();
239 wanted.dedup();
240 let mut cursor = px(0.);
241 let mut rendered = 0;
242 for unit_ix in wanted {
243 let unit = &units[unit_ix];
244 let node_ix = unit.node_ix;
245 let top = index.prefix(unit_ix);
246 if top > cursor {
247 children.push(div().h(top - cursor).into_any_element());
248 }
249 cursor = top + index.height(unit_ix);
250 rendered += 1;
251 let flow = unit.flow;
252 let is_last_node = node_ix + 1 == count;
253 let node = &snapshot.blocks[unit.block].nodes[unit.node];
254 let element = match (&unit.items, node) {
255 (
256 Some((range, ids)),
257 BlockNode::List {
258 children: items,
259 ordered,
260 start,
261 ..
262 },
263 ) => {
264 let last_chunk = range.end == ids.len();
265 let chunk = BlockNode::List {
266 children: items[range.clone()].to_vec(),
267 ordered: *ordered,
268 start: if *ordered {
269 Some(start.unwrap_or(1) + range.start as u32)
270 } else {
271 *start
272 },
273 span: None,
274 };
275 let flow = if range.start == 0 {
276 flow
277 } else {
278 flow.first(false)
279 };
280 chunk.render(
281 node_ix,
282 flow.last(!last_chunk || is_last_node),
284 &node_cx,
285 window,
286 cx,
287 )
288 }
289 _ => node.render(node_ix, flow.last(is_last_node), &node_cx, window, cx),
290 };
291 let element = match block_role(node) {
293 Some((role, level)) => div()
294 .id(("block", unit.id.as_u64() as usize))
295 .role(role)
296 .when_some(level, |this, level| this.aria_level(level))
297 .child(element)
298 .into_any_element(),
299 None => element,
300 };
301 children.push(
302 Measured {
303 id: unit.id,
304 index: unit_ix,
305 state: cull.clone(),
306 child: Some(element),
307 }
308 .into_any_element(),
309 );
310 }
311 let total = index.prefix(n);
312 if total > cursor {
313 children.push(div().h(total - cursor).into_any_element());
314 }
315 cull_state.rendered = rendered;
316 }
317
318 let content_bounds = Rc::new(Cell::new(Bounds::default()));
319 let measured_content = content_bounds.clone();
320 let mut root_style = Style::default();
321 root_style.refine(&self.style);
322 let s = &self.state;
323 div()
324 .id("block-editor")
325 .role(Role::MultilineTextInput)
328 .when(window.is_a11y_active() || cfg!(test), |this| {
329 this.aria_value(self.state.read(cx).accessible_value())
330 })
331 .key_context(self.state.read(cx).key_context())
332 .track_focus(&focus)
333 .cursor(if self.state.read(cx).grip_hover {
334 CursorStyle::OpenHand
335 } else if self.state.read(cx).pointer_hand {
336 CursorStyle::PointingHand
337 } else {
338 CursorStyle::IBeam
339 })
340 .w_full()
341 .on_action(window.listener_for(s, BlockEditorState::on_backspace))
342 .on_action(window.listener_for(s, BlockEditorState::on_delete))
343 .on_action(window.listener_for(s, BlockEditorState::on_delete_word_left))
344 .on_action(window.listener_for(s, BlockEditorState::on_delete_word_right))
345 .on_action(window.listener_for(s, BlockEditorState::on_delete_to_line_start))
346 .on_action(window.listener_for(s, BlockEditorState::on_delete_to_line_end))
347 .on_action(window.listener_for(s, BlockEditorState::on_move_paragraph_start))
348 .on_action(window.listener_for(s, BlockEditorState::on_move_paragraph_end))
349 .on_action(window.listener_for(s, BlockEditorState::on_select_paragraph_start))
350 .on_action(window.listener_for(s, BlockEditorState::on_select_paragraph_end))
351 .on_action(window.listener_for(s, BlockEditorState::on_enter))
352 .on_action(window.listener_for(s, BlockEditorState::on_structural_enter))
353 .on_action(window.listener_for(s, BlockEditorState::on_soft_break))
354 .on_action(window.listener_for(s, BlockEditorState::on_exit_block))
355 .on_action(window.listener_for(s, BlockEditorState::on_move_left))
356 .on_action(window.listener_for(s, BlockEditorState::on_move_right))
357 .on_action(window.listener_for(s, BlockEditorState::on_move_up))
358 .on_action(window.listener_for(s, BlockEditorState::on_move_down))
359 .on_action(window.listener_for(s, BlockEditorState::on_move_home))
360 .on_action(window.listener_for(s, BlockEditorState::on_move_end))
361 .on_action(window.listener_for(s, BlockEditorState::on_select_left))
362 .on_action(window.listener_for(s, BlockEditorState::on_move_word_left))
363 .on_action(window.listener_for(s, BlockEditorState::on_move_word_right))
364 .on_action(window.listener_for(s, BlockEditorState::on_select_word_left))
365 .on_action(window.listener_for(s, BlockEditorState::on_select_word_right))
366 .on_action(window.listener_for(s, BlockEditorState::on_select_right))
367 .on_action(window.listener_for(s, BlockEditorState::on_select_up))
368 .on_action(window.listener_for(s, BlockEditorState::on_select_down))
369 .on_action(window.listener_for(s, BlockEditorState::on_select_home))
370 .on_action(window.listener_for(s, BlockEditorState::on_select_end))
371 .on_action(window.listener_for(s, BlockEditorState::on_move_doc_start))
372 .on_action(window.listener_for(s, BlockEditorState::on_move_doc_end))
373 .on_action(window.listener_for(s, BlockEditorState::on_select_doc_start))
374 .on_action(window.listener_for(s, BlockEditorState::on_select_doc_end))
375 .on_action(window.listener_for(s, BlockEditorState::on_select_all))
376 .on_action(window.listener_for(s, BlockEditorState::on_undo))
377 .on_action(window.listener_for(s, BlockEditorState::on_redo))
378 .on_action(window.listener_for(s, BlockEditorState::on_normal_text))
379 .on_action(window.listener_for(s, BlockEditorState::on_table_insert_column_left))
380 .on_action(window.listener_for(s, BlockEditorState::on_table_insert_column_right))
381 .on_action(window.listener_for(s, BlockEditorState::on_table_insert_row_above))
382 .on_action(window.listener_for(s, BlockEditorState::on_table_insert_row_below))
383 .on_action(window.listener_for(s, BlockEditorState::on_table_delete_line))
384 .on_action(window.listener_for(s, BlockEditorState::on_close_table_menu))
385 .on_action(window.listener_for(s, BlockEditorState::on_toggle_bold))
386 .on_action(window.listener_for(s, BlockEditorState::on_toggle_italic))
387 .on_action(window.listener_for(s, BlockEditorState::on_edit_link))
388 .on_action(window.listener_for(s, BlockEditorState::on_toggle_code))
389 .on_action(window.listener_for(s, BlockEditorState::on_indent))
390 .on_action(window.listener_for(s, BlockEditorState::on_outdent))
391 .on_action(window.listener_for(s, BlockEditorState::on_copy))
392 .on_action(window.listener_for(s, BlockEditorState::on_cut))
393 .on_action(window.listener_for(s, BlockEditorState::on_paste))
394 .on_action(window.listener_for(s, BlockEditorState::on_native_paste))
395 .on_action(window.listener_for(s, BlockEditorState::on_paste_plain))
396 .children(super::table_ui::render_menu(&self.state, cx))
397 .child(EditorRoot {
398 state: self.state.clone(),
399 child: Some(
400 div()
401 .w_full()
402 .refine_style(&self.style)
403 .child(
404 div().relative().w_full().children(children).child(
405 canvas(
406 move |bounds, _, _| measured_content.set(bounds),
407 |_, _, _, _| {},
408 )
409 .absolute()
410 .top_0()
411 .left_0()
412 .size_full(),
413 ),
414 )
415 .into_any_element(),
416 ),
417 content_bounds,
418 padding: root_style.padding,
419 typography,
420 caret_was_visible,
421 backgrounds: Some(backgrounds),
422 snapshot,
423 style,
424 })
425 }
426}
427
428type LeafBackgrounds = HashMap<u64, Vec<(Range<usize>, Hsla)>>;
429
430struct EditorRoot {
431 state: Entity<BlockEditorState>,
432 child: Option<AnyElement>,
433 backgrounds: Option<LeafBackgrounds>,
434 snapshot: Rc<Snapshot>,
435 style: BlockEditorStyle,
436 content_bounds: Rc<Cell<Bounds<Pixels>>>,
437 padding: Edges<DefiniteLength>,
438 typography: TextStyle,
439 caret_was_visible: bool,
440}
441
442impl IntoElement for EditorRoot {
443 type Element = Self;
444 fn into_element(self) -> Self::Element {
445 self
446 }
447}
448
449pub(crate) struct Prepaint {
450 hitbox: Hitbox,
451}
452
453impl EditorRoot {
454 fn reveal_caret(&self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {
458 let state = self.state.read(cx);
459 if !state.reveal.get() {
460 return;
461 }
462 let (Some(handle), Some(pos)) = (state.scroll_handle.clone(), state.model.caret()) else {
463 state.reveal.set(false);
464 return;
465 };
466 let Some(caret) = state
467 .frame
468 .borrow()
469 .caret_bounds(pos, self.style.caret_width())
470 else {
471 return;
472 };
473 state.reveal.set(false);
474 let padding = self
477 .padding
478 .to_pixels(bounds.size.map(Into::into), window.rem_size());
479 let mut view = handle.bounds();
480 view.origin.y += padding.top;
481 view.size.height = (view.size.height - padding.top - padding.bottom).max(Pixels::ZERO);
482 let mut offset = handle.offset();
483 if caret.top() < view.top() {
484 offset.y += view.top() - caret.top();
485 } else if caret.bottom() > view.bottom() {
486 offset.y -= caret.bottom() - view.bottom();
487 } else {
488 return;
489 }
490 handle.set_offset(offset);
491 window.refresh();
492 }
493}
494
495impl Element for EditorRoot {
496 type RequestLayoutState = AnyElement;
497 type PrepaintState = Prepaint;
498
499 fn id(&self) -> Option<ElementId> {
500 None
501 }
502
503 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
504 None
505 }
506
507 fn request_layout(
508 &mut self,
509 _: Option<&GlobalElementId>,
510 _: Option<&InspectorElementId>,
511 window: &mut Window,
512 cx: &mut App,
513 ) -> (LayoutId, AnyElement) {
514 let mut child = self
515 .child
516 .take()
517 .unwrap_or_else(|| div().into_any_element());
518 let layout_id = child.request_layout(window, cx);
519 (layout_id, child)
520 }
521
522 fn prepaint(
523 &mut self,
524 _: Option<&GlobalElementId>,
525 _: Option<&InspectorElementId>,
526 bounds: Bounds<Pixels>,
527 child: &mut AnyElement,
528 window: &mut Window,
529 cx: &mut App,
530 ) -> Prepaint {
531 if let Some(backgrounds) = self.backgrounds.take() {
532 let (_, geometries) = collect_leaf_geometry(backgrounds, || child.prepaint(window, cx));
533 let mut frame = super::frame::into_frame(
535 geometries,
536 self.snapshot.leaves.clone(),
537 &self.snapshot.tables,
538 );
539 frame.content = Some(self.content_bounds.get());
540 {
541 let state = self.state.read(cx);
542 let doc = &state.model.doc;
543 let shapes: Vec<super::frame::TableShape> = self
544 .snapshot
545 .tables
546 .iter()
547 .map(|(table, _)| super::frame::TableShape {
548 table: *table,
549 grid: super::table::grid(doc, *table),
550 aligns: match doc.kind(*table) {
551 Some(super::kind::BlockKindData::Table { aligns }) => aligns.clone(),
552 _ => Vec::new(),
553 },
554 })
555 .collect();
556 let pad = window.rem_size() * 0.75;
558 let right = frame.content.map(|c| c.right());
559 frame.layout_tables(&shapes, pad, right);
560 }
561 *self.state.read(cx).frame.borrow_mut() = frame;
562 let mask = window.content_mask().bounds;
563 let content = self.content_bounds.get();
564 let cull = self.state.read(cx).cull.clone();
565 let geometry_changed = cull.borrow_mut().set_geometry(
566 content.size.width,
567 window.rem_size(),
568 &self.typography,
569 );
570 if geometry_changed {
573 if self.caret_was_visible {
574 self.state.read(cx).reveal.set(true);
575 }
576 } else {
577 self.reveal_caret(bounds, window, cx);
578 }
579 if geometry_changed || cull.borrow_mut().finish_frame(content, mask) {
580 let state = self.state.clone();
582 window.on_next_frame(move |_, cx| state.update(cx, |_, cx| cx.notify()));
583 }
584 } else {
585 child.prepaint(window, cx);
586 }
587 Prepaint {
588 hitbox: window.insert_hitbox(bounds, HitboxBehavior::Normal),
589 }
590 }
591
592 fn paint(
593 &mut self,
594 _: Option<&GlobalElementId>,
595 _: Option<&InspectorElementId>,
596 bounds: Bounds<Pixels>,
597 child: &mut AnyElement,
598 prepaint: &mut Prepaint,
599 window: &mut Window,
600 cx: &mut App,
601 ) {
602 let state = self.state.read(cx);
603 let focus = state.focus_handle.clone();
604 let collapsed = state.selection_ranges().is_empty();
605 let marked = state.marked_range();
606 let caret = state.model.caret();
607 let show_caret = state.show_caret(window, cx) && collapsed;
608 let frame = state.frame.clone();
609 let menu_line = state.table_menu.as_ref().map(|m| (m.table, m.line));
610 let grips = state.table_grips_at(window.mouse_position());
611 let selected_area = state.model.selected_blocks().and_then(|(parent, range)| {
613 let doc = &state.model.doc;
614 let ids = doc.children(parent).get(range)?.to_vec();
615 let frame = frame.borrow();
616 frame
617 .leaves
618 .iter()
619 .filter(|leaf| {
620 let path = doc.path(leaf.info.block);
621 ids.iter()
622 .any(|id| *id == leaf.info.block || path.contains(id))
623 })
624 .map(|leaf| leaf.bounds())
625 .reduce(|a, b| a.union(&b))
626 });
627
628 window.handle_input(
629 &focus,
630 ElementInputHandler::new(bounds, self.state.clone()),
631 cx,
632 );
633 child.paint(window, cx);
634
635 let frame = frame.borrow();
636 let style = &self.style;
637 let mouse = window.mouse_position();
640 let hovered = frame
641 .tables
642 .iter()
643 .map(|(_, b)| *b)
644 .chain(
645 frame
646 .leaves
647 .iter()
648 .filter(|l| state_plain(&self.state, l.info.block, cx))
649 .map(|l| l.bounds()),
650 )
651 .find(|b| {
652 mouse.y >= b.top()
653 && mouse.y < b.bottom()
654 && mouse.x >= b.left() - px(24.)
655 && mouse.x < b.right()
656 });
657 if let Some(b) = hovered {
658 let grip = Bounds::new(
659 point(b.left() - px(14.), b.top() + px(2.)),
660 size(px(4.), px(16.).min(b.size.height)),
661 );
662 window.paint_quad(fill(grip, style.caret().opacity(0.35)).corner_radii(px(2.)));
663 }
664 for (table, line, grip) in grips {
667 let open = menu_line == Some((table, line));
668 let alpha = if open { 0.8 } else { 0.35 };
669 window.paint_quad(fill(grip, style.caret().opacity(alpha)).corner_radii(px(2.)));
670 }
671 if let Some(area) = selected_area {
672 let area = area.dilate(px(4.));
673 window.paint_quad(gpui::quad(
674 area,
675 px(4.),
676 style.selection().opacity(0.35),
677 px(2.),
678 style.caret(),
679 gpui::BorderStyle::Solid,
680 ));
681 }
682 if let Some(line) = self.state.read(cx).drop_indicator() {
684 window.paint_quad(fill(line, style.caret()).corner_radii(px(1.)));
685 }
686 if let Some((block, range)) = marked
687 && let Some(leaf) = frame.leaf(block)
688 {
689 let a = leaf.caret_point(range.start);
690 let b = leaf.caret_point(range.end);
691 let thickness = style.caret_width();
692 let underline = Bounds::new(
693 point(a.x, a.y + leaf.line_height() - thickness),
694 size((b.x - a.x).max(thickness), thickness),
695 );
696 window.paint_quad(fill(underline, style.composition_underline()));
697 }
698 if show_caret
699 && let Some(pos) = caret
700 && let Some(b) = frame.caret_bounds(pos, style.caret_width())
701 {
702 let b = crate::support::snap_caret_to_device_pixels(b, window.scale_factor());
705 window.paint_quad(fill(b, style.caret()));
706 }
707 drop(frame);
708
709 let hitbox = prepaint.hitbox.clone();
711 let entity = self.state.clone();
712 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
713 if phase != DispatchPhase::Bubble
714 || event.button != MouseButton::Left
715 || !hitbox.is_hovered(window)
716 {
717 return;
718 }
719 if !event.modifiers.shift
722 && entity.update(cx, |state, cx| {
723 let hit = state.click_table_grip(event.position, cx);
724 if !hit {
725 state.close_table_menu(cx);
726 }
727 hit
728 })
729 {
730 let focus = entity.read(cx).focus_handle.clone();
731 window.focus(&focus, cx);
732 cx.stop_propagation();
733 return;
734 }
735 if !event.modifiers.shift
738 && entity.update(cx, |state, cx| state.click_checkbox(event.position, cx))
739 {
740 cx.stop_propagation();
741 return;
742 }
743 entity.update(cx, |state, cx| {
744 let focus = state.focus_handle.clone();
745 window.focus(&focus, cx);
746 if event.modifiers.secondary() && state.follow_link_at(event.position, cx) {
747 return;
748 }
749 state.dragging = true;
750 if !event.modifiers.shift {
751 state.arm_block_drag(event.position);
752 }
753 state.mouse_at(event.position, event.modifiers.shift, cx);
754 if !event.modifiers.shift && event.click_count >= 2 {
755 state.select_unit(event.click_count >= 3, cx);
756 }
757 });
758 });
759 let entity = self.state.clone();
760 window.on_mouse_event(move |event: &MouseMoveEvent, phase, _, cx| {
761 if phase != DispatchPhase::Bubble {
762 return;
763 }
764 if event.pressed_button.is_none() {
765 entity.update(cx, |state, cx| {
766 state.hover_at(event.position, event.modifiers.secondary(), cx)
767 });
768 return;
769 }
770 if event.pressed_button != Some(MouseButton::Left) {
771 return;
772 }
773 entity.update(cx, |state, cx| {
774 if state.block_drag_to(event.position, cx) {
775 return;
776 }
777 if state.dragging {
778 state.mouse_at(event.position, true, cx);
779 }
780 });
781 });
782 let entity = self.state.clone();
783 window.on_mouse_event(move |_: &MouseUpEvent, phase, _, cx| {
784 if phase == DispatchPhase::Bubble {
785 entity.update(cx, |state, cx| {
786 state.dragging = false;
787 state.finish_block_drag(cx);
788 });
789 }
790 });
791 }
792}
793
794fn state_plain(state: &Entity<BlockEditorState>, block: BlockId, cx: &App) -> bool {
795 state.read(cx).model.doc.plain(block).is_some()
796}