Repository: typst/typst
File: crates/typst-ide/src/jump.rs (Rust)

Code before:
/// Find the output location in the document for a cursor position.
pub fn jump_from_cursor(
    document: &Document,
    source: &Source,
    cursor: usize,
) -> Option<Position> {
    fn is_text(node: &LinkedNode) -> bool {
        node.get().kind() == SyntaxKind::Text
    }

    let root = LinkedNode::new(source.root());
    let node = root
        .leaf_at(cursor, Side::Before)
        .filter(is_text)
        .or_else(|| root.leaf_at(cursor, Side::After).filter(is_text))?;

    let span = node.span();
    for (i, page) in document.pages.iter().enumerate() {
        if let Some(point) = find_in_frame(&page.frame, span) {
            return Some(Position { page: NonZeroUsize::new(i + 1).unwrap(), point });
        }
    }

    None
}

/// Find the position of a span in a frame.
fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> {
    for (mut pos, item) in frame.items() {
        if let FrameItem::Group(group) = item {
            // TODO: Handle transformation.
            if let Some(point) = find_in_frame(&group.frame, span) {
                return Some(point + pos);
            }
        }

        if let FrameItem::Text(text) = item {
            for glyph in &text.glyphs {
                if glyph.span.0 == span {
                    return Some(pos);
                }
                pos.x += glyph.x_advance.at(text.size);
            }
        }
    }

    None
}

/// Whether a rectangle with the given size at the given position contains the
/// click position.

Review comment (suggestion):
I think if we're already doing this, we probably want to find multiple hits in the same frame as well. So `find_in_frame` could take an `&mut Vec<Position>` as its first argument or something like that (would need to pass the page number down for this).

Code after:
/// Find the output location in the document for a cursor position.
pub fn jump_from_cursor(
    document: &Document,
    source: &Source,
    cursor: usize,
) -> Vec<Position> {
    fn is_text(node: &LinkedNode) -> bool {
        node.get().kind() == SyntaxKind::Text
    }

    let root = LinkedNode::new(source.root());
    let Some(node) = root
        .leaf_at(cursor, Side::Before)
        .filter(is_text)
        .or_else(|| root.leaf_at(cursor, Side::After).filter(is_text))
    else {
        return vec![];
    };

    let span = node.span();
    document
        .pages
        .iter()
        .enumerate()
        .filter_map(|(i, page)| {
            find_in_frame(&page.frame, span)
                .map(|point| Position { page: NonZeroUsize::new(i + 1).unwrap(), point })
        })
        .collect()
}

/// Find the position of a span in a frame.
fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> {
    for (mut pos, item) in frame.items() {
        if let FrameItem::Group(group) = item {
            // TODO: Handle transformation.
            if let Some(point) = find_in_frame(&group.frame, span) {
                return Some(point + pos);
            }
        }

        if let FrameItem::Text(text) = item {
            for glyph in &text.glyphs {
                if glyph.span.0 == span {
                    return Some(pos);
                }
                pos.x += glyph.x_advance.at(text.size);
            }
        }
    }