///|
pub(all) struct TextMapping {
paragraph : Int
run : Int
output_start : Int
output_end : Int
source : Span
} derive(Eq, Debug, ToJson)
///|
pub(all) struct MappedText {
text : String
mappings : Array[TextMapping]
} derive(Eq, Debug, ToJson)
///|
/// UTF-16 output ranges map to encompassing byte ranges, not one-to-one bytes.
pub fn Document::mapped_text(
self : Document,
include_hidden? : Bool = false,
region? : Region = Body,
) -> MappedText {
let out = StringBuilder()
let mappings : Array[TextMapping] = []
let mut offset = 0
let mut first = true
for p in 0.. Span? {
if output_offset < 0 || output_offset >= self.text.length() {
return None
}
let mut low = 0
let mut high = self.mappings.length()
while low < high {
let mid = low + (high - low) / 2
let mapping = self.mappings[mid]
if output_offset < mapping.output_start {
high = mid
} else if output_offset >= mapping.output_end {
low = mid + 1
} else {
return Some(mapping.source)
}
}
None
}
///|
/// Map a nonempty, half-open UTF-16 selection to its contributing source runs.
/// Returns None for out-of-bounds, empty or reversed ranges. A selection of
/// generated separators is valid but has no sources. Source spans encompass
/// whole runs, even when only part of a run is selected. As with source_at,
/// offsets may address either UTF-16 unit of a supplementary character.
pub fn MappedText::source_range(
self : MappedText,
start : Int,
end : Int,
) -> TextMatch? {
if start < 0 || end <= start || end > self.text.length() {
return None
}
Some(self.match_range(start, end))
}
///|
pub fn Document::runs_at_source(
self : Document,
byte_offset : Int,
) -> Array[Run] {
let result : Array[Run] = []
if byte_offset < 0 || byte_offset >= self.input_bytes {
return result
}
for paragraph in self.paragraphs {
for run in paragraph.runs {
if run.span.contains(byte_offset) {
result.push(run)
}
}
}
result
}
///|
pub fn MappedText::to_json_text(self : MappedText) -> String {
Json::object({
"schema": Json::string("moonrtf.source-map.v1"),
"offset_unit": Json::string("utf16"),
"text": Json::string(self.text),
"mappings": Json::array(
self.mappings.map(m => {
Json::object({
"paragraph": m.paragraph.to_json(),
"run": m.run.to_json(),
"output_start": m.output_start.to_json(),
"output_end": m.output_end.to_json(),
"source": span_json(m.source),
})
}),
),
}).stringify()
}