mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Jump to source and preview
This commit is contained in:
parent
1a390deaea
commit
ca6edf5283
@ -145,12 +145,12 @@ impl ParNode {
|
|||||||
let children = par.children();
|
let children = par.children();
|
||||||
|
|
||||||
// Collect all text into one string for BiDi analysis.
|
// Collect all text into one string for BiDi analysis.
|
||||||
let (text, segments) = collect(&children, &styles, consecutive)?;
|
let (text, segments, spans) = collect(&children, &styles, consecutive)?;
|
||||||
|
|
||||||
// Perform BiDi analysis and then prepare paragraph layout by building a
|
// Perform BiDi analysis and then prepare paragraph layout by building a
|
||||||
// representation on which we can do line breaking without layouting
|
// representation on which we can do line breaking without layouting
|
||||||
// each and every line from scratch.
|
// each and every line from scratch.
|
||||||
let p = prepare(&mut vt, &children, &text, segments, styles, region)?;
|
let p = prepare(&mut vt, &children, &text, segments, spans, styles, region)?;
|
||||||
|
|
||||||
// Break the paragraph into lines.
|
// Break the paragraph into lines.
|
||||||
let lines = linebreak(&vt, &p, region.x);
|
let lines = linebreak(&vt, &p, region.x);
|
||||||
@ -264,6 +264,8 @@ struct Preparation<'a> {
|
|||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
/// Text runs, spacing and layouted nodes.
|
/// Text runs, spacing and layouted nodes.
|
||||||
items: Vec<Item<'a>>,
|
items: Vec<Item<'a>>,
|
||||||
|
/// The span mapper.
|
||||||
|
spans: SpanMapper,
|
||||||
/// The styles shared by all children.
|
/// The styles shared by all children.
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
/// Whether to hyphenate if it's the same for all children.
|
/// Whether to hyphenate if it's the same for all children.
|
||||||
@ -388,6 +390,35 @@ impl<'a> Item<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps byte offsets back to spans.
|
||||||
|
pub struct SpanMapper(Vec<(usize, Span)>);
|
||||||
|
|
||||||
|
impl SpanMapper {
|
||||||
|
/// Create a new span mapper.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a span for a segment with the given length.
|
||||||
|
pub fn push(&mut self, len: usize, span: Span) {
|
||||||
|
self.0.push((len, span));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the span at the given byte offset.
|
||||||
|
///
|
||||||
|
/// May return a detached span.
|
||||||
|
pub fn span_at(&self, offset: usize) -> (Span, u16) {
|
||||||
|
let mut cursor = 0;
|
||||||
|
for &(len, span) in &self.0 {
|
||||||
|
if (cursor..=cursor + len).contains(&offset) {
|
||||||
|
return (span, u16::try_from(offset - cursor).unwrap_or(0));
|
||||||
|
}
|
||||||
|
cursor += len;
|
||||||
|
}
|
||||||
|
(Span::detached(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A layouted line, consisting of a sequence of layouted paragraph items that
|
/// A layouted line, consisting of a sequence of layouted paragraph items that
|
||||||
/// are mostly borrowed from the preparation phase. This type enables you to
|
/// are mostly borrowed from the preparation phase. This type enables you to
|
||||||
/// measure the size of a line in a range before comitting to building the
|
/// measure the size of a line in a range before comitting to building the
|
||||||
@ -485,10 +516,11 @@ fn collect<'a>(
|
|||||||
children: &'a [Content],
|
children: &'a [Content],
|
||||||
styles: &'a StyleChain<'a>,
|
styles: &'a StyleChain<'a>,
|
||||||
consecutive: bool,
|
consecutive: bool,
|
||||||
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>)> {
|
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> {
|
||||||
let mut full = String::new();
|
let mut full = String::new();
|
||||||
let mut quoter = Quoter::new();
|
let mut quoter = Quoter::new();
|
||||||
let mut segments = vec![];
|
let mut segments = vec![];
|
||||||
|
let mut spans = SpanMapper::new();
|
||||||
let mut iter = children.iter().peekable();
|
let mut iter = children.iter().peekable();
|
||||||
|
|
||||||
if consecutive {
|
if consecutive {
|
||||||
@ -578,6 +610,8 @@ fn collect<'a>(
|
|||||||
quoter.last(last);
|
quoter.last(last);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spans.push(segment.len(), child.span());
|
||||||
|
|
||||||
if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) =
|
if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) =
|
||||||
(segments.last_mut(), segment)
|
(segments.last_mut(), segment)
|
||||||
{
|
{
|
||||||
@ -590,7 +624,7 @@ fn collect<'a>(
|
|||||||
segments.push((segment, styles));
|
segments.push((segment, styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((full, segments))
|
Ok((full, segments, spans))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||||
@ -600,6 +634,7 @@ fn prepare<'a>(
|
|||||||
children: &'a [Content],
|
children: &'a [Content],
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||||
|
spans: SpanMapper,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
region: Size,
|
region: Size,
|
||||||
) -> SourceResult<Preparation<'a>> {
|
) -> SourceResult<Preparation<'a>> {
|
||||||
@ -620,7 +655,7 @@ fn prepare<'a>(
|
|||||||
let end = cursor + segment.len();
|
let end = cursor + segment.len();
|
||||||
match segment {
|
match segment {
|
||||||
Segment::Text(_) => {
|
Segment::Text(_) => {
|
||||||
shape_range(&mut items, vt, &bidi, cursor..end, styles);
|
shape_range(&mut items, vt, &bidi, cursor..end, &spans, styles);
|
||||||
}
|
}
|
||||||
Segment::Spacing(spacing) => match spacing {
|
Segment::Spacing(spacing) => match spacing {
|
||||||
Spacing::Rel(v) => {
|
Spacing::Rel(v) => {
|
||||||
@ -655,6 +690,7 @@ fn prepare<'a>(
|
|||||||
Ok(Preparation {
|
Ok(Preparation {
|
||||||
bidi,
|
bidi,
|
||||||
items,
|
items,
|
||||||
|
spans,
|
||||||
styles,
|
styles,
|
||||||
hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
|
hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
|
||||||
lang: shared_get(styles, children, TextNode::lang_in),
|
lang: shared_get(styles, children, TextNode::lang_in),
|
||||||
@ -670,11 +706,12 @@ fn shape_range<'a>(
|
|||||||
vt: &Vt,
|
vt: &Vt,
|
||||||
bidi: &BidiInfo<'a>,
|
bidi: &BidiInfo<'a>,
|
||||||
range: Range,
|
range: Range,
|
||||||
|
spans: &SpanMapper,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) {
|
) {
|
||||||
let mut process = |text, level: BidiLevel| {
|
let mut process = |range: Range, level: BidiLevel| {
|
||||||
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
||||||
let shaped = shape(vt, text, styles, dir);
|
let shaped = shape(vt, range.start, &bidi.text[range], spans, styles, dir);
|
||||||
items.push(Item::Text(shaped));
|
items.push(Item::Text(shaped));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -694,7 +731,7 @@ fn shape_range<'a>(
|
|||||||
|
|
||||||
if level != prev_level || !is_compatible(script, prev_script) {
|
if level != prev_level || !is_compatible(script, prev_script) {
|
||||||
if cursor < i {
|
if cursor < i {
|
||||||
process(&bidi.text[cursor..i], prev_level);
|
process(cursor..i, prev_level);
|
||||||
}
|
}
|
||||||
cursor = i;
|
cursor = i;
|
||||||
prev_level = level;
|
prev_level = level;
|
||||||
@ -704,7 +741,7 @@ fn shape_range<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process(&bidi.text[cursor..range.end], prev_level);
|
process(cursor..range.end, prev_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this is not a specific script.
|
/// Whether this is not a specific script.
|
||||||
@ -1073,7 +1110,7 @@ fn line<'a>(
|
|||||||
if hyphen || start + shaped.text.len() > range.end {
|
if hyphen || start + shaped.text.len() > range.end {
|
||||||
if hyphen || start < range.end || before.is_empty() {
|
if hyphen || start < range.end || before.is_empty() {
|
||||||
let shifted = start - base..range.end - base;
|
let shifted = start - base..range.end - base;
|
||||||
let mut reshaped = shaped.reshape(vt, shifted);
|
let mut reshaped = shaped.reshape(vt, &p.spans, shifted);
|
||||||
if hyphen || shy {
|
if hyphen || shy {
|
||||||
reshaped.push_hyphen(vt);
|
reshaped.push_hyphen(vt);
|
||||||
}
|
}
|
||||||
@ -1096,7 +1133,7 @@ fn line<'a>(
|
|||||||
if range.start + shaped.text.len() > end {
|
if range.start + shaped.text.len() > end {
|
||||||
if range.start < end {
|
if range.start < end {
|
||||||
let shifted = range.start - base..end - base;
|
let shifted = range.start - base..end - base;
|
||||||
let reshaped = shaped.reshape(vt, shifted);
|
let reshaped = shaped.reshape(vt, &p.spans, shifted);
|
||||||
width += reshaped.width;
|
width += reshaped.width;
|
||||||
first = Some(Item::Text(reshaped));
|
first = Some(Item::Text(reshaped));
|
||||||
}
|
}
|
||||||
|
@ -222,6 +222,8 @@ impl GlyphFragment {
|
|||||||
c: self.c,
|
c: self.c,
|
||||||
x_advance: Em::from_length(self.width, self.font_size),
|
x_advance: Em::from_length(self.width, self.font_size),
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
|
span: Span::detached(),
|
||||||
|
offset: 0,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
let size = Size::new(self.width, self.ascent + self.descent);
|
let size = Size::new(self.width, self.ascent + self.descent);
|
||||||
|
@ -6,6 +6,7 @@ use typst::font::{Font, FontVariant};
|
|||||||
use typst::util::SliceExt;
|
use typst::util::SliceExt;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::layout::SpanMapper;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// The result of shaping text.
|
/// The result of shaping text.
|
||||||
@ -14,6 +15,8 @@ use crate::prelude::*;
|
|||||||
/// measured, used to reshape substrings more quickly and converted into a
|
/// measured, used to reshape substrings more quickly and converted into a
|
||||||
/// frame.
|
/// frame.
|
||||||
pub struct ShapedText<'a> {
|
pub struct ShapedText<'a> {
|
||||||
|
/// The start of the text in the full paragraph.
|
||||||
|
pub base: usize,
|
||||||
/// The text that was shaped.
|
/// The text that was shaped.
|
||||||
pub text: &'a str,
|
pub text: &'a str,
|
||||||
/// The text direction.
|
/// The text direction.
|
||||||
@ -53,6 +56,10 @@ pub struct ShapedGlyph {
|
|||||||
pub safe_to_break: bool,
|
pub safe_to_break: bool,
|
||||||
/// The first char in this glyph's cluster.
|
/// The first char in this glyph's cluster.
|
||||||
pub c: char,
|
pub c: char,
|
||||||
|
/// The source code location of the text.
|
||||||
|
pub span: Span,
|
||||||
|
/// The offset within the spanned text.
|
||||||
|
pub offset: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapedGlyph {
|
impl ShapedGlyph {
|
||||||
@ -110,6 +117,8 @@ impl<'a> ShapedText<'a> {
|
|||||||
},
|
},
|
||||||
x_offset: glyph.x_offset,
|
x_offset: glyph.x_offset,
|
||||||
c: glyph.c,
|
c: glyph.c,
|
||||||
|
span: glyph.span,
|
||||||
|
offset: glyph.offset,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -187,9 +196,15 @@ impl<'a> ShapedText<'a> {
|
|||||||
|
|
||||||
/// Reshape a range of the shaped text, reusing information from this
|
/// Reshape a range of the shaped text, reusing information from this
|
||||||
/// shaping process if possible.
|
/// shaping process if possible.
|
||||||
pub fn reshape(&'a self, vt: &Vt, text_range: Range<usize>) -> ShapedText<'a> {
|
pub fn reshape(
|
||||||
|
&'a self,
|
||||||
|
vt: &Vt,
|
||||||
|
spans: &SpanMapper,
|
||||||
|
text_range: Range<usize>,
|
||||||
|
) -> ShapedText<'a> {
|
||||||
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
||||||
Self {
|
Self {
|
||||||
|
base: self.base + text_range.start,
|
||||||
text: &self.text[text_range],
|
text: &self.text[text_range],
|
||||||
dir: self.dir,
|
dir: self.dir,
|
||||||
styles: self.styles,
|
styles: self.styles,
|
||||||
@ -199,7 +214,14 @@ impl<'a> ShapedText<'a> {
|
|||||||
glyphs: Cow::Borrowed(glyphs),
|
glyphs: Cow::Borrowed(glyphs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shape(vt, &self.text[text_range], self.styles, self.dir)
|
shape(
|
||||||
|
vt,
|
||||||
|
self.base + text_range.start,
|
||||||
|
&self.text[text_range],
|
||||||
|
spans,
|
||||||
|
self.styles,
|
||||||
|
self.dir,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,6 +247,8 @@ impl<'a> ShapedText<'a> {
|
|||||||
cluster,
|
cluster,
|
||||||
safe_to_break: true,
|
safe_to_break: true,
|
||||||
c: '-',
|
c: '-',
|
||||||
|
span: Span::detached(),
|
||||||
|
offset: 0,
|
||||||
});
|
});
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
@ -298,6 +322,8 @@ impl Debug for ShapedText<'_> {
|
|||||||
/// Holds shaping results and metadata common to all shaped segments.
|
/// Holds shaping results and metadata common to all shaped segments.
|
||||||
struct ShapingContext<'a> {
|
struct ShapingContext<'a> {
|
||||||
vt: &'a Vt<'a>,
|
vt: &'a Vt<'a>,
|
||||||
|
base: usize,
|
||||||
|
spans: &'a SpanMapper,
|
||||||
glyphs: Vec<ShapedGlyph>,
|
glyphs: Vec<ShapedGlyph>,
|
||||||
used: Vec<Font>,
|
used: Vec<Font>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
@ -311,13 +337,17 @@ struct ShapingContext<'a> {
|
|||||||
/// Shape text into [`ShapedText`].
|
/// Shape text into [`ShapedText`].
|
||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
vt: &Vt,
|
vt: &Vt,
|
||||||
|
base: usize,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
|
spans: &SpanMapper,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
let size = TextNode::size_in(styles);
|
let size = TextNode::size_in(styles);
|
||||||
let mut ctx = ShapingContext {
|
let mut ctx = ShapingContext {
|
||||||
vt,
|
vt,
|
||||||
|
base,
|
||||||
|
spans,
|
||||||
size,
|
size,
|
||||||
glyphs: vec![],
|
glyphs: vec![],
|
||||||
used: vec![],
|
used: vec![],
|
||||||
@ -335,6 +365,7 @@ pub fn shape<'a>(
|
|||||||
track_and_space(&mut ctx);
|
track_and_space(&mut ctx);
|
||||||
|
|
||||||
ShapedText {
|
ShapedText {
|
||||||
|
base,
|
||||||
text,
|
text,
|
||||||
dir,
|
dir,
|
||||||
styles,
|
styles,
|
||||||
@ -410,6 +441,7 @@ fn shape_segment<'a>(
|
|||||||
if info.glyph_id != 0 {
|
if info.glyph_id != 0 {
|
||||||
// Add the glyph to the shaped output.
|
// Add the glyph to the shaped output.
|
||||||
// TODO: Don't ignore y_advance.
|
// TODO: Don't ignore y_advance.
|
||||||
|
let (span, offset) = ctx.spans.span_at(ctx.base + cluster);
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
glyph_id: info.glyph_id as u16,
|
glyph_id: info.glyph_id as u16,
|
||||||
@ -419,6 +451,8 @@ fn shape_segment<'a>(
|
|||||||
cluster: base + cluster,
|
cluster: base + cluster,
|
||||||
safe_to_break: !info.unsafe_to_break(),
|
safe_to_break: !info.unsafe_to_break(),
|
||||||
c: text[cluster..].chars().next().unwrap(),
|
c: text[cluster..].chars().next().unwrap(),
|
||||||
|
span,
|
||||||
|
offset,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Determine the source text range for the tofu sequence.
|
// Determine the source text range for the tofu sequence.
|
||||||
@ -478,15 +512,19 @@ fn shape_segment<'a>(
|
|||||||
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
||||||
let x_advance = font.advance(0).unwrap_or_default();
|
let x_advance = font.advance(0).unwrap_or_default();
|
||||||
for (cluster, c) in text.char_indices() {
|
for (cluster, c) in text.char_indices() {
|
||||||
|
let cluster = base + cluster;
|
||||||
|
let (span, offset) = ctx.spans.span_at(ctx.base + cluster);
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
glyph_id: 0,
|
glyph_id: 0,
|
||||||
x_advance,
|
x_advance,
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
y_offset: Em::zero(),
|
y_offset: Em::zero(),
|
||||||
cluster: base + cluster,
|
cluster,
|
||||||
safe_to_break: true,
|
safe_to_break: true,
|
||||||
c,
|
c,
|
||||||
|
span,
|
||||||
|
offset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ use crate::geom::{
|
|||||||
};
|
};
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
use crate::model::{node, Content, Fold, StableId, StyleChain};
|
use crate::model::{node, Content, Fold, StableId, StyleChain};
|
||||||
|
use crate::syntax::Span;
|
||||||
|
|
||||||
/// A finished document with metadata and page frames.
|
/// A finished document with metadata and page frames.
|
||||||
#[derive(Debug, Default, Clone, Hash)]
|
#[derive(Debug, Default, Clone, Hash)]
|
||||||
@ -119,8 +120,8 @@ impl Frame {
|
|||||||
let mut text = EcoString::new();
|
let mut text = EcoString::new();
|
||||||
for (_, element) in self.elements() {
|
for (_, element) in self.elements() {
|
||||||
match element {
|
match element {
|
||||||
Element::Text(content) => {
|
Element::Text(element) => {
|
||||||
for glyph in &content.glyphs {
|
for glyph in &element.glyphs {
|
||||||
text.push(glyph.c);
|
text.push(glyph.c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -499,6 +500,10 @@ pub struct Glyph {
|
|||||||
pub x_offset: Em,
|
pub x_offset: Em,
|
||||||
/// The first character of the glyph's cluster.
|
/// The first character of the glyph's cluster.
|
||||||
pub c: char,
|
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.
|
/// An identifier for a natural language.
|
||||||
|
@ -62,9 +62,11 @@ impl Func {
|
|||||||
self.1
|
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 {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
self.1 = span;
|
if self.1.is_detached() {
|
||||||
|
self.1 = span;
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
93
src/ide/jump.rs
Normal file
93
src/ide/jump.rs
Normal file
@ -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
|
||||||
|
}
|
@ -3,10 +3,12 @@
|
|||||||
mod analyze;
|
mod analyze;
|
||||||
mod complete;
|
mod complete;
|
||||||
mod highlight;
|
mod highlight;
|
||||||
|
mod jump;
|
||||||
mod tooltip;
|
mod tooltip;
|
||||||
|
|
||||||
pub use self::complete::*;
|
pub use self::complete::*;
|
||||||
pub use self::highlight::*;
|
pub use self::highlight::*;
|
||||||
|
pub use self::jump::*;
|
||||||
pub use self::tooltip::*;
|
pub use self::tooltip::*;
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
@ -106,9 +106,11 @@ impl Content {
|
|||||||
self.span
|
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 {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
self.span = span;
|
if self.span.is_detached() {
|
||||||
|
self.span = span;
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,14 +204,6 @@ impl SyntaxNode {
|
|||||||
Ok(())
|
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.
|
/// Whether this is a leaf node.
|
||||||
pub(super) fn is_leaf(&self) -> bool {
|
pub(super) fn is_leaf(&self) -> bool {
|
||||||
matches!(self.0, Repr::Leaf(_))
|
matches!(self.0, Repr::Leaf(_))
|
||||||
@ -429,40 +421,6 @@ impl InnerNode {
|
|||||||
Ok(())
|
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.
|
/// Replaces a range of children with a replacement.
|
||||||
///
|
///
|
||||||
/// May have mutated the children if it returns `Err(_)`.
|
/// May have mutated the children if it returns `Err(_)`.
|
||||||
@ -669,6 +627,39 @@ impl<'a> LinkedNode<'a> {
|
|||||||
back: self.offset + self.len(),
|
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.
|
/// Access to parents and siblings.
|
||||||
|
@ -9,7 +9,7 @@ use comemo::Prehashed;
|
|||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use super::ast::Markup;
|
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::diag::SourceResult;
|
||||||
use crate::util::{PathExt, StrExt};
|
use crate::util::{PathExt, StrExt};
|
||||||
|
|
||||||
@ -149,13 +149,20 @@ impl Source {
|
|||||||
self.lines.len()
|
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.
|
/// Map a span that points into this source file to a byte range.
|
||||||
///
|
///
|
||||||
/// Panics if the span does not point into this source file.
|
/// Panics if the span does not point into this source file.
|
||||||
pub fn range(&self, span: Span) -> Range<usize> {
|
pub fn range(&self, span: Span) -> Range<usize> {
|
||||||
self.root
|
self.find(span).range()
|
||||||
.range(span, 0)
|
|
||||||
.expect("span does not point into this source file")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index of the UTF-16 code unit at the byte index.
|
/// Return the index of the UTF-16 code unit at the byte index.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user