Skip to main content

kui_core/runtime/
select_api.rs

1//! `Core`'s selection surface: what a host, a binding or the pointer
2//! model does to the window's selection, and what it reads back.
3//!
4//! Every query here answers from the frame that finished while one is
5//! being built, the way `text_hit` and `caret_rect` do: a press is made
6//! against the layout the user could see, not against the one being
7//! assembled in response to it.
8
9use crate::geom::{Rect, Vec2};
10use crate::input::{EditKey, Mods};
11use crate::key::Key;
12use crate::runtime::Core;
13use crate::select::{
14    CellEnd, CellSelection, CopyRequest, DragAnchor, Endpoint, Grain, RangeEnd, SelectDrag,
15    Selection, grained_edges, unbuilt_row_is_after,
16};
17use crate::tree::NodeContent;
18use crate::value::Value;
19
20impl Core {
21    /// The window's text selection outside an editor, if it has one.
22    pub fn selection(&self) -> Option<Selection> {
23        self.selection
24    }
25
26    /// The window's selection when it lives in a `cells` grid.
27    pub fn cell_selection(&self) -> Option<CellSelection> {
28        self.cell_selection
29    }
30
31    /// Sets it, clearing whatever else the window had selected.
32    pub fn set_cell_selection(&mut self, sel: CellSelection) {
33        self.cell_selection = Some(sel);
34        self.selection = None;
35        self.collapse_editor_selection();
36    }
37
38    /// Where `point` lands in the grid `key` drew, as an absolute line and
39    /// a column — the address a cell selection's end is. `None` when the
40    /// node drew no grid.
41    pub fn cell_at(&mut self, key: Key, point: Vec2) -> Option<CellEnd> {
42        let (row, col) = self.cell_row_col(key, point)?;
43        let id = self.cells_id_of(key)?;
44        Some(CellEnd::new(
45            self.cells.origin_line(id, self.building) + row as u64,
46            col,
47        ))
48    }
49
50    /// The grid a keyed node drew this frame, if it drew one.
51    pub(crate) fn cells_id_of(&mut self, key: Key) -> Option<crate::cells::CellsId> {
52        self.cells_id_of_ref(key)
53    }
54
55    pub(crate) fn cells_id_of_ref(&self, key: Key) -> Option<crate::cells::CellsId> {
56        // Off the cell store rather than off the tree: a host reading the
57        // selection from inside its own `view` is asking while this
58        // frame's tree is half-built, and the grid it means is last
59        // frame's — the same rule the text places follow.
60        self.cells.find(key, self.building)
61    }
62
63    /// The top-left of the cells themselves, which is the node's box
64    /// moved in by its padding — the same corner the grid is painted
65    /// from, so a hit test and the glyphs agree about where row 0 is.
66    pub(crate) fn cells_origin(&self, i: usize) -> Vec2 {
67        let pad = self.tree.specs[i].layout.padding;
68        let pos = self.tree.pos[i];
69        Vec2::new(pos.x + pad.l, pos.y + pad.t)
70    }
71
72    /// Where `point` lands in that grid, as a row and column clamped to
73    /// it — the one arithmetic, so the `cell` a click's payload carries
74    /// (`attach_pointer`), a selection and an app's own hit test agree.
75    pub(crate) fn cell_row_col(&mut self, key: Key, point: Vec2) -> Option<(usize, usize)> {
76        // Off the tree, not off the store: a hit test needs the node's
77        // box, and only a built frame has one. Which is also why this one
78        // reads the drawn frame rather than `building` — nothing hit-tests
79        // a frame that is still being declared.
80        let i = self.tree.index_of(key)?;
81        let crate::tree::NodeContent::Cells(id) = self.tree.content[i] else {
82            return None;
83        };
84        // A grid is laid out upright: a point over a turned one is read
85        // there (ADR 0043).
86        let point = self.unturned_at(i, point);
87        let cell = {
88            let sess = &mut *self.session.state();
89            self.cells
90                .cell_size(id, false, &sess.resources, &mut sess.fonts)
91        };
92        let (rows, cols) = self.cells.dims(id, false);
93        let pos = self.cells_origin(i);
94        let col = ((point.x - pos.x) / cell.w.max(f32::EPSILON)).floor();
95        let row = ((point.y - pos.y) / cell.h.max(f32::EPSILON)).floor();
96        Some((
97            (row.max(0.0) as usize).min(rows.saturating_sub(1)),
98            (col.max(0.0) as usize).min(cols.saturating_sub(1)),
99        ))
100    }
101
102    /// Selects the word under `point` in the grid `key` — a double click
103    /// on a terminal. Answers the span it took, as an absolute line and a
104    /// half-open column range, so a drag that follows can round to it.
105    pub fn select_word_in_cells(
106        &mut self,
107        key: Key,
108        point: Vec2,
109        block: bool,
110    ) -> Option<(u64, usize, usize)> {
111        let (row, col) = self.cell_row_col(key, point)?;
112        let id = self.cells_id_of_ref(key)?;
113        let (from, to) = self.cells.word_at(id, row, col, self.building)?;
114        let line = self.cells.origin_line(id, self.building) + row as u64;
115        self.set_cell_selection(
116            CellSelection::new(key, CellEnd::new(line, from), CellEnd::new(line, to)).block(block),
117        );
118        Some((line, from, to))
119    }
120
121    /// Selects the whole row under `point` — a triple click. Edge to edge,
122    /// the way a line in the middle of a linewise selection runs; the
123    /// copy is what trims the blanks off the end of it.
124    pub fn select_line_in_cells(
125        &mut self,
126        key: Key,
127        point: Vec2,
128        block: bool,
129    ) -> Option<(u64, usize, usize)> {
130        let (row, _) = self.cell_row_col(key, point)?;
131        let id = self.cells_id_of_ref(key)?;
132        let (_, cols) = self.cells.dims(id, self.building);
133        let line = self.cells.origin_line(id, self.building) + row as u64;
134        self.set_cell_selection(
135            CellSelection::new(key, CellEnd::new(line, 0), CellEnd::new(line, cols)).block(block),
136        );
137        Some((line, 0, cols))
138    }
139
140    /// Starts a cell selection at `point` in the grid `key`.
141    pub fn begin_cell_selection(&mut self, key: Key, point: Vec2, block: bool) -> bool {
142        let Some(at) = self.cell_at(key, point) else {
143            return false;
144        };
145        self.set_cell_selection(CellSelection::new(key, at, at).block(block));
146        true
147    }
148
149    /// Moves the live end of a cell selection to `point`.
150    pub fn extend_cell_selection(&mut self, point: Vec2) -> bool {
151        let Some(sel) = self.cell_selection else {
152            return false;
153        };
154        let Some(focus) = self.cell_at(sel.node, point) else {
155            return false;
156        };
157        if focus == sel.focus {
158            return false;
159        }
160        self.cell_selection = Some(CellSelection { focus, ..sel });
161        true
162    }
163
164    /// Moves the live end of a cell selection by whatever the press armed
165    /// it with: cells, words, or whole rows. The *anchor* rounds outwards
166    /// too, so a double-click-drag that turns back on itself keeps the
167    /// word it started in whole — the same rule the text side follows.
168    pub(crate) fn extend_cell_selection_grained(
169        &mut self,
170        drag: crate::select::SelectDrag,
171        point: Vec2,
172    ) -> bool {
173        let Some(crate::select::DragAnchor::Cells(a_line, a_from, a_to)) =
174            drag.anchor.filter(|_| drag.grain != Grain::Char)
175        else {
176            return self.extend_cell_selection(point);
177        };
178        let Some(sel) = self.cell_selection else {
179            return false;
180        };
181        let Some((row, col)) = self.cell_row_col(sel.node, point) else {
182            return false;
183        };
184        let Some(id) = self.cells_id_of_ref(sel.node) else {
185            return false;
186        };
187        let (_, cols) = self.cells.dims(id, self.building);
188        let line = self.cells.origin_line(id, self.building) + row as u64;
189        // The unit under the live end.
190        let (f_from, f_to) = match drag.grain {
191            Grain::Word => match self.cells.word_at(id, row, col, self.building) {
192                Some(span) => span,
193                None => return false,
194            },
195            _ => (0, cols),
196        };
197        // A block selection is ordered by column alone, because that is
198        // the only axis its two ends disagree on.
199        let backwards = if sel.block {
200            col < a_from
201        } else {
202            (line, col) < (a_line, a_from)
203        };
204        let (a_edge, f_edge) = grained_edges((a_from, a_to), (f_from, f_to), backwards);
205        let next = CellSelection::new(
206            sel.node,
207            CellEnd::new(a_line, a_edge),
208            CellEnd::new(line, f_edge),
209        )
210        .block(sel.block);
211        if next == sel {
212            return false;
213        }
214        self.cell_selection = Some(next);
215        true
216    }
217
218    /// The selected cells as text: one line per grid row it covers, each
219    /// with its trailing blanks trimmed — the rule that makes a copied
220    /// screen paste like text instead of like a rectangle of spaces.
221    ///
222    /// Only what the grid *holds*: a selection whose ends reach into the
223    /// scrollback copies the lines on screen, because the lines behind it
224    /// were never handed to the core.
225    pub fn cell_selection_text(&self) -> Option<String> {
226        let sel = self.cell_selection?;
227        let id = self.cells_id_of_ref(sel.node)?;
228        let (rows, cols) = self.cells.dims(id, self.building);
229        let origin = self.cells.origin_line(id, self.building);
230        let mut out = String::new();
231        let mut first = true;
232        let mut any = false;
233        for row in 0..rows {
234            let line = origin + row as u64;
235            let Some((from, to)) = sel.cols_on(line, cols) else {
236                continue;
237            };
238            if !first {
239                out.push('\n');
240            }
241            first = false;
242            any = true;
243            let mut text = String::new();
244            for col in from..to {
245                match self.cells.cell_char(id, row, col, self.building) {
246                    // The cell after a wide glyph is the app's spacer, and
247                    // copying it would put a blank in the middle of a word.
248                    Some((_, true)) => {}
249                    Some((ch, _)) => text.push(ch),
250                    None => {}
251                }
252            }
253            out.push_str(text.trim_end());
254        }
255        // Nothing of the grid is inside the selection — it is scrolled
256        // away entirely — so there is nothing to copy. `Some("")` here
257        // would let Cmd-C wipe whatever was on the clipboard.
258        any.then_some(out)
259    }
260
261    /// Sets it. The scope is a node that declared `selectable`; the two
262    /// ends are addresses inside it (a node key and a byte in that node's
263    /// own text). Ends the frame cannot resolve paint nothing rather than
264    /// something else, so setting a selection against a tree that has
265    /// since changed is safe.
266    ///
267    /// Clears the focused editor's own selection: there is one selection
268    /// per window.
269    pub fn set_selection(&mut self, sel: Selection) {
270        self.selection = Some(sel);
271        self.cell_selection = None;
272        self.collapse_editor_selection();
273    }
274
275    /// Drops the selection. Returns whether there was one.
276    pub fn clear_selection(&mut self) -> bool {
277        self.selection.take().is_some() | self.cell_selection.take().is_some()
278    }
279
280    /// Selects every run in `scope`, first byte to last — what Select All
281    /// does inside one. `false` when the scope drew no text.
282    pub fn select_all_in(&mut self, scope: Key) -> bool {
283        // A grid selects in cells: the whole screen it was given, from
284        // its first absolute line to its last.
285        if let Some(id) = self.cells_id_of_ref(scope) {
286            let (rows, cols) = self.cells.dims(id, self.building);
287            if rows == 0 || cols == 0 {
288                return false;
289            }
290            let origin = self.cells.origin_line(id, self.building);
291            self.set_cell_selection(CellSelection::new(
292                scope,
293                CellEnd::new(origin, 0),
294                CellEnd::new(origin + rows as u64 - 1, cols),
295            ));
296            return true;
297        }
298        // A virtual list selects its *data*: rows `0..count`, whether the
299        // frame built them or not (ADR 0017, tier 3). An end in a row the
300        // frame built is that row's first or last run, as a drag would
301        // have made it; one in a row it did not build is placed by its
302        // index alone, on a node no run matches — the scope's — with the
303        // last row's end spelled `ROW_END`, since nothing here knows how
304        // long a row it never laid out is.
305        if let Some(count) = self.row_count_in(scope) {
306            if count == 0 {
307                return false;
308            }
309            let last_row = count - 1;
310            let runs = self.text.scope_runs(scope, self.building);
311            let (mut first, mut last) = (None, None);
312            for run in &runs {
313                match self.row_of(run.place.key) {
314                    Some(0) if first.is_none() => first = Some(run.place.key),
315                    Some(r) if r == last_row => {
316                        last = Some((run.place.key, run.text.content().len()));
317                    }
318                    _ => {}
319                }
320            }
321            drop(runs);
322            let anchor = first.map_or(Endpoint::new(scope, 0), |k| Endpoint::new(k, 0));
323            let focus = last.map_or(Endpoint::new(scope, crate::select::ROW_END), |(k, len)| {
324                Endpoint::new(k, len)
325            });
326            self.set_selection(Selection::new(
327                scope,
328                anchor.in_row(Some(0)),
329                focus.in_row(Some(last_row)),
330            ));
331            return true;
332        }
333        let runs = self.text.scope_runs(scope, self.building);
334        let (Some(first), Some(last)) = (runs.first(), runs.last()) else {
335            return false;
336        };
337        let (fk, lk, llen) = (first.place.key, last.place.key, last.text.content().len());
338        drop(runs);
339        let sel = Selection::new(
340            scope,
341            Endpoint::new(fk, 0).in_row(self.row_of(fk)),
342            Endpoint::new(lk, llen).in_row(self.row_of(lk)),
343        );
344        self.set_selection(sel);
345        true
346    }
347
348    /// The `rowCount` declared on `scope` or on a node inside it, if any:
349    /// the size of the virtual list a Select All in that scope spans. The
350    /// first in tree order where two lists share one scope, which is not
351    /// a shape Select All can serve anyway.
352    fn row_count_in(&self, scope: Key) -> Option<u64> {
353        if self.tree.row_counts.is_empty() {
354            return None;
355        }
356        let top = self.tree.index_of(scope)?;
357        self.tree
358            .row_counts
359            .iter()
360            .find(|(node, _)| {
361                let mut i = *node as usize;
362                loop {
363                    if i == top {
364                        return true;
365                    }
366                    match self.tree.parent[i] {
367                        crate::tree::NIL => return false,
368                        p => i = p as usize,
369                    }
370                }
371            })
372            .map(|(_, n)| *n)
373    }
374
375    /// Where `point` (logical viewport px) lands inside `scope`, as the
376    /// address a selection end is made of. `None` when the scope drew
377    /// nothing the pointer could land in — an off-screen run is part of
378    /// the scope's text but is under no pointer.
379    pub fn selection_hit(&self, scope: Key, point: Vec2) -> Option<Endpoint> {
380        // A scope's text is laid out upright: a point over a turned scope
381        // is read there (ADR 0043).
382        let point = self.unturned(scope, point);
383        let (node, byte) = self.text.scope_hit(scope, point, self.building)?;
384        Some(Endpoint::new(node, byte).in_row(self.row_of(node)))
385    }
386
387    /// The virtualised row a node sits in, if any — what an endpoint keeps
388    /// so it can be placed after its row stops being built.
389    pub(crate) fn row_of(&self, node: Key) -> Option<u64> {
390        // Off the tree the key is looked up in, rather than off the map
391        // the last *emission* filled: during a build those are two
392        // different trees, and an index into one says nothing about the
393        // other. Free where it does not apply — a frame with no
394        // virtualised rows answers on the first line.
395        if self.tree.indexed.is_empty() {
396            return None;
397        }
398        let mut i = self.tree.index_of(node)?;
399        loop {
400            if let Some(&(_, row)) = self.tree.indexed.iter().find(|(n, _)| *n as usize == i) {
401                return Some(row);
402            }
403            match self.tree.parent[i] {
404                crate::tree::NIL => return None,
405                p => i = p as usize,
406            }
407        }
408    }
409
410    /// The selected text, assembled across every run the selection
411    /// covers — including runs the frame built but never drew, which is
412    /// what makes a selection that ran past the bottom of a scroller copy
413    /// what the reader dragged over.
414    ///
415    /// `None` with no selection; an empty string when the selection is
416    /// empty or its ends no longer resolve.
417    pub fn selection_text(&self) -> Option<String> {
418        let sel = self.selection?;
419        let prev = self.building;
420        let from = self
421            .text
422            .scope_offset(sel.scope, sel.anchor.node, sel.anchor.byte, prev)?;
423        let to = self
424            .text
425            .scope_offset(sel.scope, sel.focus.node, sel.focus.byte, prev)?;
426        Some(self.text.scope_slice(sel.scope, from, to, prev))
427    }
428
429    /// The box the window's selection occupies, logical viewport px —
430    /// what a platform panel about that selection is anchored to. The
431    /// union of the drawn runs it covers, so a selection that runs off
432    /// the screen is anchored by the part the reader can see.
433    ///
434    /// `None` with no selection, an empty one, or one whose runs the
435    /// frame never drew.
436    pub fn selection_rect(&self) -> Option<Rect> {
437        let sel = self.selection?;
438        let prev = self.building;
439        let from = self
440            .text
441            .scope_offset(sel.scope, sel.anchor.node, sel.anchor.byte, prev)?;
442        let to = self
443            .text
444            .scope_offset(sel.scope, sel.focus.node, sel.focus.byte, prev)?;
445        self.text.scope_selection_rect(sel.scope, from, to, prev)
446    }
447
448    /// Where a platform panel about the selection should point: the
449    /// baseline origin of its first line, logical viewport px. See
450    /// `TextSystem::scope_selection_anchor`.
451    pub fn selection_anchor(&self) -> Option<Vec2> {
452        let sel = self.selection?;
453        let prev = self.building;
454        let from = self
455            .text
456            .scope_offset(sel.scope, sel.anchor.node, sel.anchor.byte, prev)?;
457        let to = self
458            .text
459            .scope_offset(sel.scope, sel.focus.node, sel.focus.byte, prev)?;
460        self.text.scope_selection_anchor(sel.scope, from, to, prev)
461    }
462
463    /// The selection as HTML — the same text `selection_text` gives, with
464    /// the bold, the italic and the span colours it was declared with.
465    /// `None` with no text selection; a cells
466    /// selection has no styling to carry and answers `None` too.
467    ///
468    /// Meant as the *second* clipboard flavour, beside the plain text and
469    /// never instead of it: an editor that understands HTML takes the
470    /// formatting, and everything else takes the words.
471    pub fn selection_html(&self) -> Option<String> {
472        let sel = self.selection?;
473        let prev = self.building;
474        let from = self
475            .text
476            .scope_offset(sel.scope, sel.anchor.node, sel.anchor.byte, prev)?;
477        let to = self
478            .text
479            .scope_offset(sel.scope, sel.focus.node, sel.focus.byte, prev)?;
480        let html = self.text.scope_html(sel.scope, from, to, prev);
481        (!html.is_empty()).then_some(html)
482    }
483
484    /// The selection's two ends as the app's own addresses — the data
485    /// index of the virtualised row each is in, and the byte inside that
486    /// row's text — **in reading order**: `from` precedes `to` whichever
487    /// way the drag was made, so an app answering a `selectionrange` ask
488    /// can iterate `from..=to` (the clipboard examples do). `None` when
489    /// there is no selection, or when neither end is in a virtualised row
490    /// (nothing to ask about: the core has it all). The directed pair is
491    /// [`Self::selection_ends`].
492    pub fn selection_range(&self) -> Option<(RangeEnd, RangeEnd)> {
493        let sel = self.selection?;
494        let (a, f) = (sel.anchor, sel.focus);
495        if a.row.is_none() && f.row.is_none() {
496            return None;
497        }
498        let end = |e: Endpoint| RangeEnd {
499            row: e.row,
500            byte: e.byte,
501        };
502        if self.end_precedes(sel.scope, f, a) {
503            Some((end(f), end(a)))
504        } else {
505            Some((end(a), end(f)))
506        }
507    }
508
509    /// Whether `x` comes before `y` in the scope's reading order — the
510    /// question a backwards drag makes of two ends. Both built this frame:
511    /// by their offset in the scope's concatenation, the order the drag
512    /// itself is decided by. Both in virtualised rows: by row, then byte.
513    /// One built and one in a row the frame never built: the unbuilt row
514    /// is placed after the scope's built rows or before them by the one
515    /// rule `resolve_selection` paints by (`unbuilt_row_is_after`), so
516    /// the highlight and the answer agree. Nothing to compare by: the
517    /// pair keeps its order — `false` here, since the caller asks whether
518    /// the focus precedes the anchor.
519    fn end_precedes(&self, scope: Key, x: Endpoint, y: Endpoint) -> bool {
520        let prev = self.building;
521        let ox = self.text.scope_offset(scope, x.node, x.byte, prev);
522        let oy = self.text.scope_offset(scope, y.node, y.byte, prev);
523        match (ox, oy, x.row, y.row) {
524            (Some(ox), Some(oy), ..) => ox < oy,
525            (_, _, Some(rx), Some(ry)) => (rx, x.byte) < (ry, y.byte),
526            (Some(_), None, _, Some(ry)) => unbuilt_row_is_after(ry, self.last_built_row_in(scope)),
527            (None, Some(_), Some(rx), _) => {
528                !unbuilt_row_is_after(rx, self.last_built_row_in(scope))
529            }
530            _ => false,
531        }
532    }
533
534    /// The row of the last text run the frame built inside `scope` that
535    /// carries one — what an end the frame did not build is placed
536    /// against. This scope's rows only: a second virtual list on screen
537    /// says nothing about where a row of this one sits.
538    fn last_built_row_in(&self, scope: Key) -> Option<u64> {
539        (0..self.tree.len()).rev().find_map(|i| {
540            (self.scopes.get(i).copied().flatten() == Some(scope)
541                && matches!(self.tree.content[i], NodeContent::Text(_)))
542            .then(|| self.rows.get(i).copied().flatten())
543            .flatten()
544        })
545    }
546
547    /// The selection's two ends as the drag made them — the anchor where
548    /// the press landed, the focus where the pointer is — each as the
549    /// data index of the virtualised row it is in (`None` outside every
550    /// virtualised row) and the byte inside that node's own text. The
551    /// directed pair, unlike [`Self::selection_range`]'s: what a test or
552    /// a model that mirrors the selection reads, and what says whether a
553    /// Shift-press kept the anchor. `None` with no text
554    /// selection; a grid's is `cell_selection`.
555    pub fn selection_ends(&self) -> Option<(RangeEnd, RangeEnd)> {
556        let sel = self.selection?;
557        Some((
558            RangeEnd {
559                row: sel.anchor.row,
560                byte: sel.anchor.byte,
561            },
562            RangeEnd {
563                row: sel.focus.row,
564                byte: sel.focus.byte,
565            },
566        ))
567    }
568
569    /// Whether the core can answer a copy on its own: both ends resolve
570    /// against runs this frame built.
571    fn selection_is_whole(&self) -> bool {
572        let Some(sel) = self.selection else {
573            return false;
574        };
575        let prev = self.building;
576        self.text
577            .scope_offset(sel.scope, sel.anchor.node, sel.anchor.byte, prev)
578            .is_some()
579            && self
580                .text
581                .scope_offset(sel.scope, sel.focus.node, sel.focus.byte, prev)
582                .is_some()
583    }
584
585    /// Asks for the selection as text, and says how the answer will come.
586    ///
587    /// [`CopyRequest::Ready`] is the ordinary case: everything selected is
588    /// text the core shaped, so it hands it over. [`CopyRequest::Asked`]
589    /// is a selection that reaches rows a virtual list never built — the
590    /// core posts `{kind:"selectionrange", from:{index, byte}, to:{index,
591    /// byte}}` on the scope and waits for
592    /// [`Self::answer_selection_range`], because the rows behind that gap
593    /// are the app's and only the app has them.
594    ///
595    /// The event goes out with the frame's pending events, so a host that
596    /// calls this outside `handle_input` drains `take_pending_events`
597    /// after it.
598    pub fn request_copy(&mut self) -> CopyRequest {
599        if self.selection.is_none() && self.cell_selection.is_none() {
600            return match self
601                .edit
602                .focused()
603                .and_then(|k| self.edit.copy_selection(k))
604            {
605                Some(text) => CopyRequest::Ready(text),
606                None => CopyRequest::Nothing,
607            };
608        }
609        if self.cell_selection.is_some() || self.selection_is_whole() {
610            return match self.copy_selection() {
611                Some(text) => CopyRequest::Ready(text),
612                None => CopyRequest::Nothing,
613            };
614        }
615        let Some(sel) = self.selection else {
616            return CopyRequest::Nothing;
617        };
618        let Some((from, to)) = self.selection_range() else {
619            // Not virtualised and not resolvable: nothing to ask anyone
620            // about, and nothing to hand over.
621            return CopyRequest::Nothing;
622        };
623        self.pending.push(crate::input::UiEvent {
624            // The scope's own origin: an extension that declared the
625            // list is the one that can answer for its rows.
626            origin: self
627                .tree
628                .keys
629                .iter()
630                .position(|k| *k == sel.scope)
631                .map_or(crate::tree::OriginId::HOST, |i| self.tree.origins[i]),
632            window: crate::window::WindowId::MAIN,
633            key: sel.scope,
634            payload: Value::map([
635                ("kind", Value::str("selectionrange")),
636                ("from", from.to_value()),
637                ("to", to.to_value()),
638            ]),
639            slot: None,
640        });
641        self.awaiting_selection = true;
642        CopyRequest::Asked
643    }
644
645    /// The app's answer to a `selectionrange` ask: the text for the range
646    /// it was asked about, whole. Queues it for the clipboard the way a
647    /// menu's Copy does, and is ignored when nothing asked — a stale
648    /// answer cannot overwrite what somebody copied since.
649    pub fn answer_selection_range(&mut self, text: &str) -> bool {
650        if !std::mem::take(&mut self.awaiting_selection) {
651            return false;
652        }
653        self.menu_actions
654            .push(crate::menu::MenuAction::SetClipboard {
655                text: text.to_string(),
656                html: None,
657            });
658        true
659    }
660
661    // -- The clipboard, for an app that owns its text ---------------------
662    // A key sink hears the raw `Ctrl-c` / `Ctrl-v` and brings its own
663    // bindings — and had nowhere to bind them to (backlog C33): the only
664    // ways onto the system clipboard were a menu's Copy and Paste. These
665    // are those two actions with a door on them. The clipboard stays the
666    // host's: the core never reads it, and what a paste brings back
667    // arrives as input, the way a menu's Paste does.
668
669    /// Puts `text` on the system clipboard — queued as the
670    /// `MenuAction::SetClipboard` a menu's Copy produces, for the host to
671    /// apply at its next drain (the runner's is after every input and
672    /// every frame). `html` is a second flavour beside the text for a
673    /// host that offers one, never in place of it.
674    pub fn set_clipboard(&mut self, text: impl Into<String>, html: Option<String>) {
675        self.menu_actions
676            .push(crate::menu::MenuAction::SetClipboard {
677                text: text.into(),
678                html,
679            });
680    }
681
682    /// Puts a secret on the system clipboard the way a password manager
683    /// does — queued as `MenuAction::SetClipboardSecret`,
684    /// which the runner writes marked concealed and transient, so a
685    /// clipboard manager neither shows nor keeps it. What the marks are on
686    /// each platform is on the action. The text alone: a secret has no
687    /// second flavour to offer.
688    pub fn set_clipboard_secret(&mut self, text: impl Into<String>) {
689        self.menu_actions
690            .push(crate::menu::MenuAction::SetClipboardSecret { text: text.into() });
691    }
692
693    /// Asks for what is on the clipboard — queued as the
694    /// `MenuAction::Paste` a menu's Paste produces. The host reads the
695    /// clipboard and hands the text back as `InputEvent::Paste` (or a
696    /// bare `InputEvent::Commit`), which reaches a focused editor as
697    /// typing and a focused sink as `{kind:"text", text, tag}`, with
698    /// `concealed: true` / `transient: true` beside the text
699    /// when the pasteboard marked it so — so the app that asked
700    /// inserts it the way it inserts a committed IME string, and never
701    /// sees the clipboard any other way. The read stays on the driver's
702    /// side, where the permission lives.
703    ///
704    /// One ask at a time: while a paste is outstanding — queued, or taken
705    /// by the driver and not yet answered — a second ask is dropped, so a
706    /// view that asks on every frame until the answer lands asks once.
707    /// The answer is the `Paste` (or `Commit`) the driver sends, an empty
708    /// one when the clipboard held nothing, and [`Core::awaiting_paste`]
709    /// reads the state.
710    pub fn request_paste(&mut self) {
711        self.queue_paste();
712    }
713
714    /// Whether a paste ask is outstanding: asked and not yet answered
715    /// with a `Paste` or a `Commit`.
716    pub fn awaiting_paste(&self) -> bool {
717        self.awaiting_paste
718    }
719
720    /// Asks the host for a file dialog: an Open, a Save or
721    /// a folder picker, which the host shows as the platform's own. The
722    /// answer is an event, `{kind:"files", paths, tag}` — the `drop`
723    /// payload's shape, `paths` empty when the user cancelled — delivered
724    /// to whoever asked: the host from its own view or between frames, the
725    /// extension from inside its fill. A host drains the ask with
726    /// [`Core::take_file_requests`] and answers with `InputEvent::Files`;
727    /// the runner does both.
728    ///
729    /// One ask at a time, as for a paste: while one is outstanding —
730    /// queued, or taken and not yet answered — another is dropped and this
731    /// returns false, so a view that asks every frame until the answer
732    /// lands asks once. Between frames it asks for the frame that hands
733    /// the ask to the host.
734    #[track_caller]
735    pub fn request_files(&mut self, dialog: crate::dialog::FileDialog) -> bool {
736        if self.file_ask.pending() {
737            return false;
738        }
739        self.file_ask = crate::dialog::FileAsk::Queued(dialog, self.origin);
740        if !self.building {
741            self.owe_frame("request_files");
742        }
743        true
744    }
745
746    /// Whether a file dialog asked for is still unanswered.
747    pub fn awaiting_files(&self) -> bool {
748        self.file_ask.pending()
749    }
750
751    /// The file dialog asked for and not yet taken — at most one — for the
752    /// host to show. Taking it keeps the ask outstanding until the answer.
753    pub fn take_file_requests(&mut self) -> Vec<crate::dialog::FileDialog> {
754        match std::mem::take(&mut self.file_ask) {
755            crate::dialog::FileAsk::Queued(dialog, origin) => {
756                self.file_ask = crate::dialog::FileAsk::Taken(dialog.tag.clone(), origin);
757                vec![dialog]
758            }
759            other => {
760                self.file_ask = other;
761                Vec::new()
762            }
763        }
764    }
765
766    /// The one place a `Paste` is queued — the app's ask and a menu's
767    /// Paste row alike — so the gate is one.
768    pub(crate) fn queue_paste(&mut self) {
769        if self.awaiting_paste {
770            return;
771        }
772        self.awaiting_paste = true;
773        self.menu_actions.push(crate::menu::MenuAction::Paste);
774    }
775
776    /// Starts a selection at `point` inside `scope` — the press half of a
777    /// drag-select. Both ends land together, so nothing is selected until
778    /// the pointer moves.
779    pub fn begin_selection(&mut self, scope: Key, point: Vec2) -> bool {
780        let Some(at) = self.selection_hit(scope, point) else {
781            return false;
782        };
783        self.set_selection(Selection::new(scope, at, at));
784        true
785    }
786
787    /// Moves the live end of the selection to `point` — the motion half.
788    /// The anchor stays where the press put it, so dragging back past it
789    /// selects the other way rather than starting again.
790    pub fn extend_selection(&mut self, point: Vec2) -> bool {
791        let Some(sel) = self.selection else {
792            return false;
793        };
794        let Some(focus) = self.selection_hit(sel.scope, point) else {
795            return false;
796        };
797        if focus == sel.focus {
798            return false;
799        }
800        self.selection = Some(Selection { focus, ..sel });
801        true
802    }
803
804    /// A keyboard's selection in a `selectable` scope:
805    /// Shift with an arrow, Home or End on a focused node inside `scope`
806    /// — the scope itself when it is focusable, a control inside it —
807    /// moves the selection's focus the way the stock editor's Shift-
808    /// motions move its caret: a character (a word with `mods.word`)
809    /// left or right through the scope's runs in order, Home and End to
810    /// the scope's first and last byte. Nothing selected yet, the anchor
811    /// is placed at the scope's start, so Shift-End from a freshly
812    /// focused label selects it whole. Returns whether the selection
813    /// changed. Answered from the frame that finished, like a drag; the
814    /// endpoints carry their virtual rows like every other selection, so
815    /// a copy past the built range asks the app for the text, as any
816    /// virtualised selection does. Up and Down are not motions here: a scope has no line
817    /// geometry a caret could keep a column in.
818    pub fn keyboard_select(&mut self, scope: Key, key: EditKey, mods: Mods) -> bool {
819        let prev = self.building;
820        // Every character of the scope with its offset in the
821        // concatenation and the run it is in: what the motions step
822        // through. A run's end is a word's end — the concatenation has no
823        // separator, and a copy puts a newline there.
824        let chars: Vec<(usize, char, usize)> = self
825            .text
826            .scope_runs(scope, prev)
827            .iter()
828            .enumerate()
829            .flat_map(|(n, r)| {
830                let base = r.base;
831                r.text
832                    .content()
833                    .char_indices()
834                    .map(move |(i, c)| (base + i, c, n))
835                    .collect::<Vec<_>>()
836            })
837            .collect();
838        let total = chars.last().map_or(0, |(o, c, _)| o + c.len_utf8());
839        let sel = self.selection.filter(|s| s.scope == scope);
840        let at = |e: Endpoint| self.text.scope_offset(scope, e.node, e.byte, prev);
841        let (anchor, focus) = match sel {
842            Some(s) => match (at(s.anchor), at(s.focus)) {
843                (Some(a), Some(f)) => (a, f),
844                _ => (0, 0),
845            },
846            None => (0, 0),
847        };
848        let word = mods.word;
849        let ws = |i: usize| chars[i].1.is_whitespace();
850        let next = match key {
851            EditKey::Right => {
852                let mut i = chars
853                    .iter()
854                    .position(|(o, ..)| *o >= focus)
855                    .unwrap_or(chars.len());
856                if word {
857                    while i < chars.len() && ws(i) {
858                        i += 1;
859                    }
860                    let run = chars.get(i).map(|c| c.2);
861                    while i < chars.len() && !ws(i) && Some(chars[i].2) == run {
862                        i += 1;
863                    }
864                    chars.get(i).map_or(total, |(o, ..)| *o)
865                } else {
866                    chars.get(i).map_or(total, |(o, c, _)| o + c.len_utf8())
867                }
868            }
869            EditKey::Left => {
870                let mut i = chars
871                    .iter()
872                    .rposition(|(o, ..)| *o < focus)
873                    .map_or(0, |i| i + 1);
874                if word {
875                    while i > 0 && ws(i - 1) {
876                        i -= 1;
877                    }
878                    let run = (i > 0).then(|| chars[i - 1].2);
879                    while i > 0 && !ws(i - 1) && Some(chars[i - 1].2) == run {
880                        i -= 1;
881                    }
882                    chars.get(i).map_or(total, |(o, ..)| *o)
883                } else if i == 0 {
884                    0
885                } else {
886                    chars[i - 1].0
887                }
888            }
889            EditKey::Home => 0,
890            EditKey::End => total,
891            _ => return false,
892        };
893        if sel.is_some() && next == focus {
894            return false;
895        }
896        let Some(anchor) = self.endpoint_at_offset(scope, anchor, prev) else {
897            return false;
898        };
899        let Some(focus) = self.endpoint_at_offset(scope, next, prev) else {
900            return false;
901        };
902        self.set_selection(Selection::new(scope, anchor, focus));
903        true
904    }
905
906    /// The endpoint at `offset` in the scope's concatenation: the run it
907    /// falls in and the byte inside that run's text — the last run's end
908    /// for the offset past everything. `None` for a scope with no runs.
909    fn endpoint_at_offset(&self, scope: Key, offset: usize, prev: bool) -> Option<Endpoint> {
910        let runs = self.text.scope_runs(scope, prev);
911        let run = runs
912            .iter()
913            .find(|r| {
914                let (start, end) = r.span();
915                offset >= start && offset < end
916            })
917            .or_else(|| runs.last())?;
918        let node = run.place.key;
919        let byte = offset
920            .saturating_sub(run.base)
921            .min(run.text.content().len());
922        Some(Endpoint::new(node, byte).in_row(self.row_of(node)))
923    }
924
925    /// The motion half of a drag that is moving by *words* or by whole
926    /// runs: the live end rounds outwards to its own word (or run), and so
927    /// does the anchor, so the word the press took stays whole however far
928    /// back over itself the drag turns.
929    ///
930    /// This is what a double-click-and-drag does in every text UI, and
931    /// what the stock `<edit>` gets for free from cosmic-text's
932    /// `Selection::Word`; a `selectable` scope is the one that had to be
933    /// taught.
934    pub(crate) fn extend_selection_grained(
935        &mut self,
936        drag: crate::select::SelectDrag,
937        point: Vec2,
938    ) -> bool {
939        let grain = drag.grain;
940        if grain == Grain::Char {
941            return self.extend_selection(point);
942        }
943        let Some(sel) = self.selection else {
944            return false;
945        };
946        let Some(hit) = self.selection_hit(sel.scope, point) else {
947            return false;
948        };
949        let Some(crate::select::DragAnchor::Bytes(anode, a_from, a_to)) = drag.anchor else {
950            return self.extend_selection(point);
951        };
952        // The unit under the live end, in that node's own bytes.
953        let (f_from, f_to) = match grain {
954            Grain::Word => match self
955                .text
956                .word_at(sel.scope, hit.node, hit.byte, self.building)
957            {
958                Some(span) => span,
959                None => return false,
960            },
961            _ => match self
962                .text
963                .scope_runs(sel.scope, self.building)
964                .into_iter()
965                .find(|r| r.place.key == hit.node)
966                .map(|r| r.text.content().len())
967            {
968                Some(len) => (0, len),
969                None => return false,
970            },
971        };
972        // Which side of the anchor the live end is on, in the scope's
973        // concatenation.
974        let prev = self.building;
975        let ga = self.text.scope_offset(sel.scope, anode, a_from, prev);
976        let gf = self.text.scope_offset(sel.scope, hit.node, f_from, prev);
977        let (Some(ga), Some(gf)) = (ga, gf) else {
978            return false;
979        };
980        let (arow, frow) = (self.row_of(anode), self.row_of(hit.node));
981        let (a_edge, f_edge) = grained_edges((a_from, a_to), (f_from, f_to), gf < ga);
982        let next = Selection::new(
983            sel.scope,
984            Endpoint::new(anode, a_edge).in_row(arow),
985            Endpoint::new(hit.node, f_edge).in_row(frow),
986        );
987        if Some(next) == self.selection {
988            return false;
989        }
990        self.selection = Some(next);
991        true
992    }
993
994    /// Selects the word under `point` inside `scope` — a double click,
995    /// and (on macOS) a force click. Answers the span it took, in the
996    /// node's own bytes, so a drag that follows can round to it.
997    pub fn select_word_at(&mut self, scope: Key, point: Vec2) -> Option<(Key, usize, usize)> {
998        let at = self.selection_hit(scope, point)?;
999        let (from, to) = self.text.word_at(scope, at.node, at.byte, self.building)?;
1000        let row = self.row_of(at.node);
1001        self.set_selection(Selection::new(
1002            scope,
1003            Endpoint::new(at.node, from).in_row(row),
1004            Endpoint::new(at.node, to).in_row(row),
1005        ));
1006        Some((at.node, from, to))
1007    }
1008
1009    /// Selects the whole run under `point` — a triple click, which takes
1010    /// the line a label is. Answers the span, like `select_word_at`.
1011    pub fn select_run_at(&mut self, scope: Key, point: Vec2) -> Option<(Key, usize, usize)> {
1012        let at = self.selection_hit(scope, point)?;
1013        let len = self
1014            .text
1015            .scope_runs(scope, self.building)
1016            .into_iter()
1017            .find(|r| r.place.key == at.node)
1018            .map(|r| r.text.content().len())?;
1019        let row = self.row_of(at.node);
1020        self.set_selection(Selection::new(
1021            scope,
1022            Endpoint::new(at.node, 0).in_row(row),
1023            Endpoint::new(at.node, len).in_row(row),
1024        ));
1025        Some((at.node, 0, len))
1026    }
1027
1028    /// Arms a drag-select at a press inside `scope`, with what the click
1029    /// count says it moves by (`Grain::of_clicks`) and the span the press
1030    /// itself took, which both ends of the drag round outwards to. A grid
1031    /// selects in cells and a paragraph in bytes:
1032    /// this is where the two are told apart, once, and the anchor carries
1033    /// the answer for the drag. In a grid, Alt makes it the rectangular
1034    /// selection every terminal has. With `extend` — a Shift-press in
1035    /// the scope the selection is in — the anchor is kept and the press
1036    /// is the live end. Whether a drag was armed.
1037    pub(crate) fn arm_select_drag(
1038        &mut self,
1039        scope: Key,
1040        point: Vec2,
1041        clicks: u8,
1042        extend: bool,
1043    ) -> bool {
1044        let grain = if extend {
1045            Grain::Char
1046        } else {
1047            Grain::of_clicks(clicks)
1048        };
1049        let armed = if extend {
1050            // The anchor stays; the live end is the press, and the drag
1051            // goes on from there by characters, whatever the click count
1052            // — armed whether or not the press moved the end (one on the
1053            // focus itself moves nothing and still drags on). The
1054            // window's one selection is this one, so a focused editor's
1055            // collapses as `set_selection` would have it.
1056            let drag = SelectDrag {
1057                scope,
1058                grain,
1059                anchor: None,
1060            };
1061            self.extend_select_drag(drag, point);
1062            self.collapse_editor_selection();
1063            Some(None)
1064        } else if self.cells_id_of_ref(scope).is_some() {
1065            let block = self.interaction.modifiers().alt;
1066            match grain {
1067                Grain::Char => self
1068                    .begin_cell_selection(scope, point, block)
1069                    .then_some(None),
1070                Grain::Word => self
1071                    .select_word_in_cells(scope, point, block)
1072                    .map(|(l, f, t)| Some(DragAnchor::Cells(l, f, t))),
1073                Grain::Run => self
1074                    .select_line_in_cells(scope, point, block)
1075                    .map(|(l, f, t)| Some(DragAnchor::Cells(l, f, t))),
1076            }
1077        } else {
1078            match grain {
1079                Grain::Char => self.begin_selection(scope, point).then_some(None),
1080                Grain::Word => self
1081                    .select_word_at(scope, point)
1082                    .map(|(n, f, t)| Some(DragAnchor::Bytes(n, f, t))),
1083                Grain::Run => self
1084                    .select_run_at(scope, point)
1085                    .map(|(n, f, t)| Some(DragAnchor::Bytes(n, f, t))),
1086            }
1087        };
1088        if let Some(anchor) = armed {
1089            self.select_dragging = Some(SelectDrag {
1090                scope,
1091                grain,
1092                anchor,
1093            });
1094        }
1095        armed.is_some()
1096    }
1097
1098    /// Moves the live end of the drag [`Self::arm_select_drag`] started,
1099    /// in whichever geometry its scope has.
1100    pub(crate) fn extend_select_drag(&mut self, drag: SelectDrag, point: Vec2) -> bool {
1101        if self.cells_id_of_ref(drag.scope).is_some() {
1102            self.extend_cell_selection_grained(drag, point)
1103        } else {
1104            self.extend_selection_grained(drag, point)
1105        }
1106    }
1107
1108    /// Selects the word under `point` in `scope`, whichever way the scope
1109    /// addresses itself — what a double click takes, and what a force
1110    /// click takes before it asks for a definition. `false` when there
1111    /// was no word there.
1112    pub fn select_word_under(&mut self, scope: Key, point: Vec2) -> bool {
1113        if self.cells_id_of_ref(scope).is_some() {
1114            self.select_word_in_cells(scope, point, false).is_some()
1115        } else {
1116            self.select_word_at(scope, point).is_some()
1117        }
1118    }
1119
1120    /// Collapses the focused editor's selection, so a window never shows
1121    /// two. The caret stays where it was: the editor keeps its focus and
1122    /// its insertion point, and only the highlight goes.
1123    fn collapse_editor_selection(&mut self) {
1124        if let Some(key) = self.edit.focused() {
1125            self.edit.collapse_selection(key);
1126        }
1127    }
1128}