pkg/ui/model.go (99 lines of code) (raw):
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ui
import (
"io"
"slices"
"sync"
)
type Document struct {
mutex sync.Mutex
subscriptions []*subscription
nextID uint64
blocks []Block
}
func (d *Document) Blocks() []Block {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.blocks
}
func (d *Document) NumBlocks() int {
return len(d.Blocks())
}
func (d *Document) IndexOf(find Block) int {
blocks := d.Blocks()
for i, b := range blocks {
if b == find {
return i
}
}
return -1
}
func NewDocument() *Document {
return &Document{
nextID: 1,
}
}
type Block interface {
attached(doc *Document)
Document() *Document
}
type Subscriber interface {
DocumentChanged(doc *Document, block Block)
}
type subscription struct {
doc *Document
id uint64
subscriber Subscriber
}
func (s *subscription) Close() error {
s.doc.mutex.Lock()
defer s.doc.mutex.Unlock()
s.subscriber = nil
return nil
}
func (d *Document) AddSubscription(subscriber Subscriber) io.Closer {
d.mutex.Lock()
defer d.mutex.Unlock()
id := d.nextID
d.nextID++
s := &subscription{
doc: d,
id: id,
subscriber: subscriber,
}
newSubscriptions := make([]*subscription, 0, len(d.subscriptions)+1)
for _, s := range d.subscriptions {
if s == nil || s.subscriber == nil {
continue
}
newSubscriptions = append(newSubscriptions, s)
}
newSubscriptions = append(newSubscriptions, s)
d.subscriptions = newSubscriptions
return s
}
func (d *Document) sendDocumentChanged(b Block) {
d.mutex.Lock()
subscriptions := d.subscriptions
d.mutex.Unlock()
for _, s := range subscriptions {
if s == nil || s.subscriber == nil {
continue
}
s.subscriber.DocumentChanged(d, b)
}
}
func (d *Document) AddBlock(block Block) {
d.mutex.Lock()
// Copy-on-write to minimize locking
newBlocks := slices.Clone(d.blocks)
newBlocks = append(newBlocks, block)
d.blocks = newBlocks
block.attached(d)
d.mutex.Unlock()
d.sendDocumentChanged(block)
}
func (d *Document) blockChanged(block Block) {
if d == nil {
return
}
d.sendDocumentChanged(block)
}