Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port prosemirror-transform changes up to 1.7.1 #211

Merged
merged 5 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This package provides Python implementations of the following
[ProseMirror](https://prosemirror.net/) packages:

- [`prosemirror-model`](https://github.com/ProseMirror/prosemirror-model) version 1.18.1
- [`prosemirror-transform`](https://github.com/ProseMirror/prosemirror-transform) version 1.6.0
- [`prosemirror-transform`](https://github.com/ProseMirror/prosemirror-transform) version 1.7.1
- [`prosemirror-test-builder`](https://github.com/ProseMirror/prosemirror-test-builder)
- [`prosemirror-schema-basic`](https://github.com/ProseMirror/prosemirror-schema-basic) version 1.1.2
- [`prosemirror-schema-list`](https://github.com/ProseMirror/prosemirror-schema-list)
Expand Down Expand Up @@ -87,4 +87,4 @@ assert tr.doc.to_json() == {
}]
}]
}
```
```
10 changes: 5 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 109 additions & 0 deletions prosemirror/transform/mark_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,112 @@ def from_json(schema, json_data):


Step.json_id("removeMark", RemoveMarkStep)


class AddNodeMarkStep(Step):
def __init__(self, pos, mark):
super().__init__()
self.pos = pos
self.mark = mark

def apply(self, doc):
node = doc.node_at(self.pos)
if not node:
return StepResult.fail("No node at mark step's position")
updated = node.type.create(node.attrs, None, self.mark.add_to_set(node.marks))
return StepResult.from_replace(
doc,
self.pos,
self.pos + 1,
Slice(Fragment.from_(updated), 0, 0 if node.is_leaf else 1),
)

def invert(self, doc):
node = doc.node_at(self.pos)
if node:
new_set = self.mark.add_to_set(node.marks)
if len(new_set) == len(node.marks):
for i in range(len(node.marks)):
if not node.marks[i].is_in_set(new_set):
return AddNodeMarkStep(self.pos, node.marks[i])
return AddNodeMarkStep(self.pos, self.mark)
return RemoveNodeMarkStep(self.pos, self.mark)

def map(self, mapping):
pos = mapping.map_result(self.pos, 1)
return None if pos.deleted_after else AddNodeMarkStep(pos.pos, self.mark)

def to_json(self):
return {
"stepType": "addNodeMark",
"pos": self.pos,
"mark": self.mark.to_json(),
}

@staticmethod
def from_json(schema, json_data):
if isinstance(json_data, str):
import json

json_data = json.loads(json_data)
if not isinstance(json_data["pos"], int):
raise ValueError("Invalid input for AddNodeMarkStep.from_json")
return AddNodeMarkStep(
json_data["pos"], schema.mark_from_json(json_data["mark"])
)


Step.json_id("addNodeMark", AddNodeMarkStep)


class RemoveNodeMarkStep(Step):
def __init__(self, pos, mark):
super().__init__()
self.pos = pos
self.mark = mark

def apply(self, doc):
node = doc.node_at(self.pos)
if not node:
return StepResult.fail("No node at mark step's position")
updated = node.type.create(
node.attrs, None, self.mark.remove_from_set(node.marks)
)
return StepResult.from_replace(
doc,
self.pos,
self.pos + 1,
Slice(Fragment.from_(updated), 0, 0 if node.is_leaf else 1),
)

def invert(self, doc):
node = doc.node_at(self.pos)
if not node or not self.mark.is_in_set(node.marks):
return self
return AddNodeMarkStep(self.pos, self.mark)

def map(self, mapping):
pos = mapping.map_result(self.pos, 1)
return None if pos.deleted_after else RemoveNodeMarkStep(pos.pos, self.mark)

def to_json(self):
return {
"stepType": "removeNodeMark",
"pos": self.pos,
"mark": self.mark.to_json(),
}

@staticmethod
def from_json(schema, json_data):
if isinstance(json_data, str):
import json

json_data = json.loads(json_data)
if not isinstance(json_data["pos"], int):
raise ValueError("Invalid input for RemoveNodeMarkStep.from_json")
return RemoveNodeMarkStep(
json_data["pos"], schema.mark_from_json(json_data["mark"])
)


Step.json_id("removeNodeMark", RemoveNodeMarkStep)
16 changes: 15 additions & 1 deletion prosemirror/transform/replace.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,22 @@ def fit(self) -> Optional[Step]:
return None

def find_fittable(self) -> Optional[_Fittable]:
start_depth = self.unplaced.open_start
cur = self.unplaced.content
open_end = self.unplaced.open_end
for d in range(start_depth):
node = cur.first_child
if cur.child_count > 1:
open_end = 0
if node.type.spec.get("isolating") and open_end <= d:
start_depth = d
break
cur = node.content

for pass_ in [1, 2]:
for slice_depth in range(self.unplaced.open_start, -1, -1):
for slice_depth in range(
start_depth if pass_ == 1 else self.unplaced.open_start, -1, -1
):
if slice_depth:
parent = content_at(
self.unplaced.content, slice_depth - 1
Expand Down
17 changes: 15 additions & 2 deletions prosemirror/transform/transform.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Union

from prosemirror.model import Fragment, MarkType, Node, NodeType, Slice
from prosemirror.model import Fragment, Mark, MarkType, Node, NodeType, Slice

from . import replace, structure
from .attr_step import AttrStep
from .map import Mapping
from .mark_step import AddMarkStep, RemoveMarkStep
from .mark_step import AddMarkStep, AddNodeMarkStep, RemoveMarkStep, RemoveNodeMarkStep
from .replace import close_fragment, covered_depths, fits_trivially, replace_step
from .replace_step import ReplaceAroundStep, ReplaceStep
from .structure import can_change_type, insert_point
Expand Down Expand Up @@ -460,6 +460,19 @@ def set_node_markup(self, pos, type, attrs, marks=None):
def set_node_attribute(self, pos, attr, value):
return self.step(AttrStep(pos, attr, value))

def add_node_mark(self, pos, mark):
return self.step(AddNodeMarkStep(pos, mark))

def remove_node_mark(self, pos, mark):
if not isinstance(mark, Mark):
node = self.doc.node_at(pos)
if not node:
raise ValueError("No node at position " + pos)
mark = mark.is_in_set(node.marks)
if not mark:
return self
return self.step(RemoveNodeMarkStep(pos, mark))

def split(self, pos, depth=None, types_after=None):
if depth is None:
depth = 1
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pydash = "^5.1.2"
pandoc = "^2.3"
pytest-cov = "^4.0.0"
isort = "^5.11.4"
codecov = "^2.1.12"
codecov = "^2.1.13"
coverage = "^7.0.5"
ruff = "^0.0.230"

Expand Down
73 changes: 73 additions & 0 deletions tests/prosemirror_transform/tests/test_trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,45 @@ def test_will_insert_filler_nodes_before_a_node_when_necessary(self):
assert tr.doc.eq(doc(h(), b(p("One"))))


def test_keeps_isolating_nodes_together():
s = Schema(
{
"nodes": {
**schema.spec["nodes"],
"iso": {
"group": "block",
"content": "block+",
"isolating": True,
},
},
}
)
doc = s.node("doc", None, [s.node("paragraph", None, [s.text("one")])])
iso = Fragment.from_(
s.node("iso", None, [s.node("paragraph", None, [s.text("two")])])
)
assert (
Transform(doc)
.replace(2, 3, Slice(iso, 2, 0))
.doc.eq(
s.node(
"doc",
None,
[
s.node("paragraph", None, [s.text("o")]),
s.node("iso", None, [s.node("paragraph", None, [s.text("two")])]),
s.node("paragraph", None, [s.text("e")]),
],
)
)
)
assert (
Transform(doc)
.replace(2, 3, Slice(iso, 2, 2))
.doc.eq(s.node("doc", None, [s.node("paragraph", None, [s.text("otwoe")])]))
)


@pytest.mark.parametrize(
"doc,source,expect",
[
Expand Down Expand Up @@ -1051,3 +1090,37 @@ def test_delete_range(doc, expect, test_transform):
doc.tag.get("a"), doc.tag.get("b") or doc.tag.get("a")
)
test_transform(tr, expect)


@pytest.mark.parametrize(
"doc,mark,expect",
[
# adds a mark
(doc(p("<a>", img())), schema.mark("em"), doc(p("<a>", em(img())))),
# doesn't duplicate a mark
(doc(p("<a>", em(img()))), schema.mark("em"), doc(p("<a>", em(img())))),
# replaces a mark
(
doc(p("<a>", a(img()))),
schema.mark("link", {"href": "x"}),
doc(p("<a>", a({"href": "x"}, img()))),
),
],
)
def test_add_node_mark(doc, mark, expect, test_transform):
test_transform(Transform(doc).add_node_mark(doc.tag["a"], mark), expect)


@pytest.mark.parametrize(
"doc,mark,expect",
[
# removes a mark
(doc(p("<a>", em(img()))), schema.mark("em"), doc(p("<a>", img()))),
# doesn't do anything when there is no mark
(doc(p("<a>", img())), schema.mark("em"), doc(p("<a>", img()))),
# can remove a mark from multiple marks
(doc(p("<a>", em(a(img())))), schema.mark("em"), doc(p("<a>", a(img())))),
],
)
def test_remove_node_mark(doc, mark, expect, test_transform):
test_transform(Transform(doc).remove_node_mark(doc.tag["a"], mark), expect)