sistemazione finestra differ, cambiato il calcolo sha1
This commit is contained in:
parent
35db12d506
commit
7e0435ab7d
@ -141,9 +141,12 @@ class BaselineManager:
|
||||
except OSError:
|
||||
continue
|
||||
sha1 = None
|
||||
if compute_sha1 and st.st_size > 0:
|
||||
if compute_sha1: # also compute for 0-byte files
|
||||
try:
|
||||
sha1 = _sha1_of_file(Path(fpath))
|
||||
# Log empty __init__.py files
|
||||
if st.st_size == 0 and "__init__.py" in fn:
|
||||
print(f"[BASELINE-SCAN] {rel_unix} (size={st.st_size}) SHA1={sha1}")
|
||||
except Exception:
|
||||
sha1 = None
|
||||
files_meta.append(FileMeta(path=rel_unix, size=st.st_size, mtime=st.st_mtime, sha1=sha1))
|
||||
@ -206,6 +209,16 @@ class BaselineManager:
|
||||
os.makedirs(dst_parent, exist_ok=True)
|
||||
try:
|
||||
shutil.copy2(src_file, dst_file) # copy2 preserves metadata
|
||||
# Verify copy for empty __init__.py files
|
||||
if fm.size == 0 and "__init__.py" in fm.path:
|
||||
sha1_src = _sha1_of_file(Path(src_file))
|
||||
sha1_dst = _sha1_of_file(Path(dst_file))
|
||||
print(f"[BASELINE-COPY] {fm.path}")
|
||||
print(f" Source SHA1: {sha1_src}")
|
||||
print(f" Dest SHA1: {sha1_dst}")
|
||||
print(f" Saved SHA1: {fm.sha1}")
|
||||
if sha1_src != fm.sha1:
|
||||
print(f" WARNING: Source file changed after initial scan!")
|
||||
except Exception:
|
||||
pass # skip files that cannot be copied
|
||||
|
||||
@ -452,11 +465,14 @@ class Differ:
|
||||
except OSError:
|
||||
continue
|
||||
sha1 = None
|
||||
if st.st_size > 0:
|
||||
try:
|
||||
sha1 = _sha1_of_file(Path(fpath))
|
||||
except Exception:
|
||||
sha1 = None
|
||||
# Compute SHA1 for all files, including 0-byte files
|
||||
try:
|
||||
sha1 = _sha1_of_file(Path(fpath))
|
||||
# Log empty __init__.py files
|
||||
if st.st_size == 0 and "__init__.py" in fn:
|
||||
print(f"[DIFFER-CURRENT] {rel.replace(chr(92), '/')} (size={st.st_size}) SHA1={sha1}")
|
||||
except Exception:
|
||||
sha1 = None
|
||||
files_meta.append(FileMeta(path=rel.replace("\\", "/"), size=st.st_size, mtime=st.st_mtime, sha1=sha1))
|
||||
|
||||
# Run per-file analyzers (countings + metrics) and attach results to each FileMeta
|
||||
@ -699,6 +715,15 @@ class Differ:
|
||||
return res
|
||||
|
||||
def diff(self) -> Dict:
|
||||
import sys
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
print(f"\n{'='*80}", flush=True)
|
||||
print(f"[DIFFER-START] Using baseline: {self.baseline.baseline_id}", flush=True)
|
||||
print(f"[DIFFER-START] Baseline files dir: {self.baseline_files_dir}", flush=True)
|
||||
print(f"[DIFFER-START] Current dir: {self.current_dir}", flush=True)
|
||||
print(f"{'='*80}\n", flush=True)
|
||||
|
||||
baseline_files = self.baseline.files
|
||||
current_files = self.build_current_file_list()
|
||||
pairs = self.match_files(baseline_files, current_files)
|
||||
@ -717,11 +742,23 @@ class Differ:
|
||||
for a, b in pairs:
|
||||
fa = os.path.join(self.baseline_files_dir, a.path) if a is not None else None
|
||||
fb = os.path.join(self.current_dir, b.path) if b is not None else None
|
||||
# DEBUG: Log first few file pairs to verify paths
|
||||
if len(futures) < 3:
|
||||
print(f"[DIFFER] Comparing pair {len(futures)+1}:")
|
||||
print(f" Baseline: {fa}")
|
||||
print(f" Current: {fb}")
|
||||
|
||||
# DEBUG: Log empty __init__.py comparisons
|
||||
if a and b and a.size == 0 and "__init__.py" in a.path:
|
||||
import sys
|
||||
print(f"\n[DIFFER-COMPARE] {a.path}", flush=True)
|
||||
print(f" Baseline SHA1 (metadata): {a.sha1}", flush=True)
|
||||
print(f" Current SHA1 (metadata): {b.sha1}", flush=True)
|
||||
print(f" Baseline file: {fa}", flush=True)
|
||||
print(f" Current file: {fb}", flush=True)
|
||||
if fa and os.path.exists(fa):
|
||||
sha1_baseline_actual = _sha1_of_file(Path(fa))
|
||||
print(f" Baseline SHA1 (actual): {sha1_baseline_actual}", flush=True)
|
||||
if fb and os.path.exists(fb):
|
||||
sha1_current_actual = _sha1_of_file(Path(fb))
|
||||
print(f" Current SHA1 (actual): {sha1_current_actual}", flush=True)
|
||||
sys.stdout.flush()
|
||||
|
||||
futures.append(ex.submit(self._diff_file_pair, fa, fb))
|
||||
for (a, b), fut in zip(pairs, futures):
|
||||
res = fut.result()
|
||||
|
||||
@ -24,6 +24,7 @@ class ActionHandlers:
|
||||
gui_app: Reference to the main GUI application instance
|
||||
"""
|
||||
self.app = gui_app
|
||||
self._configure_tree_tags()
|
||||
|
||||
def handle_scan(self):
|
||||
"""Execute scan action: find source files matching profile filters."""
|
||||
@ -351,7 +352,9 @@ class ActionHandlers:
|
||||
except Exception:
|
||||
# non-fatal: continue even if export fails
|
||||
pass
|
||||
messagebox.showinfo("Differing", f"Baseline created: {baseline_id}\nReports saved: diff.csv, diff_report.txt")
|
||||
|
||||
# Show summary dialog
|
||||
self._show_differ_summary_dialog(result, baseline_id, bdir)
|
||||
except Exception:
|
||||
self.app._set_phase('Idle')
|
||||
self.app._current_task_id = None
|
||||
@ -512,11 +515,25 @@ class ActionHandlers:
|
||||
b.sha1 = None
|
||||
|
||||
# Compare hashes
|
||||
# DEBUG: Log SHA1 comparison for empty __init__.py files
|
||||
if a and b and a.size == 0 and "__init__.py" in str(a.path):
|
||||
import sys
|
||||
print(f"\n[ACTION_HANDLERS] SHA1 comparison for {a.path}", flush=True)
|
||||
print(f" a.sha1={getattr(a, 'sha1', 'NO ATTR')}", flush=True)
|
||||
print(f" b.sha1={getattr(b, 'sha1', 'NO ATTR')}", flush=True)
|
||||
print(f" hasattr(a, 'sha1')={hasattr(a, 'sha1')}", flush=True)
|
||||
print(f" a.sha1 is truthy={bool(a.sha1) if hasattr(a, 'sha1') else False}", flush=True)
|
||||
print(f" b.sha1 is truthy={bool(b.sha1)}", flush=True)
|
||||
print(f" Match: {a.sha1 == b.sha1 if (hasattr(a, 'sha1') and a.sha1 and b.sha1) else 'N/A'}", flush=True)
|
||||
sys.stdout.flush()
|
||||
|
||||
if hasattr(a, 'sha1') and a.sha1 and b.sha1 and a.sha1 == b.sha1:
|
||||
# Identical files
|
||||
res['counts'] = {'added': 0, 'deleted': 0, 'modified': 0, 'unmodified': 1}
|
||||
else:
|
||||
# Modified file
|
||||
if a and b and a.size == 0 and "__init__.py" in str(a.path):
|
||||
print(f" → Marked as MODIFIED!", flush=True)
|
||||
res['counts'] = {'added': 0, 'deleted': 0, 'modified': 1, 'unmodified': 0}
|
||||
else:
|
||||
# Both None (shouldn't happen)
|
||||
@ -716,13 +733,20 @@ class ActionHandlers:
|
||||
is_float=True
|
||||
)
|
||||
|
||||
# Add to batch buffer: Current, Baseline, then counts/deltas
|
||||
_batch_buffer.append((current, baseline, counts.get('added',0), counts.get('deleted',0), counts.get('modified',0), counts.get('unmodified',0), delta_code, delta_comment, delta_blank, delta_func, delta_cc, delta_mi))
|
||||
# Add to batch buffer with metadata for tag application
|
||||
row_data = {
|
||||
'values': (current, baseline, counts.get('added',0), counts.get('deleted',0), counts.get('modified',0), counts.get('unmodified',0), delta_code, delta_comment, delta_blank, delta_func, delta_cc, delta_mi),
|
||||
'countings_delta': countings_delta,
|
||||
'metrics_delta': metrics_delta
|
||||
}
|
||||
_batch_buffer.append(row_data)
|
||||
|
||||
# Insert batch when buffer is full
|
||||
if len(_batch_buffer) >= _batch_size:
|
||||
for row_values in _batch_buffer:
|
||||
self.app.results_tree.insert('', 'end', values=row_values)
|
||||
for row_data in _batch_buffer:
|
||||
item_id = self.app.results_tree.insert('', 'end', values=row_data['values'])
|
||||
# Apply color tags based on deltas
|
||||
self._apply_delta_tags(item_id, row_data['countings_delta'], row_data['metrics_delta'])
|
||||
_batch_buffer.clear()
|
||||
|
||||
# Update progress (less frequent)
|
||||
@ -741,8 +765,10 @@ class ActionHandlers:
|
||||
def _on_done(all_results):
|
||||
# Flush remaining batch buffer
|
||||
if _batch_buffer:
|
||||
for row_values in _batch_buffer:
|
||||
self.app.results_tree.insert('', 'end', values=row_values)
|
||||
for row_data in _batch_buffer:
|
||||
item_id = self.app.results_tree.insert('', 'end', values=row_data['values'])
|
||||
# Apply color tags based on deltas
|
||||
self._apply_delta_tags(item_id, row_data['countings_delta'], row_data['metrics_delta'])
|
||||
_batch_buffer.clear()
|
||||
|
||||
# Compute totals
|
||||
@ -905,6 +931,52 @@ class ActionHandlers:
|
||||
|
||||
return selected.get('id')
|
||||
|
||||
def _configure_tree_tags(self):
|
||||
"""Configure Treeview tags for coloring delta values."""
|
||||
# Positive changes (increases) - green tones
|
||||
self.app.results_tree.tag_configure('positive', foreground='#007700', font=('TkDefaultFont', 9, 'bold'))
|
||||
# Negative changes (decreases) - red tones
|
||||
self.app.results_tree.tag_configure('negative', foreground='#cc0000', font=('TkDefaultFont', 9, 'bold'))
|
||||
# Zero/neutral - default
|
||||
self.app.results_tree.tag_configure('neutral', foreground='#666666')
|
||||
|
||||
def _apply_delta_tags(self, item_id, countings_delta, metrics_delta):
|
||||
"""
|
||||
Apply color tags to tree item based on delta values.
|
||||
|
||||
Args:
|
||||
item_id: Treeview item identifier
|
||||
countings_delta: dict with code/comment/blank line deltas
|
||||
metrics_delta: dict with func_count/avg_cc/mi deltas
|
||||
"""
|
||||
# Determine overall change direction for visual feedback
|
||||
tags = []
|
||||
|
||||
# Check countings deltas
|
||||
if countings_delta:
|
||||
code_delta = countings_delta.get('code_lines', 0)
|
||||
if code_delta > 0:
|
||||
tags.append('positive')
|
||||
elif code_delta < 0:
|
||||
tags.append('negative')
|
||||
|
||||
# Check metrics deltas (complexity increase is "negative", MI decrease is "negative")
|
||||
if metrics_delta:
|
||||
cc_delta = metrics_delta.get('avg_cc', 0)
|
||||
mi_delta = metrics_delta.get('mi', 0)
|
||||
|
||||
# Complexity increase is bad (red)
|
||||
if cc_delta > 0.5:
|
||||
if 'negative' not in tags:
|
||||
tags.append('negative')
|
||||
# MI decrease is bad (red)
|
||||
elif mi_delta < -5:
|
||||
if 'negative' not in tags:
|
||||
tags.append('negative')
|
||||
|
||||
if tags:
|
||||
self.app.results_tree.item(item_id, tags=tags)
|
||||
|
||||
def _gather_source_files(self, paths, allowed_exts, ignore_patterns):
|
||||
"""Gather source files from given paths."""
|
||||
files = []
|
||||
@ -943,3 +1015,161 @@ class ActionHandlers:
|
||||
"""Clear results tree."""
|
||||
for c in self.app.results_tree.get_children(""):
|
||||
self.app.results_tree.delete(c)
|
||||
|
||||
def _show_differ_summary_dialog(self, result, baseline_id, baseline_dir):
|
||||
"""Show summary dialog with differ results."""
|
||||
import subprocess
|
||||
|
||||
dlg = tk.Toplevel(self.app)
|
||||
dlg.title("Differ Summary")
|
||||
dlg.geometry("700x600")
|
||||
dlg.transient(self.app)
|
||||
|
||||
# Center dialog
|
||||
dlg.update_idletasks()
|
||||
pw = self.app.winfo_width()
|
||||
ph = self.app.winfo_height()
|
||||
px = self.app.winfo_rootx()
|
||||
py = self.app.winfo_rooty()
|
||||
dw = 700
|
||||
dh = 600
|
||||
x = px + (pw - dw) // 2
|
||||
y = py + (ph - dh) // 2
|
||||
dlg.geometry(f"{dw}x{dh}+{x}+{y}")
|
||||
|
||||
# Title
|
||||
title_frame = ttk.Frame(dlg)
|
||||
title_frame.pack(fill='x', padx=10, pady=10)
|
||||
ttk.Label(title_frame, text=f"Differ Summary - {baseline_id}",
|
||||
font=('Arial', 12, 'bold')).pack()
|
||||
|
||||
# Text widget with scrollbar for summary
|
||||
text_frame = ttk.Frame(dlg)
|
||||
text_frame.pack(fill='both', expand=True, padx=10, pady=(0, 10))
|
||||
|
||||
scrollbar = ttk.Scrollbar(text_frame)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
summary_text = tk.Text(text_frame, wrap='none', font=('Courier', 9),
|
||||
yscrollcommand=scrollbar.set, height=30, width=80)
|
||||
summary_text.pack(side='left', fill='both', expand=True)
|
||||
scrollbar.config(command=summary_text.yview)
|
||||
|
||||
# Generate summary content
|
||||
summary_lines = self._generate_summary_text(result, baseline_id)
|
||||
summary_text.insert('1.0', '\n'.join(summary_lines))
|
||||
summary_text.config(state='disabled')
|
||||
|
||||
# Store summary for clipboard
|
||||
summary_content = '\n'.join(summary_lines)
|
||||
|
||||
# Buttons frame
|
||||
btn_frame = ttk.Frame(dlg)
|
||||
btn_frame.pack(fill='x', padx=10, pady=(0, 10))
|
||||
|
||||
def copy_to_clipboard():
|
||||
self.app.clipboard_clear()
|
||||
self.app.clipboard_append(summary_content)
|
||||
messagebox.showinfo("Copied", "Summary copied to clipboard!", parent=dlg)
|
||||
|
||||
def open_baseline_folder():
|
||||
try:
|
||||
if os.path.exists(baseline_dir):
|
||||
subprocess.Popen(['explorer', baseline_dir])
|
||||
else:
|
||||
messagebox.showwarning("Not Found", f"Baseline folder not found:\n{baseline_dir}", parent=dlg)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to open folder:\n{e}", parent=dlg)
|
||||
|
||||
ttk.Button(btn_frame, text="Copy to Clipboard", command=copy_to_clipboard).pack(side='left', padx=5)
|
||||
ttk.Button(btn_frame, text="Open Baseline Folder", command=open_baseline_folder).pack(side='left', padx=5)
|
||||
ttk.Button(btn_frame, text="Close", command=dlg.destroy).pack(side='right', padx=5)
|
||||
|
||||
def _generate_summary_text(self, result, baseline_id):
|
||||
"""Generate summary text lines for differ results."""
|
||||
import datetime
|
||||
|
||||
lines = []
|
||||
lines.append("=" * 80)
|
||||
lines.append("PyUcc - Differ Summary".center(80))
|
||||
lines.append("=" * 80)
|
||||
lines.append("")
|
||||
lines.append(f"Baseline ID: {baseline_id}")
|
||||
lines.append(f"Generated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
lines.append("")
|
||||
|
||||
# Summary Statistics
|
||||
lines.append("-" * 80)
|
||||
lines.append("Summary Statistics")
|
||||
lines.append("-" * 80)
|
||||
|
||||
total = result.get('total', {})
|
||||
lines.append(f"Files Added: {total.get('added', 0):>8}")
|
||||
lines.append(f"Files Deleted: {total.get('deleted', 0):>8}")
|
||||
lines.append(f"Files Modified: {total.get('modified', 0):>8}")
|
||||
lines.append(f"Files Unmodified: {total.get('unmodified', 0):>8}")
|
||||
lines.append("─" * 30)
|
||||
total_files = sum([total.get('added', 0), total.get('deleted', 0),
|
||||
total.get('modified', 0), total.get('unmodified', 0)])
|
||||
lines.append(f"Total Files: {total_files:>8}")
|
||||
lines.append("")
|
||||
|
||||
# Code Metrics Comparison
|
||||
summary = result.get('summary', {})
|
||||
if summary:
|
||||
baseline_counts = summary.get('baseline', {}).get('countings', {})
|
||||
current_counts = summary.get('current', {}).get('countings', {})
|
||||
baseline_metrics = summary.get('baseline', {}).get('metrics', {})
|
||||
current_metrics = summary.get('current', {}).get('metrics', {})
|
||||
|
||||
lines.append("-" * 80)
|
||||
lines.append("Code Metrics Comparison")
|
||||
lines.append("-" * 80)
|
||||
lines.append(f"{'Metric':<30} {'Baseline':>15} {'Current':>15} {'Delta':>15}")
|
||||
lines.append("─" * 80)
|
||||
|
||||
def _delta_str(baseline_val, current_val):
|
||||
delta = current_val - baseline_val
|
||||
sign = "+" if delta >= 0 else ""
|
||||
return f"{sign}{delta}"
|
||||
|
||||
bc_code = baseline_counts.get('code_lines', 0)
|
||||
cc_code = current_counts.get('code_lines', 0)
|
||||
lines.append(f"{'Code Lines':<30} {bc_code:>15,} {cc_code:>15,} {_delta_str(bc_code, cc_code):>15}")
|
||||
|
||||
bc_comment = baseline_counts.get('comment_lines', 0)
|
||||
cc_comment = current_counts.get('comment_lines', 0)
|
||||
lines.append(f"{'Comment Lines':<30} {bc_comment:>15,} {cc_comment:>15,} {_delta_str(bc_comment, cc_comment):>15}")
|
||||
|
||||
bc_blank = baseline_counts.get('blank_lines', 0)
|
||||
cc_blank = current_counts.get('blank_lines', 0)
|
||||
lines.append(f"{'Blank Lines':<30} {bc_blank:>15,} {cc_blank:>15,} {_delta_str(bc_blank, cc_blank):>15}")
|
||||
|
||||
bc_physical = baseline_counts.get('physical_lines', 0)
|
||||
cc_physical = current_counts.get('physical_lines', 0)
|
||||
lines.append(f"{'Physical Lines':<30} {bc_physical:>15,} {cc_physical:>15,} {_delta_str(bc_physical, cc_physical):>15}")
|
||||
|
||||
lines.append("")
|
||||
|
||||
bm_func = baseline_metrics.get('total_func_count', 0)
|
||||
cm_func = current_metrics.get('total_func_count', 0)
|
||||
lines.append(f"{'Function Count':<30} {bm_func:>15,} {cm_func:>15,} {_delta_str(bm_func, cm_func):>15}")
|
||||
|
||||
bm_avgcc = baseline_metrics.get('avg_avg_cc', 0.0)
|
||||
cm_avgcc = current_metrics.get('avg_avg_cc', 0.0)
|
||||
delta_avgcc = cm_avgcc - bm_avgcc
|
||||
sign_cc = "+" if delta_avgcc >= 0 else ""
|
||||
lines.append(f"{'Avg Cyclomatic Complexity':<30} {bm_avgcc:>15.2f} {cm_avgcc:>15.2f} {sign_cc}{delta_avgcc:>14.2f}")
|
||||
|
||||
bm_mi = baseline_metrics.get('avg_mi', 0.0)
|
||||
cm_mi = current_metrics.get('avg_mi', 0.0)
|
||||
delta_mi = cm_mi - bm_mi
|
||||
sign_mi = "+" if delta_mi >= 0 else ""
|
||||
lines.append(f"{'Maintainability Index':<30} {bm_mi:>15.2f} {cm_mi:>15.2f} {sign_mi}{delta_mi:>14.2f}")
|
||||
lines.append("")
|
||||
|
||||
lines.append("=" * 80)
|
||||
lines.append("End of Summary".center(80))
|
||||
lines.append("=" * 80)
|
||||
|
||||
return lines
|
||||
|
||||
@ -144,22 +144,24 @@ class DiffViewer(tk.Toplevel):
|
||||
def _configure_tags(self):
|
||||
"""Configura i tag per evidenziare le differenze."""
|
||||
# File A: linee cancellate in rosso più visibile
|
||||
self.text_a.tag_configure('delete', background='#ffb3b3', foreground='#000000')
|
||||
self.text_a.tag_configure('replace', background='#fff799', foreground='#000000')
|
||||
self.text_a.tag_configure('delete', background='#ffd0d0', foreground='#000000')
|
||||
self.text_a.tag_configure('replace', background='#ffffb0', foreground='#000000')
|
||||
self.text_a.tag_configure('equal', background='#ffffff')
|
||||
self.text_a.tag_configure('char_diff', background='#ff6666', foreground='#000000', font=('Courier', 9, 'bold'))
|
||||
self.text_a.tag_configure('char_diff', background='#ff8888', foreground='#000000', font=('Courier', 9, 'bold'))
|
||||
|
||||
# File B: linee aggiunte in verde più visibile
|
||||
self.text_b.tag_configure('insert', background='#b3ffb3', foreground='#000000')
|
||||
self.text_b.tag_configure('replace', background='#fff799', foreground='#000000')
|
||||
self.text_b.tag_configure('insert', background='#d0ffd0', foreground='#000000')
|
||||
self.text_b.tag_configure('replace', background='#ffffb0', foreground='#000000')
|
||||
self.text_b.tag_configure('equal', background='#ffffff')
|
||||
self.text_b.tag_configure('char_diff', background='#66ff66', foreground='#000000', font=('Courier', 9, 'bold'))
|
||||
self.text_b.tag_configure('char_diff', background='#88ff88', foreground='#000000', font=('Courier', 9, 'bold'))
|
||||
|
||||
# Numeri di riga
|
||||
self.text_a.tag_configure('linenum', foreground='#666666', font=('Courier', 8, 'bold'))
|
||||
self.text_b.tag_configure('linenum', foreground='#666666', font=('Courier', 8, 'bold'))
|
||||
self.text_a.tag_configure('linenum_modified', foreground='#cc6600', font=('Courier', 8, 'bold'))
|
||||
self.text_b.tag_configure('linenum_modified', foreground='#cc6600', font=('Courier', 8, 'bold'))
|
||||
self.text_a.tag_configure('linenum', foreground='#888888', font=('Courier', 8))
|
||||
self.text_b.tag_configure('linenum', foreground='#888888', font=('Courier', 8))
|
||||
self.text_a.tag_configure('linenum_deleted', foreground='#dd0000', font=('Courier', 8, 'bold'))
|
||||
self.text_b.tag_configure('linenum_added', foreground='#00aa00', font=('Courier', 8, 'bold'))
|
||||
self.text_a.tag_configure('linenum_modified', foreground='#cc8800', font=('Courier', 8, 'bold'))
|
||||
self.text_b.tag_configure('linenum_modified', foreground='#cc8800', font=('Courier', 8, 'bold'))
|
||||
|
||||
def _populate_content(self):
|
||||
"""Popola i text widget con il contenuto evidenziando le differenze."""
|
||||
@ -174,10 +176,17 @@ class DiffViewer(tk.Toplevel):
|
||||
total_deleted = 0
|
||||
total_modified = 0
|
||||
|
||||
# Track minimap blocks with actual display line positions
|
||||
self.minimap_blocks = []
|
||||
current_display_line = 0
|
||||
|
||||
# Processa ogni blocco di diff
|
||||
for tag, i1, i2, j1, j2 in self.diff_blocks:
|
||||
block_start_line = current_display_line
|
||||
|
||||
if tag == 'equal':
|
||||
# Linee uguali
|
||||
num_lines = i2 - i1
|
||||
for i in range(i1, i2):
|
||||
line_num = f"{i+1:4d} | "
|
||||
self.text_a.insert('end', line_num, 'linenum')
|
||||
@ -186,33 +195,44 @@ class DiffViewer(tk.Toplevel):
|
||||
line_num = f"{j+1:4d} | "
|
||||
self.text_b.insert('end', line_num, 'linenum')
|
||||
self.text_b.insert('end', self.lines_b[j], 'equal')
|
||||
|
||||
current_display_line += num_lines
|
||||
self.minimap_blocks.append((tag, block_start_line, current_display_line))
|
||||
|
||||
elif tag == 'delete':
|
||||
# Linee cancellate (solo in A)
|
||||
total_deleted += (i2 - i1)
|
||||
num_lines = i2 - i1
|
||||
total_deleted += num_lines
|
||||
for i in range(i1, i2):
|
||||
line_num = f"{i+1:4d} - "
|
||||
self.text_a.insert('end', line_num, 'linenum')
|
||||
self.text_a.insert('end', line_num, 'linenum_deleted')
|
||||
self.text_a.insert('end', self.lines_a[i], 'delete')
|
||||
# Aggiungi linee vuote in B per mantenere allineamento
|
||||
for _ in range(i2 - i1):
|
||||
for _ in range(num_lines):
|
||||
self.text_b.insert('end', " ~ \n", 'equal')
|
||||
|
||||
current_display_line += num_lines
|
||||
self.minimap_blocks.append((tag, block_start_line, current_display_line))
|
||||
|
||||
elif tag == 'insert':
|
||||
# Linee aggiunte (solo in B)
|
||||
total_added += (j2 - j1)
|
||||
num_lines = j2 - j1
|
||||
total_added += num_lines
|
||||
# Aggiungi linee vuote in A per mantenere allineamento
|
||||
for _ in range(j2 - j1):
|
||||
for _ in range(num_lines):
|
||||
self.text_a.insert('end', " ~ \n", 'equal')
|
||||
for j in range(j1, j2):
|
||||
line_num = f"{j+1:4d} + "
|
||||
self.text_b.insert('end', line_num, 'linenum')
|
||||
self.text_b.insert('end', line_num, 'linenum_added')
|
||||
self.text_b.insert('end', self.lines_b[j], 'insert')
|
||||
|
||||
current_display_line += num_lines
|
||||
self.minimap_blocks.append((tag, block_start_line, current_display_line))
|
||||
|
||||
elif tag == 'replace':
|
||||
# Linee modificate - evidenzia anche differenze a livello carattere
|
||||
total_modified += max(i2 - i1, j2 - j1)
|
||||
max_lines = max(i2 - i1, j2 - j1)
|
||||
total_modified += max_lines
|
||||
|
||||
for k in range(max_lines):
|
||||
# File A
|
||||
@ -252,6 +272,9 @@ class DiffViewer(tk.Toplevel):
|
||||
self.text_b.insert('end', self.lines_b[j1 + k], 'replace')
|
||||
else:
|
||||
self.text_b.insert('end', " ~ \n", 'equal')
|
||||
|
||||
current_display_line += max_lines
|
||||
self.minimap_blocks.append((tag, block_start_line, current_display_line))
|
||||
|
||||
self.text_a.config(state='disabled')
|
||||
self.text_b.config(state='disabled')
|
||||
@ -268,7 +291,13 @@ class DiffViewer(tk.Toplevel):
|
||||
"""Disegna la minimappa con rappresentazione visiva delle differenze."""
|
||||
self.minimap.delete('all')
|
||||
|
||||
total_lines = max(len(self.lines_a), len(self.lines_b), 1)
|
||||
# Use actual display lines (including padding lines)
|
||||
if not hasattr(self, 'minimap_blocks') or not self.minimap_blocks:
|
||||
return
|
||||
|
||||
# Total display lines is the end of the last block
|
||||
total_display_lines = self.minimap_blocks[-1][2] if self.minimap_blocks else 1
|
||||
|
||||
canvas_height = self.minimap.winfo_height()
|
||||
if canvas_height <= 1:
|
||||
canvas_height = 600 # Default se non ancora renderizzato
|
||||
@ -278,28 +307,24 @@ class DiffViewer(tk.Toplevel):
|
||||
canvas_width = 50
|
||||
|
||||
# Calcola altezza per ogni linea nella minimappa
|
||||
line_height = max(1, canvas_height / total_lines)
|
||||
line_height = canvas_height / max(total_display_lines, 1)
|
||||
|
||||
current_line = 0
|
||||
|
||||
# Disegna ogni blocco di diff sulla minimappa
|
||||
for tag, i1, i2, j1, j2 in self.diff_blocks:
|
||||
num_lines = max(i2 - i1, j2 - j1)
|
||||
y1 = current_line * line_height
|
||||
y2 = (current_line + num_lines) * line_height
|
||||
# Disegna ogni blocco usando le posizioni di visualizzazione effettive
|
||||
for tag, start_line, end_line in self.minimap_blocks:
|
||||
y1 = start_line * line_height
|
||||
y2 = end_line * line_height
|
||||
|
||||
# Colore basato sul tipo di differenza
|
||||
if tag == 'delete':
|
||||
color = '#ff9999' # Rosso
|
||||
color = '#ff6666' # Rosso più intenso
|
||||
elif tag == 'insert':
|
||||
color = '#99ff99' # Verde
|
||||
color = '#66ff66' # Verde più intenso
|
||||
elif tag == 'replace':
|
||||
color = '#ffff99' # Giallo
|
||||
color = '#ffff66' # Giallo più intenso
|
||||
else:
|
||||
color = '#e8e8e8' # Grigio chiaro per linee uguali
|
||||
color = '#f0f0f0' # Grigio chiaro per linee uguali
|
||||
|
||||
self.minimap.create_rectangle(0, y1, canvas_width, y2, fill=color, outline='')
|
||||
current_line += num_lines
|
||||
|
||||
# Bind click sulla minimappa per navigare
|
||||
self.minimap.bind('<Button-1>', self._on_minimap_click)
|
||||
@ -329,12 +354,15 @@ class DiffViewer(tk.Toplevel):
|
||||
|
||||
def _on_minimap_click(self, event):
|
||||
"""Gestisce il click sulla minimappa per navigare."""
|
||||
canvas_height = self.minimap.winfo_height()
|
||||
total_lines = max(len(self.lines_a), len(self.lines_b), 1)
|
||||
if not hasattr(self, 'minimap_blocks') or not self.minimap_blocks:
|
||||
return
|
||||
|
||||
# Calcola la linea cliccata
|
||||
canvas_height = self.minimap.winfo_height()
|
||||
total_display_lines = self.minimap_blocks[-1][2] if self.minimap_blocks else 1
|
||||
|
||||
# Calcola la linea cliccata basandosi sulle linee di visualizzazione effettive
|
||||
clicked_ratio = event.y / canvas_height
|
||||
target_line = int(clicked_ratio * total_lines)
|
||||
target_line = int(clicked_ratio * total_display_lines) + 1
|
||||
|
||||
# Scroll a quella linea
|
||||
self.text_a.see(f"{target_line}.0")
|
||||
|
||||
@ -202,23 +202,39 @@ def generate_differ_report(result, profile_config, baseline_id, output_path):
|
||||
if baseline_name != current_name:
|
||||
lines.append(f" (was: {baseline_name})")
|
||||
|
||||
# Always show countings delta, even if all zeros (file was modified at byte level)
|
||||
if cd:
|
||||
delta_code = cd.get('code_lines', 0)
|
||||
delta_comment = cd.get('comment_lines', 0)
|
||||
delta_blank = cd.get('blank_lines', 0)
|
||||
sign_code = "+" if delta_code >= 0 else ""
|
||||
sign_comment = "+" if delta_comment >= 0 else ""
|
||||
sign_blank = "+" if delta_blank >= 0 else ""
|
||||
lines.append(f" Code: {sign_code}{delta_code:>6} Comment: {sign_comment}{delta_comment:>6} Blank: {sign_blank}{delta_blank:>6}")
|
||||
|
||||
# Check if there are any non-zero deltas
|
||||
has_counting_changes = any([delta_code != 0, delta_comment != 0, delta_blank != 0])
|
||||
|
||||
if has_counting_changes:
|
||||
sign_code = "+" if delta_code >= 0 else ""
|
||||
sign_comment = "+" if delta_comment >= 0 else ""
|
||||
sign_blank = "+" if delta_blank >= 0 else ""
|
||||
lines.append(f" Code: {sign_code}{delta_code:>6} Comment: {sign_comment}{delta_comment:>6} Blank: {sign_blank}{delta_blank:>6}")
|
||||
else:
|
||||
lines.append(f" Code: + 0 Comment: + 0 Blank: + 0")
|
||||
|
||||
# Always show metrics delta, even if all zeros
|
||||
if md:
|
||||
delta_func = md.get('func_count', 0)
|
||||
delta_avgcc = md.get('avg_cc', 0.0)
|
||||
delta_mi = md.get('mi', 0.0)
|
||||
sign_func = "+" if delta_func >= 0 else ""
|
||||
sign_avgcc = "+" if delta_avgcc >= 0 else ""
|
||||
sign_mi = "+" if delta_mi >= 0 else ""
|
||||
lines.append(f" Functions: {sign_func}{delta_func:>4} Avg CC: {sign_avgcc}{delta_avgcc:>6.2f} MI: {sign_mi}{delta_mi:>6.2f}")
|
||||
|
||||
# Check if there are any non-zero deltas
|
||||
has_metric_changes = any([delta_func != 0, abs(delta_avgcc) > 0.01, abs(delta_mi) > 0.01])
|
||||
|
||||
if has_metric_changes:
|
||||
sign_func = "+" if delta_func >= 0 else ""
|
||||
sign_avgcc = "+" if delta_avgcc >= 0 else ""
|
||||
sign_mi = "+" if delta_mi >= 0 else ""
|
||||
lines.append(f" Functions: {sign_func}{delta_func:>4} Avg CC: {sign_avgcc}{delta_avgcc:>6.2f} MI: {sign_mi}{delta_mi:>6.2f}")
|
||||
else:
|
||||
lines.append(f" Functions: + 0 Avg CC: + 0.00 MI: + 0.00")
|
||||
lines.append("")
|
||||
|
||||
# Complete File Table
|
||||
|
||||
Loading…
Reference in New Issue
Block a user