wafer-core 0.1.45__py3-none-any.whl → 0.1.47__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.
@@ -0,0 +1,186 @@
1
+ """Viewport component for pytui - Elm-style scrollable text view.
2
+
3
+ A frozen dataclass + pure functions approach matching pytui's architecture.
4
+ Based on patterns from charmbracelet/bubbles viewport.
5
+
6
+ Usage:
7
+ # In your Model
8
+ @dataclass(frozen=True)
9
+ class Model:
10
+ viewport: Viewport = Viewport()
11
+
12
+ # In update()
13
+ case KeyPress(key="j"):
14
+ new_vp = viewport_scroll_down(model.viewport, 1, visible_height)
15
+ return replace(model, viewport=new_vp), Cmd.none()
16
+
17
+ # In view()
18
+ visible = viewport_visible_lines(model.viewport, visible_height)
19
+ for line in visible:
20
+ lines.append(line)
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, replace
26
+
27
+
28
+ def _clamp(value: int, min_val: int, max_val: int) -> int:
29
+ """Clamp value to [min_val, max_val]."""
30
+ if min_val > max_val:
31
+ min_val, max_val = max_val, min_val
32
+ return max(min_val, min(value, max_val))
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class Viewport:
37
+ """Immutable viewport state.
38
+
39
+ Attributes:
40
+ lines: Content as tuple of strings (one per line)
41
+ y_offset: Vertical scroll position (0 = top)
42
+ auto_follow: If True, scroll to bottom when new content added
43
+ """
44
+
45
+ lines: tuple[str, ...] = ()
46
+ y_offset: int = 0
47
+ auto_follow: bool = True
48
+
49
+
50
+ # ── Query functions ──────────────────────────────────────────────────────────
51
+
52
+
53
+ def viewport_max_offset(vp: Viewport, visible_height: int) -> int:
54
+ """Maximum valid y_offset for this viewport."""
55
+ return max(0, len(vp.lines) - visible_height)
56
+
57
+
58
+ def viewport_at_top(vp: Viewport) -> bool:
59
+ """Check if viewport is scrolled to top."""
60
+ return vp.y_offset <= 0
61
+
62
+
63
+ def viewport_at_bottom(vp: Viewport, visible_height: int) -> bool:
64
+ """Check if viewport is scrolled to bottom."""
65
+ return vp.y_offset >= viewport_max_offset(vp, visible_height)
66
+
67
+
68
+ def viewport_scroll_percent(vp: Viewport, visible_height: int) -> float:
69
+ """Get scroll position as percentage (0.0 to 1.0)."""
70
+ max_off = viewport_max_offset(vp, visible_height)
71
+ if max_off == 0:
72
+ return 1.0
73
+ return vp.y_offset / max_off
74
+
75
+
76
+ def viewport_visible_lines(vp: Viewport, visible_height: int) -> list[str]:
77
+ """Get the lines currently visible in the viewport."""
78
+ start = _clamp(vp.y_offset, 0, len(vp.lines))
79
+ end = _clamp(start + visible_height, start, len(vp.lines))
80
+ return list(vp.lines[start:end])
81
+
82
+
83
+ # ── Scroll operations ────────────────────────────────────────────────────────
84
+
85
+
86
+ def viewport_scroll_down(vp: Viewport, n: int, visible_height: int) -> Viewport:
87
+ """Scroll down by n lines. Returns unchanged viewport if already at bottom."""
88
+ if viewport_at_bottom(vp, visible_height) or n <= 0:
89
+ return vp
90
+ max_off = viewport_max_offset(vp, visible_height)
91
+ new_offset = _clamp(vp.y_offset + n, 0, max_off)
92
+ # Re-enable auto_follow if we hit bottom
93
+ auto_follow = new_offset >= max_off
94
+ return replace(vp, y_offset=new_offset, auto_follow=auto_follow)
95
+
96
+
97
+ def viewport_scroll_up(vp: Viewport, n: int) -> Viewport:
98
+ """Scroll up by n lines. Returns unchanged viewport if already at top."""
99
+ if viewport_at_top(vp) or n <= 0:
100
+ return vp
101
+ new_offset = max(0, vp.y_offset - n)
102
+ # Disable auto_follow when scrolling up
103
+ return replace(vp, y_offset=new_offset, auto_follow=False)
104
+
105
+
106
+ def viewport_page_down(vp: Viewport, visible_height: int) -> Viewport:
107
+ """Scroll down by one page."""
108
+ return viewport_scroll_down(vp, visible_height, visible_height)
109
+
110
+
111
+ def viewport_page_up(vp: Viewport, visible_height: int) -> Viewport:
112
+ """Scroll up by one page."""
113
+ return viewport_scroll_up(vp, visible_height)
114
+
115
+
116
+ def viewport_half_page_down(vp: Viewport, visible_height: int) -> Viewport:
117
+ """Scroll down by half a page (Ctrl+D style)."""
118
+ return viewport_scroll_down(vp, visible_height // 2, visible_height)
119
+
120
+
121
+ def viewport_half_page_up(vp: Viewport, visible_height: int) -> Viewport:
122
+ """Scroll up by half a page (Ctrl+U style)."""
123
+ return viewport_scroll_up(vp, visible_height // 2)
124
+
125
+
126
+ def viewport_goto_top(vp: Viewport) -> Viewport:
127
+ """Jump to top of content."""
128
+ if viewport_at_top(vp):
129
+ return vp
130
+ return replace(vp, y_offset=0, auto_follow=False)
131
+
132
+
133
+ def viewport_goto_bottom(vp: Viewport, visible_height: int) -> Viewport:
134
+ """Jump to bottom of content, re-enable auto_follow."""
135
+ max_off = viewport_max_offset(vp, visible_height)
136
+ return replace(vp, y_offset=max_off, auto_follow=True)
137
+
138
+
139
+ # ── Content operations ───────────────────────────────────────────────────────
140
+
141
+
142
+ def viewport_set_content(vp: Viewport, lines: tuple[str, ...], visible_height: int) -> Viewport:
143
+ """Replace all content. Adjusts scroll if past new content end."""
144
+ new_vp = replace(vp, lines=lines)
145
+ max_off = viewport_max_offset(new_vp, visible_height)
146
+
147
+ # If scrolled past content, jump to bottom
148
+ if new_vp.y_offset > max_off:
149
+ return replace(new_vp, y_offset=max_off)
150
+
151
+ # If auto_follow enabled, stay at bottom
152
+ if new_vp.auto_follow:
153
+ return replace(new_vp, y_offset=max_off)
154
+
155
+ return new_vp
156
+
157
+
158
+ def viewport_append_line(vp: Viewport, line: str, visible_height: int) -> Viewport:
159
+ """Append a single line. Auto-scrolls if auto_follow enabled."""
160
+ new_lines = vp.lines + (line,)
161
+ new_vp = replace(vp, lines=new_lines)
162
+
163
+ if vp.auto_follow:
164
+ max_off = viewport_max_offset(new_vp, visible_height)
165
+ return replace(new_vp, y_offset=max_off)
166
+
167
+ return new_vp
168
+
169
+
170
+ def viewport_append_lines(vp: Viewport, lines: tuple[str, ...], visible_height: int) -> Viewport:
171
+ """Append multiple lines. Auto-scrolls if auto_follow enabled."""
172
+ if not lines:
173
+ return vp
174
+ new_lines = vp.lines + lines
175
+ new_vp = replace(vp, lines=new_lines)
176
+
177
+ if vp.auto_follow:
178
+ max_off = viewport_max_offset(new_vp, visible_height)
179
+ return replace(new_vp, y_offset=max_off)
180
+
181
+ return new_vp
182
+
183
+
184
+ def viewport_clear(vp: Viewport) -> Viewport:
185
+ """Clear all content and reset scroll."""
186
+ return Viewport(lines=(), y_offset=0, auto_follow=True)
@@ -844,6 +844,18 @@ async def process_pending_tools(
844
844
  if "content" in tool_call.args:
845
845
  result_summary["content"] = tool_call.args["content"]
846
846
 
847
+ # For edit, capture file path and diff stats
848
+ elif tool_call.name == "edit":
849
+ if "file_path" in tool_call.args:
850
+ result_summary["path"] = tool_call.args["file_path"]
851
+ # Compute +/- lines from old_string and new_string
852
+ old_str = tool_call.args.get("old_string", "")
853
+ new_str = tool_call.args.get("new_string", "")
854
+ old_lines = old_str.count("\n") + (1 if old_str else 0)
855
+ new_lines = new_str.count("\n") + (1 if new_str else 0)
856
+ result_summary["lines_removed"] = old_lines
857
+ result_summary["lines_added"] = new_lines
858
+
847
859
  # Extract key metrics from details (e.g., compiled, correct for kernelbench)
848
860
  if tool_result.details:
849
861
  for k, v in tool_result.details.items():