summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-11 23:28:35 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-11 23:29:32 +0100
commitca6edf5283c258d8410134d678347977cb273cdd (patch)
treeb2b46ba70c054b0cbdefa06edbc5fd999cddb1fa /src
parent1a390deaea040191cf0e5937bd8e1427b49db71b (diff)
Jump to source and preview
Diffstat (limited to 'src')
-rw-r--r--src/doc.rs9
-rw-r--r--src/eval/func.rs6
-rw-r--r--src/ide/jump.rs93
-rw-r--r--src/ide/mod.rs2
-rw-r--r--src/model/content.rs6
-rw-r--r--src/syntax/node.rs75
-rw-r--r--src/syntax/source.rs15
7 files changed, 154 insertions, 52 deletions
diff --git a/src/doc.rs b/src/doc.rs
index 67e13bc8..76a45606 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -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.