direction: right
host: host / backend {
grid-columns: 1
a: moon ide, LSP, in-memory...
}
lang: language (this package) {
grid-columns: 1
b: provider traits
c: result DTOs
d: CancellationToken
e: LanguageSelector
}
reg: viewer/common/languages {
grid-columns: 1
f: runtime registration
}
mk: viewer/common/markers {
grid-columns: 1
g: Diagnostic -> decorations
}
host -> lang: implements traits
lang -> reg: registered by hosts
lang -> mk: Diagnostic is a shared shape only///|
test "diagnostic severities and tags own their labels" {
let severities : Array[@language.DiagnosticSeverity] = [
Error,
Warning,
Info,
Hint,
]
let tags : Array[@language.DiagnosticTag] = [Unnecessary, Deprecated]
debug_inspect(
(severities.map(s => s.label()), tags.map(t => t.label())),
content=(
#|(["Error", "Warning", "Info", "Hint"], ["Unnecessary", "Deprecated"])
),
)
}///|
test "hover content keeps plain text and markdown apart" {
let hover : @language.Hover = {
range: Range(3, 5, 3, 12),
contents: [PlainText("fn main"), Markdown("```mbt\nfn main {}\n```")],
}
debug_inspect(
hover,
content=(
#|{
#| range: {
#| start_line_number: 3,
#| start_column: 5,
#| end_line_number: 3,
#| end_column: 12,
#| },
#| contents: [PlainText("fn main"), Markdown("```mbt\nfn main {}\n```")],
#|}
),
)
}sequenceDiagram
participant R as request owner
participant S as CancellationTokenSource
participant T as CancellationToken
participant P as provider
R->>S: CancellationTokenSource(parent?)
S-->>R: token() (identity-stable)
R->>P: provide_hover(model, position, token)
P->>T: on_cancellation_requested(abort)
alt superseded
R->>S: cancel()
S->>T: notify once, registration order
T-->>P: abort
else completed
R->>S: dispose()
Note over S,T: listeners released, not cancelled
end///|
test "one source hands out one identity-stable token" {
let source = @language.CancellationTokenSource()
let first = source.token()
let second = source.token()
let fired = []
first.on_cancellation_requested(() => fired.push("first-listener")) |> ignore
second.on_cancellation_requested(() => fired.push("second-listener"))
|> ignore
source.cancel()
source.cancel()
debug_inspect(
(first.is_cancellation_requested(), fired),
content=(
#|(true, ["first-listener", "second-listener"])
),
)
}///|
test "late listeners still fire under the default inline scheduler" {
let source = @language.CancellationTokenSource()
let token = source.token()
source.cancel()
let fired = []
token.on_cancellation_requested(() => fired.push("late")) |> ignore
debug_inspect(
(token.is_cancellation_requested(), fired),
content=(
#|(true, ["late"])
),
)
}///|
test "parent cancellation propagates to children" {
let parent = @language.CancellationTokenSource()
let child = @language.CancellationTokenSource(parent=parent.token())
let grandchild = @language.CancellationTokenSource(parent=child.token())
parent.cancel()
debug_inspect(
(
parent.token().is_cancellation_requested(),
child.token().is_cancellation_requested(),
grandchild.token().is_cancellation_requested(),
),
content=(
#|(true, true, true)
),
)
}///|
test "dispose separates listener teardown from cancellation" {
let completed = @language.CancellationTokenSource()
let aborted = @language.CancellationTokenSource()
let fired = []
completed.token().on_cancellation_requested(() => fired.push("completed"))
|> ignore
aborted.token().on_cancellation_requested(() => fired.push("aborted"))
|> ignore
completed.dispose()
aborted.dispose(cancel=true)
debug_inspect(
(
completed.token().is_cancellation_requested(),
aborted.token().is_cancellation_requested(),
fired,
),
content=(
#|(false, true, ["aborted"])
),
)
}///|
test "none and cancelled are the constant tokens" {
debug_inspect(
(
@language.CancellationToken::none().is_cancellation_requested(),
@language.CancellationToken::cancelled().is_cancellation_requested(),
),
content=(
#|(false, true)
),
)
}///|
test "a filter's omitted fields do not constrain its positive score" {
let model = @model.TextModel(
@base_common.Uri::parse("file://workspace/src/main.mbt"),
"main.mbt",
"moonbit",
1,
"rev-1",
"fn main {}\n",
)
debug_inspect(
(
@language.LanguageSelector::LanguageId("moonbit").matches(model),
@language.LanguageSelector::LanguageId("javascript").matches(model),
@language.LanguageFilter::LanguageFilter().matches(model),
@language.LanguageFilter::LanguageFilter(scheme="file").matches(model),
@language.LanguageFilter::LanguageFilter(scheme="memory").matches(model),
),
content=(
#|(true, false, false, true, false)
),
)
}///|
test "the first star splits the pattern into a prefix and a suffix" {
let model = @model.TextModel(
@base_common.Uri::parse("file://workspace/src/main.mbt"),
"main.mbt",
"moonbit",
1,
"rev-1",
"fn main {}\n",
)
let matches = pattern => {
@language.LanguageFilter::LanguageFilter(pattern~).matches(model)
}
debug_inspect(
(
// exact, no star
matches("src/main.mbt"),
matches("src/other.mbt"),
// prefix and suffix around the first star
matches("src/*"),
matches("*.mbt"),
matches("src/*.mbt"),
// the second star is a literal character, so this cannot match
matches("**/*.mbt"),
),
content=(
#|(true, false, true, true, true, false)
),
)
}///|
test "a selector list matches if any member matches" {
let model = @model.TextModel(
@base_common.Uri::parse("file://workspace/src/main.mbt"),
"main.mbt",
"moonbit",
1,
"rev-1",
"fn main {}\n",
)
let selector : @language.LanguageSelector = LanguageSelectorList([
LanguageId("javascript"),
LanguageId("json"),
LanguageFilter(LanguageFilter(language="moonbit")),
])
let unmatched : @language.LanguageSelector = LanguageSelectorList([
LanguageId("javascript"),
LanguageId("json"),
])
debug_inspect(
(selector.matches(model), unmatched.matches(model)),
content=(
#|(true, false)
),
)
}pub(open) trait CodeLensProvider {
async fn provide_code_lenses(Self, TextModel, CancellationToken) -> Array[CodeLens]
}pub(open) trait DefinitionProvider {
async fn provide_definition(Self, TextModel, Position, CancellationToken) -> Array[Location]
}pub(open) trait DocumentSymbolProvider {
async fn provide_document_symbols(Self, TextModel, CancellationToken) -> Array[DocumentSymbol]
}pub(open) trait HoverProvider {
async fn provide_hover(Self, TextModel, Position, CancellationToken) -> Hover?
}pub(open) trait MarkdownCommentProvider {
fn provide_markdown_comments(Self, TextModel) -> Array[MarkdownCommentBlock] raise
}pub(open) trait ReferencesProvider {
async fn provide_references(Self, TextModel, Position, CancellationToken) -> Array[Location]
}type CancellationTokenfn CancellationToken::on_cancellation_requested(self : CancellationToken, listener : () -> Unit, disposables? : Array[Disposable]) -> Disposabletype CancellationTokenSourcefn CancellationTokenSource::CancellationTokenSource(parent? : CancellationToken, schedule? : (() -> Unit) -> Disposable) -> CancellationTokenSourcepub(all) struct Diagnostic {
range : Range
severity : DiagnosticSeverity
message : String
tags : Array[DiagnosticTag]?
} derive(Eq, Debug)fn LanguageFilter::LanguageFilter(language? : String, scheme? : String, pattern? : String) -> LanguageFilterpub(all) enum LanguageSelector {
LanguageId(String)
LanguageFilter(LanguageFilter)
LanguageSelectorList(Array[LanguageSelector])
} derive(Debug)Install
Download zipReadonly MoonBit code viewer harness inspired by Monaco and CodeMirror.