flowchart TB
L["line_height × line_count"] --> LL["LinesLayout<br>prefix sums"]
WZ["view zones / whitespace<br>(incremental transaction)"] --> LL
LL --> CH["content height"]
MW["max measured line width"] --> CW["content width"]
CH --> VL["ViewLayout<br>single scroll truth"]
CW --> VL
VL --> SC["Scrollable<br>current + future position"]
SC --> VP["Viewport / visible line window"]
SC --> RV["reveal computation"]///|
fn layout(line_count : Int32) -> @view_layout.ViewLayout {
ViewLayout(line_height=20, line_count~)
}
///|
test "vertical offsets are the prefix sum of line heights" {
let view = layout(10)
debug_inspect(
(
view.get_content_height(),
view.get_vertical_offset_for_line_number(1),
view.get_vertical_offset_for_line_number(3),
view.get_line_height_for_line_number(3),
),
content=(
#|(200, 0, 40, 20)
),
)
}///|
test "scroll positions are clamped to the scrollable content" {
let view = layout(100)
view.set_viewport_size(width=400.0, height=200.0) |> ignore
view.scroll_to(scroll_top=50.0) |> ignore
let inside = view.get_current_scroll_top()
view.scroll_to(scroll_top=-10.0) |> ignore
let negative = view.get_current_scroll_top()
view.scroll_to(scroll_top=999999.0) |> ignore
let past_end = view.get_current_scroll_top()
debug_inspect(
(inside, negative, past_end, view.get_scroll_height()),
content=(
#|(50, 0, 1800, 2000)
),
)
}///|
test "the visible line window follows from scroll top and height" {
let view = layout(100)
view.set_viewport_size(width=400.0, height=200.0) |> ignore
view.scroll_to(scroll_top=0.0) |> ignore
let at_top = view.completely_visible_line_range()
view.scroll_to(scroll_top=210.0) |> ignore
let scrolled = view.completely_visible_line_range()
debug_inspect(
(at_top, scrolled, view.get_current_viewport().top),
content=(
#|((1, 10), (12, 20), 210)
),
)
}///|
test "a wider measured line grows the content width" {
let view = layout(10)
view.set_viewport_size(width=400.0, height=200.0) |> ignore
let narrow = view.get_content_width()
view.set_max_line_width(1200.0) |> ignore
debug_inspect(
(narrow, view.get_content_width()),
content=(
#|(0, 1214)
),
)
}///|
test "whitespace inserted after a line shifts the lines below it" {
let view = layout(10)
let before = view.get_vertical_offset_for_line_number(5)
let changed = view.change_whitespace(accessor => {
accessor.insert_whitespace(3, 0, 60, 0) |> ignore
})
debug_inspect(
(
changed,
before,
view.get_vertical_offset_for_line_number(5),
view.get_content_height(),
),
content=(
#|(true, 80, 140, 260)
),
)
}///|
test "removing whitespace restores the previous vertical map" {
let view = layout(10)
let baseline = view.get_content_height()
let id = Ref("")
view.change_whitespace(accessor => {
id.val = accessor.insert_whitespace(3, 0, 60, 0)
})
|> ignore
let with_zone = view.get_content_height()
view.change_whitespace(accessor => accessor.remove_whitespace(id.val))
|> ignore
debug_inspect(
(baseline, with_zone, view.get_content_height()),
content=(
#|(200, 260, 200)
),
)
}///|
test "reveal type decides where a line lands in the viewport" {
let view = layout(100)
view.set_viewport_size(width=400.0, height=200.0) |> ignore
view.scroll_to(scroll_top=0.0) |> ignore
debug_inspect(
(
view.compute_scroll_top_to_reveal_range(50, 50, Simple),
view.compute_scroll_top_to_reveal_range(50, 50, Center),
view.compute_scroll_top_to_reveal_range(50, 50, Top),
),
content=(
#|(820, 890, 980)
),
)
}moon test --target js viewer/common/view_layoutpub struct CharacterMapping {
length : Int
entries : Array[CharacterMappingEntry]
} derive(Eq, Debug)fn CharacterMapping::column_horizontal_offset(self : CharacterMapping, column : Int, space_width~ : Double) -> Doublefn CharacterMapping::get_column(self : CharacterMapping, dom_position : DomPosition, part_length : Int) -> Intfn CharacterMapping::set_column_info(self : CharacterMapping, column : Int, part_index : Int, char_index : Int, visible_column : Int) -> Unitfn ConstantTimePrefixSumComputer::ConstantTimePrefixSumComputer(values : ArrayView[Int]) -> ConstantTimePrefixSumComputerfn ConstantTimePrefixSumComputer::get_index_of(self : ConstantTimePrefixSumComputer, sum : Int) -> PrefixSumIndexOfResultfn ConstantTimePrefixSumComputer::get_prefix_sum(self : ConstantTimePrefixSumComputer, count : Int) -> Intfn ConstantTimePrefixSumComputer::insert_values(self : ConstantTimePrefixSumComputer, insert_index : Int, insert_arr : Array[Int]) -> Unitfn ConstantTimePrefixSumComputer::remove_values(self : ConstantTimePrefixSumComputer, start : Int, delete_count : Int) -> Unitfn ConstantTimePrefixSumComputer::set_value(self : ConstantTimePrefixSumComputer, index : Int, value : Int) -> Unitfn EditorScrollDimensions::EditorScrollDimensions(width : Double, content_width : Double, height : Double, content_height : Double) -> EditorScrollDimensionsfn EditorScrollDimensions::not_equal(x : EditorScrollDimensions, y : EditorScrollDimensions) -> Booltype EditorScrollablefn EditorScrollable::EditorScrollable(smooth_scroll_duration : Double, schedule_at_next_animation_frame : (() -> Unit) -> Disposable, now : () -> Double) -> EditorScrollablefn EditorScrollable::on_did_content_size_change(self : EditorScrollable, listener : (ContentSizeChange) -> Unit) -> Disposablefn EditorScrollable::on_did_scroll(self : EditorScrollable, listener : (ScrollChange) -> Unit) -> Disposablefn EditorScrollable::scroll_by(self : EditorScrollable, delta_left? : Double, delta_top? : Double) -> ScrollChange?fn EditorScrollable::set_editor_dimensions(self : EditorScrollable, dimensions : EditorScrollDimensions) -> ScrollChange?fn EditorScrollable::set_position_now(self : EditorScrollable, update : ScrollPositionUpdate) -> ScrollChange?fn EditorScrollable::set_position_smooth(self : EditorScrollable, update : ScrollPositionUpdate) -> Unitfn EditorScrollable::validate_scroll_position(self : EditorScrollable, update : ScrollPositionUpdate) -> ScrollPositionfn EditorWhitespace::EditorWhitespace(id : String, after_line_number : Int, ordinal : Int, height : Int, min_width : Int) -> EditorWhitespacepub struct InlineDecoration {
range : OffsetRange
class_name : String
decoration_type : InlineDecorationType
} derive(Eq, Debug)fn InlineDecoration::InlineDecoration(range : OffsetRange, class_name : String, decoration_type? : InlineDecorationType) -> InlineDecorationpub struct LineDecoration {
start_column : Int
end_column : Int
class_name : String
decoration_type : InlineDecorationType
} derive(Eq, Debug)fn LineDecoration::LineDecoration(start_column : Int, end_column : Int, class_name : String, decoration_type? : InlineDecorationType) -> LineDecorationpub struct LineDecorationsNormalizer {
}fn LineDecorationsNormalizer::normalize(line_content : String, line_decorations : Array[LineDecoration]) -> Array[DecorationSegment]type LinesLayoutfn LinesLayout::LinesLayout(line_count : Int, default_line_height : Int, padding_top : Int, padding_bottom : Int) -> LinesLayoutfn LinesLayout::change_whitespace(self : LinesLayout, callback : (WhitespaceChangeAccessor) -> Unit raise?) -> Bool raise?fn LinesLayout::find_insertion_index(arr : Array[EditorWhitespace], after_line_number : Int, ordinal : Int) -> Intfn LinesLayout::get_first_whitespace_index_after_line_number(self : LinesLayout, line_number : Int) -> Intfn LinesLayout::get_line_number_at_or_after_vertical_offset(self : LinesLayout, vertical_offset : Double) -> Intfn LinesLayout::get_lines_viewport_data(self : LinesLayout, vertical_offset1 : Double, vertical_offset2 : Double) -> PartialViewLinesViewportDatafn LinesLayout::get_vertical_offset_after_line_number(self : LinesLayout, line_number : Int, include_view_zones? : Bool) -> Doublefn LinesLayout::get_vertical_offset_for_line_number(self : LinesLayout, line_number : Int, include_view_zones? : Bool) -> Doublefn LinesLayout::get_vertical_offset_for_whitespace_index(self : LinesLayout, whitespace_index : Int) -> Doublefn LinesLayout::get_whitespace_accumulated_height_before_line_number(self : LinesLayout, line_number : Int) -> Doublefn LinesLayout::get_whitespace_at_vertical_offset(self : LinesLayout, vertical_offset : Double) -> ViewWhitespaceViewportData?fn LinesLayout::get_whitespace_index_at_or_after_vertical_offset(self : LinesLayout, vertical_offset : Double) -> Intfn LinesLayout::get_whitespace_viewport_data(self : LinesLayout, vertical_offset1 : Double, vertical_offset2 : Double) -> Array[ViewWhitespaceViewportData]fn LinesLayout::on_lines_deleted(self : LinesLayout, from_line_number : Int, to_line_number : Int) -> Unitfn LinesLayout::on_lines_inserted(self : LinesLayout, from_line_number : Int, to_line_number : Int) -> Unitpub struct PartialViewLinesViewportData {
big_numbers_delta : Int
start_line_number : Int
end_line_number : Int
relative_vertical_offset : Array[Double]
centered_line_number : Int
completely_visible_start_line_number : Int
completely_visible_end_line_number : Int
line_height : Int
} derive(Eq, Debug)fn PartialViewLinesViewportData::equal(PartialViewLinesViewportData, PartialViewLinesViewportData) -> Boolfn PartialViewLinesViewportData::not_equal(x : PartialViewLinesViewportData, y : PartialViewLinesViewportData) -> Boolfn PrefixSumComputer::insert_values(self : PrefixSumComputer, insert_index : Int, insert_values : Array[Int]) -> Boolfn PrefixSumComputer::remove_values(self : PrefixSumComputer, start_index : Int, count : Int) -> Boolfn PrefixSumIndexOfResult::not_equal(x : PrefixSumIndexOfResult, y : PrefixSumIndexOfResult) -> Boolpub struct RenderLineInput {
use_monospace_optimizations : Bool
can_use_halfwidth_rightwards_arrow : Bool
line_content : String
continues_with_wrapped_line : Bool
is_basic_ascii : Bool
contains_rtl : Bool
faux_indent_length : Int
line_tokens : Array[LinePart]
line_decorations : Array[LineDecoration]
tab_size : Int
start_visible_column : Int
space_width : Double
render_space_width : Double
render_space_char_code : Int
stop_rendering_line_after : Int
render_whitespace : RenderWhitespace
render_control_characters : Bool
font_ligatures : Bool
selections_on_line : Array[OffsetRange]
text_direction : TextDirection
vertical_scrollbar_size : Double
render_new_line_when_empty : Bool
} derive(Eq, Debug)fn RenderLineInput::from_view_line(data : ViewLineRenderingData, line_decorations? : Array[LineDecoration], use_monospace_optimizations? : Bool, can_use_halfwidth_rightwards_arrow? : Bool, faux_indent_length? : Int, space_width? : Double, middot_width? : Double, wsmiddot_width? : Double, stop_rendering_line_after? : Int, render_whitespace? : RenderWhitespace, render_control_characters? : Bool, font_ligatures? : Bool, selections_on_line? : Array[OffsetRange], vertical_scrollbar_size? : Double, render_new_line_when_empty? : Bool) -> RenderLineInputpub struct RenderLineOutput {
character_mapping : CharacterMapping
contains_foreign_elements : ForeignElementType
} derive(Eq, Debug)pub struct RenderLineOutput2 {
html : String
character_mapping : CharacterMapping
contains_foreign_elements : ForeignElementType
} derive(Eq, Debug)pub(all) struct ScrollChange {
position : ScrollPosition
delta_left : Double
delta_top : Double
left_changed : Bool
top_changed : Bool
dimensions_changed : Bool
in_smooth_scrolling : Bool
old_width : Double
old_scroll_width : Double
old_scroll_left : Double
width : Double
scroll_width : Double
scroll_left : Double
old_height : Double
old_scroll_height : Double
old_scroll_top : Double
height : Double
scroll_height : Double
scroll_top : Double
width_changed : Bool
scroll_width_changed : Bool
scroll_left_changed : Bool
height_changed : Bool
scroll_height_changed : Bool
scroll_top_changed : Bool
} derive(Eq, Debug)fn ScrollDimensionsUpdate::not_equal(x : ScrollDimensionsUpdate, y : ScrollDimensionsUpdate) -> Boolfn ScrollState::ScrollState(force_integer_values~ : Bool, width : Double, scroll_width : Double, scroll_left : Double, height : Double, scroll_height : Double, scroll_top : Double) -> ScrollStatefn ScrollState::create_scroll_event(self : ScrollState, previous : ScrollState, in_smooth_scrolling : Bool) -> ScrollChangefn ScrollState::with_scroll_dimensions(self : ScrollState, update : ScrollDimensionsUpdate, use_raw_scroll_positions : Bool) -> ScrollStatefn ScrollState::with_scroll_position(self : ScrollState, update : ScrollPositionUpdate) -> ScrollStatetype Scrollablefn Scrollable::Scrollable(force_integer_values~ : Bool, smooth_scroll_duration? : Double, schedule_at_next_animation_frame? : (() -> Unit) -> Disposable, now? : () -> Double) -> Scrollablefn Scrollable::scroll_by(self : Scrollable, delta_left? : Double, delta_top? : Double) -> ScrollChange?fn Scrollable::set_position_smooth(self : Scrollable, update : ScrollPositionUpdate, reuse_animation? : Bool) -> Unitfn Scrollable::set_scroll_dimensions(self : Scrollable, update : ScrollDimensionsUpdate, use_raw_scroll_positions : Bool) -> ScrollChange?fn Scrollable::validate_scroll_position(self : Scrollable, update : ScrollPositionUpdate) -> ScrollPositiontype ScrollbarStatefn ScrollbarState::ScrollbarState(arrow_size? : Double, scrollbar_size~ : Double, opposite_scrollbar_size? : Double) -> ScrollbarStatefn ScrollbarState::desired_position_from_offset_paged(self : ScrollbarState, offset : Double) -> Doublefn ScrollbarState::desired_position_from_slider(self : ScrollbarState, slider_position : Double) -> Doublepub struct SmoothScrollingOperation {
from : SmoothScrollPosition
to : SmoothScrollPosition
duration : Double
start_time : Double
animation_frame_disposable : Disposable?
scroll_left_animation : (Double) -> Double
scroll_top_animation : (Double) -> Double
}fn SmoothScrollingOperation::SmoothScrollingOperation(from : SmoothScrollPosition, to : SmoothScrollPosition, start_time : Double, duration : Double) -> SmoothScrollingOperationfn SmoothScrollingOperation::accept_scroll_dimensions(self : SmoothScrollingOperation, state : ScrollState) -> Unitfn SmoothScrollingOperation::combine(self : SmoothScrollingOperation, from : SmoothScrollPosition, to : SmoothScrollPosition, duration : Double, now : Double) -> SmoothScrollingOperationfn SmoothScrollingOperation::start(from : SmoothScrollPosition, to : SmoothScrollPosition, duration : Double, now : Double) -> SmoothScrollingOperationfn SmoothScrollingOperation::tick_at(self : SmoothScrollingOperation, now : Double) -> SmoothScrollingUpdatefn SmoothScrollingUpdate::SmoothScrollingUpdate(scroll_left : Double, scroll_top : Double, is_done : Bool) -> SmoothScrollingUpdatefn ViewLayout::ViewLayout(line_height~ : Int, line_count? : Int, smooth_scrolling? : Bool, is_viewport_wrapping? : Bool, vertical_scrollbar_width? : Double, horizontal_scrollbar_height? : Double, schedule_at_next_animation_frame? : (() -> Unit) -> Disposable, now? : () -> Double) -> ViewLayoutfn ViewLayout::change_whitespace(self : ViewLayout, callback : (WhitespaceChangeAccessor) -> Unit raise?) -> Bool raise?fn ViewLayout::compute_scroll_left_to_reveal(self : ViewLayout, ranges : Array[(Double, Double)], vertical_scrollbar_width~ : Double, minimal_reveal? : Bool) -> Double?fn ViewLayout::compute_scroll_top_to_reveal_range(self : ViewLayout, start_line : Int, end_line : Int, vertical_type : VerticalRevealType, source? : String, minimal_reveal? : Bool) -> Doublefn ViewLayout::delta_scroll_now(self : ViewLayout, delta_scroll_left : Double, delta_scroll_top : Double) -> Unitfn ViewLayout::get_lines_viewport_data_at_scroll_top(self : ViewLayout, scroll_top : Double) -> PartialViewLinesViewportDatafn ViewLayout::get_vertical_offset_after_line_number(self : ViewLayout, line_number : Int, include_view_zones? : Bool) -> Doublefn ViewLayout::get_vertical_offset_for_line_number(self : ViewLayout, line_number : Int, include_view_zones? : Bool) -> Doublefn ViewLayout::get_whitespace_at_vertical_offset(self : ViewLayout, vertical_offset : Double) -> ViewWhitespaceViewportData?fn ViewLayout::on_did_content_size_change(self : ViewLayout, listener : (ContentSizeChange) -> Unit) -> Disposablefn ViewLayout::relative_vertical_offset_for_line(self : ViewLayout, window : LineWindow, line : Int) -> Doublefn ViewLayout::scroll_by(self : ViewLayout, delta_left? : Double, delta_top? : Double) -> ScrollChange?fn ViewLayout::scroll_to(self : ViewLayout, scroll_top~ : Double, scroll_left? : Double) -> ScrollChange?fn ViewLayout::scroll_to_smooth(self : ViewLayout, scroll_top~ : Double, scroll_left? : Double) -> Unitfn ViewLayout::set_is_viewport_wrapping(self : ViewLayout, is_viewport_wrapping : Bool) -> ScrollChange?fn ViewLayout::set_viewport_size(self : ViewLayout, width~ : Double, height~ : Double) -> ScrollChange?fn ViewLayout::validate_scroll_position(self : ViewLayout, position : ScrollPositionUpdate) -> ScrollPositionpub(all) struct ViewLineRenderingData {
min_column : Int
max_column : Int
content : String
continues_with_wrapped_line : Bool
contains_rtl : Bool
is_basic_ascii : Bool
tokens : Array[LinePart]
inline_decorations : Array[InlineDecoration]
tab_size : Int
start_visible_column : Int
text_direction : TextDirection
has_variable_fonts : Bool
} derive(Eq, Debug)fn ViewWhitespaceViewportData::equal(ViewWhitespaceViewportData, ViewWhitespaceViewportData) -> Boolfn ViewWhitespaceViewportData::not_equal(x : ViewWhitespaceViewportData, y : ViewWhitespaceViewportData) -> Boolpub(all) struct ViewportData {
start_line_number : Int
end_line_number : Int
relative_vertical_offset : Array[Double]
line_height : Int
visible_range : Range
big_numbers_delta : Int
lines : Array[ViewLineRenderingData]
line_decorations : Array[Array[LineDecoration]]
view_zones : Array[ViewWhitespaceViewportData]
} derive(Eq, Debug)fn ViewportData::decorations_for_line(self : ViewportData, line_number : Int) -> Array[LineDecoration]type WhitespaceChangeAccessorfn WhitespaceChangeAccessor::change_one_whitespace(self : WhitespaceChangeAccessor, id : String, new_after_line_number : Int, new_height : Double, new_min_width? : Double) -> Unitfn WhitespaceChangeAccessor::insert_whitespace(self : WhitespaceChangeAccessor, after_line_number : Int, ordinal : Int, height_in_px : Double, min_width : Double) -> Stringfn WhitespaceChangeAccessor::remove_whitespace(self : WhitespaceChangeAccessor, id : String) -> Unitfn current_line_content_class(in_margin~ : Bool, exact~ : Bool) -> Stringfn current_line_margin_class(in_margin~ : Bool, other~ : Bool, exact~ : Bool) -> Stringfn should_render_line_highlight_in_content(mode : RenderLineHighlight, only_when_focus : Bool, focused : Bool, selection_is_empty : Bool) -> Boolfn should_render_line_highlight_in_margin(mode : RenderLineHighlight, only_when_focus : Bool, focused : Bool) -> Boolfn view_line_span_of_model_line(view_line_number : Int, line_count : Int, model_line_of : (Int) -> Int) -> (Int, Int)Install
Download zipReadonly MoonBit code viewer harness inspired by Monaco and CodeMirror.