klaude-code 1.2.6__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.
- klaude_code/__init__.py +0 -0
- klaude_code/cli/__init__.py +1 -0
- klaude_code/cli/main.py +298 -0
- klaude_code/cli/runtime.py +331 -0
- klaude_code/cli/session_cmd.py +80 -0
- klaude_code/command/__init__.py +43 -0
- klaude_code/command/clear_cmd.py +20 -0
- klaude_code/command/command_abc.py +92 -0
- klaude_code/command/diff_cmd.py +138 -0
- klaude_code/command/export_cmd.py +86 -0
- klaude_code/command/help_cmd.py +51 -0
- klaude_code/command/model_cmd.py +43 -0
- klaude_code/command/prompt-dev-docs-update.md +56 -0
- klaude_code/command/prompt-dev-docs.md +46 -0
- klaude_code/command/prompt-init.md +45 -0
- klaude_code/command/prompt_command.py +69 -0
- klaude_code/command/refresh_cmd.py +43 -0
- klaude_code/command/registry.py +110 -0
- klaude_code/command/status_cmd.py +111 -0
- klaude_code/command/terminal_setup_cmd.py +252 -0
- klaude_code/config/__init__.py +11 -0
- klaude_code/config/config.py +177 -0
- klaude_code/config/list_model.py +162 -0
- klaude_code/config/select_model.py +67 -0
- klaude_code/const/__init__.py +133 -0
- klaude_code/core/__init__.py +0 -0
- klaude_code/core/agent.py +165 -0
- klaude_code/core/executor.py +485 -0
- klaude_code/core/manager/__init__.py +19 -0
- klaude_code/core/manager/agent_manager.py +127 -0
- klaude_code/core/manager/llm_clients.py +42 -0
- klaude_code/core/manager/llm_clients_builder.py +49 -0
- klaude_code/core/manager/sub_agent_manager.py +86 -0
- klaude_code/core/prompt.py +89 -0
- klaude_code/core/prompts/prompt-claude-code.md +98 -0
- klaude_code/core/prompts/prompt-codex.md +331 -0
- klaude_code/core/prompts/prompt-gemini.md +43 -0
- klaude_code/core/prompts/prompt-subagent-explore.md +27 -0
- klaude_code/core/prompts/prompt-subagent-oracle.md +23 -0
- klaude_code/core/prompts/prompt-subagent-webfetch.md +46 -0
- klaude_code/core/prompts/prompt-subagent.md +8 -0
- klaude_code/core/reminders.py +445 -0
- klaude_code/core/task.py +237 -0
- klaude_code/core/tool/__init__.py +75 -0
- klaude_code/core/tool/file/__init__.py +0 -0
- klaude_code/core/tool/file/apply_patch.py +492 -0
- klaude_code/core/tool/file/apply_patch_tool.md +1 -0
- klaude_code/core/tool/file/apply_patch_tool.py +204 -0
- klaude_code/core/tool/file/edit_tool.md +9 -0
- klaude_code/core/tool/file/edit_tool.py +274 -0
- klaude_code/core/tool/file/multi_edit_tool.md +42 -0
- klaude_code/core/tool/file/multi_edit_tool.py +199 -0
- klaude_code/core/tool/file/read_tool.md +14 -0
- klaude_code/core/tool/file/read_tool.py +326 -0
- klaude_code/core/tool/file/write_tool.md +8 -0
- klaude_code/core/tool/file/write_tool.py +146 -0
- klaude_code/core/tool/memory/__init__.py +0 -0
- klaude_code/core/tool/memory/memory_tool.md +16 -0
- klaude_code/core/tool/memory/memory_tool.py +462 -0
- klaude_code/core/tool/memory/skill_loader.py +245 -0
- klaude_code/core/tool/memory/skill_tool.md +24 -0
- klaude_code/core/tool/memory/skill_tool.py +97 -0
- klaude_code/core/tool/shell/__init__.py +0 -0
- klaude_code/core/tool/shell/bash_tool.md +43 -0
- klaude_code/core/tool/shell/bash_tool.py +123 -0
- klaude_code/core/tool/shell/command_safety.py +363 -0
- klaude_code/core/tool/sub_agent_tool.py +83 -0
- klaude_code/core/tool/todo/__init__.py +0 -0
- klaude_code/core/tool/todo/todo_write_tool.md +182 -0
- klaude_code/core/tool/todo/todo_write_tool.py +121 -0
- klaude_code/core/tool/todo/update_plan_tool.md +3 -0
- klaude_code/core/tool/todo/update_plan_tool.py +104 -0
- klaude_code/core/tool/tool_abc.py +25 -0
- klaude_code/core/tool/tool_context.py +106 -0
- klaude_code/core/tool/tool_registry.py +78 -0
- klaude_code/core/tool/tool_runner.py +252 -0
- klaude_code/core/tool/truncation.py +170 -0
- klaude_code/core/tool/web/__init__.py +0 -0
- klaude_code/core/tool/web/mermaid_tool.md +21 -0
- klaude_code/core/tool/web/mermaid_tool.py +76 -0
- klaude_code/core/tool/web/web_fetch_tool.md +8 -0
- klaude_code/core/tool/web/web_fetch_tool.py +159 -0
- klaude_code/core/turn.py +220 -0
- klaude_code/llm/__init__.py +21 -0
- klaude_code/llm/anthropic/__init__.py +3 -0
- klaude_code/llm/anthropic/client.py +221 -0
- klaude_code/llm/anthropic/input.py +200 -0
- klaude_code/llm/client.py +49 -0
- klaude_code/llm/input_common.py +239 -0
- klaude_code/llm/openai_compatible/__init__.py +3 -0
- klaude_code/llm/openai_compatible/client.py +211 -0
- klaude_code/llm/openai_compatible/input.py +109 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +80 -0
- klaude_code/llm/openrouter/__init__.py +3 -0
- klaude_code/llm/openrouter/client.py +200 -0
- klaude_code/llm/openrouter/input.py +160 -0
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +22 -0
- klaude_code/llm/responses/__init__.py +3 -0
- klaude_code/llm/responses/client.py +216 -0
- klaude_code/llm/responses/input.py +167 -0
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/commands.py +21 -0
- klaude_code/protocol/events.py +163 -0
- klaude_code/protocol/llm_param.py +147 -0
- klaude_code/protocol/model.py +287 -0
- klaude_code/protocol/op.py +89 -0
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/protocol/sub_agent.py +348 -0
- klaude_code/protocol/tools.py +15 -0
- klaude_code/session/__init__.py +4 -0
- klaude_code/session/export.py +624 -0
- klaude_code/session/selector.py +76 -0
- klaude_code/session/session.py +474 -0
- klaude_code/session/templates/export_session.html +1434 -0
- klaude_code/trace/__init__.py +3 -0
- klaude_code/trace/log.py +168 -0
- klaude_code/ui/__init__.py +91 -0
- klaude_code/ui/core/__init__.py +1 -0
- klaude_code/ui/core/display.py +103 -0
- klaude_code/ui/core/input.py +71 -0
- klaude_code/ui/core/stage_manager.py +55 -0
- klaude_code/ui/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/modes/debug/display.py +36 -0
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/modes/exec/display.py +63 -0
- klaude_code/ui/modes/repl/__init__.py +51 -0
- klaude_code/ui/modes/repl/clipboard.py +152 -0
- klaude_code/ui/modes/repl/completers.py +429 -0
- klaude_code/ui/modes/repl/display.py +60 -0
- klaude_code/ui/modes/repl/event_handler.py +375 -0
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
- klaude_code/ui/modes/repl/key_bindings.py +170 -0
- klaude_code/ui/modes/repl/renderer.py +281 -0
- klaude_code/ui/renderers/__init__.py +0 -0
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +8 -0
- klaude_code/ui/renderers/developer.py +158 -0
- klaude_code/ui/renderers/diffs.py +215 -0
- klaude_code/ui/renderers/errors.py +16 -0
- klaude_code/ui/renderers/metadata.py +190 -0
- klaude_code/ui/renderers/sub_agent.py +71 -0
- klaude_code/ui/renderers/thinking.py +39 -0
- klaude_code/ui/renderers/tools.py +551 -0
- klaude_code/ui/renderers/user_input.py +65 -0
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/rich/live.py +65 -0
- klaude_code/ui/rich/markdown.py +308 -0
- klaude_code/ui/rich/quote.py +34 -0
- klaude_code/ui/rich/searchable_text.py +71 -0
- klaude_code/ui/rich/status.py +240 -0
- klaude_code/ui/rich/theme.py +274 -0
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/terminal/color.py +244 -0
- klaude_code/ui/terminal/control.py +147 -0
- klaude_code/ui/terminal/notifier.py +107 -0
- klaude_code/ui/terminal/progress_bar.py +87 -0
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/utils/common.py +108 -0
- klaude_code/ui/utils/debouncer.py +42 -0
- klaude_code/version.py +163 -0
- klaude_code-1.2.6.dist-info/METADATA +178 -0
- klaude_code-1.2.6.dist-info/RECORD +167 -0
- klaude_code-1.2.6.dist-info/WHEEL +4 -0
- klaude_code-1.2.6.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from rich.console import RenderableType
|
|
5
|
+
from rich.padding import Padding
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
|
|
8
|
+
from klaude_code import const
|
|
9
|
+
from klaude_code.protocol import events, model
|
|
10
|
+
from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
|
|
11
|
+
from klaude_code.ui.renderers import diffs as r_diffs
|
|
12
|
+
from klaude_code.ui.renderers.common import create_grid
|
|
13
|
+
from klaude_code.ui.rich.theme import ThemeKey
|
|
14
|
+
from klaude_code.ui.utils.common import truncate_display
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_sub_agent_tool(tool_name: str) -> bool:
|
|
18
|
+
return _is_sub_agent_tool(tool_name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def render_path(path: str, style: str, is_directory: bool = False) -> Text:
|
|
22
|
+
if path.startswith(str(Path().cwd())):
|
|
23
|
+
path = path.replace(str(Path().cwd()), "").lstrip("/")
|
|
24
|
+
elif path.startswith(str(Path().home())):
|
|
25
|
+
path = path.replace(str(Path().home()), "~")
|
|
26
|
+
elif not path.startswith("/") and not path.startswith("."):
|
|
27
|
+
path = "./" + path
|
|
28
|
+
if is_directory:
|
|
29
|
+
path = path.rstrip("/") + "/"
|
|
30
|
+
return Text(path, style=style)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_generic_tool_call(tool_name: str, arguments: str, markup: str = "•") -> RenderableType:
|
|
34
|
+
grid = create_grid()
|
|
35
|
+
|
|
36
|
+
tool_name_column = Text.assemble((markup, ThemeKey.TOOL_MARK), " ", (tool_name, ThemeKey.TOOL_NAME))
|
|
37
|
+
arguments_column = Text("")
|
|
38
|
+
if not arguments:
|
|
39
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
40
|
+
return grid
|
|
41
|
+
try:
|
|
42
|
+
json_dict = json.loads(arguments)
|
|
43
|
+
if len(json_dict) == 0:
|
|
44
|
+
arguments_column = Text("", ThemeKey.TOOL_PARAM)
|
|
45
|
+
elif len(json_dict) == 1:
|
|
46
|
+
arguments_column = Text(str(next(iter(json_dict.values()))), ThemeKey.TOOL_PARAM)
|
|
47
|
+
else:
|
|
48
|
+
arguments_column = Text(
|
|
49
|
+
", ".join([f"{k}: {v}" for k, v in json_dict.items()]),
|
|
50
|
+
ThemeKey.TOOL_PARAM,
|
|
51
|
+
)
|
|
52
|
+
except json.JSONDecodeError:
|
|
53
|
+
arguments_column = Text(
|
|
54
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
55
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
56
|
+
)
|
|
57
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
58
|
+
return grid
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def render_update_plan_tool_call(arguments: str) -> RenderableType:
|
|
62
|
+
grid = create_grid()
|
|
63
|
+
tool_name_column = Text.assemble(("◎", ThemeKey.TOOL_MARK), " ", ("Update Plan", ThemeKey.TOOL_NAME))
|
|
64
|
+
explanation_column = Text("")
|
|
65
|
+
|
|
66
|
+
if arguments:
|
|
67
|
+
try:
|
|
68
|
+
payload = json.loads(arguments)
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
explanation_column = Text(
|
|
71
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
72
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
explanation = payload.get("explanation")
|
|
76
|
+
if isinstance(explanation, str) and explanation.strip():
|
|
77
|
+
explanation_column = Text(explanation.strip(), style=ThemeKey.TODO_EXPLANATION)
|
|
78
|
+
|
|
79
|
+
grid.add_row(tool_name_column, explanation_column)
|
|
80
|
+
return grid
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_read_tool_call(arguments: str) -> RenderableType:
|
|
84
|
+
grid = create_grid()
|
|
85
|
+
render_result: Text = Text.assemble(("Read", ThemeKey.TOOL_NAME), " ")
|
|
86
|
+
try:
|
|
87
|
+
json_dict = json.loads(arguments)
|
|
88
|
+
file_path = json_dict.get("file_path")
|
|
89
|
+
limit = json_dict.get("limit", None)
|
|
90
|
+
offset = json_dict.get("offset", None)
|
|
91
|
+
render_result = render_result.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
92
|
+
if limit is not None and offset is not None:
|
|
93
|
+
render_result = (
|
|
94
|
+
render_result.append_text(Text(" "))
|
|
95
|
+
.append_text(Text(str(offset), ThemeKey.TOOL_PARAM_BOLD))
|
|
96
|
+
.append_text(Text(":", ThemeKey.TOOL_PARAM))
|
|
97
|
+
.append_text(Text(str(offset + limit - 1), ThemeKey.TOOL_PARAM_BOLD))
|
|
98
|
+
)
|
|
99
|
+
elif limit is not None:
|
|
100
|
+
render_result = (
|
|
101
|
+
render_result.append_text(Text(" "))
|
|
102
|
+
.append_text(Text("1", ThemeKey.TOOL_PARAM_BOLD))
|
|
103
|
+
.append_text(Text(":", ThemeKey.TOOL_PARAM))
|
|
104
|
+
.append_text(Text(str(limit), ThemeKey.TOOL_PARAM_BOLD))
|
|
105
|
+
)
|
|
106
|
+
elif offset is not None:
|
|
107
|
+
render_result = (
|
|
108
|
+
render_result.append_text(Text(" "))
|
|
109
|
+
.append_text(Text(str(offset), ThemeKey.TOOL_PARAM_BOLD))
|
|
110
|
+
.append_text(Text(":", ThemeKey.TOOL_PARAM))
|
|
111
|
+
.append_text(Text("-", ThemeKey.TOOL_PARAM_BOLD))
|
|
112
|
+
)
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
render_result = render_result.append_text(
|
|
115
|
+
Text(
|
|
116
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
117
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
grid.add_row(Text("←", ThemeKey.TOOL_MARK), render_result)
|
|
121
|
+
return grid
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def render_edit_tool_call(arguments: str) -> Text:
|
|
125
|
+
render_result: Text = Text.assemble(("→ ", ThemeKey.TOOL_MARK))
|
|
126
|
+
try:
|
|
127
|
+
json_dict = json.loads(arguments)
|
|
128
|
+
file_path = json_dict.get("file_path")
|
|
129
|
+
render_result = (
|
|
130
|
+
render_result.append_text(Text("Edit", ThemeKey.TOOL_NAME))
|
|
131
|
+
.append_text(Text(" "))
|
|
132
|
+
.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
133
|
+
)
|
|
134
|
+
except json.JSONDecodeError:
|
|
135
|
+
render_result = (
|
|
136
|
+
render_result.append_text(Text("Edit", ThemeKey.TOOL_NAME))
|
|
137
|
+
.append_text(Text(" "))
|
|
138
|
+
.append_text(
|
|
139
|
+
Text(
|
|
140
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
141
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
return render_result
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def render_write_tool_call(arguments: str) -> Text:
|
|
149
|
+
render_result: Text = Text.assemble(("→ ", ThemeKey.TOOL_MARK))
|
|
150
|
+
try:
|
|
151
|
+
json_dict = json.loads(arguments)
|
|
152
|
+
file_path = json_dict.get("file_path")
|
|
153
|
+
op_label = "Create"
|
|
154
|
+
if isinstance(file_path, str):
|
|
155
|
+
abs_path = Path(file_path)
|
|
156
|
+
if not abs_path.is_absolute():
|
|
157
|
+
abs_path = (Path().cwd() / abs_path).resolve()
|
|
158
|
+
if abs_path.exists():
|
|
159
|
+
op_label = "Overwrite"
|
|
160
|
+
render_result = (
|
|
161
|
+
render_result.append_text(Text(op_label, ThemeKey.TOOL_NAME))
|
|
162
|
+
.append_text(Text(" "))
|
|
163
|
+
.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
164
|
+
)
|
|
165
|
+
except json.JSONDecodeError:
|
|
166
|
+
render_result = (
|
|
167
|
+
render_result.append_text(Text("Write", ThemeKey.TOOL_NAME))
|
|
168
|
+
.append_text(Text(" "))
|
|
169
|
+
.append_text(
|
|
170
|
+
Text(
|
|
171
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
172
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
return render_result
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def render_multi_edit_tool_call(arguments: str) -> Text:
|
|
180
|
+
render_result: Text = Text.assemble(("→ ", ThemeKey.TOOL_MARK), ("MultiEdit", ThemeKey.TOOL_NAME), " ")
|
|
181
|
+
try:
|
|
182
|
+
json_dict = json.loads(arguments)
|
|
183
|
+
file_path = json_dict.get("file_path")
|
|
184
|
+
edits = json_dict.get("edits", [])
|
|
185
|
+
render_result = (
|
|
186
|
+
render_result.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
187
|
+
.append_text(Text(" - "))
|
|
188
|
+
.append_text(Text(f"{len(edits)}", ThemeKey.TOOL_PARAM_BOLD))
|
|
189
|
+
.append_text(Text(" updates", ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
190
|
+
)
|
|
191
|
+
except json.JSONDecodeError:
|
|
192
|
+
render_result = render_result.append_text(
|
|
193
|
+
Text(
|
|
194
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
195
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
return render_result
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def render_apply_patch_tool_call(arguments: str) -> RenderableType:
|
|
202
|
+
try:
|
|
203
|
+
payload = json.loads(arguments)
|
|
204
|
+
except json.JSONDecodeError:
|
|
205
|
+
return Text.assemble(
|
|
206
|
+
("→ ", ThemeKey.TOOL_MARK),
|
|
207
|
+
("Apply Patch", ThemeKey.TOOL_NAME),
|
|
208
|
+
" ",
|
|
209
|
+
Text(
|
|
210
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
211
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
patch_content = payload.get("patch", "")
|
|
216
|
+
|
|
217
|
+
grid = create_grid()
|
|
218
|
+
header = Text.assemble(("→ ", ThemeKey.TOOL_MARK), ("Apply Patch", ThemeKey.TOOL_NAME))
|
|
219
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
220
|
+
|
|
221
|
+
if isinstance(patch_content, str):
|
|
222
|
+
lines = [line for line in patch_content.splitlines() if line and not line.startswith("*** Begin Patch")]
|
|
223
|
+
if lines:
|
|
224
|
+
summary = Text(lines[0][: const.INVALID_TOOL_CALL_MAX_LENGTH], ThemeKey.TOOL_PARAM)
|
|
225
|
+
else:
|
|
226
|
+
summary = Text(
|
|
227
|
+
str(patch_content)[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
228
|
+
ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if summary.plain:
|
|
232
|
+
grid.add_row(header, summary)
|
|
233
|
+
else:
|
|
234
|
+
grid.add_row(header, Text("", ThemeKey.TOOL_PARAM))
|
|
235
|
+
|
|
236
|
+
return grid
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
240
|
+
if tr.ui_extra is None:
|
|
241
|
+
return Text.assemble(
|
|
242
|
+
(" ✘", ThemeKey.ERROR_BOLD),
|
|
243
|
+
" ",
|
|
244
|
+
Text("(no content)", style=ThemeKey.ERROR),
|
|
245
|
+
)
|
|
246
|
+
if tr.ui_extra.type != model.ToolResultUIExtraType.TODO_LIST or tr.ui_extra.todo_list is None:
|
|
247
|
+
return Text.assemble(
|
|
248
|
+
(" ✘", ThemeKey.ERROR_BOLD),
|
|
249
|
+
" ",
|
|
250
|
+
Text("(invalid ui_extra)", style=ThemeKey.ERROR),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
ui_extra = tr.ui_extra.todo_list
|
|
254
|
+
todo_grid = create_grid()
|
|
255
|
+
for todo in ui_extra.todos:
|
|
256
|
+
is_new_completed = todo.content in ui_extra.new_completed
|
|
257
|
+
match todo.status:
|
|
258
|
+
case "pending":
|
|
259
|
+
mark = "▢"
|
|
260
|
+
mark_style = ThemeKey.TODO_PENDING_MARK
|
|
261
|
+
text_style = ThemeKey.TODO_PENDING
|
|
262
|
+
case "in_progress":
|
|
263
|
+
mark = "◉"
|
|
264
|
+
mark_style = ThemeKey.TODO_IN_PROGRESS_MARK
|
|
265
|
+
text_style = ThemeKey.TODO_IN_PROGRESS
|
|
266
|
+
case "completed":
|
|
267
|
+
mark = "✔"
|
|
268
|
+
mark_style = ThemeKey.TODO_NEW_COMPLETED_MARK if is_new_completed else ThemeKey.TODO_COMPLETED_MARK
|
|
269
|
+
text_style = ThemeKey.TODO_NEW_COMPLETED if is_new_completed else ThemeKey.TODO_COMPLETED
|
|
270
|
+
text = Text(todo.content)
|
|
271
|
+
text.stylize(text_style)
|
|
272
|
+
todo_grid.add_row(Text(mark, style=mark_style), text)
|
|
273
|
+
|
|
274
|
+
return Padding.indent(todo_grid, level=2)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def render_generic_tool_result(result: str, *, is_error: bool = False) -> RenderableType:
|
|
278
|
+
"""Render a generic tool result as indented, truncated text."""
|
|
279
|
+
style = ThemeKey.ERROR if is_error else ThemeKey.TOOL_RESULT
|
|
280
|
+
return Padding.indent(Text(truncate_display(result), style=style), level=2)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _extract_mermaid_link(
|
|
284
|
+
ui_extra: model.ToolResultUIExtra | None,
|
|
285
|
+
) -> model.MermaidLinkUIExtra | None:
|
|
286
|
+
if ui_extra is None:
|
|
287
|
+
return None
|
|
288
|
+
if ui_extra.type != model.ToolResultUIExtraType.MERMAID_LINK:
|
|
289
|
+
return None
|
|
290
|
+
return ui_extra.mermaid_link
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def render_memory_tool_call(arguments: str) -> RenderableType:
|
|
294
|
+
grid = create_grid()
|
|
295
|
+
command_display_names: dict[str, str] = {
|
|
296
|
+
"view": "View",
|
|
297
|
+
"create": "Create",
|
|
298
|
+
"str_replace": "Replace",
|
|
299
|
+
"insert": "Insert",
|
|
300
|
+
"delete": "Delete",
|
|
301
|
+
"rename": "Rename",
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
payload: dict[str, str] = json.loads(arguments)
|
|
306
|
+
except json.JSONDecodeError:
|
|
307
|
+
tool_name_column = Text.assemble(("★", ThemeKey.TOOL_MARK), " ", ("Memory", ThemeKey.TOOL_NAME))
|
|
308
|
+
summary = Text(
|
|
309
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
310
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
311
|
+
)
|
|
312
|
+
grid.add_row(tool_name_column, summary)
|
|
313
|
+
return grid
|
|
314
|
+
|
|
315
|
+
command = payload.get("command", "")
|
|
316
|
+
display_name = command_display_names.get(command, command.title())
|
|
317
|
+
tool_name_column = Text.assemble(("★", ThemeKey.TOOL_MARK), " ", (f"{display_name} Memory", ThemeKey.TOOL_NAME))
|
|
318
|
+
|
|
319
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
320
|
+
path = payload.get("path")
|
|
321
|
+
old_path = payload.get("old_path")
|
|
322
|
+
new_path = payload.get("new_path")
|
|
323
|
+
|
|
324
|
+
if command == "rename" and old_path and new_path:
|
|
325
|
+
summary = Text.assemble(
|
|
326
|
+
Text(old_path, ThemeKey.TOOL_PARAM_FILE_PATH),
|
|
327
|
+
Text(" -> ", ThemeKey.TOOL_PARAM),
|
|
328
|
+
Text(new_path, ThemeKey.TOOL_PARAM_FILE_PATH),
|
|
329
|
+
)
|
|
330
|
+
elif command == "insert" and path:
|
|
331
|
+
insert_line = payload.get("insert_line")
|
|
332
|
+
summary = Text(path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
333
|
+
if insert_line is not None:
|
|
334
|
+
summary.append(f" line {insert_line}", ThemeKey.TOOL_PARAM)
|
|
335
|
+
elif command == "view" and path:
|
|
336
|
+
view_range = payload.get("view_range")
|
|
337
|
+
summary = Text(path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
338
|
+
if view_range and isinstance(view_range, list) and len(view_range) >= 2:
|
|
339
|
+
summary.append(f" {view_range[0]}:{view_range[1]}", ThemeKey.TOOL_PARAM)
|
|
340
|
+
elif path:
|
|
341
|
+
summary = Text(path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
342
|
+
|
|
343
|
+
grid.add_row(tool_name_column, summary)
|
|
344
|
+
return grid
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def render_mermaid_tool_call(arguments: str) -> RenderableType:
|
|
348
|
+
grid = create_grid()
|
|
349
|
+
tool_name_column = Text.assemble(("⧉", ThemeKey.TOOL_MARK), " ", ("Mermaid", ThemeKey.TOOL_NAME))
|
|
350
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
payload: dict[str, str] = json.loads(arguments)
|
|
354
|
+
except json.JSONDecodeError:
|
|
355
|
+
summary = Text(
|
|
356
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
357
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
code = payload.get("code", "")
|
|
361
|
+
if code:
|
|
362
|
+
line_count = len(code.splitlines())
|
|
363
|
+
summary = Text(f"{line_count} lines", ThemeKey.TOOL_PARAM)
|
|
364
|
+
else:
|
|
365
|
+
summary = Text("0 lines", ThemeKey.TOOL_PARAM)
|
|
366
|
+
|
|
367
|
+
grid.add_row(tool_name_column, summary)
|
|
368
|
+
return grid
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def render_mermaid_tool_result(tr: events.ToolResultEvent) -> RenderableType:
|
|
372
|
+
link_info = _extract_mermaid_link(tr.ui_extra)
|
|
373
|
+
if link_info is None:
|
|
374
|
+
return render_generic_tool_result(tr.result, is_error=tr.status == "error")
|
|
375
|
+
|
|
376
|
+
link_text = Text.from_markup(f"[blue u][link={link_info.link}]Command+click to view[/link][/blue u]")
|
|
377
|
+
return Padding.indent(link_text, level=2)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _extract_truncation(
|
|
381
|
+
ui_extra: model.ToolResultUIExtra | None,
|
|
382
|
+
) -> model.TruncationUIExtra | None:
|
|
383
|
+
if ui_extra is None:
|
|
384
|
+
return None
|
|
385
|
+
if ui_extra.type != model.ToolResultUIExtraType.TRUNCATION:
|
|
386
|
+
return None
|
|
387
|
+
return ui_extra.truncation
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
|
|
391
|
+
"""Render truncation info for the user."""
|
|
392
|
+
original_kb = ui_extra.original_length / 1024
|
|
393
|
+
truncated_kb = ui_extra.truncated_length / 1024
|
|
394
|
+
text = Text.assemble(
|
|
395
|
+
("Output truncated: ", ThemeKey.TOOL_RESULT),
|
|
396
|
+
(f"{original_kb:.1f}KB", ThemeKey.TOOL_RESULT),
|
|
397
|
+
(" total, ", ThemeKey.TOOL_RESULT),
|
|
398
|
+
(f"{truncated_kb:.1f}KB", ThemeKey.TOOL_RESULT_BOLD),
|
|
399
|
+
(" hidden\nFull output saved to ", ThemeKey.TOOL_RESULT),
|
|
400
|
+
(ui_extra.saved_file_path, ThemeKey.TOOL_RESULT),
|
|
401
|
+
("\nUse Read with limit+offset or rg/grep to inspect", ThemeKey.TOOL_RESULT),
|
|
402
|
+
)
|
|
403
|
+
return Padding.indent(text, level=2)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra | None:
|
|
407
|
+
"""Extract truncation info from a tool result event."""
|
|
408
|
+
return _extract_truncation(tr.ui_extra)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# Tool name to mark mapping
|
|
412
|
+
_TOOL_MARKS: dict[str, str] = {
|
|
413
|
+
"Read": "←",
|
|
414
|
+
"Edit": "→",
|
|
415
|
+
"Write": "→",
|
|
416
|
+
"MultiEdit": "→",
|
|
417
|
+
"Bash": ">",
|
|
418
|
+
"apply_patch": "→",
|
|
419
|
+
"TodoWrite": "◎",
|
|
420
|
+
"update_plan": "◎",
|
|
421
|
+
"Mermaid": "⧉",
|
|
422
|
+
"Memory": "★",
|
|
423
|
+
"Skill": "◈",
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
# Tool name to active form mapping (for spinner status)
|
|
427
|
+
_TOOL_ACTIVE_FORM: dict[str, str] = {
|
|
428
|
+
"Bash": "Bashing",
|
|
429
|
+
"apply_patch": "Patching",
|
|
430
|
+
"Edit": "Editing",
|
|
431
|
+
"MultiEdit": "Editing",
|
|
432
|
+
"Read": "Reading",
|
|
433
|
+
"Write": "Writing",
|
|
434
|
+
"TodoWrite": "Planning",
|
|
435
|
+
"update_plan": "Planning",
|
|
436
|
+
"Skill": "Skilling",
|
|
437
|
+
"Mermaid": "Diagramming",
|
|
438
|
+
"Memory": "Memorizing",
|
|
439
|
+
"WebFetch": "Fetching",
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def get_tool_active_form(tool_name: str) -> str:
|
|
444
|
+
"""Get the active form of a tool name for spinner status.
|
|
445
|
+
|
|
446
|
+
Checks both the static mapping and sub agent profiles.
|
|
447
|
+
"""
|
|
448
|
+
if tool_name in _TOOL_ACTIVE_FORM:
|
|
449
|
+
return _TOOL_ACTIVE_FORM[tool_name]
|
|
450
|
+
|
|
451
|
+
# Check sub agent profiles
|
|
452
|
+
from klaude_code.protocol.sub_agent import get_sub_agent_profile_by_tool
|
|
453
|
+
|
|
454
|
+
profile = get_sub_agent_profile_by_tool(tool_name)
|
|
455
|
+
if profile and profile.active_form:
|
|
456
|
+
return profile.active_form
|
|
457
|
+
|
|
458
|
+
return f"Calling {tool_name}"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
|
|
462
|
+
"""Unified entry point for rendering tool calls.
|
|
463
|
+
|
|
464
|
+
Returns a Rich Renderable or None if the tool call should not be rendered.
|
|
465
|
+
"""
|
|
466
|
+
from klaude_code.protocol import tools
|
|
467
|
+
|
|
468
|
+
if is_sub_agent_tool(e.tool_name):
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
match e.tool_name:
|
|
472
|
+
case tools.READ:
|
|
473
|
+
return render_read_tool_call(e.arguments)
|
|
474
|
+
case tools.EDIT:
|
|
475
|
+
return render_edit_tool_call(e.arguments)
|
|
476
|
+
case tools.WRITE:
|
|
477
|
+
return render_write_tool_call(e.arguments)
|
|
478
|
+
case tools.MULTI_EDIT:
|
|
479
|
+
return render_multi_edit_tool_call(e.arguments)
|
|
480
|
+
case tools.BASH:
|
|
481
|
+
return render_generic_tool_call(e.tool_name, e.arguments, ">")
|
|
482
|
+
case tools.APPLY_PATCH:
|
|
483
|
+
return render_apply_patch_tool_call(e.arguments)
|
|
484
|
+
case tools.TODO_WRITE:
|
|
485
|
+
return render_generic_tool_call("Update Todos", "", "◎")
|
|
486
|
+
case tools.UPDATE_PLAN:
|
|
487
|
+
return render_update_plan_tool_call(e.arguments)
|
|
488
|
+
case tools.MERMAID:
|
|
489
|
+
return render_mermaid_tool_call(e.arguments)
|
|
490
|
+
case tools.MEMORY:
|
|
491
|
+
return render_memory_tool_call(e.arguments)
|
|
492
|
+
case tools.SKILL:
|
|
493
|
+
return render_generic_tool_call(e.tool_name, e.arguments, "◈")
|
|
494
|
+
case _:
|
|
495
|
+
return render_generic_tool_call(e.tool_name, e.arguments)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _extract_diff_text(ui_extra: model.ToolResultUIExtra | None) -> str | None:
|
|
499
|
+
if ui_extra is None:
|
|
500
|
+
return None
|
|
501
|
+
if ui_extra.type == model.ToolResultUIExtraType.DIFF_TEXT:
|
|
502
|
+
return ui_extra.diff_text
|
|
503
|
+
return None
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
|
|
507
|
+
"""Unified entry point for rendering tool results.
|
|
508
|
+
|
|
509
|
+
Returns a Rich Renderable or None if the tool result should not be rendered.
|
|
510
|
+
"""
|
|
511
|
+
from klaude_code.protocol import tools
|
|
512
|
+
from klaude_code.ui.renderers import errors as r_errors
|
|
513
|
+
|
|
514
|
+
if is_sub_agent_tool(e.tool_name):
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
# Handle error case
|
|
518
|
+
if e.status == "error" and e.ui_extra is None:
|
|
519
|
+
error_msg = Text(truncate_display(e.result))
|
|
520
|
+
return r_errors.render_error(error_msg)
|
|
521
|
+
|
|
522
|
+
# Show truncation info if output was truncated and saved to file
|
|
523
|
+
truncation_info = get_truncation_info(e)
|
|
524
|
+
if truncation_info:
|
|
525
|
+
return render_truncation_info(truncation_info)
|
|
526
|
+
|
|
527
|
+
diff_text = _extract_diff_text(e.ui_extra)
|
|
528
|
+
|
|
529
|
+
match e.tool_name:
|
|
530
|
+
case tools.READ:
|
|
531
|
+
return None
|
|
532
|
+
case tools.EDIT | tools.MULTI_EDIT | tools.WRITE:
|
|
533
|
+
return Padding.indent(r_diffs.render_diff(diff_text or ""), level=2)
|
|
534
|
+
case tools.MEMORY:
|
|
535
|
+
if diff_text:
|
|
536
|
+
return Padding.indent(r_diffs.render_diff(diff_text), level=2)
|
|
537
|
+
elif len(e.result.strip()) > 0:
|
|
538
|
+
return render_generic_tool_result(e.result)
|
|
539
|
+
return None
|
|
540
|
+
case tools.TODO_WRITE | tools.UPDATE_PLAN:
|
|
541
|
+
return render_todo(e)
|
|
542
|
+
case tools.MERMAID:
|
|
543
|
+
return render_mermaid_tool_result(e)
|
|
544
|
+
case _:
|
|
545
|
+
if e.tool_name in (tools.BASH, tools.APPLY_PATCH) and e.result.startswith("diff --git"):
|
|
546
|
+
return r_diffs.render_diff_panel(e.result, show_file_name=True)
|
|
547
|
+
if e.tool_name == tools.APPLY_PATCH and diff_text:
|
|
548
|
+
return Padding.indent(r_diffs.render_diff(diff_text, show_file_name=True), level=2)
|
|
549
|
+
if len(e.result.strip()) == 0:
|
|
550
|
+
return render_generic_tool_result("(no content)")
|
|
551
|
+
return render_generic_tool_result(e.result)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from rich.console import Group, RenderableType
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
|
|
6
|
+
from klaude_code.command import is_slash_command_name
|
|
7
|
+
from klaude_code.ui.renderers.common import create_grid
|
|
8
|
+
from klaude_code.ui.rich.theme import ThemeKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_at_pattern(
|
|
12
|
+
text: str,
|
|
13
|
+
at_style: str = ThemeKey.USER_INPUT_AT_PATTERN,
|
|
14
|
+
other_style: str = ThemeKey.USER_INPUT,
|
|
15
|
+
) -> Text:
|
|
16
|
+
if "@" in text:
|
|
17
|
+
parts = re.split(r"(\s+)", text)
|
|
18
|
+
result = Text("")
|
|
19
|
+
for s in parts:
|
|
20
|
+
if s.startswith("@"):
|
|
21
|
+
result.append_text(Text(s, at_style))
|
|
22
|
+
else:
|
|
23
|
+
result.append_text(Text(s, other_style))
|
|
24
|
+
return result
|
|
25
|
+
return Text(text, style=other_style)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def render_user_input(content: str) -> RenderableType:
|
|
29
|
+
"""Render a user message as a group of quoted lines with styles.
|
|
30
|
+
|
|
31
|
+
- Highlights slash command on the first line if recognized
|
|
32
|
+
- Highlights @file patterns in all lines
|
|
33
|
+
"""
|
|
34
|
+
lines = content.strip().split("\n")
|
|
35
|
+
renderables: list[RenderableType] = []
|
|
36
|
+
has_command = False
|
|
37
|
+
for i, line in enumerate(lines):
|
|
38
|
+
line_text = render_at_pattern(line)
|
|
39
|
+
|
|
40
|
+
if i == 0 and line.startswith("/"):
|
|
41
|
+
splits = line.split(" ", maxsplit=1)
|
|
42
|
+
if is_slash_command_name(splits[0][1:]):
|
|
43
|
+
has_command = True
|
|
44
|
+
line_text = Text.assemble(
|
|
45
|
+
(f"{splits[0]}", ThemeKey.USER_INPUT_SLASH_COMMAND),
|
|
46
|
+
" ",
|
|
47
|
+
render_at_pattern(splits[1]) if len(splits) > 1 else Text(""),
|
|
48
|
+
)
|
|
49
|
+
renderables.append(line_text)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
renderables.append(line_text)
|
|
53
|
+
grid = create_grid()
|
|
54
|
+
grid.padding = (0, 0)
|
|
55
|
+
mark = (
|
|
56
|
+
Text("❯ ", style=ThemeKey.USER_INPUT_PROMPT)
|
|
57
|
+
if not has_command
|
|
58
|
+
else Text(" ", style=ThemeKey.USER_INPUT_SLASH_COMMAND)
|
|
59
|
+
)
|
|
60
|
+
grid.add_row(mark, Group(*renderables))
|
|
61
|
+
return grid
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def render_interrupt() -> RenderableType:
|
|
65
|
+
return Text(" INTERRUPTED \n", style=ThemeKey.INTERRUPT)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Rich rendering utilities
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from rich._loop import loop_last
|
|
6
|
+
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from rich.segment import Segment
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CropAbove:
|
|
12
|
+
def __init__(self, renderable: RenderableType, style: str = "") -> None:
|
|
13
|
+
self.renderable = renderable
|
|
14
|
+
self.style = style
|
|
15
|
+
|
|
16
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
17
|
+
style = console.get_style(self.style) if self.style else None
|
|
18
|
+
lines = console.render_lines(self.renderable, options, style=style, pad=False)
|
|
19
|
+
max_height = options.size.height
|
|
20
|
+
if len(lines) > max_height:
|
|
21
|
+
lines = lines[-max_height:]
|
|
22
|
+
|
|
23
|
+
new_line = Segment.line()
|
|
24
|
+
for last, line in loop_last(lines):
|
|
25
|
+
yield from line
|
|
26
|
+
if not last:
|
|
27
|
+
yield new_line
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CropAboveLive(Live):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
renderable: RenderableType | None = None,
|
|
34
|
+
*,
|
|
35
|
+
console: Console | None = None,
|
|
36
|
+
refresh_per_second: float = 4,
|
|
37
|
+
transient: bool = False,
|
|
38
|
+
get_renderable: Any | None = None,
|
|
39
|
+
style: str = "",
|
|
40
|
+
**kwargs: Any,
|
|
41
|
+
) -> None:
|
|
42
|
+
self._crop_style: str = style
|
|
43
|
+
|
|
44
|
+
if get_renderable is not None:
|
|
45
|
+
|
|
46
|
+
def _wrapped_get() -> RenderableType:
|
|
47
|
+
assert get_renderable is not None
|
|
48
|
+
return CropAbove(get_renderable(), style=self._crop_style)
|
|
49
|
+
|
|
50
|
+
get_renderable = _wrapped_get
|
|
51
|
+
|
|
52
|
+
if renderable is not None:
|
|
53
|
+
renderable = CropAbove(renderable, style=self._crop_style)
|
|
54
|
+
|
|
55
|
+
super().__init__(
|
|
56
|
+
renderable,
|
|
57
|
+
console=console,
|
|
58
|
+
refresh_per_second=refresh_per_second,
|
|
59
|
+
transient=transient,
|
|
60
|
+
get_renderable=get_renderable,
|
|
61
|
+
**kwargs,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def update(self, renderable: RenderableType, refresh: bool = True) -> None: # type: ignore[override]
|
|
65
|
+
super().update(CropAbove(renderable, style=self._crop_style), refresh=refresh)
|