code-puppy 0.0.214__py3-none-any.whl → 0.0.366__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.
- code_puppy/__init__.py +7 -1
- code_puppy/agents/__init__.py +2 -0
- code_puppy/agents/agent_c_reviewer.py +59 -6
- code_puppy/agents/agent_code_puppy.py +7 -1
- code_puppy/agents/agent_code_reviewer.py +12 -2
- code_puppy/agents/agent_cpp_reviewer.py +73 -6
- code_puppy/agents/agent_creator_agent.py +45 -4
- code_puppy/agents/agent_golang_reviewer.py +92 -3
- code_puppy/agents/agent_javascript_reviewer.py +101 -8
- code_puppy/agents/agent_manager.py +81 -4
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_planning.py +163 -0
- code_puppy/agents/agent_python_programmer.py +165 -0
- code_puppy/agents/agent_python_reviewer.py +28 -6
- code_puppy/agents/agent_qa_expert.py +98 -6
- code_puppy/agents/agent_qa_kitten.py +12 -7
- code_puppy/agents/agent_security_auditor.py +113 -3
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +106 -7
- code_puppy/agents/base_agent.py +802 -176
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +142 -4
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/claude_cache_client.py +586 -0
- code_puppy/cli_runner.py +916 -0
- code_puppy/command_line/add_model_menu.py +1079 -0
- code_puppy/command_line/agent_menu.py +395 -0
- code_puppy/command_line/attachments.py +10 -5
- code_puppy/command_line/autosave_menu.py +605 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +176 -738
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +715 -0
- code_puppy/command_line/core_commands.py +792 -0
- code_puppy/command_line/diff_menu.py +863 -0
- code_puppy/command_line/load_context_completion.py +15 -22
- code_puppy/command_line/mcp/base.py +0 -3
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +9 -4
- code_puppy/command_line/mcp/help_command.py +6 -5
- code_puppy/command_line/mcp/install_command.py +15 -26
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +2 -2
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +12 -4
- code_puppy/command_line/mcp/search_command.py +16 -10
- code_puppy/command_line/mcp/start_all_command.py +18 -6
- code_puppy/command_line/mcp/start_command.py +47 -25
- code_puppy/command_line/mcp/status_command.py +4 -5
- code_puppy/command_line/mcp/stop_all_command.py +7 -1
- code_puppy/command_line/mcp/stop_command.py +8 -4
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/wizard_utils.py +20 -16
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +75 -25
- code_puppy/command_line/model_settings_menu.py +884 -0
- code_puppy/command_line/motd.py +14 -8
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +463 -63
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +898 -112
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +602 -0
- code_puppy/http_utils.py +210 -148
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -698
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/async_lifecycle.py +35 -4
- code_puppy/mcp_/blocking_startup.py +70 -43
- code_puppy/mcp_/captured_stdio_server.py +2 -2
- code_puppy/mcp_/config_wizard.py +4 -4
- code_puppy/mcp_/dashboard.py +15 -6
- code_puppy/mcp_/managed_server.py +65 -38
- code_puppy/mcp_/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +6 -6
- code_puppy/mcp_/server_registry_catalog.py +24 -5
- code_puppy/messaging/__init__.py +199 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +17 -48
- code_puppy/messaging/messages.py +500 -0
- code_puppy/messaging/queue_console.py +1 -24
- code_puppy/messaging/renderers.py +43 -146
- code_puppy/messaging/rich_renderer.py +1027 -0
- code_puppy/messaging/spinner/__init__.py +21 -5
- code_puppy/messaging/spinner/console_spinner.py +86 -51
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +634 -83
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +66 -68
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +164 -10
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +767 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
- code_puppy/plugins/claude_code_oauth/config.py +50 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/utils.py +518 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/round_robin_model.py +9 -12
- code_puppy/session_storage.py +2 -1
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +41 -13
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +37 -1
- code_puppy/tools/agent_tools.py +536 -52
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +19 -23
- code_puppy/tools/browser/browser_interactions.py +41 -48
- code_puppy/tools/browser/browser_locators.py +36 -38
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +16 -16
- code_puppy/tools/browser/browser_screenshot.py +79 -143
- code_puppy/tools/browser/browser_scripts.py +32 -42
- code_puppy/tools/browser/browser_workflows.py +44 -27
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +930 -147
- code_puppy/tools/common.py +1113 -5
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +226 -154
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +30 -11
- code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
- code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/messaging/spinner/textual_spinner.py +0 -106
- code_puppy/tools/browser/camoufox_manager.py +0 -216
- code_puppy/tools/browser/vqa_agent.py +0 -70
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -1105
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -551
- code_puppy/tui/components/command_history_modal.py +0 -218
- code_puppy/tui/components/copy_button.py +0 -139
- code_puppy/tui/components/custom_widgets.py +0 -63
- code_puppy/tui/components/human_input_modal.py +0 -175
- code_puppy/tui/components/input_area.py +0 -167
- code_puppy/tui/components/sidebar.py +0 -309
- code_puppy/tui/components/status_bar.py +0 -185
- code_puppy/tui/messages.py +0 -27
- code_puppy/tui/models/__init__.py +0 -8
- code_puppy/tui/models/chat_message.py +0 -25
- code_puppy/tui/models/command_history.py +0 -89
- code_puppy/tui/models/enums.py +0 -24
- code_puppy/tui/screens/__init__.py +0 -17
- code_puppy/tui/screens/autosave_picker.py +0 -175
- code_puppy/tui/screens/help.py +0 -130
- code_puppy/tui/screens/mcp_install_wizard.py +0 -803
- code_puppy/tui/screens/settings.py +0 -306
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy/tui_state.py +0 -55
- code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
- code_puppy-0.0.214.dist-info/RECORD +0 -131
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
"""Interactive nested menu for diff configuration.
|
|
2
|
+
|
|
3
|
+
Now using the fixed arrow_select_async with proper HTML escaping.
|
|
4
|
+
Supports cycling through all supported languages with left/right arrows!
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import io
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from typing import Callable, Optional
|
|
11
|
+
|
|
12
|
+
from prompt_toolkit import Application
|
|
13
|
+
from prompt_toolkit.formatted_text import ANSI, FormattedText
|
|
14
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
15
|
+
from prompt_toolkit.layout import Layout, VSplit, Window
|
|
16
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
17
|
+
from prompt_toolkit.widgets import Frame
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
# Sample code snippets for each language
|
|
21
|
+
LANGUAGE_SAMPLES = {
|
|
22
|
+
"python": (
|
|
23
|
+
"calculator.py",
|
|
24
|
+
"""--- a/calculator.py
|
|
25
|
+
+++ b/calculator.py
|
|
26
|
+
@@ -1,12 +1,15 @@
|
|
27
|
+
def calculate_total(items, tax_rate=0.08):
|
|
28
|
+
+ # Calculate total price
|
|
29
|
+
+ total = 0
|
|
30
|
+
+ for item in items:
|
|
31
|
+
+ total += item['price']
|
|
32
|
+
- # Calculate subtotal with discount
|
|
33
|
+
- subtotal = sum(item['price'] * item.get('quantity', 1) for item in items)
|
|
34
|
+
- discount = subtotal * 0.1 if subtotal > 100 else 0
|
|
35
|
+
|
|
36
|
+
+ # Add tax
|
|
37
|
+
+ tax = total * tax_rate
|
|
38
|
+
+ final_total = total + tax
|
|
39
|
+
- # Apply tax to discounted amount
|
|
40
|
+
- taxable_amount = subtotal - discount
|
|
41
|
+
- tax = round(taxable_amount * tax_rate, 2)
|
|
42
|
+
- final_total = taxable_amount + tax
|
|
43
|
+
|
|
44
|
+
+ return final_total
|
|
45
|
+
- return {
|
|
46
|
+
- 'subtotal': subtotal,
|
|
47
|
+
- 'discount': discount,
|
|
48
|
+
- 'tax': tax,
|
|
49
|
+
- 'total': final_total
|
|
50
|
+
- }""",
|
|
51
|
+
),
|
|
52
|
+
"javascript": (
|
|
53
|
+
"app.js",
|
|
54
|
+
"""--- a/app.js
|
|
55
|
+
+++ b/app.js
|
|
56
|
+
@@ -1,10 +1,12 @@
|
|
57
|
+
-function fetchUserData(userId) {
|
|
58
|
+
- return fetch(`/api/users/${userId}`)
|
|
59
|
+
- .then(response => response.json())
|
|
60
|
+
- .then(data => {
|
|
61
|
+
- return data.user;
|
|
62
|
+
- })
|
|
63
|
+
- .catch(error => console.error(error));
|
|
64
|
+
+async function fetchUserData(userId) {
|
|
65
|
+
+ try {
|
|
66
|
+
+ const response = await fetch(`/api/users/${userId}`);
|
|
67
|
+
+ const data = await response.json();
|
|
68
|
+
+ return data.user;
|
|
69
|
+
+ } catch (error) {
|
|
70
|
+
+ console.error('Failed to fetch user:', error);
|
|
71
|
+
+ throw error;
|
|
72
|
+
+ }
|
|
73
|
+
}""",
|
|
74
|
+
),
|
|
75
|
+
"typescript": (
|
|
76
|
+
"service.ts",
|
|
77
|
+
"""--- a/service.ts
|
|
78
|
+
+++ b/service.ts
|
|
79
|
+
@@ -1,8 +1,11 @@
|
|
80
|
+
-class UserService {
|
|
81
|
+
- getUser(id: number) {
|
|
82
|
+
- return this.http.get(`/users/${id}`);
|
|
83
|
+
+interface User {
|
|
84
|
+
+ id: number;
|
|
85
|
+
+ name: string;
|
|
86
|
+
+}
|
|
87
|
+
+
|
|
88
|
+
+class UserService {
|
|
89
|
+
+ async getUser(id: number): Promise<User> {
|
|
90
|
+
+ const response = await this.http.get<User>(`/users/${id}`);
|
|
91
|
+
+ return response.data;
|
|
92
|
+
}
|
|
93
|
+
- deleteUser(id: number) {
|
|
94
|
+
- return this.http.delete(`/users/${id}`);
|
|
95
|
+
- }
|
|
96
|
+
}""",
|
|
97
|
+
),
|
|
98
|
+
"rust": (
|
|
99
|
+
"main.rs",
|
|
100
|
+
"""--- a/main.rs
|
|
101
|
+
+++ b/main.rs
|
|
102
|
+
@@ -1,8 +1,10 @@
|
|
103
|
+
-fn calculate_sum(numbers: Vec<i32>) -> i32 {
|
|
104
|
+
- let mut total = 0;
|
|
105
|
+
- for num in numbers {
|
|
106
|
+
- total = total + num;
|
|
107
|
+
+fn calculate_sum(numbers: &[i32]) -> i32 {
|
|
108
|
+
+ numbers.iter().sum()
|
|
109
|
+
+}
|
|
110
|
+
+
|
|
111
|
+
+fn calculate_average(numbers: &[i32]) -> f64 {
|
|
112
|
+
+ if numbers.is_empty() {
|
|
113
|
+
+ return 0.0;
|
|
114
|
+
}
|
|
115
|
+
- total
|
|
116
|
+
+ calculate_sum(numbers) as f64 / numbers.len() as f64
|
|
117
|
+
}""",
|
|
118
|
+
),
|
|
119
|
+
"go": (
|
|
120
|
+
"handler.go",
|
|
121
|
+
"""--- a/handler.go
|
|
122
|
+
+++ b/handler.go
|
|
123
|
+
@@ -1,10 +1,15 @@
|
|
124
|
+
-func HandleRequest(w http.ResponseWriter, r *http.Request) {
|
|
125
|
+
- data := getData()
|
|
126
|
+
- json.NewEncoder(w).Encode(data)
|
|
127
|
+
+func HandleRequest(w http.ResponseWriter, r *http.Request) error {
|
|
128
|
+
+ data, err := getData()
|
|
129
|
+
+ if err != nil {
|
|
130
|
+
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
131
|
+
+ return err
|
|
132
|
+
+ }
|
|
133
|
+
+ w.Header().Set("Content-Type", "application/json")
|
|
134
|
+
+ return json.NewEncoder(w).Encode(data)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
-func getData() map[string]interface{} {
|
|
138
|
+
- return map[string]interface{}{"status": "ok"}
|
|
139
|
+
+func getData() (map[string]interface{}, error) {
|
|
140
|
+
+ return map[string]interface{}{"status": "ok"}, nil
|
|
141
|
+
}""",
|
|
142
|
+
),
|
|
143
|
+
"java": (
|
|
144
|
+
"Calculator.java",
|
|
145
|
+
"""--- a/Calculator.java
|
|
146
|
+
+++ b/Calculator.java
|
|
147
|
+
@@ -1,8 +1,12 @@
|
|
148
|
+
public class Calculator {
|
|
149
|
+
- public int add(int a, int b) {
|
|
150
|
+
- return a + b;
|
|
151
|
+
+ public double calculateTotal(List<Double> prices) {
|
|
152
|
+
+ return prices.stream()
|
|
153
|
+
+ .reduce(0.0, Double::sum);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
- public int multiply(int a, int b) {
|
|
157
|
+
- return a * b;
|
|
158
|
+
+ public double calculateAverage(List<Double> prices) {
|
|
159
|
+
+ if (prices.isEmpty()) {
|
|
160
|
+
+ return 0.0;
|
|
161
|
+
+ }
|
|
162
|
+
+ return calculateTotal(prices) / prices.size();
|
|
163
|
+
}
|
|
164
|
+
}""",
|
|
165
|
+
),
|
|
166
|
+
"ruby": (
|
|
167
|
+
"calculator.rb",
|
|
168
|
+
"""--- a/calculator.rb
|
|
169
|
+
+++ b/calculator.rb
|
|
170
|
+
@@ -1,8 +1,10 @@
|
|
171
|
+
class Calculator
|
|
172
|
+
- def add(a, b)
|
|
173
|
+
- a + b
|
|
174
|
+
+ def calculate_total(items)
|
|
175
|
+
+ items.sum { |item| item[:price] }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
- def multiply(a, b)
|
|
179
|
+
- a * b
|
|
180
|
+
+ def calculate_average(items)
|
|
181
|
+
+ return 0 if items.empty?
|
|
182
|
+
+
|
|
183
|
+
+ calculate_total(items) / items.size.to_f
|
|
184
|
+
end
|
|
185
|
+
end""",
|
|
186
|
+
),
|
|
187
|
+
"csharp": (
|
|
188
|
+
"Calculator.cs",
|
|
189
|
+
"""--- a/Calculator.cs
|
|
190
|
+
+++ b/Calculator.cs
|
|
191
|
+
@@ -1,10 +1,14 @@
|
|
192
|
+
-public class Calculator {
|
|
193
|
+
- public int Add(int a, int b) {
|
|
194
|
+
- return a + b;
|
|
195
|
+
+public class Calculator
|
|
196
|
+
+{
|
|
197
|
+
+ public decimal CalculateTotal(IEnumerable<decimal> prices)
|
|
198
|
+
+ {
|
|
199
|
+
+ return prices.Sum();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
- public int Multiply(int a, int b) {
|
|
203
|
+
- return a * b;
|
|
204
|
+
+ public decimal CalculateAverage(IEnumerable<decimal> prices)
|
|
205
|
+
+ {
|
|
206
|
+
+ var priceList = prices.ToList();
|
|
207
|
+
+ return priceList.Any() ? priceList.Average() : 0m;
|
|
208
|
+
}
|
|
209
|
+
}""",
|
|
210
|
+
),
|
|
211
|
+
"php": (
|
|
212
|
+
"Calculator.php",
|
|
213
|
+
"""--- a/Calculator.php
|
|
214
|
+
+++ b/Calculator.php
|
|
215
|
+
@@ -1,10 +1,14 @@
|
|
216
|
+
<?php
|
|
217
|
+
class Calculator {
|
|
218
|
+
- public function add($a, $b) {
|
|
219
|
+
- return $a + $b;
|
|
220
|
+
+ public function calculateTotal(array $items): float {
|
|
221
|
+
+ return array_sum(array_column($items, 'price'));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- public function multiply($a, $b) {
|
|
225
|
+
- return $a * $b;
|
|
226
|
+
+ public function calculateAverage(array $items): float {
|
|
227
|
+
+ if (empty($items)) {
|
|
228
|
+
+ return 0.0;
|
|
229
|
+
+ }
|
|
230
|
+
+ return $this->calculateTotal($items) / count($items);
|
|
231
|
+
}
|
|
232
|
+
}""",
|
|
233
|
+
),
|
|
234
|
+
"html": (
|
|
235
|
+
"index.html",
|
|
236
|
+
"""--- a/index.html
|
|
237
|
+
+++ b/index.html
|
|
238
|
+
@@ -1,5 +1,8 @@
|
|
239
|
+
<div class="container">
|
|
240
|
+
- <h1>Welcome</h1>
|
|
241
|
+
- <p>Hello World</p>
|
|
242
|
+
+ <header>
|
|
243
|
+
+ <h1>Welcome to Our Site</h1>
|
|
244
|
+
+ <nav>
|
|
245
|
+
+ <a href="#home">Home</a>
|
|
246
|
+
+ <a href="#about">About</a>
|
|
247
|
+
+ </nav>
|
|
248
|
+
+ </header>
|
|
249
|
+
</div>""",
|
|
250
|
+
),
|
|
251
|
+
"css": (
|
|
252
|
+
"styles.css",
|
|
253
|
+
"""--- a/styles.css
|
|
254
|
+
+++ b/styles.css
|
|
255
|
+
@@ -1,5 +1,10 @@
|
|
256
|
+
.container {
|
|
257
|
+
- width: 100%;
|
|
258
|
+
- padding: 20px;
|
|
259
|
+
+ max-width: 1200px;
|
|
260
|
+
+ margin: 0 auto;
|
|
261
|
+
+ padding: 2rem;
|
|
262
|
+
+}
|
|
263
|
+
+
|
|
264
|
+
+.container header {
|
|
265
|
+
+ display: flex;
|
|
266
|
+
+ justify-content: space-between;
|
|
267
|
+
+ align-items: center;
|
|
268
|
+
}""",
|
|
269
|
+
),
|
|
270
|
+
"json": (
|
|
271
|
+
"config.json",
|
|
272
|
+
"""--- a/config.json
|
|
273
|
+
+++ b/config.json
|
|
274
|
+
@@ -1,5 +1,8 @@
|
|
275
|
+
{
|
|
276
|
+
- "name": "app",
|
|
277
|
+
- "version": "1.0.0"
|
|
278
|
+
+ "name": "my-awesome-app",
|
|
279
|
+
+ "version": "2.0.0",
|
|
280
|
+
+ "description": "An awesome application",
|
|
281
|
+
+ "author": "Code Puppy",
|
|
282
|
+
+ "license": "MIT"
|
|
283
|
+
}""",
|
|
284
|
+
),
|
|
285
|
+
"yaml": (
|
|
286
|
+
"config.yml",
|
|
287
|
+
"""--- a/config.yml
|
|
288
|
+
+++ b/config.yml
|
|
289
|
+
@@ -1,4 +1,8 @@
|
|
290
|
+
app:
|
|
291
|
+
name: myapp
|
|
292
|
+
- version: 1.0
|
|
293
|
+
+ version: 2.0
|
|
294
|
+
+ environment: production
|
|
295
|
+
+
|
|
296
|
+
+database:
|
|
297
|
+
+ host: localhost
|
|
298
|
+
+ port: 5432""",
|
|
299
|
+
),
|
|
300
|
+
"bash": (
|
|
301
|
+
"deploy.sh",
|
|
302
|
+
"""--- a/deploy.sh
|
|
303
|
+
+++ b/deploy.sh
|
|
304
|
+
@@ -1,5 +1,9 @@
|
|
305
|
+
#!/bin/bash
|
|
306
|
+
-echo \"Deploying...\"
|
|
307
|
+
-npm run build
|
|
308
|
+
+set -e
|
|
309
|
+
+
|
|
310
|
+
+echo \"Starting deployment...\"
|
|
311
|
+
+npm run build --production
|
|
312
|
+
+npm run test
|
|
313
|
+
+echo \"Deployment complete!\"""",
|
|
314
|
+
),
|
|
315
|
+
"sql": (
|
|
316
|
+
"schema.sql",
|
|
317
|
+
"""--- a/schema.sql
|
|
318
|
+
+++ b/schema.sql
|
|
319
|
+
@@ -1,5 +1,9 @@
|
|
320
|
+
CREATE TABLE users (
|
|
321
|
+
id INTEGER PRIMARY KEY,
|
|
322
|
+
- name TEXT
|
|
323
|
+
+ name TEXT NOT NULL,
|
|
324
|
+
+ email TEXT UNIQUE NOT NULL,
|
|
325
|
+
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
326
|
+
);
|
|
327
|
+
+
|
|
328
|
+
+CREATE INDEX idx_users_email ON users(email);""",
|
|
329
|
+
),
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
# Get all supported languages in a consistent order
|
|
333
|
+
SUPPORTED_LANGUAGES = [
|
|
334
|
+
"python",
|
|
335
|
+
"javascript",
|
|
336
|
+
"typescript",
|
|
337
|
+
"rust",
|
|
338
|
+
"go",
|
|
339
|
+
"java",
|
|
340
|
+
"ruby",
|
|
341
|
+
"csharp",
|
|
342
|
+
"php",
|
|
343
|
+
"html",
|
|
344
|
+
"css",
|
|
345
|
+
"json",
|
|
346
|
+
"yaml",
|
|
347
|
+
"bash",
|
|
348
|
+
"sql",
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class DiffConfiguration:
|
|
353
|
+
"""Holds the current diff configuration state."""
|
|
354
|
+
|
|
355
|
+
def __init__(self):
|
|
356
|
+
"""Initialize configuration from current settings."""
|
|
357
|
+
from code_puppy.config import (
|
|
358
|
+
get_diff_addition_color,
|
|
359
|
+
get_diff_deletion_color,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
self.current_add_color = get_diff_addition_color()
|
|
363
|
+
self.current_del_color = get_diff_deletion_color()
|
|
364
|
+
self.original_add_color = self.current_add_color
|
|
365
|
+
self.original_del_color = self.current_del_color
|
|
366
|
+
self.current_language_index = 0 # Track current language for preview
|
|
367
|
+
|
|
368
|
+
def has_changes(self) -> bool:
|
|
369
|
+
"""Check if any changes have been made."""
|
|
370
|
+
return (
|
|
371
|
+
self.current_add_color != self.original_add_color
|
|
372
|
+
or self.current_del_color != self.original_del_color
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def next_language(self):
|
|
376
|
+
"""Cycle to the next language."""
|
|
377
|
+
self.current_language_index = (self.current_language_index + 1) % len(
|
|
378
|
+
SUPPORTED_LANGUAGES
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def prev_language(self):
|
|
382
|
+
"""Cycle to the previous language."""
|
|
383
|
+
self.current_language_index = (self.current_language_index - 1) % len(
|
|
384
|
+
SUPPORTED_LANGUAGES
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def get_current_language(self) -> str:
|
|
388
|
+
"""Get the currently selected language."""
|
|
389
|
+
return SUPPORTED_LANGUAGES[self.current_language_index]
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
async def interactive_diff_picker() -> Optional[dict]:
|
|
393
|
+
"""Show an interactive full-screen terminal UI to configure diff settings.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
A dict with changes or None if cancelled
|
|
397
|
+
"""
|
|
398
|
+
from code_puppy.tools.command_runner import set_awaiting_user_input
|
|
399
|
+
|
|
400
|
+
config = DiffConfiguration()
|
|
401
|
+
|
|
402
|
+
set_awaiting_user_input(True)
|
|
403
|
+
|
|
404
|
+
# Enter alternate screen buffer once for entire session
|
|
405
|
+
sys.stdout.write("\033[?1049h") # Enter alternate buffer
|
|
406
|
+
sys.stdout.write("\033[2J\033[H") # Clear and home
|
|
407
|
+
sys.stdout.flush()
|
|
408
|
+
time.sleep(0.1) # Minimal delay for state sync
|
|
409
|
+
|
|
410
|
+
try:
|
|
411
|
+
# Main menu loop
|
|
412
|
+
while True:
|
|
413
|
+
choices = [
|
|
414
|
+
"Configure Addition Color",
|
|
415
|
+
"Configure Deletion Color",
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
if config.has_changes():
|
|
419
|
+
choices.append("Save & Exit")
|
|
420
|
+
else:
|
|
421
|
+
choices.append("Exit")
|
|
422
|
+
|
|
423
|
+
# Dummy update function for main menu (config doesn't change on navigation)
|
|
424
|
+
def dummy_update(choice: str):
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
def get_main_preview():
|
|
428
|
+
return _get_preview_text_for_prompt_toolkit(config)
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
selected = await _split_panel_selector(
|
|
432
|
+
"Diff Color Configuration",
|
|
433
|
+
choices,
|
|
434
|
+
dummy_update,
|
|
435
|
+
get_preview=get_main_preview,
|
|
436
|
+
config=config,
|
|
437
|
+
)
|
|
438
|
+
except KeyboardInterrupt:
|
|
439
|
+
break
|
|
440
|
+
|
|
441
|
+
# Handle selection
|
|
442
|
+
if "Addition" in selected:
|
|
443
|
+
await _handle_color_menu(config, "additions")
|
|
444
|
+
elif "Deletion" in selected:
|
|
445
|
+
await _handle_color_menu(config, "deletions")
|
|
446
|
+
else:
|
|
447
|
+
# Exit
|
|
448
|
+
break
|
|
449
|
+
|
|
450
|
+
except Exception:
|
|
451
|
+
# Silent error - just exit cleanly
|
|
452
|
+
return None
|
|
453
|
+
finally:
|
|
454
|
+
set_awaiting_user_input(False)
|
|
455
|
+
# Exit alternate screen buffer once at end
|
|
456
|
+
sys.stdout.write("\033[?1049l") # Exit alternate buffer
|
|
457
|
+
sys.stdout.flush()
|
|
458
|
+
|
|
459
|
+
# Clear exit message
|
|
460
|
+
from code_puppy.messaging import emit_info
|
|
461
|
+
|
|
462
|
+
emit_info("✓ Exited diff color configuration")
|
|
463
|
+
|
|
464
|
+
# Return changes if any
|
|
465
|
+
if config.has_changes():
|
|
466
|
+
return {
|
|
467
|
+
"add_color": config.current_add_color,
|
|
468
|
+
"del_color": config.current_del_color,
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return None
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
async def _split_panel_selector(
|
|
475
|
+
title: str,
|
|
476
|
+
choices: list[str],
|
|
477
|
+
on_change: Callable[[str], None],
|
|
478
|
+
get_preview: Callable[[], ANSI],
|
|
479
|
+
config: Optional[DiffConfiguration] = None,
|
|
480
|
+
) -> Optional[str]:
|
|
481
|
+
"""Split-panel selector with menu on left and live preview on right.
|
|
482
|
+
|
|
483
|
+
Supports left/right arrow navigation through languages if config is provided.
|
|
484
|
+
"""
|
|
485
|
+
selected_index = [0]
|
|
486
|
+
result = [None]
|
|
487
|
+
|
|
488
|
+
def get_left_panel_text():
|
|
489
|
+
"""Generate the selector menu text."""
|
|
490
|
+
try:
|
|
491
|
+
lines = []
|
|
492
|
+
lines.append(("bold cyan", title))
|
|
493
|
+
lines.append(("", "\n\n"))
|
|
494
|
+
|
|
495
|
+
if not choices:
|
|
496
|
+
lines.append(("fg:ansiyellow", "No choices available"))
|
|
497
|
+
lines.append(("", "\n"))
|
|
498
|
+
else:
|
|
499
|
+
for i, choice in enumerate(choices):
|
|
500
|
+
if i == selected_index[0]:
|
|
501
|
+
lines.append(("fg:ansigreen", "▶ "))
|
|
502
|
+
lines.append(("fg:ansigreen bold", choice))
|
|
503
|
+
else:
|
|
504
|
+
lines.append(("", " "))
|
|
505
|
+
lines.append(("", choice))
|
|
506
|
+
lines.append(("", "\n"))
|
|
507
|
+
|
|
508
|
+
lines.append(("", "\n"))
|
|
509
|
+
|
|
510
|
+
# Add language navigation hint if config is available
|
|
511
|
+
if config is not None:
|
|
512
|
+
current_lang = config.get_current_language()
|
|
513
|
+
lang_hint = f"Language: {current_lang.upper()} (←→ to change)"
|
|
514
|
+
lines.append(("fg:ansiyellow", lang_hint))
|
|
515
|
+
lines.append(("", "\n"))
|
|
516
|
+
|
|
517
|
+
lines.append(
|
|
518
|
+
("fg:ansicyan", "↑↓ Navigate │ Enter Confirm │ Ctrl-C Cancel")
|
|
519
|
+
)
|
|
520
|
+
return FormattedText(lines)
|
|
521
|
+
except Exception as e:
|
|
522
|
+
return FormattedText([("fg:ansired", f"Error: {e}")])
|
|
523
|
+
|
|
524
|
+
def get_right_panel_text():
|
|
525
|
+
"""Generate the preview panel text."""
|
|
526
|
+
try:
|
|
527
|
+
preview = get_preview()
|
|
528
|
+
# get_preview() now returns ANSI, which is already FormattedText-compatible
|
|
529
|
+
return preview
|
|
530
|
+
except Exception as e:
|
|
531
|
+
return FormattedText([("fg:ansired", f"Preview error: {e}")])
|
|
532
|
+
|
|
533
|
+
kb = KeyBindings()
|
|
534
|
+
|
|
535
|
+
@kb.add("up")
|
|
536
|
+
def move_up(event):
|
|
537
|
+
if choices:
|
|
538
|
+
selected_index[0] = (selected_index[0] - 1) % len(choices)
|
|
539
|
+
on_change(choices[selected_index[0]])
|
|
540
|
+
event.app.invalidate()
|
|
541
|
+
|
|
542
|
+
@kb.add("down")
|
|
543
|
+
def move_down(event):
|
|
544
|
+
if choices:
|
|
545
|
+
selected_index[0] = (selected_index[0] + 1) % len(choices)
|
|
546
|
+
on_change(choices[selected_index[0]])
|
|
547
|
+
event.app.invalidate()
|
|
548
|
+
|
|
549
|
+
@kb.add("left")
|
|
550
|
+
def prev_lang(event):
|
|
551
|
+
if config is not None:
|
|
552
|
+
config.prev_language()
|
|
553
|
+
event.app.invalidate()
|
|
554
|
+
|
|
555
|
+
@kb.add("right")
|
|
556
|
+
def next_lang(event):
|
|
557
|
+
if config is not None:
|
|
558
|
+
config.next_language()
|
|
559
|
+
event.app.invalidate()
|
|
560
|
+
|
|
561
|
+
@kb.add("enter")
|
|
562
|
+
def accept(event):
|
|
563
|
+
if choices:
|
|
564
|
+
result[0] = choices[selected_index[0]]
|
|
565
|
+
else:
|
|
566
|
+
result[0] = None
|
|
567
|
+
event.app.exit()
|
|
568
|
+
|
|
569
|
+
@kb.add("c-c")
|
|
570
|
+
def cancel(event):
|
|
571
|
+
result[0] = None
|
|
572
|
+
event.app.exit()
|
|
573
|
+
|
|
574
|
+
# Create split layout with left (selector) and right (preview) panels
|
|
575
|
+
left_panel = Window(
|
|
576
|
+
content=FormattedTextControl(lambda: get_left_panel_text()),
|
|
577
|
+
width=50,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
right_panel = Window(
|
|
581
|
+
content=FormattedTextControl(lambda: get_right_panel_text()),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Create vertical split (side-by-side panels)
|
|
585
|
+
root_container = VSplit(
|
|
586
|
+
[
|
|
587
|
+
Frame(left_panel, title="Menu"),
|
|
588
|
+
Frame(right_panel, title="Preview"),
|
|
589
|
+
]
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
layout = Layout(root_container)
|
|
593
|
+
app = Application(
|
|
594
|
+
layout=layout,
|
|
595
|
+
key_bindings=kb,
|
|
596
|
+
full_screen=False, # Don't use full_screen to avoid buffer issues
|
|
597
|
+
mouse_support=False,
|
|
598
|
+
color_depth="DEPTH_24_BIT", # Enable truecolor support
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
sys.stdout.flush()
|
|
602
|
+
sys.stdout.flush()
|
|
603
|
+
|
|
604
|
+
# Trigger initial update only if choices is not empty
|
|
605
|
+
if choices:
|
|
606
|
+
on_change(choices[selected_index[0]])
|
|
607
|
+
|
|
608
|
+
# Just clear the current buffer (don't switch buffers)
|
|
609
|
+
sys.stdout.write("\033[2J\033[H") # Clear screen within current buffer
|
|
610
|
+
sys.stdout.flush()
|
|
611
|
+
|
|
612
|
+
# Run application (stays in same alternate buffer)
|
|
613
|
+
await app.run_async()
|
|
614
|
+
|
|
615
|
+
if result[0] is None:
|
|
616
|
+
raise KeyboardInterrupt()
|
|
617
|
+
|
|
618
|
+
return result[0]
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
ADDITION_COLORS = {
|
|
622
|
+
# primary first (darkened)
|
|
623
|
+
"dark green": "#0b3e0b",
|
|
624
|
+
"darker green": "#0b1f0b",
|
|
625
|
+
"dark aqua": "#164952",
|
|
626
|
+
"deep teal": "#143f3c",
|
|
627
|
+
# blues (darkened)
|
|
628
|
+
"sky blue": "#406884",
|
|
629
|
+
"soft blue": "#315c78",
|
|
630
|
+
"steel blue": "#20394e",
|
|
631
|
+
"forest teal": "#124831",
|
|
632
|
+
"cool teal": "#1b4b54",
|
|
633
|
+
"marine aqua": "#275860",
|
|
634
|
+
"slate blue": "#304f69",
|
|
635
|
+
"deep steel": "#1e3748",
|
|
636
|
+
"shadow olive": "#2f3a15",
|
|
637
|
+
"deep moss": "#1f3310",
|
|
638
|
+
# G
|
|
639
|
+
"midnight spruce": "#0f3a29",
|
|
640
|
+
"shadow jade": "#0d4a3a",
|
|
641
|
+
# B
|
|
642
|
+
"abyss blue": "#0d2f4d",
|
|
643
|
+
"midnight fjord": "#133552",
|
|
644
|
+
# I
|
|
645
|
+
"dusky indigo": "#1a234d",
|
|
646
|
+
"nocturne indigo": "#161d3f",
|
|
647
|
+
# V
|
|
648
|
+
"midnight violet": "#2a1a3f",
|
|
649
|
+
"deep amethyst": "#3a2860",
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
DELETION_COLORS = {
|
|
653
|
+
# primary first (darkened)
|
|
654
|
+
"dark red": "#4a0f0f",
|
|
655
|
+
# pinks / reds (darkened)
|
|
656
|
+
"pink": "#7f143b",
|
|
657
|
+
"soft red": "#741f3c",
|
|
658
|
+
"salmon": "#842848",
|
|
659
|
+
"rose": "#681c35",
|
|
660
|
+
"deep rose": "#4f1428",
|
|
661
|
+
# oranges (darkened)
|
|
662
|
+
"burnt orange": "#753b10",
|
|
663
|
+
"deep orange": "#5b2b0d",
|
|
664
|
+
# yellows (darkened)
|
|
665
|
+
"amber": "#69551c",
|
|
666
|
+
# reds (darkened)
|
|
667
|
+
"red": "#5d0b0b",
|
|
668
|
+
"ruby": "#5b141f",
|
|
669
|
+
"wine": "#390e1a",
|
|
670
|
+
# purples (darkened)
|
|
671
|
+
"purple": "#5a4284",
|
|
672
|
+
"soft purple": "#503977",
|
|
673
|
+
"violet": "#432758",
|
|
674
|
+
# ROYGBIV deletions (unchanged)
|
|
675
|
+
# R
|
|
676
|
+
"ember crimson": "#5a0e12",
|
|
677
|
+
"smoked ruby": "#4b0b16",
|
|
678
|
+
# O
|
|
679
|
+
"molten orange": "#70340c",
|
|
680
|
+
"baked amber": "#5c2b0a",
|
|
681
|
+
# Y
|
|
682
|
+
"burnt ochre": "#5a4110",
|
|
683
|
+
"tawny umber": "#4c3810",
|
|
684
|
+
# G
|
|
685
|
+
"swamp olive": "#3c3a14",
|
|
686
|
+
"bog green": "#343410",
|
|
687
|
+
# B
|
|
688
|
+
"dusky petrol": "#2a3744",
|
|
689
|
+
"warm slate": "#263038",
|
|
690
|
+
# I
|
|
691
|
+
"wine indigo": "#311b3f",
|
|
692
|
+
"mulberry dusk": "#3f1f52",
|
|
693
|
+
# V
|
|
694
|
+
"garnet plum": "#4a1e3a",
|
|
695
|
+
"dusky magenta": "#5a1f4c",
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _convert_rich_color_to_prompt_toolkit(color: str) -> str:
|
|
700
|
+
"""Convert Rich color names to prompt-toolkit compatible names."""
|
|
701
|
+
# Hex colors pass through as-is
|
|
702
|
+
if color.startswith("#"):
|
|
703
|
+
return color
|
|
704
|
+
# Map bright_ colors to ansi equivalents
|
|
705
|
+
if color.startswith("bright_"):
|
|
706
|
+
return "ansi" + color.replace("bright_", "")
|
|
707
|
+
# Basic terminal colors
|
|
708
|
+
if color.lower() in [
|
|
709
|
+
"black",
|
|
710
|
+
"red",
|
|
711
|
+
"green",
|
|
712
|
+
"yellow",
|
|
713
|
+
"blue",
|
|
714
|
+
"magenta",
|
|
715
|
+
"cyan",
|
|
716
|
+
"white",
|
|
717
|
+
"gray",
|
|
718
|
+
"grey",
|
|
719
|
+
]:
|
|
720
|
+
return color.lower()
|
|
721
|
+
# Default safe fallback for unknown colors
|
|
722
|
+
return "white"
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _get_preview_text_for_prompt_toolkit(config: DiffConfiguration) -> ANSI:
|
|
726
|
+
"""Get preview as ANSI for embedding in selector with live colors.
|
|
727
|
+
|
|
728
|
+
Returns ANSI-formatted text that prompt_toolkit can render with full colors.
|
|
729
|
+
"""
|
|
730
|
+
from code_puppy.tools.common import format_diff_with_colors
|
|
731
|
+
|
|
732
|
+
# Get the current language and its sample
|
|
733
|
+
current_lang = config.get_current_language()
|
|
734
|
+
filename, sample_diff = LANGUAGE_SAMPLES.get(
|
|
735
|
+
current_lang,
|
|
736
|
+
LANGUAGE_SAMPLES["python"], # Fallback to Python
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
# Build header with current settings info using Rich markup
|
|
740
|
+
header_parts = []
|
|
741
|
+
header_parts.append("[bold]═" * 50 + "[/bold]")
|
|
742
|
+
header_parts.append(
|
|
743
|
+
"[bold cyan] LIVE PREVIEW - Syntax Highlighted Diff[/bold cyan]"
|
|
744
|
+
)
|
|
745
|
+
header_parts.append("[bold]═" * 50 + "[/bold]")
|
|
746
|
+
header_parts.append("")
|
|
747
|
+
header_parts.append(f" Addition Color: [bold]{config.current_add_color}[/bold]")
|
|
748
|
+
header_parts.append(f" Deletion Color: [bold]{config.current_del_color}[/bold]")
|
|
749
|
+
header_parts.append("")
|
|
750
|
+
header_parts.append(
|
|
751
|
+
f" [bold yellow]Language: {current_lang.upper()}[/bold yellow] "
|
|
752
|
+
f"[dim](← → to cycle)[/dim]"
|
|
753
|
+
)
|
|
754
|
+
header_parts.append("")
|
|
755
|
+
header_parts.append(f"[bold] Example ({filename}):[/bold]")
|
|
756
|
+
header_parts.append("")
|
|
757
|
+
|
|
758
|
+
header_text = "\n".join(header_parts)
|
|
759
|
+
|
|
760
|
+
# Temporarily override config to use current preview settings
|
|
761
|
+
from code_puppy.config import (
|
|
762
|
+
get_diff_addition_color,
|
|
763
|
+
get_diff_deletion_color,
|
|
764
|
+
set_diff_addition_color,
|
|
765
|
+
set_diff_deletion_color,
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
# Save original values
|
|
769
|
+
original_add_color = get_diff_addition_color()
|
|
770
|
+
original_del_color = get_diff_deletion_color()
|
|
771
|
+
|
|
772
|
+
try:
|
|
773
|
+
# Temporarily set config to preview values
|
|
774
|
+
set_diff_addition_color(config.current_add_color)
|
|
775
|
+
set_diff_deletion_color(config.current_del_color)
|
|
776
|
+
|
|
777
|
+
# Get the formatted diff (either Rich Text or Rich markup string)
|
|
778
|
+
formatted_diff = format_diff_with_colors(sample_diff)
|
|
779
|
+
|
|
780
|
+
# Render everything with Rich Console to get ANSI output with proper color support
|
|
781
|
+
buffer = io.StringIO()
|
|
782
|
+
console = Console(
|
|
783
|
+
file=buffer,
|
|
784
|
+
force_terminal=True,
|
|
785
|
+
width=90,
|
|
786
|
+
legacy_windows=False,
|
|
787
|
+
color_system="truecolor",
|
|
788
|
+
no_color=False,
|
|
789
|
+
force_interactive=True, # Force interactive mode for better color support
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
# Print header
|
|
793
|
+
console.print(header_text, end="\n")
|
|
794
|
+
|
|
795
|
+
# Print diff (handles both Text objects and markup strings)
|
|
796
|
+
console.print(formatted_diff, end="\n\n")
|
|
797
|
+
|
|
798
|
+
# Print footer
|
|
799
|
+
console.print("[bold]═" * 50 + "[/bold]", end="")
|
|
800
|
+
|
|
801
|
+
ansi_output = buffer.getvalue()
|
|
802
|
+
|
|
803
|
+
finally:
|
|
804
|
+
# Restore original config values
|
|
805
|
+
set_diff_addition_color(original_add_color)
|
|
806
|
+
set_diff_deletion_color(original_del_color)
|
|
807
|
+
|
|
808
|
+
# Wrap in ANSI() so prompt_toolkit can render it
|
|
809
|
+
return ANSI(ansi_output)
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
async def _handle_color_menu(config: DiffConfiguration, color_type: str) -> None:
|
|
813
|
+
"""Handle color selection with live preview updates."""
|
|
814
|
+
# Text mode only (highlighted disabled)
|
|
815
|
+
if color_type == "additions":
|
|
816
|
+
color_dict = ADDITION_COLORS
|
|
817
|
+
current = config.current_add_color
|
|
818
|
+
title = "Select addition color:"
|
|
819
|
+
else:
|
|
820
|
+
color_dict = DELETION_COLORS
|
|
821
|
+
current = config.current_del_color
|
|
822
|
+
title = "Select deletion color:"
|
|
823
|
+
|
|
824
|
+
# Build choices with nice names
|
|
825
|
+
choices = []
|
|
826
|
+
for name, color_value in color_dict.items():
|
|
827
|
+
marker = " ← current" if color_value == current else ""
|
|
828
|
+
choices.append(f"{name}{marker}")
|
|
829
|
+
|
|
830
|
+
# Store original color for potential cancellation
|
|
831
|
+
original_color = current
|
|
832
|
+
|
|
833
|
+
# Callback for live preview updates
|
|
834
|
+
def update_preview(selected_choice: str):
|
|
835
|
+
# Extract color name and look up the actual color value
|
|
836
|
+
color_name = selected_choice.replace(" ← current", "").strip()
|
|
837
|
+
selected_color = color_dict.get(color_name, list(color_dict.values())[0])
|
|
838
|
+
if color_type == "additions":
|
|
839
|
+
config.current_add_color = selected_color
|
|
840
|
+
else:
|
|
841
|
+
config.current_del_color = selected_color
|
|
842
|
+
|
|
843
|
+
# Function to get live preview header with colored diff
|
|
844
|
+
def get_preview_header():
|
|
845
|
+
return _get_preview_text_for_prompt_toolkit(config)
|
|
846
|
+
|
|
847
|
+
try:
|
|
848
|
+
# Use split panel selector with live preview (pass config for language switching)
|
|
849
|
+
await _split_panel_selector(
|
|
850
|
+
title,
|
|
851
|
+
choices,
|
|
852
|
+
update_preview,
|
|
853
|
+
get_preview=get_preview_header,
|
|
854
|
+
config=config,
|
|
855
|
+
)
|
|
856
|
+
except KeyboardInterrupt:
|
|
857
|
+
# Restore original color on cancel
|
|
858
|
+
if color_type == "additions":
|
|
859
|
+
config.current_add_color = original_color
|
|
860
|
+
else:
|
|
861
|
+
config.current_del_color = original_color
|
|
862
|
+
except Exception:
|
|
863
|
+
pass # Silent error handling
|