janito 0.6.0__py3-none-any.whl → 0.8.0__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.
- janito/__main__.py +127 -134
- janito/agents/__init__.py +22 -16
- janito/agents/agent.py +24 -20
- janito/agents/claudeai.py +41 -55
- janito/agents/deepseekai.py +47 -0
- janito/change/applied_blocks.py +34 -0
- janito/change/applier.py +167 -0
- janito/change/edit_blocks.py +148 -0
- janito/change/finder.py +72 -0
- janito/change/request.py +144 -0
- janito/change/validator.py +87 -251
- janito/change/view/content.py +63 -0
- janito/change/{viewer → view}/diff.py +44 -43
- janito/change/view/panels.py +201 -0
- janito/change/view/sections.py +69 -0
- janito/change/view/styling.py +140 -0
- janito/change/view/summary.py +37 -0
- janito/change/{viewer → view}/themes.py +62 -55
- janito/change/view/viewer.py +59 -0
- janito/cli/__init__.py +1 -1
- janito/cli/commands.py +68 -45
- janito/cli/functions.py +66 -111
- janito/common.py +132 -53
- janito/config.py +99 -101
- janito/data/change_prompt.txt +81 -0
- janito/data/system_prompt.txt +3 -0
- janito/qa.py +56 -66
- janito/version.py +22 -22
- janito/workspace/__init__.py +8 -7
- janito/workspace/analysis.py +120 -120
- janito/workspace/models.py +97 -0
- janito/workspace/show.py +115 -0
- janito/workspace/stats.py +42 -0
- janito/workspace/workset.py +135 -0
- janito/workspace/workspace.py +335 -0
- janito-0.8.0.dist-info/METADATA +106 -0
- janito-0.8.0.dist-info/RECORD +40 -0
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
- janito/__init__.py +0 -2
- janito/agents/openai.py +0 -53
- janito/agents/test.py +0 -34
- janito/change/__init__.py +0 -32
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +0 -23
- janito/change/analysis/__main__.py +0 -7
- janito/change/analysis/analyze.py +0 -61
- janito/change/analysis/formatting.py +0 -78
- janito/change/analysis/options.py +0 -81
- janito/change/analysis/prompts.py +0 -98
- janito/change/analysis/view/__init__.py +0 -9
- janito/change/analysis/view/terminal.py +0 -171
- janito/change/applier/__init__.py +0 -5
- janito/change/applier/file.py +0 -58
- janito/change/applier/main.py +0 -156
- janito/change/applier/text.py +0 -245
- janito/change/applier/workspace_dir.py +0 -58
- janito/change/core.py +0 -131
- janito/change/history.py +0 -44
- janito/change/operations.py +0 -7
- janito/change/parser.py +0 -289
- janito/change/play.py +0 -54
- janito/change/preview.py +0 -82
- janito/change/prompts.py +0 -126
- janito/change/test.py +0 -0
- janito/change/viewer/__init__.py +0 -11
- janito/change/viewer/content.py +0 -66
- janito/change/viewer/pager.py +0 -56
- janito/change/viewer/panels.py +0 -555
- janito/change/viewer/styling.py +0 -103
- janito/clear_statement_parser/clear_statement_format.txt +0 -328
- janito/clear_statement_parser/examples.txt +0 -326
- janito/clear_statement_parser/models.py +0 -104
- janito/clear_statement_parser/parser.py +0 -496
- janito/cli/base.py +0 -30
- janito/cli/handlers/ask.py +0 -22
- janito/cli/handlers/demo.py +0 -22
- janito/cli/handlers/request.py +0 -24
- janito/cli/handlers/scan.py +0 -9
- janito/cli/history.py +0 -61
- janito/cli/registry.py +0 -26
- janito/demo/__init__.py +0 -4
- janito/demo/data.py +0 -13
- janito/demo/mock_data.py +0 -20
- janito/demo/operations.py +0 -45
- janito/demo/runner.py +0 -59
- janito/demo/scenarios.py +0 -32
- janito/prompts.py +0 -2
- janito/review.py +0 -13
- janito/search_replace/README.md +0 -146
- janito/search_replace/__init__.py +0 -6
- janito/search_replace/__main__.py +0 -21
- janito/search_replace/core.py +0 -119
- janito/search_replace/parser.py +0 -52
- janito/search_replace/play.py +0 -61
- janito/search_replace/replacer.py +0 -36
- janito/search_replace/searcher.py +0 -299
- janito/shell/__init__.py +0 -39
- janito/shell/bus.py +0 -31
- janito/shell/commands.py +0 -195
- janito/shell/handlers.py +0 -122
- janito/shell/history.py +0 -20
- janito/shell/processor.py +0 -52
- janito/tui/__init__.py +0 -21
- janito/tui/base.py +0 -22
- janito/tui/flows/__init__.py +0 -5
- janito/tui/flows/changes.py +0 -65
- janito/tui/flows/content.py +0 -128
- janito/tui/flows/selection.py +0 -117
- janito/tui/screens/__init__.py +0 -3
- janito/tui/screens/app.py +0 -1
- janito/workspace/manager.py +0 -48
- janito/workspace/scan.py +0 -232
- janito-0.6.0.dist-info/METADATA +0 -185
- janito-0.6.0.dist-info/RECORD +0 -95
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,201 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from typing import List, Tuple
|
3
|
+
from rich.panel import Panel
|
4
|
+
from rich.syntax import Syntax
|
5
|
+
from pathlib import Path
|
6
|
+
from rich.columns import Columns
|
7
|
+
from rich.text import Text
|
8
|
+
from rich.layout import Layout
|
9
|
+
from ..edit_blocks import EditType, CodeChange
|
10
|
+
from .styling import format_content
|
11
|
+
from .sections import find_modified_sections
|
12
|
+
|
13
|
+
# Constants for panel layout
|
14
|
+
PANEL_MIN_WIDTH = 40
|
15
|
+
PANEL_MAX_WIDTH = 120
|
16
|
+
PANEL_PADDING = 4
|
17
|
+
COLUMN_SPACING = 4
|
18
|
+
|
19
|
+
def create_diff_columns(
|
20
|
+
original_section: List[str],
|
21
|
+
modified_section: List[str],
|
22
|
+
filename: str,
|
23
|
+
start: int,
|
24
|
+
term_width: int,
|
25
|
+
context_lines: int = 3,
|
26
|
+
current_change: int = 1,
|
27
|
+
total_changes: int = 1,
|
28
|
+
operation: str = "Edit",
|
29
|
+
reason: str = None,
|
30
|
+
is_removal: bool = False
|
31
|
+
) -> Tuple[Text, Columns]: # Changed return type to return header and content separately
|
32
|
+
"""Create side-by-side diff view with consistent styling and context."""
|
33
|
+
# Create header with progress info and rule
|
34
|
+
header = Text()
|
35
|
+
header_text, header_style = create_progress_header(
|
36
|
+
operation=operation,
|
37
|
+
filename=filename,
|
38
|
+
current=current_change,
|
39
|
+
total=total_changes,
|
40
|
+
term_width=term_width,
|
41
|
+
reason=reason
|
42
|
+
)
|
43
|
+
|
44
|
+
header.append(header_text)
|
45
|
+
header.append("\n")
|
46
|
+
header.append("─" * term_width, style="dim")
|
47
|
+
|
48
|
+
# Find sections that have changed
|
49
|
+
sections = find_modified_sections(
|
50
|
+
original_section,
|
51
|
+
modified_section,
|
52
|
+
context_lines=context_lines
|
53
|
+
)
|
54
|
+
|
55
|
+
if not sections:
|
56
|
+
# If no differences, show full content
|
57
|
+
diff_columns = _create_single_section_columns(
|
58
|
+
original_section,
|
59
|
+
modified_section,
|
60
|
+
filename,
|
61
|
+
start,
|
62
|
+
term_width,
|
63
|
+
is_removal=is_removal
|
64
|
+
)
|
65
|
+
else:
|
66
|
+
# Create columns for each modified section
|
67
|
+
rendered_sections = []
|
68
|
+
for i, (orig, mod) in enumerate(sections):
|
69
|
+
if i > 0:
|
70
|
+
rendered_sections.append(Text("...\n", style="dim"))
|
71
|
+
|
72
|
+
section_columns = _create_single_section_columns(
|
73
|
+
orig, mod, filename, start, term_width, is_removal=is_removal
|
74
|
+
)
|
75
|
+
rendered_sections.append(section_columns)
|
76
|
+
|
77
|
+
# Create single column containing all sections
|
78
|
+
diff_columns = Columns(
|
79
|
+
rendered_sections,
|
80
|
+
equal=False,
|
81
|
+
expand=False,
|
82
|
+
padding=(0, 0)
|
83
|
+
)
|
84
|
+
|
85
|
+
return header, diff_columns
|
86
|
+
|
87
|
+
def _create_single_section_columns(
|
88
|
+
original: List[str],
|
89
|
+
modified: List[str],
|
90
|
+
filename: str,
|
91
|
+
start: int,
|
92
|
+
term_width: int,
|
93
|
+
is_removal: bool = False # Add parameter with default value
|
94
|
+
) -> Columns:
|
95
|
+
"""Create columns for a single diff section."""
|
96
|
+
left_width, right_width = calculate_panel_widths(
|
97
|
+
'\n'.join(original),
|
98
|
+
'\n'.join(modified),
|
99
|
+
term_width
|
100
|
+
)
|
101
|
+
|
102
|
+
# Format content with correct parameters
|
103
|
+
left_content = format_content(
|
104
|
+
original,
|
105
|
+
search_lines=original,
|
106
|
+
replace_lines=modified,
|
107
|
+
is_search=True, # This indicates it's the original/search content
|
108
|
+
width=left_width,
|
109
|
+
is_removal=is_removal # Pass the parameter
|
110
|
+
)
|
111
|
+
right_content = format_content(
|
112
|
+
modified,
|
113
|
+
search_lines=original,
|
114
|
+
replace_lines=modified,
|
115
|
+
is_search=False, # This indicates it's the modified/replace content
|
116
|
+
width=right_width
|
117
|
+
)
|
118
|
+
|
119
|
+
left_text = create_panel_text("Original", left_content, left_width)
|
120
|
+
right_text = create_panel_text("Modified", right_content, right_width)
|
121
|
+
|
122
|
+
# Create columns without manual padding
|
123
|
+
columns = Columns(
|
124
|
+
[
|
125
|
+
left_text,
|
126
|
+
Text(" " * COLUMN_SPACING),
|
127
|
+
right_text
|
128
|
+
],
|
129
|
+
equal=False,
|
130
|
+
expand=False,
|
131
|
+
padding=(0, 0)
|
132
|
+
)
|
133
|
+
|
134
|
+
return columns
|
135
|
+
|
136
|
+
def calculate_panel_widths(left_content: str, right_content: str, term_width: int) -> Tuple[int, int]:
|
137
|
+
"""Calculate optimal widths for side-by-side panels with overflow protection."""
|
138
|
+
available_width = term_width - PANEL_PADDING - COLUMN_SPACING
|
139
|
+
|
140
|
+
left_max = max((len(line) for line in left_content.splitlines()), default=0)
|
141
|
+
right_max = max((len(line) for line in right_content.splitlines()), default=0)
|
142
|
+
|
143
|
+
left_max = max(PANEL_MIN_WIDTH, min(left_max, PANEL_MAX_WIDTH))
|
144
|
+
right_max = max(PANEL_MIN_WIDTH, min(right_max, PANEL_MAX_WIDTH))
|
145
|
+
|
146
|
+
if (left_max + right_max) <= available_width:
|
147
|
+
return left_max, right_max
|
148
|
+
|
149
|
+
ratio = left_max / (left_max + right_max)
|
150
|
+
left_width = min(
|
151
|
+
PANEL_MAX_WIDTH,
|
152
|
+
max(PANEL_MIN_WIDTH, int(available_width * ratio))
|
153
|
+
)
|
154
|
+
right_width = min(
|
155
|
+
PANEL_MAX_WIDTH,
|
156
|
+
max(PANEL_MIN_WIDTH, available_width - left_width)
|
157
|
+
)
|
158
|
+
|
159
|
+
return left_width, right_width
|
160
|
+
|
161
|
+
def create_panel_text(title: str, content: str, width: int) -> Text:
|
162
|
+
"""Create a text container with centered title."""
|
163
|
+
text = Text()
|
164
|
+
title_padding = (width - len(title)) // 2
|
165
|
+
color = "red" if title == "Original" else "green"
|
166
|
+
text.append(" " * title_padding + title + " " * title_padding, style=f"{color} bold")
|
167
|
+
text.append("\n")
|
168
|
+
text.append(content)
|
169
|
+
return text
|
170
|
+
|
171
|
+
def create_progress_header(operation: str, filename: str, current: int, total: int,
|
172
|
+
term_width: int, reason: str = None, style: str = "cyan") -> Tuple[Text, str]:
|
173
|
+
"""Create a header showing filename and global change counter.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
operation: Type of operation being performed
|
177
|
+
filename: Name of the file being modified
|
178
|
+
current: Current global change number
|
179
|
+
total: Total number of changes
|
180
|
+
term_width: Width of the terminal
|
181
|
+
reason: Optional reason for the change
|
182
|
+
style: Color style for the header
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
Tuple of (Rich Text object, style)
|
186
|
+
"""
|
187
|
+
text = Text()
|
188
|
+
header = f"{operation}: {filename} | Progress {current}/{total}"
|
189
|
+
if reason:
|
190
|
+
header += f" | {reason}"
|
191
|
+
|
192
|
+
# Calculate padding for centering
|
193
|
+
padding = (term_width - len(header)) // 2
|
194
|
+
|
195
|
+
# Create full-width background by padding both sides
|
196
|
+
full_line = " " * padding + header + " " * (term_width - len(header) - padding)
|
197
|
+
|
198
|
+
# Apply background color to entire line with better contrast
|
199
|
+
text.append(full_line, style=f"white on dark_blue")
|
200
|
+
|
201
|
+
return text, style
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from typing import List, Tuple, Set
|
2
|
+
|
3
|
+
def find_modified_sections(original: list[str], modified: list[str], context_lines: int = 3) -> list[tuple[list[str], list[str]]]:
|
4
|
+
"""
|
5
|
+
Find modified sections between original and modified text with surrounding context.
|
6
|
+
Merges sections with separator lines.
|
7
|
+
|
8
|
+
Args:
|
9
|
+
original: List of original lines
|
10
|
+
modified: List of modified linesn
|
11
|
+
context_lines: Number of unchanged context lines to include
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
List of tuples containing (original_section, modified_section) line pairs
|
15
|
+
"""
|
16
|
+
# Find different lines
|
17
|
+
different_lines = get_different_lines(original, modified)
|
18
|
+
if not different_lines:
|
19
|
+
return []
|
20
|
+
|
21
|
+
return create_sections(original, modified, different_lines, context_lines)
|
22
|
+
|
23
|
+
def get_different_lines(original: List[str], modified: List[str]) -> Set[int]:
|
24
|
+
"""Find lines that differ between original and modified content"""
|
25
|
+
different_lines = set()
|
26
|
+
for i in range(max(len(original), len(modified))):
|
27
|
+
if i >= len(original) or i >= len(modified):
|
28
|
+
different_lines.add(i)
|
29
|
+
elif original[i] != modified[i]:
|
30
|
+
different_lines.add(i)
|
31
|
+
return different_lines
|
32
|
+
|
33
|
+
def create_sections(original: List[str], modified: List[str],
|
34
|
+
different_lines: Set[int], context_lines: int) -> List[Tuple[List[str], List[str]]]:
|
35
|
+
"""Create sections from different lines with context"""
|
36
|
+
current_section = set()
|
37
|
+
orig_content = []
|
38
|
+
mod_content = []
|
39
|
+
|
40
|
+
for line_num in sorted(different_lines):
|
41
|
+
if not current_section or line_num <= max(current_section) + context_lines * 2:
|
42
|
+
current_section.add(line_num)
|
43
|
+
else:
|
44
|
+
process_section(original, modified, current_section, orig_content,
|
45
|
+
mod_content, context_lines)
|
46
|
+
current_section = {line_num}
|
47
|
+
|
48
|
+
if current_section:
|
49
|
+
process_section(original, modified, current_section, orig_content,
|
50
|
+
mod_content, context_lines)
|
51
|
+
|
52
|
+
return [(orig_content, mod_content)] if orig_content else []
|
53
|
+
|
54
|
+
def process_section(original: List[str], modified: List[str],
|
55
|
+
current_section: Set[int], orig_content: List[str],
|
56
|
+
mod_content: List[str], context_lines: int) -> None:
|
57
|
+
"""Process a section and add it to the content lists"""
|
58
|
+
start = max(0, min(current_section) - context_lines)
|
59
|
+
end = min(max(len(original), len(modified)),
|
60
|
+
max(current_section) + context_lines + 1)
|
61
|
+
|
62
|
+
# Add separator if needed
|
63
|
+
if orig_content:
|
64
|
+
orig_content.append("...")
|
65
|
+
mod_content.append("...")
|
66
|
+
|
67
|
+
# Add section content
|
68
|
+
orig_content.extend(original[start:end])
|
69
|
+
mod_content.extend(modified[start:end])
|
@@ -0,0 +1,140 @@
|
|
1
|
+
from rich.text import Text
|
2
|
+
from rich.console import Console
|
3
|
+
from typing import List
|
4
|
+
from .diff import find_similar_lines
|
5
|
+
from .themes import DEFAULT_THEME, ColorTheme, ThemeType, get_theme_by_type
|
6
|
+
|
7
|
+
current_theme = DEFAULT_THEME
|
8
|
+
|
9
|
+
def set_theme(theme: ColorTheme) -> None:
|
10
|
+
"""Set the current color theme"""
|
11
|
+
global current_theme
|
12
|
+
current_theme = theme
|
13
|
+
|
14
|
+
def get_min_indent(lines: List[str]) -> int:
|
15
|
+
"""Calculate the minimum indentation level across all non-empty lines."""
|
16
|
+
non_empty_lines = [line for line in lines if line.strip()]
|
17
|
+
if not non_empty_lines:
|
18
|
+
return 0
|
19
|
+
return min(len(line) - len(line.lstrip()) for line in non_empty_lines)
|
20
|
+
|
21
|
+
def apply_line_style(line: str, style: str, width: int, full_width: bool = False) -> Text:
|
22
|
+
"""Apply consistent styling to a single line with optional full width background"""
|
23
|
+
text = Text()
|
24
|
+
if full_width:
|
25
|
+
# For full width, pad the entire line
|
26
|
+
padded_line = line.ljust(width)
|
27
|
+
text.append(padded_line, style=style)
|
28
|
+
else:
|
29
|
+
# Left align the content and pad to width
|
30
|
+
text.append(line, style=style)
|
31
|
+
padding = " " * max(0, width - len(line))
|
32
|
+
text.append(padding, style=style)
|
33
|
+
|
34
|
+
text.append("\n", style=style)
|
35
|
+
return text
|
36
|
+
|
37
|
+
from textwrap import wrap
|
38
|
+
|
39
|
+
def format_content(lines: List[str], search_lines: List[str] = None, replace_lines: List[str] = None,
|
40
|
+
is_search: bool = False, width: int = 80, is_delete: bool = False,
|
41
|
+
is_removal: bool = False, syntax_type: str = None, is_append: bool = False) -> Text:
|
42
|
+
"""Format content with appropriate styling based on operation type.
|
43
|
+
|
44
|
+
Handles different content types:
|
45
|
+
- Regular content with diff highlighting
|
46
|
+
- Delete operations (red background)
|
47
|
+
- Append operations (green background)
|
48
|
+
- Text wrapping for long lines
|
49
|
+
"""
|
50
|
+
text = Text()
|
51
|
+
|
52
|
+
# For delete operations, show all content with red background in a single column
|
53
|
+
if is_delete or is_removal:
|
54
|
+
bg_color = current_theme.line_backgrounds['removed' if is_removal else 'deleted']
|
55
|
+
style = f"{current_theme.text_color} on {bg_color}"
|
56
|
+
min_indent = get_min_indent(lines)
|
57
|
+
for line in lines:
|
58
|
+
processed_line = line[min_indent:] if line.strip() else line
|
59
|
+
text.append(apply_line_style(processed_line, style, width, full_width=True))
|
60
|
+
return text
|
61
|
+
|
62
|
+
# For append operations, show all lines as added
|
63
|
+
if is_append:
|
64
|
+
bg_color = current_theme.line_backgrounds['added']
|
65
|
+
style = f"{current_theme.text_color} on {bg_color}"
|
66
|
+
min_indent = get_min_indent(lines)
|
67
|
+
for line in lines:
|
68
|
+
if line.strip():
|
69
|
+
line = line[min_indent:]
|
70
|
+
text.append(apply_line_style(line, style, width, full_width=True))
|
71
|
+
return text
|
72
|
+
|
73
|
+
# Regular diff formatting
|
74
|
+
if search_lines and replace_lines:
|
75
|
+
# Find similar lines for better diff visualization
|
76
|
+
similar_pairs = find_similar_lines(search_lines, replace_lines)
|
77
|
+
similar_added = {j for _, j, _ in similar_pairs}
|
78
|
+
similar_deleted = {i for i, _, _ in similar_pairs}
|
79
|
+
|
80
|
+
# Create sets for comparison
|
81
|
+
search_set = set(search_lines)
|
82
|
+
replace_set = set(replace_lines)
|
83
|
+
common_lines = search_set & replace_set
|
84
|
+
|
85
|
+
def add_line(line: str, line_type: str = 'unchanged'):
|
86
|
+
bg_color = current_theme.line_backgrounds.get(line_type, current_theme.line_backgrounds['unchanged'])
|
87
|
+
style = f"{current_theme.text_color} on {bg_color}"
|
88
|
+
|
89
|
+
if syntax_type == 'python':
|
90
|
+
# Just use the background color without syntax highlighting
|
91
|
+
text.append(apply_line_style(line, style, width))
|
92
|
+
return
|
93
|
+
|
94
|
+
# Wrap long lines
|
95
|
+
if len(line) > width:
|
96
|
+
wrapped_lines = wrap(line, width=width, break_long_words=True, break_on_hyphens=False)
|
97
|
+
for wrapped in wrapped_lines:
|
98
|
+
text.append(apply_line_style(wrapped, style, width))
|
99
|
+
else:
|
100
|
+
text.append(apply_line_style(line, style, width))
|
101
|
+
|
102
|
+
for i, line in enumerate(lines):
|
103
|
+
if not line.strip(): # Handle empty lines
|
104
|
+
add_line("", 'unchanged')
|
105
|
+
elif line in common_lines:
|
106
|
+
add_line(line, 'unchanged')
|
107
|
+
elif not is_search:
|
108
|
+
add_line(line, 'added')
|
109
|
+
else:
|
110
|
+
add_line(line, 'deleted')
|
111
|
+
|
112
|
+
return text
|
113
|
+
|
114
|
+
from rich.panel import Panel
|
115
|
+
from rich.columns import Columns
|
116
|
+
|
117
|
+
def create_legend_items(console: Console) -> Panel:
|
118
|
+
"""Create a compact legend panel with color blocks
|
119
|
+
|
120
|
+
Args:
|
121
|
+
console: Console instance for width calculation
|
122
|
+
"""
|
123
|
+
text = Text()
|
124
|
+
term_width = console.width or 120
|
125
|
+
|
126
|
+
# Add color blocks for each type
|
127
|
+
for label, bg_type in [("Unchanged", "unchanged"),
|
128
|
+
("Deleted", "deleted"),
|
129
|
+
("Added", "added")]:
|
130
|
+
style = f"{current_theme.text_color} on {current_theme.line_backgrounds[bg_type]}"
|
131
|
+
text.append(" ", style=style) # Color block
|
132
|
+
text.append(" " + label + " ") # Label with spacing
|
133
|
+
|
134
|
+
return Panel(
|
135
|
+
text,
|
136
|
+
padding=(0, 1),
|
137
|
+
expand=False,
|
138
|
+
title="Legend",
|
139
|
+
title_align="center"
|
140
|
+
)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from rich.table import Table
|
2
|
+
from rich.console import Console
|
3
|
+
from typing import List, Dict
|
4
|
+
|
5
|
+
def show_changes_summary(changes_summary: List[Dict], console: Console) -> None:
|
6
|
+
"""Show summary table of all changes."""
|
7
|
+
table = Table(title="Changes Summary")
|
8
|
+
table.add_column("File", style="cyan")
|
9
|
+
table.add_column("Type", style="magenta")
|
10
|
+
table.add_column("Lines Changed", justify="right", style="yellow")
|
11
|
+
table.add_column("Delta", justify="right", style="green")
|
12
|
+
table.add_column("Block", style="blue", justify="center")
|
13
|
+
table.add_column("Reason")
|
14
|
+
|
15
|
+
for change in changes_summary:
|
16
|
+
delta = change['lines_modified'] - change['lines_original']
|
17
|
+
delta_str = f"{'+' if delta > 0 else ''}{delta}"
|
18
|
+
|
19
|
+
if change['type'] == "CREATE":
|
20
|
+
lines_info = f"+{change['lines_modified']} lines"
|
21
|
+
elif change['type'] in ("DELETE", "CLEAN"):
|
22
|
+
lines_info = f"-{change['lines_original']} lines"
|
23
|
+
else:
|
24
|
+
lines_info = f"{change['lines_original']} → {change['lines_modified']} lines"
|
25
|
+
|
26
|
+
table.add_row(
|
27
|
+
str(change['file']),
|
28
|
+
change['type'],
|
29
|
+
lines_info,
|
30
|
+
delta_str,
|
31
|
+
change['block_marker'] or '-',
|
32
|
+
change['reason']
|
33
|
+
)
|
34
|
+
|
35
|
+
console.print("\n")
|
36
|
+
console.print(table, justify="center")
|
37
|
+
console.print("\n")
|
@@ -1,55 +1,62 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from typing import Dict
|
3
|
-
from enum import Enum
|
4
|
-
|
5
|
-
class ThemeType(Enum):
|
6
|
-
DARK = "dark"
|
7
|
-
LIGHT = "light"
|
8
|
-
|
9
|
-
# Dark theme colors
|
10
|
-
DARK_LINE_BACKGROUNDS = {
|
11
|
-
'unchanged': '#1A1A1A',
|
12
|
-
'added': '#003300',
|
13
|
-
'deleted': '#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
'
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Dict
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
class ThemeType(Enum):
|
6
|
+
DARK = "dark"
|
7
|
+
LIGHT = "light"
|
8
|
+
|
9
|
+
# Dark theme colors
|
10
|
+
DARK_LINE_BACKGROUNDS = {
|
11
|
+
'unchanged': '#1A1A1A',
|
12
|
+
'added': '#003300',
|
13
|
+
'deleted': '#330000',
|
14
|
+
'removed': '#330000',
|
15
|
+
'success': '#004400' # Dark green for success messages
|
16
|
+
}
|
17
|
+
|
18
|
+
# Light theme colors
|
19
|
+
LIGHT_LINE_BACKGROUNDS = {
|
20
|
+
'unchanged': 'grey93', # Light gray for unchanged
|
21
|
+
'added': 'light_green', # Semantic light green for additions
|
22
|
+
'deleted': '#FFE6E6', # Soft light red for deletions
|
23
|
+
'removed': '#FFE6E6',
|
24
|
+
'success': '#E6FFE6' # Light green for success messages
|
25
|
+
}
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class ColorTheme:
|
29
|
+
text_color: str
|
30
|
+
line_backgrounds: Dict[str, str]
|
31
|
+
theme_type: ThemeType
|
32
|
+
success_color: str = "green"
|
33
|
+
|
34
|
+
# Predefined themes
|
35
|
+
DARK_THEME = ColorTheme(
|
36
|
+
text_color="white",
|
37
|
+
line_backgrounds=DARK_LINE_BACKGROUNDS,
|
38
|
+
theme_type=ThemeType.DARK,
|
39
|
+
success_color="green"
|
40
|
+
)
|
41
|
+
|
42
|
+
LIGHT_THEME = ColorTheme(
|
43
|
+
text_color="black",
|
44
|
+
line_backgrounds=LIGHT_LINE_BACKGROUNDS,
|
45
|
+
theme_type=ThemeType.LIGHT,
|
46
|
+
success_color="dark_green"
|
47
|
+
)
|
48
|
+
|
49
|
+
DEFAULT_THEME = DARK_THEME
|
50
|
+
|
51
|
+
def validate_theme(theme: ColorTheme) -> bool:
|
52
|
+
"""Validate that a theme contains all required colors"""
|
53
|
+
required_line_backgrounds = {'unchanged', 'added', 'deleted'}
|
54
|
+
|
55
|
+
if not isinstance(theme, ColorTheme):
|
56
|
+
return False
|
57
|
+
|
58
|
+
return all(bg in theme.line_backgrounds for bg in required_line_backgrounds)
|
59
|
+
|
60
|
+
def get_theme_by_type(theme_type: ThemeType) -> ColorTheme:
|
61
|
+
"""Get a predefined theme by type"""
|
62
|
+
return LIGHT_THEME if theme_type == ThemeType.LIGHT else DARK_THEME
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from ..applied_blocks import AppliedBlocks
|
3
|
+
from .panels import create_diff_columns
|
4
|
+
from .summary import show_changes_summary
|
5
|
+
|
6
|
+
class ChangeViewer:
|
7
|
+
def __init__(self):
|
8
|
+
self.console = Console()
|
9
|
+
|
10
|
+
def show_changes(self, applied_blocks: AppliedBlocks):
|
11
|
+
"""Show all changes with diffs and summary"""
|
12
|
+
for block in applied_blocks.blocks:
|
13
|
+
self._show_block(block)
|
14
|
+
|
15
|
+
# Show summary table
|
16
|
+
show_changes_summary(applied_blocks.get_changes_summary(), self.console)
|
17
|
+
|
18
|
+
def _show_block(self, block):
|
19
|
+
"""Show a single change block with appropriate visualization"""
|
20
|
+
if block.edit_type.name == "CREATE":
|
21
|
+
header, columns = create_diff_columns(
|
22
|
+
[], # No original content
|
23
|
+
block.modified_content,
|
24
|
+
str(block.filename),
|
25
|
+
0,
|
26
|
+
self.console.width
|
27
|
+
)
|
28
|
+
elif block.edit_type.name == "DELETE":
|
29
|
+
header, columns = create_diff_columns(
|
30
|
+
block.original_content,
|
31
|
+
[], # No modified content
|
32
|
+
str(block.filename),
|
33
|
+
0,
|
34
|
+
self.console.width,
|
35
|
+
is_removal=True
|
36
|
+
)
|
37
|
+
elif block.edit_type.name == "CLEAN":
|
38
|
+
header, columns = create_diff_columns(
|
39
|
+
block.original_content,
|
40
|
+
[], # No modified content for clean
|
41
|
+
str(block.filename),
|
42
|
+
block.range_start - 1,
|
43
|
+
self.console.width,
|
44
|
+
operation="Clean",
|
45
|
+
reason=block.reason,
|
46
|
+
is_removal=True
|
47
|
+
)
|
48
|
+
else: # EDIT operation
|
49
|
+
header, columns = create_diff_columns(
|
50
|
+
block.original_content,
|
51
|
+
block.modified_content,
|
52
|
+
str(block.filename),
|
53
|
+
block.range_start - 1,
|
54
|
+
self.console.width,
|
55
|
+
reason=block.reason
|
56
|
+
)
|
57
|
+
|
58
|
+
self.console.print(header)
|
59
|
+
self.console.print(columns)
|
janito/cli/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
# This file is intentionally left empty to make the cli directory a package.
|