diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-11 23:28:35 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-11 23:29:32 +0100 |
| commit | ca6edf5283c258d8410134d678347977cb273cdd (patch) | |
| tree | b2b46ba70c054b0cbdefa06edbc5fd999cddb1fa /src | |
| parent | 1a390deaea040191cf0e5937bd8e1427b49db71b (diff) | |
Jump to source and preview
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc.rs | 9 | ||||
| -rw-r--r-- | src/eval/func.rs | 6 | ||||
| -rw-r--r-- | src/ide/jump.rs | 93 | ||||
| -rw-r--r-- | src/ide/mod.rs | 2 | ||||
| -rw-r--r-- | src/model/content.rs | 6 | ||||
| -rw-r--r-- | src/syntax/node.rs | 75 | ||||
| -rw-r--r-- | src/syntax/source.rs | 15 |
7 files changed, 154 insertions, 52 deletions
@@ -15,6 +15,7 @@ use crate::geom::{ }; use crate::image::Image; use crate::model::{node, Content, Fold, StableId, StyleChain}; +use crate::syntax::Span; /// A finished document with metadata and page frames. #[derive(Debug, Default, Clone, Hash)] @@ -119,8 +120,8 @@ impl Frame { let mut text = EcoString::new(); for (_, element) in self.elements() { match element { - Element::Text(content) => { - for glyph in &content.glyphs { + Element::Text(element) => { + for glyph in &element.glyphs { text.push(glyph.c); } } @@ -499,6 +500,10 @@ pub struct Glyph { pub x_offset: Em, /// The first character of the glyph's cluster. pub c: char, + /// The source code location of the text. + pub span: Span, + /// The offset within the spanned text. + pub offset: u16, } /// An identifier for a natural language. diff --git a/src/eval/func.rs b/src/eval/func.rs index 26854240..a5fa6fa1 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -62,9 +62,11 @@ impl Func { self.1 } - /// Attach a span to the function. + /// Attach a span to this function if it doesn't already have one. pub fn spanned(mut self, span: Span) -> Self { - self.1 = span; + if self.1.is_detached() { + self.1 = span; + } self } diff --git a/src/ide/jump.rs b/src/ide/jump.rs new file mode 100644 index 00000000..1a96fbbe --- /dev/null +++ b/src/ide/jump.rs @@ -0,0 +1,93 @@ +use std::num::NonZeroUsize; + +use crate::doc::{Element, Frame, Location}; +use crate::geom::Point; +use crate::syntax::{LinkedNode, Source, 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)> { + for (mut pos, element) in frame.elements() { + if let Element::Text(text) = element { + for glyph in &text.glyphs { + if glyph.span.is_detached() { + continue; + } + + 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 + { + let source = world.source(glyph.span.source()); + let node = source.find(glyph.span); + let pos = if node.kind() == SyntaxKind::Text { + let range = node.range(); + (range.start + usize::from(glyph.offset)).min(range.end) + } else { + node.offset() + }; + return Some((source, 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); + } + } + } + + None +} + +/// Find the output location for a cursor position. +pub fn jump_to_preview( + frames: &[Frame], + source: &Source, + cursor: usize, +) -> Option<Location> { + let node = LinkedNode::new(source.root()).leaf_at(cursor)?; + if node.kind() != SyntaxKind::Text { + return None; + } + + let span = node.span(); + for (i, frame) in frames.iter().enumerate() { + if let Some(pos) = find_in_frame(frame, span) { + return Some(Location { page: NonZeroUsize::new(i + 1).unwrap(), pos }); + } + } + + None +} + +/// Find the position of a span in a frame. +fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> { + for (mut pos, element) in frame.elements() { + if let Element::Text(text) = element { + for glyph in &text.glyphs { + if glyph.span == span { + return Some(pos); + } + 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 +} diff --git a/src/ide/mod.rs b/src/ide/mod.rs index 4999da52..bee959cd 100644 --- a/src/ide/mod.rs +++ b/src/ide/mod.rs @@ -3,10 +3,12 @@ mod analyze; mod complete; mod highlight; +mod jump; mod tooltip; pub use self::complete::*; pub use self::highlight::*; +pub use self::jump::*; pub use self::tooltip::*; use std::fmt::Write; diff --git a/src/model/content.rs b/src/model/content.rs index 012ad05f..071a5862 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -106,9 +106,11 @@ impl Content { self.span } - /// Attach a span to the content. + /// Attach a span to the content if it doesn't already have one. pub fn spanned(mut self, span: Span) -> Self { - self.span = span; + if self.span.is_detached() { + self.span = span; + } self } diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 392633f6..afbebe97 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -204,14 +204,6 @@ impl SyntaxNode { Ok(()) } - /// If the span points into this node, convert it to a byte range. - pub(super) fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> { - match &self.0 { - Repr::Inner(inner) => inner.range(span, offset), - _ => (self.span() == span).then(|| offset..offset + self.len()), - } - } - /// Whether this is a leaf node. pub(super) fn is_leaf(&self) -> bool { matches!(self.0, Repr::Leaf(_)) @@ -429,40 +421,6 @@ impl InnerNode { Ok(()) } - /// If the span points into this node, convert it to a byte range. - fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> { - // Check whether we found it. - if span == self.span { - return Some(offset..offset + self.len); - } - - // The parent of a subtree has a smaller span number than all of its - // descendants. Therefore, we can bail out early if the target span's - // number is smaller than our number. - if span.number() < self.span.number() { - return None; - } - - let mut children = self.children.iter().peekable(); - while let Some(child) = children.next() { - // Every node in this child's subtree has a smaller span number than - // the next sibling. Therefore we only need to recurse if the next - // sibling's span number is larger than the target span's number. - if children - .peek() - .map_or(true, |next| next.span().number() > span.number()) - { - if let Some(range) = child.range(span, offset) { - return Some(range); - } - } - - offset += child.len(); - } - - None - } - /// Replaces a range of children with a replacement. /// /// May have mutated the children if it returns `Err(_)`. @@ -669,6 +627,39 @@ impl<'a> LinkedNode<'a> { back: self.offset + self.len(), } } + + /// Find a descendant with the given span. + pub fn find(&self, span: Span) -> Option<LinkedNode<'a>> { + if self.span() == span { + return Some(self.clone()); + } + + if let Repr::Inner(inner) = &self.0 { + // The parent of a subtree has a smaller span number than all of its + // descendants. Therefore, we can bail out early if the target span's + // number is smaller than our number. + if span.number() < inner.span.number() { + return None; + } + + let mut children = self.children().peekable(); + while let Some(child) = children.next() { + // Every node in this child's subtree has a smaller span number than + // the next sibling. Therefore we only need to recurse if the next + // sibling's span number is larger than the target span's number. + if children + .peek() + .map_or(true, |next| next.span().number() > span.number()) + { + if let Some(found) = child.find(span) { + return Some(found); + } + } + } + } + + None + } } /// Access to parents and siblings. diff --git a/src/syntax/source.rs b/src/syntax/source.rs index f00d779b..607a2603 100644 --- a/src/syntax/source.rs +++ b/src/syntax/source.rs @@ -9,7 +9,7 @@ use comemo::Prehashed; use unscanny::Scanner; use super::ast::Markup; -use super::{is_newline, parse, reparse, Span, SyntaxNode}; +use super::{is_newline, parse, reparse, LinkedNode, Span, SyntaxNode}; use crate::diag::SourceResult; use crate::util::{PathExt, StrExt}; @@ -149,13 +149,20 @@ impl Source { self.lines.len() } + /// Find the node with the given span. + /// + /// Panics if the span does not point into this source file. + pub fn find(&self, span: Span) -> LinkedNode<'_> { + LinkedNode::new(&self.root) + .find(span) + .expect("span does not point into this source file") + } + /// Map a span that points into this source file to a byte range. /// /// Panics if the span does not point into this source file. pub fn range(&self, span: Span) -> Range<usize> { - self.root - .range(span, 0) - .expect("span does not point into this source file") + self.find(span).range() } /// Return the index of the UTF-16 code unit at the byte index. |
