239 lines
8.8 KiB
Markdown
239 lines
8.8 KiB
Markdown
# Diff Viewer - Visualizzazione Side-by-Side delle Differenze
|
|
|
|
## Descrizione
|
|
|
|
Il Diff Viewer è una nuova funzionalità che permette di visualizzare le differenze tra due versioni di un file in modo affiancato, con evidenziazione delle modifiche e una minimappa per navigare rapidamente tra le differenze.
|
|
|
|
## Come Utilizzare
|
|
|
|
### Accesso dalla GUI
|
|
|
|
1. Esegui un'operazione di **Differing** dalla GUI principale
|
|
2. Nella tabella dei risultati, **doppio-click** su qualsiasi riga per aprire il Diff Viewer
|
|
3. La finestra mostrerà il file della baseline (a sinistra) e il file corrente (a destra) affiancati
|
|
|
|
### Funzionalità
|
|
|
|
#### Visualizzazione Affiancata
|
|
- **Colonna Sinistra**: File dalla baseline (versione precedente)
|
|
- **Colonna Centrale**: Minimappa con rappresentazione visiva delle differenze
|
|
- **Colonna Destra**: File corrente (versione attuale)
|
|
|
|
#### Evidenziazione delle Differenze
|
|
- 🔴 **Rosso**: Linee **cancellate** (presenti solo nella baseline)
|
|
- 🟢 **Verde**: Linee **aggiunte** (presenti solo nella versione corrente)
|
|
- 🟡 **Giallo**: Linee **modificate** (diverse tra baseline e corrente)
|
|
- ⚪ **Bianco**: Linee **uguali** (nessuna modifica)
|
|
|
|
#### Numeri di Riga
|
|
- Ogni riga mostra il numero di linea originale
|
|
- Simboli speciali:
|
|
- `#### | ` = Linea uguale
|
|
- `#### - ` = Linea cancellata
|
|
- `#### + ` = Linea aggiunta
|
|
- `#### ~ ` = Linea modificata
|
|
- ` ~ ` = Linea vuota (per allineamento)
|
|
|
|
#### Minimappa
|
|
- Visualizzazione compatta di tutte le differenze
|
|
- I colori corrispondono al tipo di modifica
|
|
- **Click sulla minimappa** per navigare rapidamente a una sezione specifica
|
|
- Il rettangolo blu indica la porzione attualmente visibile
|
|
|
|
#### Navigazione
|
|
- **Scroll sincronizzato**: entrambi i file scorrono insieme
|
|
- **Mouse wheel**: scorri verticalmente
|
|
- **Frecce su/giù**: navigazione linea per linea
|
|
- **Scrollbar condivisa**: controllo centralizzato dello scroll verticale
|
|
- **Scrollbar orizzontali**: separate per ogni file
|
|
|
|
#### Informazioni
|
|
- **Barra info** in basso:
|
|
- Numero di linee: baseline → corrente
|
|
- Conteggio linee: aggiunte, cancellate, modificate
|
|
|
|
### Casi d'Uso
|
|
|
|
#### File Modificato
|
|
```
|
|
Baseline (test.py) Current (test.py)
|
|
------------------ -----------------
|
|
1 | def hello(): 1 | def hello():
|
|
2 | ~ print("Hi") 2 + ~ # nuovo
|
|
3 | ~ return 42 3 + ~ print("Hi")
|
|
4 | ~ return 42
|
|
```
|
|
|
|
#### File Cancellato
|
|
- Baseline: mostra il contenuto del file
|
|
- Current: mostra "N/A" (file non esiste più)
|
|
|
|
#### File Aggiunto
|
|
- Baseline: mostra "N/A" (file non esisteva)
|
|
- Current: mostra il contenuto del nuovo file
|
|
|
|
## Architettura Tecnica
|
|
|
|
### File: `pyucc/gui/diff_viewer.py`
|
|
|
|
#### Classe `DiffViewer(tk.Toplevel)`
|
|
Finestra modale per la visualizzazione delle differenze.
|
|
|
|
**Parametri del costruttore:**
|
|
- `parent`: Widget parent (finestra principale)
|
|
- `file_a_path`: Path del file baseline (può essere None)
|
|
- `file_b_path`: Path del file corrente (può essere None)
|
|
- `title_a`: Titolo per il pannello sinistro (default: "Baseline")
|
|
- `title_b`: Titolo per il pannello destro (default: "Current")
|
|
|
|
**Metodi principali:**
|
|
- `_load_file(filepath)`: Carica il contenuto di un file
|
|
- `_compute_diff_blocks()`: Calcola i blocchi di differenze usando `difflib.SequenceMatcher`
|
|
- `_build_ui()`: Costruisce l'interfaccia utente
|
|
- `_populate_content()`: Popola i text widget con evidenziazione
|
|
- `_draw_minimap()`: Disegna la minimappa delle differenze
|
|
- `_setup_scroll_sync()`: Configura la sincronizzazione dello scroll
|
|
|
|
#### Funzione Helper `show_diff_viewer()`
|
|
Funzione di convenienza per aprire il diff viewer.
|
|
|
|
```python
|
|
from pyucc.gui.diff_viewer import show_diff_viewer
|
|
|
|
show_diff_viewer(parent, file_a_path, file_b_path,
|
|
title_a="Baseline", title_b="Current")
|
|
```
|
|
|
|
### Integrazione con GUI Principale
|
|
|
|
#### File: `pyucc/gui/gui.py`
|
|
|
|
**Modifiche apportate:**
|
|
|
|
1. **Tracking della modalità corrente** (`_current_mode`):
|
|
- `'scan'`: Modalità scanning
|
|
- `'countings'`: Modalità conteggi
|
|
- `'metrics'`: Modalità metriche
|
|
- `'differ'`: Modalità differ (abilita double-click)
|
|
|
|
2. **Memorizzazione dei path root**:
|
|
- `_differ_baseline_root`: Path root della baseline
|
|
- `_differ_current_root`: Path root del progetto corrente
|
|
|
|
3. **Event handler per double-click**:
|
|
```python
|
|
def _on_results_double_click(self, event):
|
|
# Verifica modalità differ
|
|
# Ottiene file dalla riga selezionata
|
|
# Costruisce path completi
|
|
# Apre DiffViewer
|
|
```
|
|
|
|
4. **Binding**:
|
|
```python
|
|
self.results_tree.bind('<Double-Button-1>',
|
|
self._on_results_double_click)
|
|
```
|
|
|
|
## Algoritmo di Diff
|
|
|
|
Il diff viewer utilizza `difflib.SequenceMatcher` di Python per calcolare le differenze:
|
|
|
|
1. **Caricamento file**: Legge entrambi i file in liste di linee
|
|
2. **Calcolo differenze**: `SequenceMatcher.get_opcodes()` restituisce blocchi:
|
|
- `'equal'`: Blocco di linee uguali
|
|
- `'delete'`: Linee presenti solo in A (baseline)
|
|
- `'insert'`: Linee presenti solo in B (corrente)
|
|
- `'replace'`: Linee diverse tra A e B
|
|
3. **Rendering**: Ogni blocco viene colorato appropriatamente
|
|
4. **Allineamento**: Linee vuote (`~`) mantengono l'allineamento visivo
|
|
|
|
## Limitazioni e Note
|
|
|
|
### Limitazioni Attuali
|
|
- ⚠️ La finestra è modale (blocca interazione con finestra principale)
|
|
- ⚠️ Non supporta diff a livello di carattere (solo linee intere)
|
|
- ⚠️ File molto grandi (>10k linee) potrebbero rallentare l'interfaccia
|
|
- ⚠️ Encoding: assume UTF-8 con fallback a ignore errors
|
|
|
|
### Gestione Errori
|
|
- Se un file non esiste, viene mostrato come vuoto
|
|
- Se entrambi i file non esistono, viene mostrato un warning
|
|
- Errori di lettura file vengono gestiti silenziosamente
|
|
|
|
### Performance
|
|
- Ottimizzato per file fino a ~5000 linee
|
|
- La minimappa usa rendering canvas nativo (veloce)
|
|
- Scroll sincronizzato con evento batching
|
|
|
|
## Estensioni Future Possibili
|
|
|
|
1. **Diff inline**: Evidenziare caratteri specifici modificati dentro una linea
|
|
2. **Navigazione tra differenze**: Bottoni "Previous/Next Difference"
|
|
3. **Merge tool**: Permettere di selezionare quale versione tenere
|
|
4. **Export diff**: Salvare il diff in formato unified o patch
|
|
5. **Fold equal blocks**: Nascondere blocchi di linee uguali lunghi
|
|
6. **Syntax highlighting**: Colorazione sintassi del linguaggio
|
|
7. **Line numbers toggle**: Nascondere/mostrare numeri di riga
|
|
8. **Search**: Cercare testo all'interno del diff
|
|
|
|
## Test
|
|
|
|
Per testare manualmente il diff viewer:
|
|
|
|
```bash
|
|
python tests/test_diff_viewer_manual.py
|
|
```
|
|
|
|
Questo script crea due file temporanei con differenze e apre il diff viewer.
|
|
|
|
## Esempio di Utilizzo Programmatico
|
|
|
|
```python
|
|
import tkinter as tk
|
|
from pyucc.gui.diff_viewer import show_diff_viewer
|
|
|
|
# Crea finestra principale
|
|
root = tk.Tk()
|
|
root.withdraw()
|
|
|
|
# Apri diff viewer
|
|
show_diff_viewer(
|
|
root,
|
|
file_a_path="/path/to/baseline/file.py",
|
|
file_b_path="/path/to/current/file.py",
|
|
title_a="Version 1.0",
|
|
title_b="Version 2.0"
|
|
)
|
|
|
|
root.mainloop()
|
|
```
|
|
|
|
## Screenshot delle Funzionalità
|
|
|
|
### Layout Principale
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Baseline: file.py Diff Map Current: file.py │
|
|
├───────────────────────────┬───────┬─────────────────────────┤
|
|
│ │ │ │
|
|
│ 1 | def hello(): │ ████ │ 1 | def hello(): │
|
|
│ 2 | ~ print("Hi") │ ████ │ 2 + ~ # comment │
|
|
│ 3 | ~ return 42 │ ████ │ 3 + ~ print("Hi") │
|
|
│ │ ████ │ 4 | ~ return 42 │
|
|
│ 4 | def goodbye(): │ ──── │ 5 | def goodbye(): │
|
|
│ 5 | ~ print("Bye") │ ──── │ 6 | ~ print("Bye") │
|
|
│ │ ████ │ 7 + def new(): │
|
|
│ │ ████ │ 8 + ~ pass │
|
|
│ │ │ │
|
|
├───────────────────────────┴───────┴─────────────────────────┤
|
|
│ Lines: 5 → 8 | Added: 3 Deleted: 0 Modified: 2 [Close]│
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
Legenda colori:
|
|
- `████` Rosso = Linee cancellate
|
|
- `████` Verde = Linee aggiunte
|
|
- `████` Giallo = Linee modificate
|
|
- `────` Grigio = Linee uguali
|