flowchart TB
H["host text"] -->|"normalize \r\n, \r → \n"| S["TextSnapshot<br>immutable, owns line starts"]
S --> M["TextModel<br>URI, language, version, revision"]
M --> D["decorations<br>interval trees"]
M --> T["TokenizationTextModelPart<br>shared model state"]
M --> G["GuidesTextModelPart"]
M -->|"set_value replaces the whole snapshot"| S
T -.->|"passive reads,<br>attachment-driven refresh"| TK["viewer/common/tokens"]///|
fn doc(text : String) -> @model.TextModel raise {
TextModel(
@base_common.Uri::parse("file:///model-doc.mbt"),
"model-doc.mbt",
"moonbit",
1,
"rev-1",
text,
)
}
///|
test "every line terminator normalizes to a single \\n" {
let model = doc("a\r\nb\rc\nd")
debug_inspect(
(model.get_line_count(), model.get_lines_content(), model.get_value()),
content=(
#|(4, ["a", "b", "c", "d"], "a\nb\nc\nd")
),
)
}///|
test "offsets and positions convert through the snapshot" {
let model = doc("let x = 1\nlet y = 2\n")
debug_inspect(
(
model.get_offset_at(Position(2, 1)),
model.get_position_at(10),
model.get_position_at(0),
// out-of-range input is clamped rather than raising
model.get_position_at(9999),
),
content=(
#|(
#| 10,
#| { line_number: 2, column: 1 },
#| { line_number: 1, column: 1 },
#| { line_number: 3, column: 1 },
#|)
),
)
}///|
test "reads are clamped, and the text survives disposal" {
let model = doc("ab\ncd\n")
let clamped = model.get_value_in_range(Range(1, 1, 99, 99))
model.dispose()
debug_inspect(
(clamped, model.is_disposed(), model.get_value()),
content=(
#|("ab\ncd\n", true, "ab\ncd\n")
),
)
}///|
test "identity and version id are the freshness authority, not the URI" {
let first = doc("one\n")
let second = doc("two\n")
let version_before = first.get_version_id()
first.set_value("one changed\n")
debug_inspect(
(
first.uri == second.uri,
first.identity() == second.identity(),
version_before,
first.get_version_id(),
),
content=(
#|(true, false, 1, 2)
),
)
}///|
test "word_at returns the word containing the position" {
let model = doc("let value = 1\n")
debug_inspect(
(
model.get_word_at_position(Position(1, 6)),
model.get_word_at_position(Position(1, 4)),
),
content=(
#|(
#| Some({ word: "value", start_column: 5, end_column: 10 }),
#| Some({ word: "let", start_column: 1, end_column: 4 }),
#|)
),
)
}///|
test "delta_decorations replaces one id set with another" {
let model = doc("alpha\nbeta\ngamma\n")
let options = @model.ModelDecorationOptions("doc-example")
let ids = model.delta_decorations([], [
{ range: Range(1, 1, 1, 6), options, },
{ range: Range(3, 1, 3, 6), options, },
])
let in_line_one = model.get_line_decorations(1).length()
let after = model.delta_decorations(ids, [])
debug_inspect(
(
ids.length(),
in_line_one,
after,
model.get_decorations_in_range(model.get_full_model_range()).length(),
),
content=(
#|(2, 1, [], 0)
),
)
}///|
test "set_value destroys existing decorations" {
let model = doc("alpha\nbeta\n")
let ids = model.delta_decorations([], [
{
range: Range(1, 1, 1, 6),
options: ModelDecorationOptions("doc-example"),
},
])
let before = model.get_decoration_range(ids[0]) is Some(_)
model.set_value("replaced\n")
debug_inspect(
(before, model.get_decoration_range(ids[0])),
content=(
#|(true, None)
),
)
}///|
test "queries report the tracked range, filtered by the queried span" {
let model = doc("alpha\nbeta\ngamma\n")
let options = @model.ModelDecorationOptions("doc-example")
model.delta_decorations([], [{ range: Range(2, 1, 2, 5), options, }])
|> ignore
debug_inspect(
(
model.get_decorations_in_range(Range(1, 1, 1, 6)).length(),
model.get_decorations_in_range(Range(2, 1, 2, 5)).length(),
model.get_decorations_in_range(Range(1, 1, 3, 6)).length(),
),
content=(
#|(0, 1, 1)
),
)
}moon test --target js viewer/common/model
moon test --target native viewer/common/model
moon test --target js viewer/common/view_model
moon test --target native viewer/common/view_modeltype AttachedViewImplfn AttachedViewImpl::set_visible_lines(self : AttachedViewImpl, visible_lines : Array[VisibleLineRange], stabilized : Bool) -> Unittype DecorationsTreestype DidChangeContentEmittertype DidChangeDecorationsEmitterfn InjectedTextOptions::InjectedTextOptions(content : String, inline_class_name? : String, attached_index? : Int) -> InjectedTextOptionspub struct InternalModelContentChangeEvent {
raw_content_changed_event : ModelRawContentChangedEvent
content_changed_event : ModelContentChangedEvent
} derive(Debug)pub struct ModelContentChangedEvent {
changes : Array[ModelContentChange]
eol : String
version_id : Int
is_undoing : Bool
is_redoing : Bool
is_flush : Bool
is_eol_change : Bool
} derive(Debug)pub(all) struct ModelDecoration {
id : String
owner_id : Int
range : Range
options : ModelDecorationOptions
} derive(Debug)pub(all) struct ModelDecorationMinimapOptions {
color : String
position : MinimapPosition
} derive(Eq, Debug)fn ModelDecorationMinimapOptions::equal(ModelDecorationMinimapOptions, ModelDecorationMinimapOptions) -> Boolfn ModelDecorationMinimapOptions::not_equal(x : ModelDecorationMinimapOptions, y : ModelDecorationMinimapOptions) -> Boolpub struct ModelDecorationOptions {
class_name : String
message : String?
inline_class_name : String?
inline_class_name_affects_letter_spacing : Bool
z_index : Int
is_whole_line : Bool
show_if_collapsed : Bool
should_fill_line_on_line_break : Bool
stickiness : TrackedRangeStickiness
overview_ruler : ModelDecorationOverviewRulerOptions?
minimap : ModelDecorationMinimapOptions?
glyph_margin_class_name : String?
lines_decorations_class_name : String?
first_line_decoration_class_name : String?
margin_class_name : String?
after_content_class_name : String?
collapse_on_replace_edit : Bool
description : String
before : InjectedTextOptions?
after : InjectedTextOptions?
} derive(Debug)fn ModelDecorationOptions::ModelDecorationOptions(class_name : String, message? : String, inline_class_name? : String, inline_class_name_affects_letter_spacing? : Bool, z_index? : Int, is_whole_line? : Bool, show_if_collapsed? : Bool, should_fill_line_on_line_break? : Bool, stickiness? : TrackedRangeStickiness, overview_ruler? : ModelDecorationOverviewRulerOptions, minimap? : ModelDecorationMinimapOptions, glyph_margin_class_name? : String, lines_decorations_class_name? : String, first_line_decoration_class_name? : String, margin_class_name? : String, after_content_class_name? : String, collapse_on_replace_edit? : Bool, description? : String, before? : InjectedTextOptions, after? : InjectedTextOptions) -> ModelDecorationOptionspub(all) struct ModelDecorationOverviewRulerOptions {
color : String?
position : OverviewRulerLane
} derive(Eq, Debug)fn ModelDecorationOverviewRulerOptions::not_equal(x : ModelDecorationOverviewRulerOptions, y : ModelDecorationOverviewRulerOptions) -> Boolpub struct ModelDecorationsChangeAccessor {
add_decoration : (Range, ModelDecorationOptions) -> String
change_decoration : (String, Range) -> Unit
change_decoration_options : (String, ModelDecorationOptions) -> Unit
remove_decoration : (String) -> Unit
delta_decorations : (Array[String], Array[ModelDeltaDecoration]) -> Array[String]
}fn ModelDecorationsChangedEvent::equal(ModelDecorationsChangedEvent, ModelDecorationsChangedEvent) -> Boolfn ModelDecorationsChangedEvent::not_equal(x : ModelDecorationsChangedEvent, y : ModelDecorationsChangedEvent) -> Boolpub(all) struct ModelDeltaDecoration {
range : Range
options : ModelDecorationOptions
} derive(Debug)pub enum ModelRawChange {
ModelRawFlush
ModelRawLineChanged(line_number~ : Int, line_number_post_edit~ : Int)
ModelRawLinesDeleted(from_line_number~ : Int, to_line_number~ : Int, last_untouched_line_post_edit~ : Int)
ModelRawLinesInserted(from_line_number~ : Int, from_line_number_post_edit~ : Int, count~ : Int)
ModelRawEOLChanged
} derive(Debug)pub struct ModelRawContentChangedEvent {
changes : Array[ModelRawChange]
version_id : Int
is_undoing : Bool
is_redoing : Bool
} derive(Debug)fn ModelRawContentChangedEvent::contains_event(self : ModelRawContentChangedEvent, change_type : RawContentChangedType) -> Boolpub(all) struct ModelTokensChangedEvent {
semantic_tokens_applied : Bool
ranges : Array[ModelTokensChangedRange]
}pub(all) struct ModelTokensChangedRange {
from_line_number : Int
to_line_number : Int
}pub struct TextModel {
uri : Uri
display_name : String
language_id : String
version : Int
revision : String
snapshot : TextSnapshot
tokenization : TokenizationTextModelPart
on_will_dispose : Emitter[Unit]
on_did_change_decorations : DidChangeDecorationsEmitter
// private fields
}fn[T] TextModel::change_decorations(self : TextModel, callback : (ModelDecorationsChangeAccessor) -> T, owner_id? : Int) -> Tfn TextModel::get_decorations_in_range(self : TextModel, range : Range, owner_id? : Int, filter_out_validation? : Bool, filter_font_decorations? : Bool, only_margin_decorations? : Bool) -> Array[ModelDecoration]fn TextModel::get_injected_text_decorations(self : TextModel, owner_id? : Int) -> Array[ModelDecoration]fn TextModel::get_line_decorations(self : TextModel, line_number : Int, owner_id? : Int, filter_out_validation? : Bool, filter_font_decorations? : Bool) -> Array[ModelDecoration]fn TextModel::on_before_attached(self : TextModel, scheduler? : TokenizationScheduler) -> AttachedViewImplfn TextModel::on_did_change_content(self : TextModel, listener : (ModelContentChangedEvent) -> Unit) -> Disposablefn TextModel::on_did_change_decorations(self : TextModel, listener : (ModelDecorationsChangedEvent) -> Unit) -> Disposablefn TextModel::on_did_change_tokens(self : TextModel, listener : (ModelTokensChangedEvent) -> Unit) -> Disposablefn TextModel::register_view_model(self : TextModel, on_did_change_content_or_injected_text : (InternalModelContentChangeEvent) -> Unit, emit_content_change_event : (InternalModelContentChangeEvent) -> Unit) -> Disposabletype TokenizationIdleDeadlinefn TokenizationIdleDeadline::TokenizationIdleDeadline(read_time_remaining : () -> Double) -> TokenizationIdleDeadlinetype TokenizationSchedulerfn TokenizationScheduler::TokenizationScheduler(idle~ : ((TokenizationIdleDeadline) -> Unit) -> Disposable, zero_timeout~ : (() -> Unit) -> Disposable, delay~ : (Int, () -> Unit) -> Disposable, now~ : () -> Double) -> TokenizationSchedulertype TokenizationTextModelPartfn TokenizationTextModelPart::get_line_tokens(self : TokenizationTextModelPart, line_number : Int) -> LineTokensfn TrackedRangeStickiness::not_equal(x : TrackedRangeStickiness, y : TrackedRangeStickiness) -> Boolfn compute_indent_level(line : String, tab_size : Int) -> Intfn line_tokens_from_lexer_tokens(line_text : String, tokens : Array[LineToken], decoder : LanguageIdCodec, language_id? : Int) -> LineTokensInstall
Download zipReadonly MoonBit code viewer harness inspired by Monaco and CodeMirror.