///|
/// Constructs `@view_layout.ViewportData` from view-model state. These live in
/// `view_model` (not `view_layout`) to keep the dependency one-directional:
/// `view_model -> view_layout`. `viewLayout/viewLinesViewportData.ts` in Monaco
/// takes an `IViewModel` in its constructor, which is the file-level
/// `viewLayout <-> viewModel` cycle TypeScript tolerates; MoonBit forbids the
/// cross-package cycle, so the view-model-dependent constructors are inverted
/// here as free functions over the `pub(all)` `ViewportData`.
///|
/// Builds viewport data directly from a view model and layout window.
pub fn viewport_data_from_view_model(
view_model : ViewModel,
window : @view_layout.LineWindow,
line_height~ : Int32,
layout? : @view_layout.ViewLayout,
partial_viewport? : @view_layout.PartialViewLinesViewportData,
) -> @view_layout.ViewportData {
let source_window : @view_layout.LineWindow = match partial_viewport {
Some(partial) =>
{ start: partial.start_line_number, end: partial.end_line_number + 1, }
None => window
}
let source_line_height = match partial_viewport {
Some(partial) => partial.line_height
None => line_height
}
let start_line = source_window.start.clamp(min=1, max=view_model.line_count())
let end_line = source_window.end.clamp(
min=start_line + 1,
max=view_model.line_count() + 1,
)
let lines : Array[@view_layout.ViewLineRenderingData] = []
let line_decorations : Array[Array[@view_layout.LineDecoration]] = []
let relative_vertical_offset = match partial_viewport {
Some(partial) => partial.relative_vertical_offset.copy()
None => []
}
let view_model_lines = view_model.lines()
// The Monaco flow (`viewModelImpl.getViewportViewLineRenderingData`):
// resolve the decorations for the whole viewport range lazily through
// `ViewModelDecorations`, then clip per line.
let end_line_inclusive = end_line - 1
let viewport_range : @base_common.Range = Range(
start_line,
view_model_lines.get_view_line_min_column(start_line),
end_line_inclusive,
view_model_lines.get_view_line_max_column(end_line_inclusive),
)
let collection = view_model
.decorations()
.get_decorations_viewport_data(viewport_range)
let normalized_window : @view_layout.LineWindow = {
start: start_line,
end: end_line,
}
let big_numbers_delta = match partial_viewport {
Some(partial) => partial.big_numbers_delta
None =>
match layout {
Some(layout) => layout.big_numbers_delta_for_window(normalized_window)
None => 0
}
}
// Use the source batch path once for the complete viewport. Every rendered
// viewport line is needed; minimap-style sparse masks are covered directly
// by `get_view_lines_data` tests.
let needed : Array[Bool] = [ for _ in start_line.. true ]
let line_data_batch = view_model_lines.get_view_lines_data(
start_line,
end_line - 1,
needed,
)
for line_number in start_line..
layout.relative_vertical_offset_for_line(
normalized_window, line_number,
)
None => ((line_number - start_line) * source_line_height).to_double()
},
)
}
}
if relative_vertical_offset.length() != lines.length() {
abort("Partial viewport line offsets do not match its line range")
}
let start_line_number = start_line
let end_line_number = end_line - 1
let end_column = match lines.last() {
Some(line) => line.max_column
None => 1
}
{
start_line_number,
end_line_number,
relative_vertical_offset,
line_height: source_line_height,
visible_range: {
start_line_number,
start_column: 1,
end_line_number,
end_column,
},
big_numbers_delta,
lines,
line_decorations,
view_zones: match layout {
Some(layout) => layout.get_whitespace_viewport_data()
None => []
},
}
}