///|
/// One-based line decoration range plus CSS class.
pub struct LineDecoration {
start_column : Int32
end_column : Int32
class_name : String
decoration_type : InlineDecorationType
} derive(Eq, Debug)
///|
/// Builds a line decoration.
pub fn LineDecoration::LineDecoration(
start_column : Int32,
end_column : Int32,
class_name : String,
decoration_type~ : InlineDecorationType = Regular,
) -> LineDecoration {
{ start_column, end_column, class_name, decoration_type, }
}
///|
/// Sort order used before decoration normalization.
pub fn LineDecoration::compare(
left : LineDecoration,
right : LineDecoration,
) -> Int32 {
if left.start_column != right.start_column {
left.start_column - right.start_column
} else if left.end_column != right.end_column {
left.end_column - right.end_column
} else {
let type_cmp = inline_decoration_type_order(left.decoration_type) -
inline_decoration_type_order(right.decoration_type)
if type_cmp != 0 {
type_cmp
} else {
// Monaco compares class names with JS string `<`, i.e. lexicographic by
// UTF-16 code unit. MoonBit's `String::compare` is length-first, so this
// must go through `@base_common.compare`.
@base_common.compare(left.class_name, right.class_name)
}
}
}
///|
fn inline_decoration_type_order(
decoration_type : InlineDecorationType,
) -> Int32 {
match decoration_type {
Before => 0
After => 1
Regular => 2
RegularAffectingLetterSpacing => 3
}
}
///|
/// Normalized decoration segment in zero-based offsets.
pub(all) struct DecorationSegment {
start_offset : Int32
end_offset : Int32
class_name : String
metadata : Int32
} derive(Eq, Debug)
///|
/// Normalizes overlapping line decorations into contiguous CSS segments.
#warnings("-struct_never_constructed")
pub struct LineDecorationsNormalizer {}
///|
/// Converts sorted line decorations into renderer segments, preserving
/// surrogate-pair boundaries.
pub fn LineDecorationsNormalizer::normalize(
line_content : String,
line_decorations : Array[LineDecoration],
) -> Array[DecorationSegment] {
if line_decorations.length() == 0 {
return []
}
let result : Array[DecorationSegment] = []
let stack = DecorationSegmentStack::DecorationSegmentStack()
let line_length = line_content.length()
let mut next_start_offset = 0
for decoration in line_decorations {
let mut start_column = decoration.start_column
let mut end_column = decoration.end_column
if start_column > 1 &&
start_column - 2 < line_length &&
@base_common.is_high_surrogate(line_content[start_column - 2].to_int()) {
start_column -= 1
}
if end_column > 1 &&
end_column - 2 < line_length &&
@base_common.is_high_surrogate(line_content[end_column - 2].to_int()) {
end_column -= 1
}
let start_offset = (start_column - 1).clamp(min=0, max=line_length)
let end_offset_high = if line_length == 0 {
start_offset - 1
} else {
(line_length - 1).max(start_offset - 1)
}
let end_offset = (end_column - 2).clamp(
min=start_offset - 1,
max=end_offset_high,
)
next_start_offset = stack.consume_lower_than(
start_offset, next_start_offset, result,
)
if stack.count == 0 {
next_start_offset = start_offset
}
stack.insert(
end_offset,
decoration.class_name,
line_decoration_metadata(decoration.decoration_type),
)
}
stack.consume_lower_than(2147483647, next_start_offset, result) |> ignore
result
}
///|
priv struct DecorationSegmentStack {
stop_offsets : Array[Int32]
class_names : Array[String]
metadata : Array[Int32]
mut count : Int32
}
///|
fn DecorationSegmentStack::DecorationSegmentStack() -> DecorationSegmentStack {
{ stop_offsets: [], class_names: [], metadata: [], count: 0, }
}
///|
fn DecorationSegmentStack::consume_lower_than(
self : DecorationSegmentStack,
max_stop_offset : Int32,
next_start_offset : Int32,
result : Array[DecorationSegment],
) -> Int32 {
let mut offset = next_start_offset
while self.count > 0 && self.stop_offsets[0] < max_stop_offset {
let mut stop_index = 0
while stop_index + 1 < self.count &&
self.stop_offsets[stop_index] == self.stop_offsets[stop_index + 1] {
stop_index += 1
}
result.push({
start_offset: offset,
end_offset: self.stop_offsets[stop_index],
class_name: self.class_names.join(" "),
metadata: decoration_stack_metadata(self.metadata),
})
offset = self.stop_offsets[stop_index] + 1
for _ in 0..<=stop_index {
self.stop_offsets.remove(0) |> ignore
self.class_names.remove(0) |> ignore
self.metadata.remove(0) |> ignore
}
self.count = self.count - stop_index - 1
}
if self.count > 0 && offset < max_stop_offset {
result.push({
start_offset: offset,
end_offset: max_stop_offset - 1,
class_name: self.class_names.join(" "),
metadata: decoration_stack_metadata(self.metadata),
})
offset = max_stop_offset
}
offset
}
///|
fn DecorationSegmentStack::insert(
self : DecorationSegmentStack,
stop_offset : Int32,
class_name : String,
metadata : Int32,
) -> Unit {
if self.count == 0 || self.stop_offsets[self.count - 1] <= stop_offset {
self.stop_offsets.push(stop_offset)
self.class_names.push(class_name)
self.metadata.push(metadata)
} else {
let mut inserted = false
let mut index = 0
while index < self.count && !inserted {
if self.stop_offsets[index] >= stop_offset {
self.stop_offsets.insert(index, stop_offset)
self.class_names.insert(index, class_name)
self.metadata.insert(index, metadata)
inserted = true
}
index += 1
}
}
self.count += 1
}
///|
fn decoration_stack_metadata(metadata : Array[Int32]) -> Int32 {
metadata.fold(init=0, (bits, item) => bits | item)
}
///|
fn line_decoration_metadata(decoration_type : InlineDecorationType) -> Int32 {
match decoration_type {
Regular => 0
Before => line_part_metadata_pseudo_before
After => line_part_metadata_pseudo_after
RegularAffectingLetterSpacing => 0
}
}
///|
pub extend DecorationSegment with Debug::{to_repr}
///|
pub extend DecorationSegment with Eq::{equal, not_equal}
///|
pub extend LineDecoration with Debug::{to_repr}
///|
pub extend LineDecoration with Eq::{equal, not_equal}