///|
pub(all) struct Scene {
ordinal : Int
number : String
heading : String
line : Int
end_line : Int
start : Int
end : Int
characters : Array[String]
dialogue_lines : Int
} derive(Eq, Debug, ToJson)
///|
/// Scene ranges include following blank lines, stopping before the next heading.
/// Preamble is intentionally not fabricated as a numbered scene.
pub fn scenes(doc : Document) -> Array[Scene] {
let headings : Array[Int] = []
for i = 0; i < doc.elements.length(); i = i + 1 {
if doc.elements[i].kind == SceneHeading {
headings.push(i)
}
}
let out : Array[Scene] = []
let final_line = source_lines(doc.source).length()
for n = 0; n < headings.length(); n = n + 1 {
let e = doc.elements[headings[n]]
let stop = if n + 1 < headings.length() {
headings[n + 1]
} else {
doc.elements.length()
}
let characters : Array[String] = []
let mut dialogue_lines = 0
for i = headings[n] + 1; i < stop; i = i + 1 {
let item = doc.elements[i]
if item.kind == Character && !characters.contains(item.text) {
characters.push(item.text)
}
if item.kind == Dialogue {
dialogue_lines += 1
}
}
out.push({
ordinal: n + 1,
number: e.detail,
heading: e.text,
line: e.line,
end_line: if stop < doc.elements.length() {
doc.elements[stop].line - 1
} else {
final_line
},
start: e.start,
end: if stop < doc.elements.length() {
doc.elements[stop].start
} else {
doc.source.length()
},
characters,
dialogue_lines,
})
}
out
}
///|
/// 0 denotes preamble or a document without scene headings.
pub fn scene_at_line(items : Array[Scene], line : Int) -> Int {
let mut low = 0
let mut high = items.length()
while low < high {
let mid = low + (high - low) / 2
if items[mid].line <= line {
low = mid + 1
} else {
high = mid
}
}
if low == 0 {
0
} else if line <= items[low - 1].end_line {
items[low - 1].ordinal
} else {
0
}
}