///| Attached-view priority and shared syntactic backend contracts.
///
/// Source: `vscode/src/vs/editor/common/model/tokens/abstractSyntaxTokenBackend.ts`
/// in the checked-in source, lines 24-187. The local
/// emitter/generation scheduler is the Gate-B-approved FFI-free observable
/// adaptation; member order, range conversion, 50 ms delay, and early returns
/// follow that unit.
///|
/// Inclusive visible model-line range supplied by a view.
pub(all) struct VisibleLineRange {
start_line_number : Int32
end_line_number : Int32
} derive(Eq, Debug)
///|
/// Immutable normalized visible ranges plus stabilization state.
priv struct AttachedViewState {
visible_line_ranges : Array[@base_common.LineRange]
stabilized : Bool
}
///|
/// Retains caller-provided facts without normalization.
fn AttachedViewState::AttachedViewState(
visible_line_ranges : Array[@base_common.LineRange],
stabilized : Bool,
) -> AttachedViewState {
{ visible_line_ranges, stabilized, }
}
///|
/// Source-order equality: physical identity, then ranges, then stabilization.
fn AttachedViewState::equals(
self : AttachedViewState,
other : AttachedViewState,
) -> Bool {
if physical_equal(self, other) {
return true
}
if !line_range_arrays_equal(
self.visible_line_ranges[:],
other.visible_line_ranges[:],
) {
return false
}
if self.stabilized != other.stabilized {
return false
}
true
}
///|
/// Event payload preserving the exact attached-view identity and optional state.
priv struct AttachedViewStateChange {
view : AttachedViewImpl
state : AttachedViewState?
}
///|
/// One externally-owned attached-view handle.
struct AttachedViewImpl {
id : Int32
mut state_value : AttachedViewState?
handle_state_change : (AttachedViewImpl, AttachedViewState) -> Unit
}
///|
/// Converts inclusive ranges to `LineRange` and notifies after committing the
/// new state. Equality suppresses only the stored-state write; the source still
/// invokes the owner callback for every `setVisibleLines` call.
pub fn AttachedViewImpl::set_visible_lines(
self : AttachedViewImpl,
visible_lines : Array[VisibleLineRange],
stabilized : Bool,
) -> Unit {
let ranges = visible_lines.map(line => {
@base_common.LineRange(line.start_line_number, line.end_line_number + 1)
})
let state = AttachedViewState(ranges, stabilized)
match self.state_value {
Some(old) => if !old.equals(state) { self.state_value = Some(state) }
None => self.state_value = Some(state)
}
(self.handle_state_change)(self, state)
}
///|
/// Identity comparison used by the owning manager.
fn AttachedViewImpl::same(
self : AttachedViewImpl,
other : AttachedViewImpl,
) -> Bool {
self.id == other.id
}
///|
/// Owner of all live attached-view identities and aggregate ranges.
priv struct AttachedViews {
views : Array[AttachedViewImpl]
next_id : Ref[Int32]
did_change_visible_ranges : @base_common.Emitter[AttachedViewStateChange]
}
///|
/// Creates an empty attached-view manager.
fn AttachedViews::AttachedViews() -> AttachedViews {
{ views: [], next_id: Ref(0), did_change_visible_ranges: Emitter(), }
}
///|
/// Subscribes to state commits and explicit detach notifications.
fn AttachedViews::on_did_change_visible_ranges(
self : AttachedViews,
listener : (AttachedViewStateChange) -> Unit,
) -> @base_common.Disposable {
self.did_change_visible_ranges.event(listener)
}
///|
/// Adds and returns one exact externally-owned handle.
fn AttachedViews::attach_view(self : AttachedViews) -> AttachedViewImpl {
let id = self.next_id.val
self.next_id.val = id + 1
let view : AttachedViewImpl = {
id,
state_value: None,
handle_state_change: (view, state) => {
self.did_change_visible_ranges.fire({ view, state: Some(state), })
},
}
self.views.push(view)
view
}
///|
/// Deletes exact identity, emits the explicit absent-state signal, then invalidates aggregate state.
fn AttachedViews::detach_view(
self : AttachedViews,
view : AttachedViewImpl,
) -> Unit {
if self.views.search_by(candidate => candidate.same(view)) is Some(found) {
self.views.remove(found) |> ignore
}
self.did_change_visible_ranges.fire({ view, state: None, })
}
///|
/// Disposes only the manager event; handles remain externally owned values.
fn AttachedViews::dispose(self : AttachedViews) -> Unit {
self.did_change_visible_ranges.dispose()
}
///|
/// Exact visible-range debounce delay from `AttachedViewHandler`.
const ATTACHED_VIEW_REFRESH_DELAY_MS : Int32 = 50
///|
/// Per-view stabilized/debounced visible refresh owner.
priv struct AttachedViewHandler {
refresh_tokens : () -> Unit
scheduler : () -> TokenizationScheduler?
mut computed_line_ranges : Array[@base_common.LineRange]
mut requested_line_ranges : Array[@base_common.LineRange]
mut pending : @base_common.Disposable?
generation : Ref[Int32]
mut disposed : Bool
}
///|
/// Creates a handler using the model's current scheduler at schedule time.
fn AttachedViewHandler::AttachedViewHandler(
refresh_tokens : () -> Unit,
scheduler : () -> TokenizationScheduler?,
) -> AttachedViewHandler {
{
refresh_tokens,
scheduler,
computed_line_ranges: [],
requested_line_ranges: [],
pending: None,
generation: Ref(0),
disposed: false,
}
}
///|
/// Latest requested ranges.
fn AttachedViewHandler::line_ranges(
self : AttachedViewHandler,
) -> Array[@base_common.LineRange] {
self.requested_line_ranges
}
///|
/// Equality guard, commit, then one refresh.
fn AttachedViewHandler::update(self : AttachedViewHandler) -> Unit {
if self.disposed ||
line_range_arrays_equal(
self.computed_line_ranges[:],
self.requested_line_ranges[:],
) {
return
}
self.computed_line_ranges = self.requested_line_ranges
(self.refresh_tokens)()
}
///|
/// Replaces requested ranges and chooses immediate or 50 ms debounced refresh.
fn AttachedViewHandler::handle_state_change(
self : AttachedViewHandler,
state : AttachedViewState,
) -> Unit {
self.requested_line_ranges = state.visible_line_ranges
if state.stabilized {
self.cancel_pending()
self.update()
} else {
self.cancel_pending()
match (self.scheduler)() {
Some(scheduler) => {
let generation = self.generation.val
self.pending = Some(
scheduler.delay(ATTACHED_VIEW_REFRESH_DELAY_MS, () => {
if !self.disposed && generation == self.generation.val {
self.pending = None
self.update()
}
}),
)
}
None => ()
}
}
}
///|
/// Cancels the current one-shot and invalidates stale callbacks.
fn AttachedViewHandler::cancel_pending(self : AttachedViewHandler) -> Unit {
self.generation.val += 1
match self.pending {
Some(handle) => handle.dispose()
None => ()
}
self.pending = None
}
///|
/// Releases the one-shot owner.
fn AttachedViewHandler::dispose(self : AttachedViewHandler) -> Unit {
if !self.disposed {
self.disposed = true
self.cancel_pending()
}
}
///|
/// Shared state/events held by the concrete syntactic backend.
priv struct AbstractSyntaxTokenBackend {
did_change_tokens : @base_common.Emitter[ModelTokensChangedEvent]
}
///|
/// Creates the shared backend contract.
fn AbstractSyntaxTokenBackend::AbstractSyntaxTokenBackend() -> AbstractSyntaxTokenBackend {
{ did_change_tokens: Emitter(), }
}
///|
/// Subscribes to exact changed line ranges.
fn AbstractSyntaxTokenBackend::on_did_change_tokens(
self : AbstractSyntaxTokenBackend,
listener : (ModelTokensChangedEvent) -> Unit,
) -> @base_common.Disposable {
self.did_change_tokens.event(listener)
}
///|
/// Releases shared emitters.
fn AbstractSyntaxTokenBackend::dispose(
self : AbstractSyntaxTokenBackend,
) -> Unit {
self.did_change_tokens.dispose()
}
///|
/// Normalizes ranges in ascending order and joins touching intervals.
fn join_line_ranges(
ranges : ArrayView[@base_common.LineRange],
) -> Array[@base_common.LineRange] {
let set = @base_common.LineRangeSet()
for range in ranges {
set.add_range(range)
}
set.ranges()
}
///|
/// Element equality without allocation.
fn line_range_arrays_equal(
left : ArrayView[@base_common.LineRange],
right : ArrayView[@base_common.LineRange],
) -> Bool {
left == right
}
///|
pub extend VisibleLineRange with Debug::{to_repr}
///|
pub extend VisibleLineRange with Eq::{equal, not_equal}