mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46: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();
|
||||
|
||||
// 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
|
||||
// representation on which we can do line breaking without layouting
|
||||
// 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.
|
||||
let lines = linebreak(&vt, &p, region.x);
|
||||
@ -264,6 +264,8 @@ struct Preparation<'a> {
|
||||
bidi: BidiInfo<'a>,
|
||||
/// Text runs, spacing and layouted nodes.
|
||||
items: Vec<Item<'a>>,
|
||||
/// The span mapper.
|
||||
spans: SpanMapper,
|
||||
/// The styles shared by all children.
|
||||
styles: StyleChain<'a>,
|
||||
/// 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
|
||||
/// 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
|
||||
@ -485,10 +516,11 @@ fn collect<'a>(
|
||||
children: &'a [Content],
|
||||
styles: &'a StyleChain<'a>,
|
||||
consecutive: bool,
|
||||
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>)> {
|
||||
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> {
|
||||
let mut full = String::new();
|
||||
let mut quoter = Quoter::new();
|
||||
let mut segments = vec![];
|
||||
let mut spans = SpanMapper::new();
|
||||
let mut iter = children.iter().peekable();
|
||||
|
||||
if consecutive {
|
||||
@ -578,6 +610,8 @@ fn collect<'a>(
|
||||
quoter.last(last);
|
||||
}
|
||||
|
||||
spans.push(segment.len(), child.span());
|
||||
|
||||
if let (Some((Segment::Text(last_len), last_styles)), Segment::Text(len)) =
|
||||
(segments.last_mut(), segment)
|
||||
{
|
||||
@ -590,7 +624,7 @@ fn collect<'a>(
|
||||
segments.push((segment, styles));
|
||||
}
|
||||
|
||||
Ok((full, segments))
|
||||
Ok((full, segments, spans))
|
||||
}
|
||||
|
||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||
@ -600,6 +634,7 @@ fn prepare<'a>(
|
||||
children: &'a [Content],
|
||||
text: &'a str,
|
||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||
spans: SpanMapper,
|
||||
styles: StyleChain<'a>,
|
||||
region: Size,
|
||||
) -> SourceResult<Preparation<'a>> {
|
||||
@ -620,7 +655,7 @@ fn prepare<'a>(
|
||||
let end = cursor + segment.len();
|
||||
match segment {
|
||||
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 {
|
||||
Spacing::Rel(v) => {
|
||||
@ -655,6 +690,7 @@ fn prepare<'a>(
|
||||
Ok(Preparation {
|
||||
bidi,
|
||||
items,
|
||||
spans,
|
||||
styles,
|
||||
hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
|
||||
lang: shared_get(styles, children, TextNode::lang_in),
|
||||
@ -670,11 +706,12 @@ fn shape_range<'a>(
|
||||
vt: &Vt,
|
||||
bidi: &BidiInfo<'a>,
|
||||
range: Range,
|
||||
spans: &SpanMapper,
|
||||
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 shaped = shape(vt, text, styles, dir);
|
||||
let shaped = shape(vt, range.start, &bidi.text[range], spans, styles, dir);
|
||||
items.push(Item::Text(shaped));
|
||||
};
|
||||
|
||||
@ -694,7 +731,7 @@ fn shape_range<'a>(
|
||||
|
||||
if level != prev_level || !is_compatible(script, prev_script) {
|
||||
if cursor < i {
|
||||
process(&bidi.text[cursor..i], prev_level);
|
||||
process(cursor..i, prev_level);
|
||||
}
|
||||
cursor = i;
|
||||
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.
|
||||
@ -1073,7 +1110,7 @@ fn line<'a>(
|
||||
if hyphen || start + shaped.text.len() > range.end {
|
||||
if hyphen || start < range.end || before.is_empty() {
|
||||
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 {
|
||||
reshaped.push_hyphen(vt);
|
||||
}
|
||||
@ -1096,7 +1133,7 @@ fn line<'a>(
|
||||
if range.start + shaped.text.len() > end {
|
||||
if range.start < end {
|
||||
let shifted = range.start - base..end - base;
|
||||
let reshaped = shaped.reshape(vt, shifted);
|
||||
let reshaped = shaped.reshape(vt, &p.spans, shifted);
|
||||
width += reshaped.width;
|
||||
first = Some(Item::Text(reshaped));
|
||||
}
|
||||
|
@ -222,6 +222,8 @@ impl GlyphFragment {
|
||||
c: self.c,
|
||||
x_advance: Em::from_length(self.width, self.font_size),
|
||||
x_offset: Em::zero(),
|
||||
span: Span::detached(),
|
||||
offset: 0,
|
||||
}],
|
||||
};
|
||||
let size = Size::new(self.width, self.ascent + self.descent);
|
||||
|
@ -6,6 +6,7 @@ use typst::font::{Font, FontVariant};
|
||||
use typst::util::SliceExt;
|
||||
|
||||
use super::*;
|
||||
use crate::layout::SpanMapper;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// The result of shaping text.
|
||||
@ -14,6 +15,8 @@ use crate::prelude::*;
|
||||
/// measured, used to reshape substrings more quickly and converted into a
|
||||
/// frame.
|
||||
pub struct ShapedText<'a> {
|
||||
/// The start of the text in the full paragraph.
|
||||
pub base: usize,
|
||||
/// The text that was shaped.
|
||||
pub text: &'a str,
|
||||
/// The text direction.
|
||||
@ -53,6 +56,10 @@ pub struct ShapedGlyph {
|
||||
pub safe_to_break: bool,
|
||||
/// The first char in this 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,
|
||||
}
|
||||
|
||||
impl ShapedGlyph {
|
||||
@ -110,6 +117,8 @@ impl<'a> ShapedText<'a> {
|
||||
},
|
||||
x_offset: glyph.x_offset,
|
||||
c: glyph.c,
|
||||
span: glyph.span,
|
||||
offset: glyph.offset,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -187,9 +196,15 @@ impl<'a> ShapedText<'a> {
|
||||
|
||||
/// Reshape a range of the shaped text, reusing information from this
|
||||
/// 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()) {
|
||||
Self {
|
||||
base: self.base + text_range.start,
|
||||
text: &self.text[text_range],
|
||||
dir: self.dir,
|
||||
styles: self.styles,
|
||||
@ -199,7 +214,14 @@ impl<'a> ShapedText<'a> {
|
||||
glyphs: Cow::Borrowed(glyphs),
|
||||
}
|
||||
} 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,
|
||||
safe_to_break: true,
|
||||
c: '-',
|
||||
span: Span::detached(),
|
||||
offset: 0,
|
||||
});
|
||||
Some(())
|
||||
});
|
||||
@ -298,6 +322,8 @@ impl Debug for ShapedText<'_> {
|
||||
/// Holds shaping results and metadata common to all shaped segments.
|
||||
struct ShapingContext<'a> {
|
||||
vt: &'a Vt<'a>,
|
||||
base: usize,
|
||||
spans: &'a SpanMapper,
|
||||
glyphs: Vec<ShapedGlyph>,
|
||||
used: Vec<Font>,
|
||||
styles: StyleChain<'a>,
|
||||
@ -311,13 +337,17 @@ struct ShapingContext<'a> {
|
||||
/// Shape text into [`ShapedText`].
|
||||
pub fn shape<'a>(
|
||||
vt: &Vt,
|
||||
base: usize,
|
||||
text: &'a str,
|
||||
spans: &SpanMapper,
|
||||
styles: StyleChain<'a>,
|
||||
dir: Dir,
|
||||
) -> ShapedText<'a> {
|
||||
let size = TextNode::size_in(styles);
|
||||
let mut ctx = ShapingContext {
|
||||
vt,
|
||||
base,
|
||||
spans,
|
||||
size,
|
||||
glyphs: vec![],
|
||||
used: vec![],
|
||||
@ -335,6 +365,7 @@ pub fn shape<'a>(
|
||||
track_and_space(&mut ctx);
|
||||
|
||||
ShapedText {
|
||||
base,
|
||||
text,
|
||||
dir,
|
||||
styles,
|
||||
@ -410,6 +441,7 @@ fn shape_segment<'a>(
|
||||
if info.glyph_id != 0 {
|
||||
// Add the glyph to the shaped output.
|
||||
// TODO: Don't ignore y_advance.
|
||||
let (span, offset) = ctx.spans.span_at(ctx.base + cluster);
|
||||
ctx.glyphs.push(ShapedGlyph {
|
||||
font: font.clone(),
|
||||
glyph_id: info.glyph_id as u16,
|
||||
@ -419,6 +451,8 @@ fn shape_segment<'a>(
|
||||
cluster: base + cluster,
|
||||
safe_to_break: !info.unsafe_to_break(),
|
||||
c: text[cluster..].chars().next().unwrap(),
|
||||
span,
|
||||
offset,
|
||||
});
|
||||
} else {
|
||||
// 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) {
|
||||
let x_advance = font.advance(0).unwrap_or_default();
|
||||
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 {
|
||||
font: font.clone(),
|
||||
glyph_id: 0,
|
||||
x_advance,
|
||||
x_offset: Em::zero(),
|
||||
y_offset: Em::zero(),
|
||||
cluster: base + cluster,
|
||||
cluster,
|
||||
safe_to_break: true,
|
||||
c,
|
||||
span,
|
||||
offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
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 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;
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user