urwid 2.6.0.post0__py3-none-any.whl

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.

Potentially problematic release.


This version of urwid might be problematic. Click here for more details.

Files changed (75) hide show
  1. urwid/__init__.py +333 -0
  2. urwid/canvas.py +1413 -0
  3. urwid/command_map.py +137 -0
  4. urwid/container.py +59 -0
  5. urwid/decoration.py +65 -0
  6. urwid/display/__init__.py +97 -0
  7. urwid/display/_posix_raw_display.py +413 -0
  8. urwid/display/_raw_display_base.py +914 -0
  9. urwid/display/_web.css +12 -0
  10. urwid/display/_web.js +462 -0
  11. urwid/display/_win32.py +171 -0
  12. urwid/display/_win32_raw_display.py +269 -0
  13. urwid/display/common.py +1219 -0
  14. urwid/display/curses.py +690 -0
  15. urwid/display/escape.py +624 -0
  16. urwid/display/html_fragment.py +251 -0
  17. urwid/display/lcd.py +518 -0
  18. urwid/display/raw.py +37 -0
  19. urwid/display/web.py +636 -0
  20. urwid/event_loop/__init__.py +55 -0
  21. urwid/event_loop/abstract_loop.py +175 -0
  22. urwid/event_loop/asyncio_loop.py +231 -0
  23. urwid/event_loop/glib_loop.py +294 -0
  24. urwid/event_loop/main_loop.py +721 -0
  25. urwid/event_loop/select_loop.py +230 -0
  26. urwid/event_loop/tornado_loop.py +206 -0
  27. urwid/event_loop/trio_loop.py +302 -0
  28. urwid/event_loop/twisted_loop.py +269 -0
  29. urwid/event_loop/zmq_loop.py +275 -0
  30. urwid/font.py +695 -0
  31. urwid/graphics.py +96 -0
  32. urwid/highlight.css +19 -0
  33. urwid/listbox.py +1899 -0
  34. urwid/monitored_list.py +522 -0
  35. urwid/numedit.py +376 -0
  36. urwid/signals.py +330 -0
  37. urwid/split_repr.py +130 -0
  38. urwid/str_util.py +358 -0
  39. urwid/text_layout.py +632 -0
  40. urwid/treetools.py +515 -0
  41. urwid/util.py +557 -0
  42. urwid/version.py +16 -0
  43. urwid/vterm.py +1806 -0
  44. urwid/widget/__init__.py +181 -0
  45. urwid/widget/attr_map.py +161 -0
  46. urwid/widget/attr_wrap.py +140 -0
  47. urwid/widget/bar_graph.py +649 -0
  48. urwid/widget/big_text.py +77 -0
  49. urwid/widget/box_adapter.py +126 -0
  50. urwid/widget/columns.py +1145 -0
  51. urwid/widget/constants.py +574 -0
  52. urwid/widget/container.py +227 -0
  53. urwid/widget/divider.py +110 -0
  54. urwid/widget/edit.py +718 -0
  55. urwid/widget/filler.py +403 -0
  56. urwid/widget/frame.py +539 -0
  57. urwid/widget/grid_flow.py +539 -0
  58. urwid/widget/line_box.py +194 -0
  59. urwid/widget/overlay.py +829 -0
  60. urwid/widget/padding.py +597 -0
  61. urwid/widget/pile.py +971 -0
  62. urwid/widget/popup.py +170 -0
  63. urwid/widget/progress_bar.py +141 -0
  64. urwid/widget/scrollable.py +597 -0
  65. urwid/widget/solid_fill.py +44 -0
  66. urwid/widget/text.py +354 -0
  67. urwid/widget/widget.py +852 -0
  68. urwid/widget/widget_decoration.py +166 -0
  69. urwid/widget/wimp.py +792 -0
  70. urwid/wimp.py +23 -0
  71. urwid-2.6.0.post0.dist-info/COPYING +504 -0
  72. urwid-2.6.0.post0.dist-info/METADATA +332 -0
  73. urwid-2.6.0.post0.dist-info/RECORD +75 -0
  74. urwid-2.6.0.post0.dist-info/WHEEL +5 -0
  75. urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,522 @@
1
+ # Urwid MonitoredList class
2
+ # Copyright (C) 2004-2011 Ian Ward
3
+ #
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License as published by the Free Software Foundation; either
7
+ # version 2.1 of the License, or (at your option) any later version.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+ #
18
+ # Urwid web site: https://urwid.org/
19
+
20
+ from __future__ import annotations
21
+
22
+ import functools
23
+ import typing
24
+ import warnings
25
+
26
+ if typing.TYPE_CHECKING:
27
+ from collections.abc import Callable, Collection, Iterator
28
+
29
+ from typing_extensions import Concatenate, ParamSpec
30
+
31
+ ArgSpec = ParamSpec("ArgSpec")
32
+ Ret = typing.TypeVar("Ret")
33
+
34
+ _T = typing.TypeVar("_T")
35
+
36
+
37
+ def _call_modified(
38
+ fn: Callable[Concatenate[MonitoredList, ArgSpec], Ret]
39
+ ) -> Callable[Concatenate[MonitoredList, ArgSpec], Ret]:
40
+ @functools.wraps(fn)
41
+ def call_modified_wrapper(self: MonitoredList, *args: ArgSpec.args, **kwargs: ArgSpec.kwargs) -> Ret:
42
+ rval = fn(self, *args, **kwargs)
43
+ self._modified() # pylint: disable=protected-access
44
+ return rval
45
+
46
+ return call_modified_wrapper
47
+
48
+
49
+ class MonitoredList(typing.List[_T], typing.Generic[_T]):
50
+ """
51
+ This class can trigger a callback any time its contents are changed
52
+ with the usual list operations append, extend, etc.
53
+ """
54
+
55
+ def _modified(self) -> None: # pylint: disable=method-hidden # monkeypatch used
56
+ pass
57
+
58
+ def set_modified_callback(self, callback: Callable[[], typing.Any]) -> None:
59
+ """
60
+ Assign a callback function with no parameters that is called any
61
+ time the list is modified. Callback's return value is ignored.
62
+
63
+ >>> import sys
64
+ >>> ml = MonitoredList([1,2,3])
65
+ >>> ml.set_modified_callback(lambda: sys.stdout.write("modified\\n"))
66
+ >>> ml
67
+ MonitoredList([1, 2, 3])
68
+ >>> ml.append(10)
69
+ modified
70
+ >>> len(ml)
71
+ 4
72
+ >>> ml += [11, 12, 13]
73
+ modified
74
+ >>> ml[:] = ml[:2] + ml[-2:]
75
+ modified
76
+ >>> ml
77
+ MonitoredList([1, 2, 12, 13])
78
+ """
79
+ self._modified = callback # monkeypatch
80
+
81
+ def __repr__(self) -> str:
82
+ return f"{self.__class__.__name__}({list(self)!r})"
83
+
84
+ # noinspection PyMethodParameters
85
+ def __rich_repr__(self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
86
+ for item in self:
87
+ yield None, item
88
+
89
+ __add__ = _call_modified(list.__add__) # type: ignore[assignment] # magic like old __super__
90
+ __delitem__ = _call_modified(list.__delitem__) # type: ignore[assignment] # magic like old __super__
91
+
92
+ __iadd__ = _call_modified(list.__iadd__) # type: ignore[assignment] # magic like old __super__
93
+ __imul__ = _call_modified(list.__imul__) # type: ignore[assignment] # magic like old __super__
94
+ __rmul__ = _call_modified(list.__rmul__) # type: ignore[assignment] # magic like old __super__
95
+ __setitem__ = _call_modified(list.__setitem__) # type: ignore[assignment] # magic like old __super__
96
+
97
+ append = _call_modified(list.append) # type: ignore[assignment] # magic like old __super__
98
+ extend = _call_modified(list.extend) # type: ignore[assignment] # magic like old __super__
99
+ insert = _call_modified(list.insert) # type: ignore[assignment] # magic like old __super__
100
+ pop = _call_modified(list.pop) # type: ignore[assignment] # magic like old __super__
101
+ remove = _call_modified(list.remove) # type: ignore[assignment] # magic like old __super__
102
+ reverse = _call_modified(list.reverse) # type: ignore[assignment] # magic like old __super__
103
+ sort = _call_modified(list.sort) # type: ignore[assignment] # magic like old __super__
104
+ if hasattr(list, "clear"):
105
+ clear = _call_modified(list.clear) # type: ignore[assignment] # magic like old __super__
106
+
107
+
108
+ class MonitoredFocusList(MonitoredList[_T], typing.Generic[_T]):
109
+ """
110
+ This class can trigger a callback any time its contents are modified,
111
+ before and/or after modification, and any time the focus index is changed.
112
+ """
113
+
114
+ def __init__(self, *args, focus: int = 0, **kwargs) -> None:
115
+ """
116
+ This is a list that tracks one item as the focus item. If items
117
+ are inserted or removed it will update the focus.
118
+
119
+ >>> ml = MonitoredFocusList([10, 11, 12, 13, 14], focus=3)
120
+ >>> ml
121
+ MonitoredFocusList([10, 11, 12, 13, 14], focus=3)
122
+ >>> del(ml[1])
123
+ >>> ml
124
+ MonitoredFocusList([10, 12, 13, 14], focus=2)
125
+ >>> ml[:2] = [50, 51, 52, 53]
126
+ >>> ml
127
+ MonitoredFocusList([50, 51, 52, 53, 13, 14], focus=4)
128
+ >>> ml[4] = 99
129
+ >>> ml
130
+ MonitoredFocusList([50, 51, 52, 53, 99, 14], focus=4)
131
+ >>> ml[:] = []
132
+ >>> ml
133
+ MonitoredFocusList([], focus=None)
134
+ """
135
+
136
+ super().__init__(*args, **kwargs)
137
+
138
+ self._focus = focus
139
+ self._focus_modified = lambda ml, indices, new_items: None
140
+
141
+ def __repr__(self) -> str:
142
+ return f"{self.__class__.__name__}({list(self)!r}, focus={self.focus!r})"
143
+
144
+ @property
145
+ def focus(self) -> int | None:
146
+ """
147
+ Get/set the focus index. This value is read as None when the list
148
+ is empty, and may only be set to a value between 0 and len(self)-1
149
+ or an IndexError will be raised.
150
+
151
+ Return the index of the item "in focus" or None if
152
+ the list is empty.
153
+
154
+ >>> MonitoredFocusList([1,2,3], focus=2).focus
155
+ 2
156
+ >>> MonitoredFocusList().focus
157
+ """
158
+ if not self:
159
+ return None
160
+ return self._focus
161
+
162
+ @focus.setter
163
+ def focus(self, index: int) -> None:
164
+ """
165
+ index -- index into this list, any index out of range will
166
+ raise an IndexError, except when the list is empty and
167
+ the index passed is ignored.
168
+
169
+ This function may call self._focus_changed when the focus
170
+ is modified, passing the new focus position to the
171
+ callback just before changing the old focus setting.
172
+ That method may be overridden on the
173
+ instance with set_focus_changed_callback().
174
+
175
+ >>> ml = MonitoredFocusList([9, 10, 11])
176
+ >>> ml.focus = 2; ml.focus
177
+ 2
178
+ >>> ml.focus = 0; ml.focus
179
+ 0
180
+ >>> ml.focus = -2
181
+ Traceback (most recent call last):
182
+ ...
183
+ IndexError: focus index is out of range: -2
184
+ """
185
+ if not self:
186
+ self._focus = 0
187
+ return
188
+ if not isinstance(index, int):
189
+ raise TypeError("index must be an integer")
190
+ if index < 0 or index >= len(self):
191
+ raise IndexError(f"focus index is out of range: {index}")
192
+
193
+ if index != self._focus:
194
+ self._focus_changed(index)
195
+ self._focus = index
196
+
197
+ def _get_focus(self) -> int | None:
198
+ warnings.warn(
199
+ f"method `{self.__class__.__name__}._get_focus` is deprecated, "
200
+ f"please use `{self.__class__.__name__}.focus` property",
201
+ DeprecationWarning,
202
+ stacklevel=3,
203
+ )
204
+ return self.focus
205
+
206
+ def _set_focus(self, index: int) -> None:
207
+ warnings.warn(
208
+ f"method `{self.__class__.__name__}._set_focus` is deprecated, "
209
+ f"please use `{self.__class__.__name__}.focus` property",
210
+ DeprecationWarning,
211
+ stacklevel=3,
212
+ )
213
+ self.focus = index
214
+
215
+ def _focus_changed(self, new_focus: int) -> None: # pylint: disable=method-hidden # monkeypatch used
216
+ pass
217
+
218
+ def set_focus_changed_callback(self, callback: Callable[[int], typing.Any]) -> None:
219
+ """
220
+ Assign a callback to be called when the focus index changes
221
+ for any reason. The callback is in the form:
222
+
223
+ callback(new_focus)
224
+ new_focus -- new focus index
225
+
226
+ >>> import sys
227
+ >>> ml = MonitoredFocusList([1,2,3], focus=1)
228
+ >>> ml.set_focus_changed_callback(lambda f: sys.stdout.write("focus: %d\\n" % (f,)))
229
+ >>> ml
230
+ MonitoredFocusList([1, 2, 3], focus=1)
231
+ >>> ml.append(10)
232
+ >>> ml.insert(1, 11)
233
+ focus: 2
234
+ >>> ml
235
+ MonitoredFocusList([1, 11, 2, 3, 10], focus=2)
236
+ >>> del ml[:2]
237
+ focus: 0
238
+ >>> ml[:0] = [12, 13, 14]
239
+ focus: 3
240
+ >>> ml.focus = 5
241
+ focus: 5
242
+ >>> ml
243
+ MonitoredFocusList([12, 13, 14, 2, 3, 10], focus=5)
244
+ """
245
+ self._focus_changed = callback # Monkeypatch
246
+
247
+ def _validate_contents_modified( # pylint: disable=method-hidden # monkeypatch used
248
+ self,
249
+ indices: tuple[int, int, int],
250
+ new_items: Collection[_T],
251
+ ) -> int | None:
252
+ return None
253
+
254
+ def set_validate_contents_modified(self, callback: Callable[[tuple[int, int, int], Collection[_T]], int | None]):
255
+ """
256
+ Assign a callback function to handle validating changes to the list.
257
+ This may raise an exception if the change should not be performed.
258
+ It may also return an integer position to be the new focus after the
259
+ list is modified, or None to use the default behaviour.
260
+
261
+ The callback is in the form:
262
+
263
+ callback(indices, new_items)
264
+ indices -- a (start, stop, step) tuple whose range covers the
265
+ items being modified
266
+ new_items -- an iterable of items replacing those at range(*indices),
267
+ empty if items are being removed, if step==1 this list may
268
+ contain any number of items
269
+ """
270
+ self._validate_contents_modified = callback # Monkeypatch
271
+
272
+ def _adjust_focus_on_contents_modified(self, slc: slice, new_items: Collection[_T] = ()) -> int:
273
+ """
274
+ Default behaviour is to move the focus to the item following
275
+ any removed items, unless that item was simply replaced.
276
+
277
+ Failing that choose the last item in the list.
278
+
279
+ returns focus position for after change is applied
280
+ """
281
+ num_new_items = len(new_items)
282
+ start, stop, step = indices = slc.indices(len(self))
283
+ num_removed = len(list(range(*indices)))
284
+
285
+ focus = self._validate_contents_modified(indices, new_items)
286
+ if focus is not None:
287
+ return focus
288
+
289
+ focus = self._focus
290
+ if step == 1:
291
+ if start + num_new_items <= focus < stop:
292
+ focus = stop
293
+ # adjust for added/removed items
294
+ if stop <= focus:
295
+ focus += num_new_items - (stop - start)
296
+
297
+ else: # noqa: PLR5501 # pylint: disable=else-if-used # readability
298
+ if not num_new_items:
299
+ # extended slice being removed
300
+ if focus in range(start, stop, step):
301
+ focus += 1
302
+
303
+ # adjust for removed items
304
+ focus -= len(list(range(start, min(focus, stop), step)))
305
+
306
+ return min(focus, len(self) + num_new_items - num_removed - 1)
307
+
308
+ # override all the list methods that modify the list
309
+
310
+ def __delitem__(self, y: int | slice) -> None:
311
+ """
312
+ >>> ml = MonitoredFocusList([0,1,2,3,4], focus=2)
313
+ >>> del ml[3]; ml
314
+ MonitoredFocusList([0, 1, 2, 4], focus=2)
315
+ >>> del ml[-1]; ml
316
+ MonitoredFocusList([0, 1, 2], focus=2)
317
+ >>> del ml[0]; ml
318
+ MonitoredFocusList([1, 2], focus=1)
319
+ >>> del ml[1]; ml
320
+ MonitoredFocusList([1], focus=0)
321
+ >>> del ml[0]; ml
322
+ MonitoredFocusList([], focus=None)
323
+ >>> ml = MonitoredFocusList([5,4,6,4,5,4,6,4,5], focus=4)
324
+ >>> del ml[1::2]; ml
325
+ MonitoredFocusList([5, 6, 5, 6, 5], focus=2)
326
+ >>> del ml[::2]; ml
327
+ MonitoredFocusList([6, 6], focus=1)
328
+ >>> ml = MonitoredFocusList([0,1,2,3,4,6,7], focus=2)
329
+ >>> del ml[-2:]; ml
330
+ MonitoredFocusList([0, 1, 2, 3, 4], focus=2)
331
+ >>> del ml[-4:-2]; ml
332
+ MonitoredFocusList([0, 3, 4], focus=1)
333
+ >>> del ml[:]; ml
334
+ MonitoredFocusList([], focus=None)
335
+ """
336
+ if isinstance(y, slice):
337
+ focus = self._adjust_focus_on_contents_modified(y)
338
+ else:
339
+ focus = self._adjust_focus_on_contents_modified(slice(y, y + 1 or None))
340
+ super().__delitem__(y)
341
+ self.focus = focus
342
+
343
+ @typing.overload
344
+ def __setitem__(self, i: int, y: _T) -> None: ...
345
+
346
+ @typing.overload
347
+ def __setitem__(self, i: slice, y: Collection[_T]) -> None: ...
348
+
349
+ def __setitem__(self, i: int | slice, y: _T | Collection[_T]) -> None:
350
+ """
351
+ >>> def modified(indices, new_items):
352
+ ... print(f"range{indices!r} <- {new_items!r}" )
353
+ >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
354
+ >>> ml.set_validate_contents_modified(modified)
355
+ >>> ml[0] = 9
356
+ range(0, 1, 1) <- [9]
357
+ >>> ml[2] = 6
358
+ range(2, 3, 1) <- [6]
359
+ >>> ml.focus
360
+ 2
361
+ >>> ml[-1] = 8
362
+ range(3, 4, 1) <- [8]
363
+ >>> ml
364
+ MonitoredFocusList([9, 1, 6, 8], focus=2)
365
+ >>> ml[1::2] = [12, 13]
366
+ range(1, 4, 2) <- [12, 13]
367
+ >>> ml[::2] = [10, 11]
368
+ range(0, 4, 2) <- [10, 11]
369
+ >>> ml[-3:-1] = [21, 22, 23]
370
+ range(1, 3, 1) <- [21, 22, 23]
371
+ >>> ml
372
+ MonitoredFocusList([10, 21, 22, 23, 13], focus=2)
373
+ >>> ml[:] = []
374
+ range(0, 5, 1) <- []
375
+ >>> ml
376
+ MonitoredFocusList([], focus=None)
377
+ """
378
+ if isinstance(i, slice):
379
+ focus = self._adjust_focus_on_contents_modified(i, y)
380
+ else:
381
+ focus = self._adjust_focus_on_contents_modified(slice(i, i + 1 or None), [y])
382
+ super().__setitem__(i, y)
383
+ self.focus = focus
384
+
385
+ def __imul__(self, n: int):
386
+ """
387
+ >>> def modified(indices, new_items):
388
+ ... print(f"range{indices!r} <- {list(new_items)!r}" )
389
+ >>> ml = MonitoredFocusList([0,1,2], focus=2)
390
+ >>> ml.set_validate_contents_modified(modified)
391
+ >>> ml *= 3
392
+ range(3, 3, 1) <- [0, 1, 2, 0, 1, 2]
393
+ >>> ml
394
+ MonitoredFocusList([0, 1, 2, 0, 1, 2, 0, 1, 2], focus=2)
395
+ >>> ml *= 0
396
+ range(0, 9, 1) <- []
397
+ >>> print(ml.focus)
398
+ None
399
+ """
400
+ if n > 0:
401
+ focus = self._adjust_focus_on_contents_modified(slice(len(self), len(self)), list(self) * (n - 1))
402
+ else: # all contents are being removed
403
+ focus = self._adjust_focus_on_contents_modified(slice(0, len(self)))
404
+ rval = super().__imul__(n)
405
+ self.focus = focus
406
+ return rval
407
+
408
+ def append(self, item: _T) -> None:
409
+ """
410
+ >>> def modified(indices, new_items):
411
+ ... print(f"range{indices!r} <- {new_items!r}" )
412
+ >>> ml = MonitoredFocusList([0,1,2], focus=2)
413
+ >>> ml.set_validate_contents_modified(modified)
414
+ >>> ml.append(6)
415
+ range(3, 3, 1) <- [6]
416
+ """
417
+ focus = self._adjust_focus_on_contents_modified(slice(len(self), len(self)), [item])
418
+ super().append(item)
419
+ self.focus = focus
420
+
421
+ def extend(self, items: Collection[_T]) -> None:
422
+ """
423
+ >>> def modified(indices, new_items):
424
+ ... print(f"range{indices!r} <- {list(new_items)!r}" )
425
+ >>> ml = MonitoredFocusList([0,1,2], focus=2)
426
+ >>> ml.set_validate_contents_modified(modified)
427
+ >>> ml.extend((6,7,8))
428
+ range(3, 3, 1) <- [6, 7, 8]
429
+ """
430
+ focus = self._adjust_focus_on_contents_modified(slice(len(self), len(self)), items)
431
+ super().extend(items)
432
+ self.focus = focus
433
+
434
+ def insert(self, index: int, item: _T) -> None:
435
+ """
436
+ >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
437
+ >>> ml.insert(-1, -1); ml
438
+ MonitoredFocusList([0, 1, 2, -1, 3], focus=2)
439
+ >>> ml.insert(0, -2); ml
440
+ MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3)
441
+ >>> ml.insert(3, -3); ml
442
+ MonitoredFocusList([-2, 0, 1, -3, 2, -1, 3], focus=4)
443
+ """
444
+ focus = self._adjust_focus_on_contents_modified(slice(index, index), [item])
445
+ super().insert(index, item)
446
+ self.focus = focus
447
+
448
+ def pop(self, index: int = -1) -> _T:
449
+ """
450
+ >>> ml = MonitoredFocusList([-2,0,1,-3,2,3], focus=4)
451
+ >>> ml.pop(3); ml
452
+ -3
453
+ MonitoredFocusList([-2, 0, 1, 2, 3], focus=3)
454
+ >>> ml.pop(0); ml
455
+ -2
456
+ MonitoredFocusList([0, 1, 2, 3], focus=2)
457
+ >>> ml.pop(-1); ml
458
+ 3
459
+ MonitoredFocusList([0, 1, 2], focus=2)
460
+ >>> ml.pop(2); ml
461
+ 2
462
+ MonitoredFocusList([0, 1], focus=1)
463
+ """
464
+ focus = self._adjust_focus_on_contents_modified(slice(index, index + 1 or None))
465
+ rval = super().pop(index)
466
+ self.focus = focus
467
+ return rval
468
+
469
+ def remove(self, value: _T) -> None:
470
+ """
471
+ >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4)
472
+ >>> ml.remove(-3); ml
473
+ MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3)
474
+ >>> ml.remove(-2); ml
475
+ MonitoredFocusList([0, 1, 2, -1, 3], focus=2)
476
+ >>> ml.remove(3); ml
477
+ MonitoredFocusList([0, 1, 2, -1], focus=2)
478
+ """
479
+ index = self.index(value)
480
+ focus = self._adjust_focus_on_contents_modified(slice(index, index + 1 or None))
481
+ super().remove(value)
482
+ self.focus = focus
483
+
484
+ def reverse(self) -> None:
485
+ """
486
+ >>> ml = MonitoredFocusList([0,1,2,3,4], focus=1)
487
+ >>> ml.reverse(); ml
488
+ MonitoredFocusList([4, 3, 2, 1, 0], focus=3)
489
+ """
490
+ rval = super().reverse()
491
+ self.focus = max(0, len(self) - self._focus - 1)
492
+ return rval
493
+
494
+ def sort(self, **kwargs) -> None:
495
+ """
496
+ >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4)
497
+ >>> ml.sort(); ml
498
+ MonitoredFocusList([-3, -2, -1, 0, 1, 2, 3], focus=5)
499
+ """
500
+ if not self:
501
+ return None
502
+ value = self[self._focus]
503
+ rval = super().sort(**kwargs)
504
+ self.focus = self.index(value)
505
+ return rval
506
+
507
+ if hasattr(list, "clear"):
508
+
509
+ def clear(self) -> None:
510
+ focus = self._adjust_focus_on_contents_modified(slice(0, 0))
511
+ super().clear()
512
+ self.focus = focus
513
+
514
+
515
+ def _test():
516
+ import doctest
517
+
518
+ doctest.testmod()
519
+
520
+
521
+ if __name__ == "__main__":
522
+ _test()