Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,34 @@ impl SourceFile {
}
Some(SourceSpan::from(start..end))
}

/// Creates a span for an item using a substring of `contents`
///
/// Note that substr must be a literal substring, as in it must be
/// a pointer into the same string! If it's not we'll return None.
pub fn span_for_substr(&self, substr: &str) -> Option<SourceSpan> {
// Get the bounds of the full string
let base_addr = self.inner.contents.as_ptr() as usize;
let base_len = self.inner.contents.len();

// Get the bounds of the substring
let substr_addr = substr.as_ptr() as usize;
let substr_len = substr.len();

// The index of the substring is just the number of bytes it is from the start
// (This will bail out if the """substring""" has an address *before* the full string)
let start = substr_addr.checked_sub(base_addr)?;
// The end index (exclusive) is just the start index + sublen
// (This will bail out if this overflows)
let end = start.checked_add(substr_len)?;
// Finally, make sure the substr endpoint isn't past the end of the full string
if end > base_len {
return None;
}

// At this point it's definitely a substring, nice!
Some(SourceSpan::from(start..end))
}
}

impl SourceCode for SourceFile {
Expand Down
31 changes: 31 additions & 0 deletions tests/source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use miette::SourceCode;

#[test]
fn substr_span() {
// Make the file
let contents = String::from("hello !there!");
let source = axoasset::SourceFile::new("file.md", contents).unwrap();

// Do some random parsing operation
let mut parse = source.contents().split('!');
let _ = parse.next();
let there = parse.next().unwrap();

// Get the span
let there_span = source.span_for_substr(there).unwrap();

// Assert the span is correct
let span_bytes = source.read_span(&there_span, 0, 0).unwrap().data();
assert_eq!(std::str::from_utf8(span_bytes).unwrap(), "there");
}

#[test]
fn substr_span_invalid() {
// Make the file
let contents = String::from("hello !there!");
let source = axoasset::SourceFile::new("file.md", contents).unwrap();

// Get the span for a non-substring (string literal isn't pointing into the String)
let there_span = source.span_for_substr("there");
assert_eq!(there_span, None);
}