omdev 0.0.0.dev460__py3-none-any.whl → 0.0.0.dev462__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 omdev might be problematic. Click here for more details.

Files changed (37) hide show
  1. omdev/.omlish-manifests.json +1 -1
  2. omdev/cexts/cmake.py +4 -1
  3. omdev/irc/__init__.py +0 -0
  4. omdev/irc/messages/__init__.py +0 -0
  5. omdev/irc/messages/base.py +50 -0
  6. omdev/irc/messages/formats.py +92 -0
  7. omdev/irc/messages/messages.py +775 -0
  8. omdev/irc/messages/parsing.py +99 -0
  9. omdev/irc/numerics/__init__.py +0 -0
  10. omdev/irc/numerics/formats.py +97 -0
  11. omdev/irc/numerics/numerics.py +865 -0
  12. omdev/irc/numerics/types.py +59 -0
  13. omdev/irc/protocol/LICENSE +11 -0
  14. omdev/irc/protocol/__init__.py +61 -0
  15. omdev/irc/protocol/consts.py +6 -0
  16. omdev/irc/protocol/errors.py +30 -0
  17. omdev/irc/protocol/message.py +21 -0
  18. omdev/irc/protocol/nuh.py +55 -0
  19. omdev/irc/protocol/parsing.py +158 -0
  20. omdev/irc/protocol/rendering.py +153 -0
  21. omdev/irc/protocol/tags.py +102 -0
  22. omdev/irc/protocol/utils.py +30 -0
  23. omdev/markdown/__init__.py +0 -0
  24. omdev/markdown/incparse.py +116 -0
  25. omdev/markdown/tokens.py +51 -0
  26. omdev/tui/apps/markdown/cli.py +3 -4
  27. omdev/tui/rich/__init__.py +33 -0
  28. omdev/tui/rich/console2.py +20 -0
  29. omdev/tui/rich/markdown2.py +186 -0
  30. omdev/tui/textual/__init__.py +156 -0
  31. omdev/tui/textual/drivers2.py +55 -0
  32. {omdev-0.0.0.dev460.dist-info → omdev-0.0.0.dev462.dist-info}/METADATA +2 -2
  33. {omdev-0.0.0.dev460.dist-info → omdev-0.0.0.dev462.dist-info}/RECORD +37 -9
  34. {omdev-0.0.0.dev460.dist-info → omdev-0.0.0.dev462.dist-info}/WHEEL +0 -0
  35. {omdev-0.0.0.dev460.dist-info → omdev-0.0.0.dev462.dist-info}/entry_points.txt +0 -0
  36. {omdev-0.0.0.dev460.dist-info → omdev-0.0.0.dev462.dist-info}/licenses/LICENSE +0 -0
  37. {omdev-0.0.0.dev460.dist-info → omdev-0.0.0.dev462.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from omlish import lang
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ import markdown_it as md
9
+ import markdown_it.token # noqa
10
+
11
+
12
+ ##
13
+
14
+
15
+ class IncrementalMarkdownParser:
16
+ def __init__(
17
+ self,
18
+ *,
19
+ parser: ta.Optional['md.MarkdownIt'] = None,
20
+ ) -> None:
21
+ super().__init__()
22
+
23
+ if parser is None:
24
+ parser = md.MarkdownIt()
25
+ self._parser = parser
26
+
27
+ self._stable_tokens: list[md.token.Token] = []
28
+ self._buffer = ''
29
+ self._num_stable_lines = 0 # Number of lines in stable tokens
30
+
31
+ class FeedOutput(ta.NamedTuple):
32
+ stable: ta.Sequence['md.token.Token']
33
+ new_stable: ta.Sequence['md.token.Token']
34
+ unstable: ta.Sequence['md.token.Token']
35
+
36
+ def feed2(self, chunk: str) -> FeedOutput:
37
+ self._buffer += chunk
38
+
39
+ # Parse the current buffer
40
+ new_tokens = self._parser.parse(self._buffer)
41
+
42
+ # Adjust ALL tokens to account for stable lines from previous parses (new_tokens have line numbers relative to
43
+ # current buffer)
44
+ adjusted_tokens = self._adjust_token_line_numbers(new_tokens, self._num_stable_lines)
45
+
46
+ # Find stable tokens (all but the last parent and its children)
47
+ stable_count = self._find_stable_token_count(adjusted_tokens)
48
+
49
+ newly_stable: ta.Sequence[md.token.Token]
50
+ if stable_count > 0:
51
+ # Extract newly stable tokens (already have adjusted line numbers)
52
+ newly_stable = adjusted_tokens[:stable_count]
53
+
54
+ # Calculate how many lines these stable tokens cover
55
+ max_line = 0
56
+ for token in newly_stable:
57
+ if token.map:
58
+ max_line = max(max_line, token.map[1])
59
+
60
+ # Update buffer to only contain unstable content
61
+ if max_line > self._num_stable_lines:
62
+ # max_line is absolute, num_stable_lines is the current buffer offset
63
+ lines_to_remove = max_line - self._num_stable_lines
64
+ lines = self._buffer.split('\n')
65
+ self._buffer = '\n'.join(lines[lines_to_remove:])
66
+
67
+ # Store newly stable tokens (with adjusted line numbers)
68
+ self._stable_tokens.extend(newly_stable)
69
+ self._num_stable_lines = max_line
70
+
71
+ else:
72
+ newly_stable = ()
73
+
74
+ return IncrementalMarkdownParser.FeedOutput(
75
+ stable=self._stable_tokens,
76
+ new_stable=newly_stable,
77
+ unstable=adjusted_tokens[stable_count:],
78
+ )
79
+
80
+ def feed(self, chunk: str) -> list['md.token.Token']:
81
+ out = self.feed2(chunk)
82
+ return [*out.stable, *out.unstable]
83
+
84
+ def _find_stable_token_count(self, tokens: list['md.token.Token']) -> int:
85
+ if not tokens:
86
+ return 0
87
+
88
+ # Find indices of all parent-level tokens (nesting = 0)
89
+ parent_indices = []
90
+ for i, token in enumerate(tokens):
91
+ if token.nesting in (1, 0) and token.level == 0:
92
+ parent_indices.append(i)
93
+
94
+ if len(parent_indices) < 2:
95
+ # Need at least 2 parent tokens to have stable content
96
+ return 0
97
+
98
+ # The last parent and everything after it is unstable. Everything before the second-to-last parent is stable.
99
+ return parent_indices[-2]
100
+
101
+ def _adjust_token_line_numbers(
102
+ self,
103
+ tokens: list['md.token.Token'],
104
+ line_offset: int,
105
+ ) -> list['md.token.Token']:
106
+ adjusted = []
107
+ for token in tokens:
108
+ if token.map:
109
+ token = dc.replace(
110
+ token,
111
+ map=[token.map[0] + line_offset, token.map[1] + line_offset],
112
+ )
113
+
114
+ adjusted.append(token)
115
+
116
+ return adjusted
@@ -0,0 +1,51 @@
1
+ # ruff: noqa: TC002
2
+ import typing as ta
3
+
4
+ from omlish import lang
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ import markdown_it as md
9
+ import markdown_it.token # noqa
10
+
11
+
12
+ ##
13
+
14
+
15
+ def token_repr(t: 'md.token.Token') -> str:
16
+ return ''.join([
17
+ 'Token(',
18
+ f'type={t.type!r}',
19
+ *([f', tag={t.tag!r}'] if t.tag else []),
20
+ *([f', nesting={t.nesting!r}'] if t.nesting else []),
21
+ *([f', attrs={t.attrs!r}'] if t.attrs else []),
22
+ *([f', map={t.map!r}'] if t.map else []),
23
+ *([f', level={t.level!r}'] if t.level else []),
24
+ *([f', children={t.children!r}'] if t.children else []),
25
+ *([f', content={t.content!r}'] if t.content else []),
26
+ *([f', markup={t.markup!r}'] if t.markup else []),
27
+ *([f', info={t.info!r}'] if t.info else []),
28
+ *([f', meta={t.meta!r}'] if t.meta else []),
29
+ *([f', block={t.block!r}'] if t.block else []),
30
+ *([f', hidden={t.hidden!r}'] if t.hidden else []),
31
+ ')',
32
+ ])
33
+
34
+
35
+ ##
36
+
37
+
38
+ def flatten_tokens(
39
+ tokens: ta.Iterable['md.token.Token'],
40
+ *,
41
+ filter: ta.Callable[['md.token.Token'], bool] | None = None, # noqa
42
+ ) -> ta.Iterable['md.token.Token']:
43
+ def rec(tks: ta.Iterable['md.token.Token']) -> ta.Iterator['md.token.Token']:
44
+ for tk in tks:
45
+ if tk.children and not (filter is not None and not filter(tk)):
46
+ yield from rec(tk.children)
47
+
48
+ else:
49
+ yield tk
50
+
51
+ return rec(tokens)
@@ -1,8 +1,7 @@
1
1
  import argparse
2
2
  import sys
3
3
 
4
- import rich.console
5
- import rich.markdown
4
+ from ... import rich
6
5
 
7
6
 
8
7
  ##
@@ -19,8 +18,8 @@ def _main() -> None:
19
18
  else:
20
19
  src = sys.stdin.read()
21
20
 
22
- console = rich.console.Console()
23
- markdown = rich.markdown.Markdown(src)
21
+ console = rich.Console()
22
+ markdown = rich.Markdown(src)
24
23
  console.print(markdown)
25
24
  print()
26
25
 
@@ -0,0 +1,33 @@
1
+ # ruff: noqa: F401
2
+ # flake8: noqa: F401
3
+ from omlish import lang as _lang
4
+
5
+
6
+ with _lang.auto_proxy_init(globals()):
7
+ ##
8
+
9
+ from rich import console # noqa
10
+ from rich import live # noqa
11
+ from rich import markdown # noqa
12
+ from rich import text # noqa
13
+ from rich.console import Console # noqa
14
+ from rich.live import Live # noqa
15
+ from rich.markdown import Markdown # noqa
16
+ from rich.text import Text # noqa
17
+
18
+ ##
19
+
20
+ from .console2 import ( # noqa
21
+ console_render,
22
+ )
23
+
24
+ from .markdown2 import ( # noqa
25
+ configure_markdown_parser,
26
+ markdown_from_tokens,
27
+ flatten_tokens_filter,
28
+ flatten_tokens,
29
+
30
+ MarkdownLiveStream,
31
+ NaiveMarkdownLiveStream,
32
+ IncrementalMarkdownLiveStream,
33
+ )
@@ -0,0 +1,20 @@
1
+ import io
2
+ import typing as ta
3
+
4
+ from omlish import lang
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ import rich.console
9
+
10
+
11
+ ##
12
+
13
+
14
+ def console_render(obj: ta.Any, **kwargs: ta.Any) -> str:
15
+ temp_console = rich.console.Console(
16
+ file=(out := io.StringIO()),
17
+ **kwargs,
18
+ )
19
+ temp_console.print(obj)
20
+ return out.getvalue()
@@ -0,0 +1,186 @@
1
+ import abc
2
+ import typing as ta
3
+
4
+ from omlish import lang
5
+
6
+ from ...markdown.incparse import IncrementalMarkdownParser
7
+ from ...markdown.tokens import flatten_tokens as _flatten_tokens
8
+ from .console2 import console_render
9
+
10
+
11
+ with lang.auto_proxy_import(globals()):
12
+ import markdown_it as md # noqa
13
+ import markdown_it.token # noqa
14
+ import rich.console
15
+ import rich.live
16
+ import rich.markdown
17
+ import rich.text
18
+
19
+
20
+ ##
21
+
22
+
23
+ def configure_markdown_parser(parser: ta.Optional['md.MarkdownIt'] = None) -> 'md.MarkdownIt':
24
+ if parser is None:
25
+ parser = md.MarkdownIt()
26
+
27
+ return (
28
+ parser
29
+ .enable('strikethrough')
30
+ .enable('table')
31
+ )
32
+
33
+
34
+ def markdown_from_tokens(tokens: ta.Sequence['md.token.Token']) -> 'rich.markdown.Markdown':
35
+ rmd = rich.markdown.Markdown('')
36
+ rmd.parsed = tokens # type: ignore[assignment]
37
+ return rmd
38
+
39
+
40
+ def flatten_tokens_filter(token: 'md.token.Token') -> bool:
41
+ return (
42
+ token.type != 'fence' and
43
+ token.tag != 'img'
44
+ )
45
+
46
+
47
+ def flatten_tokens(tokens: ta.Iterable['md.token.Token']) -> ta.Iterable['md.token.Token']:
48
+ return _flatten_tokens(tokens, filter=flatten_tokens_filter)
49
+
50
+
51
+ ##
52
+
53
+
54
+ class MarkdownLiveStream(lang.ExitStacked, lang.Abstract):
55
+ def __init__(
56
+ self,
57
+ *,
58
+ parser: ta.Optional['md.MarkdownIt'] = None,
59
+ console: ta.Optional['rich.console.Console'] = None,
60
+ ) -> None:
61
+ super().__init__()
62
+
63
+ if console is None:
64
+ console = rich.console.Console()
65
+ self._console = console
66
+
67
+ if parser is None:
68
+ parser = configure_markdown_parser()
69
+ self._parser = parser
70
+
71
+ self._lines_printed_to_scrollback = 0
72
+
73
+ _live: 'rich.live.Live' # noqa
74
+
75
+ def _enter_contexts(self) -> None:
76
+ super()._enter_contexts()
77
+
78
+ self._live = self._enter_context(rich.live.Live(
79
+ rich.text.Text(''),
80
+ console=self._console,
81
+ refresh_per_second=10,
82
+ ))
83
+
84
+ def _console_render(self, obj: ta.Any) -> list[str]:
85
+ return console_render(
86
+ obj,
87
+ force_terminal=True,
88
+ width=self._console.width,
89
+ ).splitlines()
90
+
91
+ def _console_render_markdown(self, src: str) -> list[str]:
92
+ return self._console_render(markdown_from_tokens(self._parser.parse(src)))
93
+
94
+ @abc.abstractmethod
95
+ def feed(self, s: str) -> None:
96
+ raise NotImplementedError
97
+
98
+
99
+ class NaiveMarkdownLiveStream(MarkdownLiveStream):
100
+ _accumulated = ''
101
+
102
+ def feed(self, s: str) -> None:
103
+ self._accumulated += s
104
+ all_lines = self._console_render_markdown(self._accumulated)
105
+
106
+ # Calculate how many lines fit in the live window
107
+ available_height = self._console.height - 2
108
+
109
+ # Determine which lines overflow and need to be printed to scrollback
110
+ total_lines = len(all_lines)
111
+ if total_lines > available_height:
112
+ # Lines that should be in scrollback
113
+ lines_for_scrollback = total_lines - available_height
114
+
115
+ # Print any new lines that weren't already printed
116
+ if lines_for_scrollback > self._lines_printed_to_scrollback:
117
+ new_lines_to_print = all_lines[self._lines_printed_to_scrollback:lines_for_scrollback]
118
+ for line in new_lines_to_print:
119
+ self._live.console.print(rich.text.Text.from_ansi(line))
120
+ self._lines_printed_to_scrollback = lines_for_scrollback
121
+
122
+ # Show only the bottom portion in the live window
123
+ visible_lines = all_lines[-available_height:]
124
+
125
+ else:
126
+ visible_lines = all_lines
127
+
128
+ # Update the live display
129
+ self._live.update(rich.text.Text.from_ansi('\n'.join(visible_lines)))
130
+
131
+
132
+ class IncrementalMarkdownLiveStream(MarkdownLiveStream):
133
+ def __init__(
134
+ self,
135
+ *,
136
+ parser: ta.Optional['md.MarkdownIt'] = None,
137
+ console: ta.Optional['rich.console.Console'] = None,
138
+ ) -> None:
139
+ super().__init__(
140
+ parser=parser,
141
+ console=console,
142
+ )
143
+
144
+ self._inc_parser = IncrementalMarkdownParser(parser=self._parser)
145
+
146
+ def feed(self, s: str) -> None:
147
+ ip_out = self._inc_parser.feed2(s)
148
+
149
+ if ip_out.new_stable:
150
+ # try:
151
+ # srs = getattr(self, '_srs')
152
+ # except AttributeError:
153
+ # setattr(self, '_srs', srs := [])
154
+ # from ...markdown.tokens import token_repr, flatten_tokens
155
+ # srs.extend(map(token_repr, flatten_tokens(ip_out.new_stable)))
156
+
157
+ stable_lines = self._console_render(markdown_from_tokens(ip_out.new_stable))
158
+ stable_lines.append(' ') # FIXME: lame hack
159
+ self._live.console.print(rich.text.Text.from_ansi('\n'.join(stable_lines), no_wrap=True))
160
+ self._lines_printed_to_scrollback = max(0, self._lines_printed_to_scrollback - len(stable_lines))
161
+
162
+ unstable_lines = self._console_render(markdown_from_tokens(ip_out.unstable))
163
+
164
+ # Calculate how many lines fit in the live window
165
+ available_height = self._console.height - 2
166
+
167
+ # Determine which lines overflow and need to be printed to scrollback
168
+ total_lines = len(unstable_lines)
169
+ if total_lines > available_height:
170
+ # Lines that should be in scrollback
171
+ lines_for_scrollback = total_lines - available_height
172
+
173
+ # Print any new lines that weren't already printed
174
+ if lines_for_scrollback > self._lines_printed_to_scrollback:
175
+ new_lines_to_print = unstable_lines[self._lines_printed_to_scrollback:lines_for_scrollback]
176
+ self._live.console.print(rich.text.Text.from_ansi('\n'.join(new_lines_to_print)))
177
+ self._lines_printed_to_scrollback = lines_for_scrollback
178
+
179
+ # Show only the bottom portion in the live window
180
+ visible_lines = unstable_lines[-available_height:]
181
+
182
+ else:
183
+ visible_lines = unstable_lines
184
+
185
+ # Update the live display
186
+ self._live.update(rich.text.Text.from_ansi('\n'.join(visible_lines)))
@@ -0,0 +1,156 @@
1
+ # ruff: noqa: F401
2
+ # flake8: noqa: F401
3
+ from omlish import lang as _lang
4
+
5
+
6
+ with _lang.auto_proxy_init(globals()):
7
+ ##
8
+
9
+ from textual import app # noqa
10
+ from textual import binding # noqa
11
+ from textual import containers # noqa
12
+ from textual import events # noqa
13
+ from textual import events # noqa
14
+ from textual import message # noqa
15
+ from textual import reactive # noqa
16
+ from textual import widgets # noqa
17
+ from textual.app import ActionError # noqa
18
+ from textual.app import ActiveModeError # noqa
19
+ from textual.app import App # noqa
20
+ from textual.app import AppError # noqa
21
+ from textual.app import AutopilotCallbackType # noqa
22
+ from textual.app import CallThreadReturnType # noqa
23
+ from textual.app import CommandCallback # noqa
24
+ from textual.app import ComposeResult # noqa
25
+ from textual.app import InvalidModeError # noqa
26
+ from textual.app import InvalidThemeError # noqa
27
+ from textual.app import ModeError # noqa
28
+ from textual.app import RenderResult # noqa
29
+ from textual.app import ReturnType # noqa
30
+ from textual.app import ScreenError # noqa
31
+ from textual.app import ScreenStackError # noqa
32
+ from textual.app import ScreenType # noqa
33
+ from textual.app import SuspendNotSupported # noqa
34
+ from textual.app import SystemCommand # noqa
35
+ from textual.app import UnknownModeError # noqa
36
+ from textual.app import get_system_commands_provider # noqa
37
+ from textual.binding import ActiveBinding # noqa
38
+ from textual.binding import Binding # noqa
39
+ from textual.binding import BindingError # noqa
40
+ from textual.binding import BindingIDString # noqa
41
+ from textual.binding import BindingType # noqa
42
+ from textual.binding import BindingsMap # noqa
43
+ from textual.binding import InvalidBinding # noqa
44
+ from textual.binding import KeyString # noqa
45
+ from textual.binding import Keymap # noqa
46
+ from textual.binding import KeymapApplyResult # noqa
47
+ from textual.binding import NoBinding # noqa
48
+ from textual.containers import Center # noqa
49
+ from textual.containers import CenterMiddle # noqa
50
+ from textual.containers import Container # noqa
51
+ from textual.containers import Grid # noqa
52
+ from textual.containers import Horizontal # noqa
53
+ from textual.containers import HorizontalGroup # noqa
54
+ from textual.containers import HorizontalScroll # noqa
55
+ from textual.containers import ItemGrid # noqa
56
+ from textual.containers import Middle # noqa
57
+ from textual.containers import Right # noqa
58
+ from textual.containers import ScrollableContainer # noqa
59
+ from textual.containers import Vertical # noqa
60
+ from textual.containers import VerticalGroup # noqa
61
+ from textual.containers import VerticalScroll # noqa
62
+ from textual.driver import Driver # noqa
63
+ from textual.events import Action # noqa
64
+ from textual.events import AppBlur # noqa
65
+ from textual.events import AppFocus # noqa
66
+ from textual.events import Blur # noqa
67
+ from textual.events import Callback # noqa
68
+ from textual.events import Click # noqa
69
+ from textual.events import Compose # noqa
70
+ from textual.events import CursorPosition # noqa
71
+ from textual.events import DeliveryComplete # noqa
72
+ from textual.events import DeliveryFailed # noqa
73
+ from textual.events import DescendantBlur # noqa
74
+ from textual.events import DescendantFocus # noqa
75
+ from textual.events import Enter # noqa
76
+ from textual.events import Event # noqa
77
+ from textual.events import Focus # noqa
78
+ from textual.events import Hide # noqa
79
+ from textual.events import Idle # noqa
80
+ from textual.events import InputEvent # noqa
81
+ from textual.events import Key # noqa
82
+ from textual.events import Leave # noqa
83
+ from textual.events import Load # noqa
84
+ from textual.events import Mount # noqa
85
+ from textual.events import MouseCapture # noqa
86
+ from textual.events import MouseDown # noqa
87
+ from textual.events import MouseEvent # noqa
88
+ from textual.events import MouseMove # noqa
89
+ from textual.events import MouseRelease # noqa
90
+ from textual.events import MouseScrollDown # noqa
91
+ from textual.events import MouseScrollLeft # noqa
92
+ from textual.events import MouseScrollRight # noqa
93
+ from textual.events import MouseScrollUp # noqa
94
+ from textual.events import MouseUp # noqa
95
+ from textual.events import Paste # noqa
96
+ from textual.events import Print # noqa
97
+ from textual.events import Ready # noqa
98
+ from textual.events import Resize # noqa
99
+ from textual.events import ScreenResume # noqa
100
+ from textual.events import ScreenSuspend # noqa
101
+ from textual.events import Show # noqa
102
+ from textual.events import Timer # noqa
103
+ from textual.events import Unmount # noqa
104
+ from textual.message import Message # noqa
105
+ from textual.reactive import Reactive # noqa
106
+ from textual.widget import Widget # noqa
107
+ from textual.widgets import Button # noqa
108
+ from textual.widgets import Checkbox # noqa
109
+ from textual.widgets import Collapsible # noqa
110
+ from textual.widgets import CollapsibleTitle # noqa
111
+ from textual.widgets import ContentSwitcher # noqa
112
+ from textual.widgets import DataTable # noqa
113
+ from textual.widgets import Digits # noqa
114
+ from textual.widgets import DirectoryTree # noqa
115
+ from textual.widgets import Footer # noqa
116
+ from textual.widgets import Header # noqa
117
+ from textual.widgets import HelpPanel # noqa
118
+ from textual.widgets import Input # noqa
119
+ from textual.widgets import KeyPanel # noqa
120
+ from textual.widgets import Label # noqa
121
+ from textual.widgets import Link # noqa
122
+ from textual.widgets import ListItem # noqa
123
+ from textual.widgets import ListView # noqa
124
+ from textual.widgets import LoadingIndicator # noqa
125
+ from textual.widgets import Log # noqa
126
+ from textual.widgets import Markdown # noqa
127
+ from textual.widgets import MarkdownViewer # noqa
128
+ from textual.widgets import MaskedInput # noqa
129
+ from textual.widgets import OptionList # noqa
130
+ from textual.widgets import Placeholder # noqa
131
+ from textual.widgets import Pretty # noqa
132
+ from textual.widgets import ProgressBar # noqa
133
+ from textual.widgets import RadioButton # noqa
134
+ from textual.widgets import RadioSet # noqa
135
+ from textual.widgets import RichLog # noqa
136
+ from textual.widgets import Rule # noqa
137
+ from textual.widgets import Select # noqa
138
+ from textual.widgets import SelectionList # noqa
139
+ from textual.widgets import Sparkline # noqa
140
+ from textual.widgets import Static # noqa
141
+ from textual.widgets import Switch # noqa
142
+ from textual.widgets import Tab # noqa
143
+ from textual.widgets import TabPane # noqa
144
+ from textual.widgets import TabbedContent # noqa
145
+ from textual.widgets import Tabs # noqa
146
+ from textual.widgets import TextArea # noqa
147
+ from textual.widgets import Tooltip # noqa
148
+ from textual.widgets import Tree # noqa
149
+ from textual.widgets import Welcome # noqa
150
+
151
+ ##
152
+
153
+ from .drivers2 import ( # noqa
154
+ PendingWritesDriverMixin,
155
+ get_pending_writes_driver_class,
156
+ )
@@ -0,0 +1,55 @@
1
+ import threading
2
+ import typing as ta
3
+
4
+ from omlish import lang
5
+
6
+
7
+ if ta.TYPE_CHECKING:
8
+ from textual.driver import Driver
9
+
10
+
11
+ ##
12
+
13
+
14
+ class PendingWritesDriverMixin:
15
+ def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
16
+ super().__init__(*args, **kwargs)
17
+
18
+ self._pending_primary_buffer_writes: list[str] = []
19
+
20
+ def queue_primary_buffer_write(self, *s: str) -> None:
21
+ self._pending_primary_buffer_writes.extend(s)
22
+
23
+ def write(self, data: str) -> None:
24
+ if (pw := self._pending_primary_buffer_writes):
25
+ data = ''.join([*pw, data])
26
+ pw.clear()
27
+ super().write(data) # type: ignore
28
+
29
+
30
+ _PENDING_WRITES_DRIVER_CLASSES_LOCK = threading.RLock()
31
+ _PENDING_WRITES_DRIVER_CLASSES: dict[type['Driver'], type['Driver']] = {}
32
+
33
+
34
+ def get_pending_writes_driver_class(cls: type['Driver']) -> type['Driver']:
35
+ if issubclass(cls, PendingWritesDriverMixin):
36
+ return cls # noqa
37
+
38
+ try:
39
+ return _PENDING_WRITES_DRIVER_CLASSES[cls]
40
+ except KeyError:
41
+ pass
42
+
43
+ with _PENDING_WRITES_DRIVER_CLASSES_LOCK:
44
+ try:
45
+ return _PENDING_WRITES_DRIVER_CLASSES[cls]
46
+ except KeyError:
47
+ pass
48
+
49
+ cls = _PENDING_WRITES_DRIVER_CLASSES[cls] = lang.new_type( # noqa
50
+ f'PendingWrites{cls.__name__}',
51
+ (PendingWritesDriverMixin, cls),
52
+ {},
53
+ )
54
+
55
+ return cls # noqa
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omdev
3
- Version: 0.0.0.dev460
3
+ Version: 0.0.0.dev462
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.13
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: omlish==0.0.0.dev460
17
+ Requires-Dist: omlish==0.0.0.dev462
18
18
  Provides-Extra: all
19
19
  Requires-Dist: black~=25.9; extra == "all"
20
20
  Requires-Dist: pycparser~=2.23; extra == "all"