jupyter-ydoc 3.3.1__tar.gz → 3.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/check-release.yml +1 -1
  2. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/license-header.yml +1 -1
  3. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/test.yml +2 -2
  4. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.pre-commit-config.yaml +1 -1
  5. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/CHANGELOG.md +36 -2
  6. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/PKG-INFO +1 -1
  7. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/javascript/package.json +1 -1
  8. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/_version.py +1 -1
  9. jupyter_ydoc-3.3.3/jupyter_ydoc/yunicode.py +181 -0
  10. jupyter_ydoc-3.3.1/jupyter_ydoc/yunicode.py +0 -88
  11. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/dependabot.yml +0 -0
  12. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/auto_author_assign.yml +0 -0
  13. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/enforce-label.yml +0 -0
  14. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/prep-release.yml +0 -0
  15. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.github/workflows/publish-release.yml +0 -0
  16. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.gitignore +0 -0
  17. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.licenserc.yaml +0 -0
  18. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.yarn/releases/yarn-3.4.1.cjs +0 -0
  19. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/.yarnrc.yml +0 -0
  20. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/LICENSE +0 -0
  21. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/README.md +0 -0
  22. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/Makefile +0 -0
  23. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/make.bat +0 -0
  24. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/_static/jupyter_logo.svg +0 -0
  25. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/_static/logo-icon.png +0 -0
  26. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/conf.py +0 -0
  27. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/custom.md +0 -0
  28. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/index.md +0 -0
  29. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/javascript_api.rst +0 -0
  30. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/overview.md +0 -0
  31. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/python_api.rst +0 -0
  32. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/docs/source/schema.md +0 -0
  33. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/__init__.py +0 -0
  34. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/py.typed +0 -0
  35. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/utils.py +0 -0
  36. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/ybasedoc.py +0 -0
  37. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/yblob.py +0 -0
  38. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/yfile.py +0 -0
  39. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/jupyter_ydoc/ynotebook.py +0 -0
  40. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/lerna.json +0 -0
  41. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/package.json +0 -0
  42. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/pyproject.toml +0 -0
  43. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/pytest.ini +0 -0
  44. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/readthedocs.yml +0 -0
  45. {jupyter_ydoc-3.3.1 → jupyter_ydoc-3.3.3}/yarn.lock +0 -0
@@ -19,7 +19,7 @@ jobs:
19
19
  node-version: ["14.x"]
20
20
  steps:
21
21
  - name: Checkout
22
- uses: actions/checkout@v5
22
+ uses: actions/checkout@v6
23
23
  - name: Base Setup
24
24
  uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
25
25
  - name: Install Dependencies
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  steps:
15
15
  - name: Checkout
16
- uses: actions/checkout@v5
16
+ uses: actions/checkout@v6
17
17
  with:
18
18
  token: ${{ secrets.GITHUB_TOKEN }}
19
19
 
@@ -11,7 +11,7 @@ jobs:
11
11
  name: pre-commit
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
- - uses: actions/checkout@v5
14
+ - uses: actions/checkout@v6
15
15
  - uses: actions/setup-python@v6
16
16
  - uses: pre-commit/action@v3.0.1
17
17
  with:
@@ -41,7 +41,7 @@ jobs:
41
41
  shell: bash -l {0}
42
42
  steps:
43
43
  - name: Checkout repository
44
- uses: actions/checkout@v5
44
+ uses: actions/checkout@v6
45
45
  - name: Install mamba
46
46
  uses: mamba-org/setup-micromamba@v2
47
47
  with:
@@ -17,7 +17,7 @@ repos:
17
17
  exclude: ^\.yarn
18
18
 
19
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
- rev: v0.14.5
20
+ rev: v0.14.8
21
21
  hooks:
22
22
  - id: ruff
23
23
  args: [--fix, --show-fixes]
@@ -2,6 +2,42 @@
2
2
 
3
3
  <!-- <START NEW CHANGELOG ENTRY> -->
4
4
 
5
+ ## 3.3.3
6
+
7
+ ([Full Changelog](https://github.com/jupyter-server/jupyter_ydoc/compare/@jupyter/ydoc@3.3.2...3900efdeefef21eb6e117fdd30827ee4c9149ff3))
8
+
9
+ ### Bugs fixed
10
+
11
+ - Fix multi-byte Unicode handling in files [#370](https://github.com/jupyter-server/jupyter_ydoc/pull/370) ([@krassowski](https://github.com/krassowski))
12
+
13
+ ### Maintenance and upkeep improvements
14
+
15
+ ### Contributors to this release
16
+
17
+ ([GitHub contributors page for this release](https://github.com/jupyter-server/jupyter_ydoc/graphs/contributors?from=2025-12-01&to=2025-12-10&type=c))
18
+
19
+ [@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_ydoc+involves%3Akrassowski+updated%3A2025-12-01..2025-12-10&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_ydoc+involves%3Apre-commit-ci+updated%3A2025-12-01..2025-12-10&type=Issues)
20
+
21
+ <!-- <END NEW CHANGELOG ENTRY> -->
22
+
23
+ ## 3.3.2
24
+
25
+ ([Full Changelog](https://github.com/jupyter-server/jupyter_ydoc/compare/@jupyter/ydoc@3.3.1...87e205209dbfed2d367424e6913c88916e77d98e))
26
+
27
+ ### Enhancements made
28
+
29
+ - Perform granular updates if reloading textual files [#366](https://github.com/jupyter-server/jupyter_ydoc/pull/366) ([@krassowski](https://github.com/krassowski))
30
+
31
+ ### Maintenance and upkeep improvements
32
+
33
+ - Bump actions/checkout from 5 to 6 [#363](https://github.com/jupyter-server/jupyter_ydoc/pull/363) ([@dependabot](https://github.com/dependabot))
34
+
35
+ ### Contributors to this release
36
+
37
+ ([GitHub contributors page for this release](https://github.com/jupyter-server/jupyter_ydoc/graphs/contributors?from=2025-11-21&to=2025-12-01&type=c))
38
+
39
+ [@dependabot](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_ydoc+involves%3Adependabot+updated%3A2025-11-21..2025-12-01&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_ydoc+involves%3Akrassowski+updated%3A2025-11-21..2025-12-01&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_ydoc+involves%3Apre-commit-ci+updated%3A2025-11-21..2025-12-01&type=Issues)
40
+
5
41
  ## 3.3.1
6
42
 
7
43
  ([Full Changelog](https://github.com/jupyter-server/jupyter_ydoc/compare/@jupyter/ydoc@3.3.0...0a0d0bc23b5894db0b00c5b073861025757c72db))
@@ -16,8 +52,6 @@
16
52
 
17
53
  [@krassowski](https://github.com/search?q=repo%3Ajupyter-server%2Fjupyter_ydoc+involves%3Akrassowski+updated%3A2025-11-21..2025-11-21&type=Issues)
18
54
 
19
- <!-- <END NEW CHANGELOG ENTRY> -->
20
-
21
55
  ## 3.3.0
22
56
 
23
57
  ([Full Changelog](https://github.com/jupyter-server/jupyter_ydoc/compare/@jupyter/ydoc@3.2.1...91d2742a5faff878f7d802e994922e7c3bcf9d33))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-ydoc
3
- Version: 3.3.1
3
+ Version: 3.3.3
4
4
  Summary: Document structures for collaborative editing using Ypy
5
5
  Project-URL: Homepage, https://jupyter.org
6
6
  Project-URL: Source, https://github.com/jupyter-server/jupyter_ydoc
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyter/ydoc",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "type": "module",
5
5
  "description": "Jupyter document structures for collaborative editing using YJS",
6
6
  "homepage": "https://github.com/jupyter-server/jupyter_ydoc",
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '3.3.1'
4
+ __version__ = VERSION = '3.3.3'
@@ -0,0 +1,181 @@
1
+ # Copyright (c) Jupyter Development Team.
2
+ # Distributed under the terms of the Modified BSD License.
3
+
4
+ from collections.abc import Callable
5
+ from difflib import SequenceMatcher
6
+ from functools import partial
7
+ from typing import Any
8
+
9
+ from pycrdt import Awareness, Doc, Text
10
+
11
+ from .ybasedoc import YBaseDoc
12
+
13
+ # Heuristic threshold as recommended in difflib documentation
14
+ SIMILARITY_THREESHOLD = 0.6
15
+
16
+
17
+ class YUnicode(YBaseDoc):
18
+ """
19
+ Extends :class:`YBaseDoc`, and represents a plain text document, encoded as UTF-8.
20
+
21
+ Schema:
22
+
23
+ .. code-block:: json
24
+
25
+ {
26
+ "state": YMap,
27
+ "source": YText
28
+ }
29
+ """
30
+
31
+ def __init__(self, ydoc: Doc | None = None, awareness: Awareness | None = None):
32
+ """
33
+ Constructs a YUnicode.
34
+
35
+ :param ydoc: The :class:`pycrdt.Doc` that will hold the data of the document, if provided.
36
+ :type ydoc: :class:`pycrdt.Doc`, optional.
37
+ :param awareness: The :class:`pycrdt.Awareness` that shares non persistent data
38
+ between clients.
39
+ :type awareness: :class:`pycrdt.Awareness`, optional.
40
+ """
41
+ super().__init__(ydoc, awareness)
42
+ self._ysource: Text = self._ydoc.get("source", type=Text)
43
+ self.undo_manager.expand_scope(self._ysource)
44
+
45
+ @property
46
+ def version(self) -> str:
47
+ """
48
+ Returns the version of the document.
49
+
50
+ :return: Document's version.
51
+ :rtype: str
52
+ """
53
+ return "1.0.0"
54
+
55
+ def get(self) -> str:
56
+ """
57
+ Returns the content of the document.
58
+
59
+ :return: Document's content.
60
+ :rtype: str
61
+ """
62
+ return str(self._ysource)
63
+
64
+ def set(self, value: str) -> None:
65
+ """
66
+ Sets the content of the document.
67
+
68
+ :param value: The content of the document.
69
+ :type value: str
70
+ """
71
+ old_value = self.get()
72
+ if old_value == value:
73
+ # no-op if the values are already the same,
74
+ # to avoid side-effects such as cursor jumping to the top
75
+ return
76
+
77
+ before_bytes = old_value.encode("utf-8")
78
+ after_bytes = value.encode("utf-8")
79
+
80
+ with self._ydoc.transaction():
81
+ matcher = SequenceMatcher(a=before_bytes, b=after_bytes)
82
+
83
+ if (
84
+ matcher.real_quick_ratio() >= SIMILARITY_THREESHOLD
85
+ and matcher.ratio() >= SIMILARITY_THREESHOLD
86
+ ):
87
+ operations = matcher.get_opcodes()
88
+
89
+ # Fix byte ranges and check for problematic overlaps
90
+ fixed_operations = []
91
+ prev_end = 0
92
+ prev_tag = None
93
+ has_overlap = False
94
+
95
+ for tag, i1, i2, j1, j2 in operations:
96
+ # Fix byte ranges to proper UTF-8 character boundaries
97
+ i1_fixed, i2_fixed = _fix_byte_range_to_char_boundary(before_bytes, i1, i2)
98
+ j1_fixed, j2_fixed = _fix_byte_range_to_char_boundary(after_bytes, j1, j2)
99
+
100
+ # Check if this operation overlaps with the previous one
101
+ # which can happen with grapheme clusters (emoji + modifiers, etc.)
102
+ if i1_fixed < prev_end and prev_tag != "equal":
103
+ has_overlap = True
104
+ break
105
+
106
+ prev_end = i2_fixed
107
+ prev_tag = tag
108
+ fixed_operations.append((tag, i1_fixed, i2_fixed, j1_fixed, j2_fixed))
109
+
110
+ # If we detected overlapping operations, fall back to hard reload
111
+ if has_overlap:
112
+ self._ysource.clear()
113
+ if value:
114
+ self._ysource += value
115
+ else:
116
+ # Apply granular operations
117
+ offset = 0
118
+ for tag, i1, i2, j1, j2 in fixed_operations:
119
+ match tag:
120
+ case "replace":
121
+ self._ysource[i1 + offset : i2 + offset] = after_bytes[
122
+ j1:j2
123
+ ].decode("utf-8")
124
+ offset += (j2 - j1) - (i2 - i1)
125
+ case "delete":
126
+ del self._ysource[i1 + offset : i2 + offset]
127
+ offset -= i2 - i1
128
+ case "insert":
129
+ self._ysource.insert(
130
+ i1 + offset, after_bytes[j1:j2].decode("utf-8")
131
+ )
132
+ offset += j2 - j1
133
+ case "equal":
134
+ pass
135
+ case _:
136
+ raise ValueError(f"Unknown tag '{tag}' in sequence matcher")
137
+ else:
138
+ # for very different strings, just replace the whole content;
139
+ # this avoids generating a huge number of operations
140
+
141
+ # clear document
142
+ self._ysource.clear()
143
+ # initialize document
144
+ if value:
145
+ self._ysource += value
146
+
147
+ def observe(self, callback: Callable[[str, Any], None]) -> None:
148
+ """
149
+ Subscribes to document changes.
150
+
151
+ :param callback: Callback that will be called when the document changes.
152
+ :type callback: Callable[[str, Any], None]
153
+ """
154
+ self.unobserve()
155
+ self._subscriptions[self._ystate] = self._ystate.observe(partial(callback, "state"))
156
+ self._subscriptions[self._ysource] = self._ysource.observe(partial(callback, "source"))
157
+
158
+
159
+ def _is_utf8_continuation_byte(byte: int) -> bool:
160
+ """Check if a byte is a UTF-8 continuation byte (10xxxxxx)."""
161
+ return (byte & 0xC0) == 0x80
162
+
163
+
164
+ def _fix_byte_range_to_char_boundary(data: bytes, start: int, end: int) -> tuple[int, int]:
165
+ """
166
+ Adjust byte indices to proper UTF-8 character boundaries.
167
+
168
+ :param data: The byte data.
169
+ :param start: The start byte index.
170
+ :param end: The end byte index.
171
+ :return: A tuple of (adjusted_start, adjusted_end).
172
+ """
173
+ # Move start backward to the beginning of a UTF-8 character
174
+ while start > 0 and start < len(data) and _is_utf8_continuation_byte(data[start]):
175
+ start -= 1
176
+
177
+ # Move end forward to the end of a UTF-8 character
178
+ while end < len(data) and _is_utf8_continuation_byte(data[end]):
179
+ end += 1
180
+
181
+ return start, end
@@ -1,88 +0,0 @@
1
- # Copyright (c) Jupyter Development Team.
2
- # Distributed under the terms of the Modified BSD License.
3
-
4
- from collections.abc import Callable
5
- from functools import partial
6
- from typing import Any
7
-
8
- from pycrdt import Awareness, Doc, Text
9
-
10
- from .ybasedoc import YBaseDoc
11
-
12
-
13
- class YUnicode(YBaseDoc):
14
- """
15
- Extends :class:`YBaseDoc`, and represents a plain text document, encoded as UTF-8.
16
-
17
- Schema:
18
-
19
- .. code-block:: json
20
-
21
- {
22
- "state": YMap,
23
- "source": YText
24
- }
25
- """
26
-
27
- def __init__(self, ydoc: Doc | None = None, awareness: Awareness | None = None):
28
- """
29
- Constructs a YUnicode.
30
-
31
- :param ydoc: The :class:`pycrdt.Doc` that will hold the data of the document, if provided.
32
- :type ydoc: :class:`pycrdt.Doc`, optional.
33
- :param awareness: The :class:`pycrdt.Awareness` that shares non persistent data
34
- between clients.
35
- :type awareness: :class:`pycrdt.Awareness`, optional.
36
- """
37
- super().__init__(ydoc, awareness)
38
- self._ysource = self._ydoc.get("source", type=Text)
39
- self.undo_manager.expand_scope(self._ysource)
40
-
41
- @property
42
- def version(self) -> str:
43
- """
44
- Returns the version of the document.
45
-
46
- :return: Document's version.
47
- :rtype: str
48
- """
49
- return "1.0.0"
50
-
51
- def get(self) -> str:
52
- """
53
- Returns the content of the document.
54
-
55
- :return: Document's content.
56
- :rtype: str
57
- """
58
- return str(self._ysource)
59
-
60
- def set(self, value: str) -> None:
61
- """
62
- Sets the content of the document.
63
-
64
- :param value: The content of the document.
65
- :type value: str
66
- """
67
- if self.get() == value:
68
- # no-op if the values are already the same,
69
- # to avoid side-effects such as cursor jumping to the top
70
- return
71
-
72
- with self._ydoc.transaction():
73
- # clear document
74
- self._ysource.clear()
75
- # initialize document
76
- if value:
77
- self._ysource += value
78
-
79
- def observe(self, callback: Callable[[str, Any], None]) -> None:
80
- """
81
- Subscribes to document changes.
82
-
83
- :param callback: Callback that will be called when the document changes.
84
- :type callback: Callable[[str, Any], None]
85
- """
86
- self.unobserve()
87
- self._subscriptions[self._ystate] = self._ystate.observe(partial(callback, "state"))
88
- self._subscriptions[self._ysource] = self._ysource.observe(partial(callback, "source"))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes