Skip to content

Commit 710d279

Browse files
committed
update
1 parent 08befd3 commit 710d279

26 files changed

Lines changed: 1832 additions & 1558 deletions

examples/incremental-rendering/incremental-rendering.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,22 @@ def progress_bar(value: int) -> str:
7575
return ("█" * filled) + ("░" * empty)
7676

7777

78+
def truncate_text(text: str, max_length: int) -> str:
79+
if max_length <= 0:
80+
return ""
81+
if len(text) <= max_length:
82+
return text
83+
if max_length <= 3:
84+
return text[:max_length]
85+
return f"{text[: max_length - 3]}..."
86+
87+
7888
def incremental_rendering_example():
7989
app = useApp()
80-
_terminal_width, terminal_height = useWindowSize()
90+
terminal_width, terminal_height = useWindowSize()
8191

82-
available_lines = max(terminal_height - 15, 10)
92+
compact_mode = terminal_height < 20
93+
available_lines = max(terminal_height - 15, 0)
8394
log_line_count = max(int(available_lines * 0.3), 3)
8495
service_count = min(max(int(available_lines * 0.7), 5), len(ROWS))
8596

@@ -200,6 +211,49 @@ def on_input(input_char, key):
200211
useInput(on_input)
201212

202213
visible_rows = ROWS[:service_count]
214+
safe_selected_index = min(selected_index, max(len(visible_rows) - 1, 0))
215+
selected_row = visible_rows[safe_selected_index] if visible_rows else ""
216+
217+
if compact_mode:
218+
compact_service_count = min(max(terminal_height - 5, 1), len(ROWS))
219+
compact_rows = ROWS[:compact_service_count]
220+
compact_selected_index = min(selected_index, max(len(compact_rows) - 1, 0))
221+
compact_selected_row = compact_rows[compact_selected_index] if compact_rows else ""
222+
compact_label_width = max(terminal_width - 12, 20)
223+
224+
return Box(
225+
Text(
226+
"Incremental Rendering Demo",
227+
bold=True,
228+
color="cyan",
229+
),
230+
Text(
231+
f"FPS: {fps} • Time: {timestamp} • Updates: {counter} • Random: {random_value}",
232+
dim_color=True,
233+
),
234+
Text(
235+
f"Services ({compact_service_count} of {len(ROWS)}):",
236+
bold=True,
237+
color="magenta",
238+
),
239+
*[
240+
Text(
241+
f"{'> ' if index == compact_selected_index else ' '}"
242+
f"{truncate_text(row, max(terminal_width - 4, 12))}",
243+
color="blue" if index == compact_selected_index else "white",
244+
)
245+
for index, row in enumerate(compact_rows)
246+
],
247+
Text(
248+
"Selected: ",
249+
Text(
250+
truncate_text(compact_selected_row, compact_label_width),
251+
bold=True,
252+
color="magenta",
253+
),
254+
),
255+
flexDirection="column",
256+
)
203257

204258
return Box(
205259
Box(
@@ -267,8 +321,8 @@ def on_input(input_char, key):
267321
),
268322
*[
269323
Text(
270-
f"{'> ' if index == selected_index else ' '}{row}",
271-
color="blue" if index == selected_index else "white",
324+
f"{'> ' if index == safe_selected_index else ' '}{row}",
325+
color="blue" if index == safe_selected_index else "white",
272326
)
273327
for index, row in enumerate(visible_rows)
274328
],
@@ -283,15 +337,14 @@ def on_input(input_char, key):
283337
Box(
284338
Text(
285339
"Selected: ",
286-
Text(visible_rows[selected_index], bold=True, color="magenta"),
340+
Text(selected_row, bold=True, color="magenta"),
287341
),
288342
borderStyle="round",
289343
borderColor="magenta",
290344
paddingX=2,
291345
marginTop=1,
292346
),
293347
flexDirection="column",
294-
height="100%",
295348
)
296349

297350

src/pyinkcli/components/App.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
from ..hooks.use_stdin import useStdin
77
from .AppContext import AppContext, set_app_context_value
88
from .StderrContext import StderrContext, set_stderr_context_value
9-
from .StdinContext import StdinContext, set_stdin_context_value
9+
from .StdinContext import (
10+
StdinContext,
11+
create_stdin_handle,
12+
create_stdin_runtime_bridge,
13+
set_stdin_context_value,
14+
)
1015
from .StdoutContext import StdoutContext, set_stdout_context_value
1116

1217

@@ -18,11 +23,7 @@ def App(*children, **props):
1823

1924
def manage_runtime():
2025
def cleanup():
21-
stdin._clear_pending_escape_flush()
22-
while getattr(stdin, "_raw_mode_enabled_count", 0) > 0:
23-
stdin.setRawMode(False)
24-
while getattr(stdin, "_bracketed_paste_enabled_count", 0) > 0:
25-
stdin.setBracketedPasteMode(False)
26+
stdin.teardown()
2627

2728
return cleanup
2829

@@ -53,14 +54,28 @@ def create_app_tree(node, *, contexts):
5354

5455

5556
def create_runtime_contexts(*, app, stdin, stdout, stderr, interactive: bool, exit_on_ctrl_c: bool = True):
57+
stdin_handle = create_stdin_handle(
58+
stdin=stdin,
59+
output_stream=stdout,
60+
is_raw_mode_supported=bool(getattr(stdin, "isatty", lambda: False)()),
61+
exit_on_ctrl_c=exit_on_ctrl_c,
62+
on_exit=app.exit,
63+
)
64+
stdin_runtime = create_stdin_runtime_bridge(
65+
stdin_handle,
66+
exit_on_ctrl_c=exit_on_ctrl_c,
67+
on_exit=app.exit,
68+
)
5669
return {
5770
"app": set_app_context_value(app),
5871
"stdin": set_stdin_context_value(
59-
stdin=stdin,
60-
is_raw_mode_supported=bool(getattr(stdin, "isatty", lambda: False)()),
61-
exit_on_ctrl_c=exit_on_ctrl_c,
72+
handle=stdin_handle,
73+
runtime=stdin_runtime,
6274
on_exit=app.exit,
75+
exit_on_ctrl_c=exit_on_ctrl_c,
6376
),
77+
"stdin_handle": stdin_handle,
78+
"stdin_runtime": stdin_runtime,
6479
"stdout": set_stdout_context_value(stdout=stdout, write=app._write_to_stdout),
6580
"stderr": set_stderr_context_value(stderr=stderr, write=app._write_to_stderr),
6681
"interactive": interactive,

src/pyinkcli/components/AppContext.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def waitUntilRenderFlush(self, timeout=None):
2727
def exit(self, errorOrResult=None):
2828
return self._app.exit(errorOrResult)
2929

30+
def _call_on_render_thread(self, callback):
31+
return self._app._call_on_render_thread(callback)
32+
3033

3134
def create_app_context_value(app=None):
3235
if app is None:
@@ -50,6 +53,10 @@ def wait_until_render_flush(self, timeout=None):
5053
def exit(self, error_or_result=None):
5154
return None
5255

56+
def _call_on_render_thread(self, callback):
57+
callback()
58+
return None
59+
5360

5461
AppContext = createContext(create_app_context_value())
5562
AppContext.displayName = "InternalAppContext"
@@ -60,4 +67,5 @@ def set_app_context_value(app=None):
6067
AppContext.current_value = value
6168
return value
6269

70+
6371
__all__ = ["AppContext", "Props"]

src/pyinkcli/components/StdinContext.py

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,105 @@
22

33
import sys
44

5-
from ..hooks.use_stdin import _StdinHandle
5+
from ..hooks.use_stdin import (
6+
_StdinHandle,
7+
create_default_stdin_context_value,
8+
create_stdin_context_value,
9+
create_stdin_runtime,
10+
get_stdin_handle,
11+
get_stdin_runtime,
12+
)
613
from ..packages.react_context import createContext
714

815
PublicProps = dict
916
Props = dict
1017

1118

12-
def create_stdin_context_value(
19+
def create_stdin_handle(
1320
*,
1421
stdin=sys.stdin,
22+
output_stream=None,
1523
is_raw_mode_supported: bool = False,
1624
exit_on_ctrl_c: bool = True,
1725
internal_event_emitter=None,
1826
on_exit=None,
1927
):
20-
return _StdinHandle(
28+
return get_stdin_handle(
29+
create_stdin_context_value(
30+
stream=stdin,
31+
output_stream=output_stream,
32+
is_raw_mode_supported=is_raw_mode_supported,
33+
exit_on_ctrl_c=exit_on_ctrl_c,
34+
internal_event_emitter=internal_event_emitter,
35+
on_exit=on_exit,
36+
)
37+
)
38+
39+
40+
def create_stdin_runtime_bridge(
41+
handle: _StdinHandle,
42+
*,
43+
exit_on_ctrl_c: bool = True,
44+
on_exit=None,
45+
):
46+
return create_stdin_runtime(
47+
handle,
48+
exit_on_ctrl_c=exit_on_ctrl_c,
49+
on_exit=on_exit,
50+
)
51+
52+
53+
def build_stdin_context_value(
54+
*,
55+
handle: _StdinHandle | None = None,
56+
runtime=None,
57+
stdin=sys.stdin,
58+
output_stream=None,
59+
is_raw_mode_supported: bool = False,
60+
exit_on_ctrl_c: bool = True,
61+
internal_event_emitter=None,
62+
on_exit=None,
63+
):
64+
if handle is not None:
65+
return create_stdin_context_value(
66+
handle=handle,
67+
runtime=runtime
68+
or create_stdin_runtime_bridge(
69+
handle,
70+
exit_on_ctrl_c=exit_on_ctrl_c,
71+
on_exit=on_exit,
72+
),
73+
)
74+
return create_stdin_context_value(
2175
stream=stdin,
76+
output_stream=output_stream,
2277
is_raw_mode_supported=is_raw_mode_supported,
2378
exit_on_ctrl_c=exit_on_ctrl_c,
2479
internal_event_emitter=internal_event_emitter,
2580
on_exit=on_exit,
2681
)
2782

2883

29-
StdinContext = createContext(create_stdin_context_value())
84+
StdinContext = createContext(create_default_stdin_context_value())
3085
StdinContext.displayName = "InternalStdinContext"
3186

3287

3388
def set_stdin_context_value(
3489
*,
90+
handle: _StdinHandle | None = None,
91+
runtime=None,
3592
stdin=sys.stdin,
93+
output_stream=None,
3694
is_raw_mode_supported: bool = False,
3795
exit_on_ctrl_c: bool = True,
3896
internal_event_emitter=None,
3997
on_exit=None,
4098
):
41-
value = create_stdin_context_value(
99+
value = build_stdin_context_value(
100+
handle=handle,
101+
runtime=runtime,
42102
stdin=stdin,
103+
output_stream=output_stream,
43104
is_raw_mode_supported=is_raw_mode_supported,
44105
exit_on_ctrl_c=exit_on_ctrl_c,
45106
internal_event_emitter=internal_event_emitter,
@@ -48,4 +109,10 @@ def set_stdin_context_value(
48109
StdinContext.current_value = value
49110
return value
50111

51-
__all__ = ["StdinContext"]
112+
__all__ = [
113+
"StdinContext",
114+
"build_stdin_context_value",
115+
"create_stdin_handle",
116+
"create_stdin_runtime_bridge",
117+
"set_stdin_context_value",
118+
]

src/pyinkcli/dom.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class DOMNode:
2121
onComputeLayout: Any = None
2222
onRender: Any = None
2323
onImmediateRender: Any = None
24+
isStaticDirty: bool = False
25+
staticNode: DOMNode | None = None
26+
_static_signature: Any = None
2427

2528

2629
@dataclass
@@ -104,4 +107,3 @@ def emitLayoutListeners(node: DOMNode) -> None:
104107
"emitLayoutListeners",
105108
"squashTextNodes",
106109
]
107-

0 commit comments

Comments
 (0)