///| Readonly-applicable `TokenizationTextModelPart` over a live stateful backend.
///
/// Source: `vscode/src/vs/editor/common/model/tokens/tokenizationTextModelPart.ts`
/// and `vscode/src/vs/editor/common/tokenizationTextModelPart.ts` at
/// checked-in source. Semantic, dynamic-language,
/// embedded-language, bracket, and editable prediction arms remain the
/// explicitly deferred/N-A ledger rows; the syntactic lifecycle below keeps
/// source event order and per-line validation/demand.
///|
/// Exact syntactic/semantic token-change event payload.
pub(all) struct ModelTokensChangedEvent {
semantic_tokens_applied : Bool
ranges : Array[ModelTokensChangedRange]
}
///|
/// One inclusive changed line range.
pub(all) struct ModelTokensChangedRange {
from_line_number : Int32
to_line_number : Int32
}
///|
/// Public model tokenization part. Reads are passive; attached visible ranges
/// and the background worker drive lexing.
struct TokenizationTextModelPart {
model_state : TokenizationModelState
backend : TokenizerSyntaxTokenBackend
did_change_tokens : @base_common.Emitter[ModelTokensChangedEvent]
mut backend_token_subscription : @base_common.Disposable?
mut disposed : Bool
}
///|
/// Constructs the registry-backed live backend and event forwarding in source order.
fn TokenizationTextModelPart::TokenizationTextModelPart(
model_state : TokenizationModelState,
attached_views : AttachedViews,
) -> TokenizationTextModelPart {
TokenizationTextModelPart::new_with_support_resolver(
model_state,
attached_views,
language_id => {
@syntax.tokenization_registry
.get(language_id)
.map(tokenizer => {
public_tokenizer_adapter(
tokenizer, @services.language_id_codec, language_id,
)
})
},
subscribe_registry=true,
)
}
///|
/// Internal-support test seam preserving failing and `has_eol`-aware adapters.
fn TokenizationTextModelPart::new_with_support_resolver(
model_state : TokenizationModelState,
attached_views : AttachedViews,
resolve_support : (String) -> InternalTokenizationSupportAdapter?,
subscribe_registry~ : Bool = false,
) -> TokenizationTextModelPart {
let backend = TokenizerSyntaxTokenBackend::new_with_support_resolver(
model_state,
attached_views,
resolve_support,
subscribe_registry~,
)
let part : TokenizationTextModelPart = {
model_state,
backend,
did_change_tokens: Emitter(),
backend_token_subscription: None,
disposed: false,
}
part.backend_token_subscription = Some(
backend.on_did_change_tokens(event => {
if !part.model_state.disposing.val {
part.did_change_tokens.fire(event)
}
}),
)
part
}
///|
/// Subscribes to exact changed token ranges.
fn TokenizationTextModelPart::on_did_change_tokens(
self : TokenizationTextModelPart,
listener : (ModelTokensChangedEvent) -> Unit,
) -> @base_common.Disposable {
self.did_change_tokens.event(listener)
}
///|
/// Construction-fixed language id read from the shared model state.
fn TokenizationTextModelPart::get_language_id(
self : TokenizationTextModelPart,
) -> String {
self.model_state.language_id
}
///|
/// Inherited `TextModelPart` liveness guard. Guarded work continues while live
/// and enters the exact failure arm after terminal disposal.
fn TokenizationTextModelPart::assert_not_disposed(
self : TokenizationTextModelPart,
) -> Unit {
if self.disposed {
abort("TextModelPart is disposed!")
}
}
///|
/// Validates the inclusive one-based model-line domain.
fn TokenizationTextModelPart::validate_line_number(
self : TokenizationTextModelPart,
line_number : Int32,
) -> Unit {
if line_number < 1 || line_number > self.model_state.snapshot.val.line_count() {
abort("Illegal value for lineNumber")
}
}
///|
/// Passive stored/default syntactic tokens; never invokes the lexer.
pub fn TokenizationTextModelPart::get_line_tokens(
self : TokenizationTextModelPart,
line_number : Int32,
) -> @common_tokens.LineTokens {
self.validate_line_number(line_number)
self.backend.get_line_tokens(line_number)
}
///|
/// Flush content handling; live lines were replaced before this call.
fn TokenizationTextModelPart::handle_did_change_content(
self : TokenizationTextModelPart,
is_flush~ : Bool,
) -> Unit {
self.backend.handle_did_change_content(is_flush~)
}
///|
/// Re-evaluates scheduling after aggregate attach/scheduler transitions.
fn TokenizationTextModelPart::handle_did_change_attached(
self : TokenizationTextModelPart,
) -> Unit {
self.backend.handle_did_change_attached()
}
///|
/// Passive telemetry count. One stored/default read per model line and zero
/// lexer calls, including initial attach and token-change notification paths.
pub fn TokenizationTextModelPart::token_count(
self : TokenizationTextModelPart,
) -> Int32 {
self.assert_not_disposed()
let mut count = 0
for line_number in 1..<=self.model_state.snapshot.val.line_count() {
count += self.backend.get_line_tokens(line_number).get_count()
}
count
}
///|
/// Retained token listeners participate in model listener ownership.
fn TokenizationTextModelPart::has_listeners(
self : TokenizationTextModelPart,
) -> Bool {
self.did_change_tokens.has_listeners()
}
///|
/// Releases every registered resource while the inherited disposed flag is
/// still false: forwarding subscription, registered emitter, then backend.
fn TokenizationTextModelPart::dispose_owned_resources(
self : TokenizationTextModelPart,
) -> Unit {
match self.backend_token_subscription {
Some(subscription) => subscription.dispose()
None => ()
}
self.backend_token_subscription = None
self.did_change_tokens.dispose()
self.backend.dispose()
}
///|
/// Source-order teardown: registered resources first, terminal flag second.
fn TokenizationTextModelPart::dispose(self : TokenizationTextModelPart) -> Unit {
if self.disposed {
return
}
self.dispose_owned_resources()
self.disposed = true
}