-
Notifications
You must be signed in to change notification settings - Fork 61
/
replace_step.js
163 lines (142 loc) · 6.42 KB
/
replace_step.js
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
import {Slice} from "prosemirror-model"
import {Step, StepResult} from "./step"
import {StepMap} from "./map"
// ::- Replace a part of the document with a slice of new content.
export class ReplaceStep extends Step {
// :: (number, number, Slice, ?bool)
// The given `slice` should fit the 'gap' between `from` and
// `to`—the depths must line up, and the surrounding nodes must be
// able to be joined with the open sides of the slice. When
// `structure` is true, the step will fail if the content between
// from and to is not just a sequence of closing and then opening
// tokens (this is to guard against rebased replace steps
// overwriting something they weren't supposed to).
constructor(from, to, slice, structure) {
super()
this.from = from
this.to = to
this.slice = slice
this.structure = !!structure
}
apply(doc) {
if (this.structure && contentBetween(doc, this.from, this.to))
return StepResult.fail("Structure replace would overwrite content")
return StepResult.fromReplace(doc, this.from, this.to, this.slice)
}
getMap() {
return new StepMap([this.from, this.to - this.from, this.slice.size])
}
invert(doc) {
return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
}
map(mapping) {
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
if (from.deleted && to.deleted) return null
return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice)
}
merge(other) {
if (!(other instanceof ReplaceStep) || other.structure != this.structure) return null
if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
: new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd)
return new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure)
} else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) {
let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
: new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd)
return new ReplaceStep(other.from, this.to, slice, this.structure)
} else {
return null
}
}
toJSON() {
let json = {stepType: "replace", from: this.from, to: this.to}
if (this.slice.size) json.slice = this.slice.toJSON()
if (this.structure) json.structure = true
return json
}
static fromJSON(schema, json) {
if (typeof json.from != "number" || typeof json.to != "number")
throw new RangeError("Invalid input for ReplaceStep.fromJSON")
return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure)
}
}
Step.jsonID("replace", ReplaceStep)
// ::- Replace a part of the document with a slice of content, but
// preserve a range of the replaced content by moving it into the
// slice.
export class ReplaceAroundStep extends Step {
// :: (number, number, number, number, Slice, number, ?bool)
// Create a replace-around step with the given range and gap.
// `insert` should be the point in the slice into which the content
// of the gap should be moved. `structure` has the same meaning as
// it has in the [`ReplaceStep`](#transform.ReplaceStep) class.
constructor(from, to, gapFrom, gapTo, slice, insert, structure) {
super()
this.from = from
this.to = to
this.gapFrom = gapFrom
this.gapTo = gapTo
this.slice = slice
this.insert = insert
this.structure = !!structure
}
apply(doc) {
if (this.structure && (contentBetween(doc, this.from, this.gapFrom) ||
contentBetween(doc, this.gapTo, this.to)))
return StepResult.fail("Structure gap-replace would overwrite content")
let gap = doc.slice(this.gapFrom, this.gapTo)
if (gap.openStart || gap.openEnd)
return StepResult.fail("Gap is not a flat range")
let inserted = this.slice.insertAt(this.insert, gap.content)
if (!inserted) return StepResult.fail("Content does not fit in gap")
return StepResult.fromReplace(doc, this.from, this.to, inserted)
}
getMap() {
return new StepMap([this.from, this.gapFrom - this.from, this.insert,
this.gapTo, this.to - this.gapTo, this.slice.size - this.insert])
}
invert(doc) {
let gap = this.gapTo - this.gapFrom
return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap,
this.from + this.insert, this.from + this.insert + gap,
doc.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from),
this.gapFrom - this.from, this.structure)
}
map(mapping) {
let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
let gapFrom = mapping.map(this.gapFrom, -1), gapTo = mapping.map(this.gapTo, 1)
if ((from.deleted && to.deleted) || gapFrom < from.pos || gapTo > to.pos) return null
return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure)
}
toJSON() {
let json = {stepType: "replaceAround", from: this.from, to: this.to,
gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert}
if (this.slice.size) json.slice = this.slice.toJSON()
if (this.structure) json.structure = true
return json
}
static fromJSON(schema, json) {
if (typeof json.from != "number" || typeof json.to != "number" ||
typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON")
return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo,
Slice.fromJSON(schema, json.slice), json.insert, !!json.structure)
}
}
Step.jsonID("replaceAround", ReplaceAroundStep)
function contentBetween(doc, from, to) {
let $from = doc.resolve(from), dist = to - from, depth = $from.depth
while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
depth--
dist--
}
if (dist > 0) {
let next = $from.node(depth).maybeChild($from.indexAfter(depth))
while (dist > 0) {
if (!next || next.isLeaf) return true
next = next.firstChild
dist--
}
}
return false
}