sticky.go
1package ygo
2
3/*
4#include "libyrs.h"
5*/
6import "C"
7import (
8 "fmt"
9 "unsafe"
10)
11
12// StickyIndex represents a position in a document that survives changes.
13// Unlike numeric positions, it tracks the logical location in the document.
14type StickyIndex struct {
15 ptr *C.YStickyIndex
16}
17
18// Assoc determines whether position is before or after a character.
19type Assoc int8
20
21const (
22 // AssocAfter means the position points after the referenced character.
23 AssocAfter Assoc = 0
24 // AssocBefore means the position points before the referenced character.
25 AssocBefore Assoc = -1
26)
27
28// NewStickyIndexFromIndex creates a sticky index at a human-readable position.
29func NewStickyIndexFromIndex(t interface{ Branch() unsafe.Pointer }, txn *Transaction, index uint32, assoc Assoc) (*StickyIndex, error) {
30 if t == nil {
31 return nil, fmt.Errorf("target is nil")
32 }
33 if txn == nil || txn.ptr == nil {
34 return nil, ErrNilTransaction
35 }
36 branch := t.Branch()
37 if branch == nil {
38 return nil, ErrNilBranch
39 }
40
41 ptr := C.ysticky_index_from_index(
42 (*C.Branch)(branch),
43 txn.ptr,
44 C.uint32_t(index),
45 C.int8_t(assoc))
46
47 if ptr == nil {
48 return nil, fmt.Errorf("failed to create sticky index")
49 }
50
51 si := &StickyIndex{ptr: ptr}
52 return si, nil
53}
54
55// Destroy releases resources.
56func (si *StickyIndex) Destroy() {
57 if si.ptr != nil {
58 C.ysticky_index_destroy(si.ptr)
59 si.ptr = nil
60 }
61}
62
63// Assoc returns the association of this index.
64func (si *StickyIndex) Assoc() (Assoc, error) {
65 if si.ptr == nil {
66 return AssocAfter, ErrNilBranch
67 }
68 return Assoc(C.ysticky_index_assoc(si.ptr)), nil
69}
70
71// Encode serializes the sticky index to binary.
72func (si *StickyIndex) Encode() ([]byte, error) {
73 if si.ptr == nil {
74 return nil, fmt.Errorf("sticky index is nil")
75 }
76 var length C.uint32_t
77 ptr := C.ysticky_index_encode(si.ptr, &length)
78 if ptr == nil {
79 return nil, fmt.Errorf("failed to encode sticky index")
80 }
81 defer C.ybinary_destroy(ptr, length)
82
83 data := make([]byte, int(length))
84 copy(data, (*[1 << 30]byte)(unsafe.Pointer(ptr))[:length:length])
85 return data, nil
86}
87
88// DecodeStickyIndex deserializes from binary.
89func DecodeStickyIndex(data []byte) (*StickyIndex, error) {
90 if len(data) == 0 {
91 return nil, fmt.Errorf("data is empty")
92 }
93 ptr := C.ysticky_index_decode(
94 (*C.char)(unsafe.Pointer(&data[0])),
95 C.uint32_t(len(data)))
96 if ptr == nil {
97 return nil, fmt.Errorf("failed to decode sticky index")
98 }
99
100 si := &StickyIndex{ptr: ptr}
101 return si, nil
102}
103
104// ToJSON serializes to JSON format (Yjs RelativePosition compatible).
105func (si *StickyIndex) ToJSON() (string, error) {
106 if si.ptr == nil {
107 return "", fmt.Errorf("sticky index is nil")
108 }
109 return cStringToGoAndFree(C.ysticky_index_to_json(si.ptr)), nil
110}
111
112// ParseStickyIndexJSON deserializes from JSON.
113func ParseStickyIndexJSON(jsonStr string) (*StickyIndex, error) {
114 cStr := C.CString(jsonStr)
115 defer C.free(unsafe.Pointer(cStr))
116
117 ptr := C.ysticky_index_from_json(cStr)
118 if ptr == nil {
119 return nil, fmt.Errorf("failed to parse sticky index from JSON")
120 }
121
122 si := &StickyIndex{ptr: ptr}
123 return si, nil
124}
125
126// Read resolves the sticky index to a current branch and numeric position.
127// Returns nil branch if the position is no longer valid.
128func (si *StickyIndex) Read(txn *Transaction) (unsafe.Pointer, uint32, error) {
129 if si.ptr == nil {
130 return nil, 0, fmt.Errorf("sticky index is nil")
131 }
132 if txn == nil || txn.ptr == nil {
133 return nil, 0, ErrNilTransaction
134 }
135
136 var branch *C.Branch
137 var index C.uint32_t
138
139 C.ysticky_index_read(si.ptr, txn.ptr, &branch, &index)
140
141 if branch == nil {
142 return nil, 0, fmt.Errorf("sticky index position is no longer valid")
143 }
144 return unsafe.Pointer(branch), uint32(index), nil
145}