diff --git a/src/doc.rs b/src/doc.rs index 76a456066..11f079d81 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -280,7 +280,7 @@ impl Frame { self.clear(); break; } - self.push(Point::zero(), Element::Meta(meta, self.size)); + self.prepend(Point::zero(), Element::Meta(meta, self.size)); } } diff --git a/src/ide/jump.rs b/src/ide/jump.rs index 1a96fbbed..8a19c1fde 100644 --- a/src/ide/jump.rs +++ b/src/ide/jump.rs @@ -1,17 +1,29 @@ use std::num::NonZeroUsize; -use crate::doc::{Element, Frame, Location}; -use crate::geom::Point; -use crate::syntax::{LinkedNode, Source, Span, SyntaxKind}; +use crate::doc::{Destination, Element, Frame, Location, Meta}; +use crate::geom::{Point, Size}; +use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind}; use crate::World; -/// Find the source file and byte offset for a click position. -pub fn jump_to_source<'a>( - world: &'a dyn World, - frame: &Frame, - click: Point, -) -> Option<(&'a Source, usize)> { +/// Where to [jump](jump_from_click) to. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Jump { + /// Jump to a position in a source file. + Source(SourceId, usize), + /// Jump to position in the output or to an external URL. + Dest(Destination), +} + +/// Determine where to jump to based on a click in a frame. +pub fn jump_from_click(world: &dyn World, frame: &Frame, click: Point) -> Option { for (mut pos, element) in frame.elements() { + if let Element::Group(group) = element { + // TODO: Handle transformation. + if let Some(span) = jump_from_click(world, &group.frame, click - pos) { + return Some(span); + } + } + if let Element::Text(text) = element { for glyph in &text.glyphs { if glyph.span.is_detached() { @@ -19,11 +31,11 @@ pub fn jump_to_source<'a>( } let width = glyph.x_advance.at(text.size); - if pos.x <= click.x - && pos.x + width >= click.x - && pos.y >= click.y - && pos.y - text.size <= click.y - { + if is_in_rect( + Point::new(pos.x, pos.y - text.size), + Size::new(width, text.size), + click, + ) { let source = world.source(glyph.span.source()); let node = source.find(glyph.span); let pos = if node.kind() == SyntaxKind::Text { @@ -32,16 +44,16 @@ pub fn jump_to_source<'a>( } else { node.offset() }; - return Some((source, pos)); + return Some(Jump::Source(source.id(), pos)); } pos.x += width; } } - if let Element::Group(group) = element { - if let Some(span) = jump_to_source(world, &group.frame, click - pos) { - return Some(span); + if let Element::Meta(Meta::Link(dest), size) = element { + if is_in_rect(pos, *size, click) { + return Some(Jump::Dest(dest.clone())); } } } @@ -49,8 +61,8 @@ pub fn jump_to_source<'a>( None } -/// Find the output location for a cursor position. -pub fn jump_to_preview( +/// Find the output location in the document for a cursor position. +pub fn jump_from_cursor( frames: &[Frame], source: &Source, cursor: usize, @@ -73,6 +85,13 @@ pub fn jump_to_preview( /// Find the position of a span in a frame. fn find_in_frame(frame: &Frame, span: Span) -> Option { for (mut pos, element) in frame.elements() { + if let Element::Group(group) = element { + // TODO: Handle transformation. + if let Some(point) = find_in_frame(&group.frame, span) { + return Some(point + pos); + } + } + if let Element::Text(text) = element { for glyph in &text.glyphs { if glyph.span == span { @@ -81,13 +100,16 @@ fn find_in_frame(frame: &Frame, span: Span) -> Option { pos.x += glyph.x_advance.at(text.size); } } - - if let Element::Group(group) = element { - if let Some(point) = find_in_frame(&group.frame, span) { - return Some(point + pos); - } - } } None } + +/// Whether a rectangle with the given size at the given position contains the +/// click position. +fn is_in_rect(pos: Point, size: Size, click: Point) -> bool { + pos.x <= click.x + && pos.x + size.x >= click.x + && pos.y <= click.y + && pos.y + size.y >= click.y +}