pgwidgets-python 0.4.0__tar.gz → 0.4.2__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 (80) hide show
  1. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/.github/workflows/tests.yml +3 -3
  2. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/PKG-INFO +3 -2
  3. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/WhatsNew.rst +40 -1
  4. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/sync.rst +2 -2
  5. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/sync/text_source.py +46 -7
  6. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets_python.egg-info/PKG-INFO +3 -2
  7. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets_python.egg-info/requires.txt +1 -1
  8. pgwidgets_python-0.4.2/pgwidgets_python.egg-info/scm_version.json +8 -0
  9. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pyproject.toml +13 -8
  10. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_text_source.py +55 -0
  11. pgwidgets_python-0.4.0/pgwidgets_python.egg-info/scm_version.json +0 -8
  12. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/.flake8 +0 -0
  13. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/.gitignore +0 -0
  14. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/.readthedocs.yaml +0 -0
  15. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/LICENSE.md +0 -0
  16. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/README.md +0 -0
  17. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/Makefile +0 -0
  18. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/api/async.rst +0 -0
  19. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/api/index.rst +0 -0
  20. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/api/sync.rst +0 -0
  21. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/architecture.rst +0 -0
  22. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/async.rst +0 -0
  23. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/callbacks.rst +0 -0
  24. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/conf.py +0 -0
  25. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/extras.rst +0 -0
  26. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/getting-started.rst +0 -0
  27. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/index.rst +0 -0
  28. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/subclassing.rst +0 -0
  29. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/utilities.rst +0 -0
  30. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/web-servers.rst +0 -0
  31. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/docs/widgets.rst +0 -0
  32. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/README.md +0 -0
  33. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/all_widgets.py +0 -0
  34. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/all_widgets_async.py +0 -0
  35. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/demo_async.py +0 -0
  36. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/demo_sync.py +0 -0
  37. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/demo_treeview.py +0 -0
  38. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/flask-multi-process/README.md +0 -0
  39. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/flask-multi-process/gunicorn.conf.py +0 -0
  40. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/flask-multi-process/nginx.conf +0 -0
  41. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/flask-multi-process/server.py +0 -0
  42. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/examples/flask-multi-process/user_app.py +0 -0
  43. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/__init__.py +0 -0
  44. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/_json.py +0 -0
  45. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/async_/Widgets.py +0 -0
  46. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/async_/__init__.py +0 -0
  47. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/async_/application.py +0 -0
  48. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/async_/widget.py +0 -0
  49. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/buffer.py +0 -0
  50. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/callbacks.py +0 -0
  51. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/defs.py +0 -0
  52. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/extras/__init__.py +0 -0
  53. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/extras/file_browser.py +0 -0
  54. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/icons/pgw-python-logo.png +0 -0
  55. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/method_types.py +0 -0
  56. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/sync/Widgets.py +0 -0
  57. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/sync/__init__.py +0 -0
  58. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/sync/application.py +0 -0
  59. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/sync/widget.py +0 -0
  60. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/text_model.py +0 -0
  61. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets/tree_model.py +0 -0
  62. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets_python.egg-info/SOURCES.txt +0 -0
  63. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets_python.egg-info/dependency_links.txt +0 -0
  64. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets_python.egg-info/scm_file_list.json +0 -0
  65. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/pgwidgets_python.egg-info/top_level.txt +0 -0
  66. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/setup.cfg +0 -0
  67. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/test_pg2.py +0 -0
  68. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/__init__.py +0 -0
  69. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_async_session.py +0 -0
  70. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_defs.py +0 -0
  71. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_extras.py +0 -0
  72. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_fonts.py +0 -0
  73. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_protocol.py +0 -0
  74. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_reconstruct.py +0 -0
  75. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_resolve_kwargs.py +0 -0
  76. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_session.py +0 -0
  77. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_stateful.py +0 -0
  78. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_tree_model.py +0 -0
  79. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tests/test_widget_classes.py +0 -0
  80. {pgwidgets_python-0.4.0 → pgwidgets_python-0.4.2}/tools/sync_async_tree_model.py +0 -0
@@ -11,13 +11,13 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- python-version: ["3.12", "3.13"]
14
+ python-version: ["3.12", "3.13", "3.14"]
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v7
18
18
 
19
19
  - name: Set up Python ${{ matrix.python-version }}
20
- uses: actions/setup-python@v5
20
+ uses: actions/setup-python@v6
21
21
  with:
22
22
  python-version: ${{ matrix.python-version }}
23
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgwidgets-python
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Python bindings for the pgwidgets JavaScript widget library
5
5
  Author: PGWidgets Developers
6
6
  License-Expression: BSD-3-Clause
@@ -12,11 +12,12 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
15
16
  Classifier: Topic :: Software Development :: User Interfaces
16
17
  Requires-Python: >=3.12
17
18
  Description-Content-Type: text/markdown
18
19
  License-File: LICENSE.md
19
- Requires-Dist: pgwidgets-js>=0.4.0
20
+ Requires-Dist: pgwidgets-js>=0.4.2
20
21
  Requires-Dist: websockets>=12
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: sphinx; extra == "dev"
@@ -1,6 +1,45 @@
1
1
  What's New
2
2
  ==========
3
3
 
4
+ Recent changes — since ``v0.4.0``
5
+ ---------------------------------
6
+
7
+ TreeView: selecting from code needs pgwidgets-js 0.4.2
8
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9
+
10
+ ``select_path(path)``, ``select_paths(paths)`` and ``select_all()``
11
+ select, as their signatures say. Nothing changed on this side: the
12
+ generated proxy forwards only the arguments it was given, so a call
13
+ that leaves ``state`` off sent nothing for it, and the browser used to
14
+ read that as "deselect" -- the row never highlighted,
15
+ ``get_subtree('selected')`` stayed empty, and ``select_all()`` cleared
16
+ the selection instead of filling it. pgwidgets-js 0.4.2 defaults the
17
+ argument, and the dependency floor now requires it.
18
+
19
+ Dragging a column divider in the browser also resizes just that column
20
+ now, qt-style, instead of taking the width out of the next one, and the
21
+ last column has a divider of its own.
22
+
23
+ TextSource: scroll alignment and caret styling
24
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25
+
26
+ ``scroll_to_ref(ref, align)`` and ``scroll_to_lineno(lineno, align)``
27
+ take an alignment -- ``'nearest'`` (the default, as before),
28
+ ``'center'`` or ``'top'`` -- and pass it to the browser alongside the
29
+ offset. A search that scrolls to its match can now put the match in
30
+ the middle of the view rather than merely bringing it into sight.
31
+
32
+ ``set_cursor_style(style, color)`` picks a ``'line'`` or ``'block'``
33
+ caret in any colour, and ``get_cursor_style()`` reads the pair back.
34
+ The choice is part of the model, replayed from
35
+ ``_reconstruct_model()``, so a browser that (re)connects gets the caret
36
+ along with everything else. It is styled through the browser's own
37
+ ``caret-color`` / ``caret-shape``, so it shows only while the editor
38
+ has the focus.
39
+
40
+ These need the matching methods in pgwidgets-js 0.4.1, which the
41
+ dependency floor now requires.
42
+
4
43
  Recent changes — since ``v0.3.5``
5
44
  ---------------------------------
6
45
 
@@ -156,7 +195,7 @@ the 4th argument, so handlers that double-click a file can see
156
195
  which column they hit. Existing handlers (3-arg) keep working.
157
196
 
158
197
  ``_resolve_kwargs``: skipped-positional kwargs
159
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
198
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160
199
 
161
200
  The kwarg resolver used by every generated widget method now
162
201
  allows a caller to skip a positional argument and supply the
@@ -114,8 +114,8 @@ Bytes are served from ``/_pgwidgets/font/<id>`` by the built-in
114
114
  HTTP server with ``Cache-Control: immutable``; behind Flask /
115
115
  nginx the same route is served from the Python process.
116
116
 
117
- See also the JS-side write-up at :ref:`custom-fonts` in the
118
- pgwidgets-js documentation.
117
+ See also the "Custom fonts" section of the pgwidgets-js
118
+ documentation for the browser side of this.
119
119
 
120
120
  Opening a URL in the browser
121
121
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -29,6 +29,13 @@ from pgwidgets.text_model import TextModel
29
29
  _GeneratedTextSource = build_widget_class("TextSource", WIDGETS["TextSource"])
30
30
 
31
31
 
32
+ def _check_align(align):
33
+ if align not in ('nearest', 'center', 'top'):
34
+ raise ValueError("align should be one of 'nearest', 'center' "
35
+ "or 'top'")
36
+ return align
37
+
38
+
32
39
  class _BrowserTextModel(TextModel):
33
40
  """A TextModel whose render hooks push offset-based operations to the
34
41
  browser via the owning widget. When no browser is connected the calls
@@ -118,6 +125,8 @@ class TextSource(_GeneratedTextSource):
118
125
  text = self._state.get("text")
119
126
  self._model._text = "" if text is None else str(text)
120
127
  self._state.pop("text", None)
128
+ # caret appearance (see set_cursor_style); replayed on reconnect
129
+ self._cursor_style = ('line', None)
121
130
 
122
131
  def create_tag(self, name, attrs=None, **kwdargs):
123
132
  """Define a display tag locally and push its styling to the browser."""
@@ -126,18 +135,46 @@ class TextSource(_GeneratedTextSource):
126
135
  self._model.create_tag(name, merged)
127
136
  self._call("create_tag", name, merged)
128
137
 
129
- def scroll_to_ref(self, ref):
130
- """Scroll the view to a ref, addressed by its current offset."""
131
- self._call("_scrollToOffset", self._model._offset_of(ref))
138
+ def scroll_to_ref(self, ref, align='nearest'):
139
+ """Scroll the view to a ref, addressed by its current offset.
132
140
 
133
- def scroll_to_lineno(self, lineno):
134
- """Scroll so that line ``lineno`` is visible."""
141
+ ``align`` says where the line should end up: 'nearest' scrolls the
142
+ least amount that brings it into view (the default), 'center' puts
143
+ it in the middle of the view and 'top' at the top.
144
+ """
145
+ self._call("_scrollToOffset", self._model._offset_of(ref),
146
+ _check_align(align))
147
+
148
+ def scroll_to_lineno(self, lineno, align='nearest'):
149
+ """Scroll so that line ``lineno`` is visible; see
150
+ ``scroll_to_ref`` for ``align``."""
135
151
  self._call("_scrollToOffset",
136
- self._model._offset_of_line_start(lineno))
152
+ self._model._offset_of_line_start(lineno),
153
+ _check_align(align))
137
154
 
138
155
  def scroll_to_end(self):
139
156
  """Scroll to the end of the buffer."""
140
- self._call("_scrollToOffset", self._model.get_length())
157
+ self._call("_scrollToOffset", self._model.get_length(), 'nearest')
158
+
159
+ def set_cursor_style(self, style='line', color=None):
160
+ """Set the appearance of the text caret.
161
+
162
+ ``style`` is 'line' (a thin vertical bar, the default) or 'block';
163
+ ``color`` is any CSS color, or None for the browser default.
164
+
165
+ NOTE: this styles the browser's own caret, so it is only drawn
166
+ while the editor has the keyboard focus, and the block shape needs
167
+ a browser that supports the CSS 'caret-shape' property (elsewhere
168
+ the caret keeps its color but stays a thin bar).
169
+ """
170
+ if style not in ('line', 'block'):
171
+ raise ValueError("style should be one of 'line' or 'block'")
172
+ self._cursor_style = (style, color)
173
+ self._call("set_cursor_style", style, color)
174
+
175
+ def get_cursor_style(self):
176
+ """Return the caret appearance as a ``(style, color)`` pair."""
177
+ return self._cursor_style
141
178
 
142
179
  def show_tooltip(self, text, x, y):
143
180
  """Show a hover tooltip (``text``) near viewport point (x, y), or
@@ -182,6 +219,8 @@ class TextSource(_GeneratedTextSource):
182
219
  self._call("_setSelectionOffsets", m._sel_start, m._sel_end)
183
220
  else:
184
221
  self._call("_setCursorOffset", m._cursor)
222
+ if self._cursor_style != ('line', None):
223
+ self._call("set_cursor_style", *self._cursor_style)
185
224
 
186
225
 
187
226
  def _install_delegators():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgwidgets-python
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Python bindings for the pgwidgets JavaScript widget library
5
5
  Author: PGWidgets Developers
6
6
  License-Expression: BSD-3-Clause
@@ -12,11 +12,12 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
15
16
  Classifier: Topic :: Software Development :: User Interfaces
16
17
  Requires-Python: >=3.12
17
18
  Description-Content-Type: text/markdown
18
19
  License-File: LICENSE.md
19
- Requires-Dist: pgwidgets-js>=0.4.0
20
+ Requires-Dist: pgwidgets-js>=0.4.2
20
21
  Requires-Dist: websockets>=12
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: sphinx; extra == "dev"
@@ -1,4 +1,4 @@
1
- pgwidgets-js>=0.4.0
1
+ pgwidgets-js>=0.4.2
2
2
  websockets>=12
3
3
 
4
4
  [dev]
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.2",
3
+ "distance": 0,
4
+ "node": "gcccf4eb8110bcb5a4eacf3ad84af4092cdddccfb",
5
+ "dirty": false,
6
+ "branch": "main",
7
+ "node_date": "2026-09-23"
8
+ }
@@ -14,14 +14,18 @@ authors = [
14
14
  {name = "PGWidgets Developers"},
15
15
  ]
16
16
  dependencies = [
17
- # Tests and runtime expect the incremental TreeView/TableView updates
18
- # and the "batch" / set_colors protocol messages introduced in
19
- # pgwidgets-js 0.4.0. (Earlier requirements — the TextSource
20
- # selection/tag-region and tooltip/scroll APIs, Separator,
21
- # Button.set_hover, ButtonBox.min_button_width, Dialog.set_title
22
- # date to 0.3.4; set_min_size/set_max_size, the dict-tree TreeView,
23
- # the binary Image protocol, TopLevel window-control options to 0.2.0.)
24
- "pgwidgets-js>=0.4.0",
17
+ # TreeView.select_path()/select_paths()/select_all() rely on the
18
+ # browser side defaulting their `state` argument, since the proxy
19
+ # forwards only the arguments it was given -- pgwidgets-js 0.4.2.
20
+ # (TextSource's scroll alignment and set_cursor_style() need the
21
+ # matching browser-side methods, introduced in pgwidgets-js 0.4.1;
22
+ # earlier requirements the incremental TreeView/TableView updates
23
+ # and the "batch" / set_colors protocol messages date to 0.4.0; the
24
+ # TextSource selection/tag-region and tooltip/scroll APIs, Separator,
25
+ # Button.set_hover, ButtonBox.min_button_width, Dialog.set_title to
26
+ # 0.3.4; set_min_size/set_max_size, the dict-tree TreeView, the
27
+ # binary Image protocol, TopLevel window-control options to 0.2.0.)
28
+ "pgwidgets-js>=0.4.2",
25
29
  "websockets>=12",
26
30
  ]
27
31
  keywords = ["widgets", "ui", "gui", "websocket", "browser"]
@@ -31,6 +35,7 @@ classifiers = [
31
35
  "Programming Language :: Python :: 3",
32
36
  "Programming Language :: Python :: 3.12",
33
37
  "Programming Language :: Python :: 3.13",
38
+ "Programming Language :: Python :: 3.14",
34
39
  "Topic :: Software Development :: User Interfaces",
35
40
  ]
36
41
 
@@ -5,6 +5,8 @@ replayed on connect), which is exactly the case that used to crash callers
5
5
  that queried refs during construction.
6
6
  """
7
7
 
8
+ import pytest
9
+
8
10
  from pgwidgets.sync.Widgets import TextSource
9
11
  from pgwidgets.text_model import TextBufferRef
10
12
 
@@ -159,3 +161,56 @@ def test_reconstruct_pushes_full_state():
159
161
  assert sess.sent[0][1][0] == "abcdef"
160
162
  assert "create_tag" in methods
161
163
  assert "_restoreTagIntervals" in methods
164
+
165
+
166
+ def test_scroll_alignment_rides_along_with_the_offset():
167
+ """A caller can ask for the target line to be centered rather than
168
+ merely brought into view."""
169
+ tw = _ts()
170
+ tw.insert_text(tw.get_ref_end(), "l0\nl1\nl2")
171
+ sess = tw._session
172
+ sess.sent.clear()
173
+
174
+ tw.scroll_to_lineno(2, align='center')
175
+ tw.scroll_to_ref(tw.get_ref_line_start(1), align='top')
176
+ tw.scroll_to_lineno(0)
177
+
178
+ calls = [m[1] for m in sess.sent if m[0] == "_scrollToOffset"]
179
+ assert calls == [[6, 'center'], [3, 'top'], [0, 'nearest']]
180
+
181
+
182
+ def test_an_unknown_alignment_is_rejected():
183
+ tw = _ts()
184
+ with pytest.raises(ValueError):
185
+ tw.scroll_to_lineno(0, align='middle')
186
+
187
+
188
+ def test_cursor_style_is_pushed_and_remembered():
189
+ tw = _ts()
190
+ sess = tw._session
191
+ assert tw.get_cursor_style() == ('line', None)
192
+ sess.sent.clear()
193
+
194
+ tw.set_cursor_style('block', color='indianred')
195
+
196
+ assert ("set_cursor_style", ['block', 'indianred']) in [
197
+ (m[0], m[1]) for m in sess.sent]
198
+ assert tw.get_cursor_style() == ('block', 'indianred')
199
+
200
+
201
+ def test_cursor_style_is_replayed_to_a_reconnecting_browser():
202
+ tw = _ts()
203
+ tw.set_cursor_style('block', color='indianred')
204
+ sess = tw._session
205
+ sess.sent.clear()
206
+
207
+ tw._reconstruct_model()
208
+
209
+ assert ("set_cursor_style", ['block', 'indianred']) in [
210
+ (m[0], m[1]) for m in sess.sent]
211
+
212
+
213
+ def test_an_unknown_cursor_style_is_rejected():
214
+ tw = _ts()
215
+ with pytest.raises(ValueError):
216
+ tw.set_cursor_style('underline')
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.4.0",
3
- "distance": 0,
4
- "node": "g5481e84d4ee1d988cdd6d7afbf05f4072640d67f",
5
- "dirty": false,
6
- "branch": "main",
7
- "node_date": "2026-08-17"
8
- }