-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathresolvepos.go
189 lines (153 loc) · 4.4 KB
/
resolvepos.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package prosemirror
import (
"fmt"
)
// resolve resolves the position within the node's content to a position in the document.
func resolve(doc Node, pos int) (ResolvedPos, error) {
if pos < 0 || pos > doc.Content.Size {
return ResolvedPos{}, fmt.Errorf("position out of bounds: %d (of %d)", pos, doc.Content.Size)
}
r := ResolvedPos{
Pos: pos,
ParentOffset: pos,
NodePath: []Node{},
IndexPath: []int{},
OffsetPath: []int{},
}
start := 0
for node := doc; ; {
index, offset := node.Content.findIndex(r.ParentOffset)
r.NodePath = append(r.NodePath, node)
r.IndexPath = append(r.IndexPath, index)
r.OffsetPath = append(r.OffsetPath, start+offset)
rem := r.ParentOffset - offset
if rem == 0 {
break
}
node = *node.Child(index)
if node.IsText() {
break
}
r.ParentOffset = rem - 1
start += offset + 1
}
r.Depth = len(r.NodePath) - 1
return r, nil
}
type ResolvedPos struct {
Depth int
Pos int
ParentOffset int
// in the original JS implementation, paths is a any[] type
// they index it to retrieve the right one.
// nodepath is *3
NodePath []Node
// indexpath is *3+1
IndexPath []int
// offsetpath is *3+2 or *3-1
OffsetPath []int
}
func (r ResolvedPos) String() string {
return fmt.Sprintf("ResolvedPos{Pos: %d, ParentOffset: %d, IndexPath: %v, OffsetPath: %v}", r.Pos, r.ParentOffset, r.IndexPath, r.OffsetPath)
}
func (r ResolvedPos) Node(depth int) Node {
return r.NodePath[r.resolveDepth(depth)]
}
func (r ResolvedPos) Index(depth int) int {
return r.IndexPath[r.resolveDepth(depth)]
}
// The index pointing after this position into the ancestor at the
// given level.
func (r ResolvedPos) IndexAfter(depth int) int {
depth = r.resolveDepth(depth)
i := r.Index(depth)
if depth != r.Depth || r.TextOffset() {
i += 1
}
return i
}
// The Parent node that the position points into. Note that even if
// a position points into a text node, that node is not considered
// the Parent—text nodes are ‘flat’ in this model, and have no content.
func (r ResolvedPos) Parent() Node {
return r.Node(r.Depth)
}
// When this position points into a text node, this returns the
// distance between the position and the start of the text node.
// Will be zero for positions that point between nodes.
func (r ResolvedPos) TextOffset() bool {
return r.Pos-r.OffsetPath[len(r.OffsetPath)-1] != 0
}
// Get the node directly after the position, if any. If the position
// points into a text node, only the part of that node after the
// position is returned.
func (r ResolvedPos) NodeAfter() *Node {
parent := r.Parent()
index := r.Index(r.Depth)
if index == parent.ChildCount() {
return nil
}
dOff := r.Pos - r.OffsetPath[len(r.OffsetPath)-1]
if dOff != 0 {
n := parent.Child(index).cut(dOff, -1)
return &n
}
return parent.Child(index)
}
// Get the node directly before the position, if any. If the
// position points into a text node, only the part of that node
// before the position is returned.
func (r ResolvedPos) NodeBefore() *Node {
index := r.Index(r.Depth)
dOff := r.Pos - r.OffsetPath[len(r.OffsetPath)-1]
if dOff != 0 {
n := r.Parent().Child(index).cut(0, dOff)
return &n
}
if index == 0 {
return nil
}
return r.Parent().Child(index - 1)
}
func (r ResolvedPos) SharedDepth(pos int) int {
for depth := r.Depth; depth > 0; depth-- {
if r.Start(depth) <= pos && r.End(depth) >= pos {
return depth
}
}
return 0
}
func (r ResolvedPos) Start(depth int) int {
depth = r.resolveDepth(depth)
if depth == 0 {
return 0
}
// Weirdly complex in the original impl. This actually queries the *parent* index path.
// https://github.com/ProseMirror/prosemirror-model/blob/a37b6b3adeb548dc9822211b680ce9d31be65842/src/resolvedpos.ts#L65
last := len(r.OffsetPath) - 1
return r.OffsetPath[last-1] + 1
}
func (r ResolvedPos) End(depth int) int {
depth = r.resolveDepth(depth)
return r.Start(depth) + r.Node(depth).Content.Size
}
func (r ResolvedPos) resolveDepth(depth int) int {
if depth < 0 {
return r.Depth + depth
}
return depth
}
func joinable(before, after ResolvedPos, depth int) (*Node, error) {
node := before.Node(depth)
err := checkJoin(node, after.Node(depth))
if err != nil {
return nil, err
}
return &node, nil
}
func checkJoin(main, sub Node) error {
if !sub.Type.compatibleContent(main.Type) {
return fmt.Errorf("can't join incompatible nodes (%s onto %s)", sub.Type.Name, main.Type.Name)
}
return nil
}