melhore o ux e faca outras melhorias
melhoria na ux:
Layout mais organizado e profissional
Barras de rolagem sincronizadas entre os dois textos
Melhor organização dos componentes
Barra de progresso que mostra o andamento da comparação
Contagem de arquivos processados
Comparação em thread separada para não travar a interface
Mensagens mais claras e informativas
Feedback visual consistente
Navegação mais intuitiva
quero o codigo COMPLETO

import os
import tkinter as tk
from tkinter import filedialog, ttk
import difflib

IGNORAR_TAGS = ['@JRDoc_EN', '@JRDoc_PT']

def listar_arquivos(pasta):
    arquivos = {}
    for raiz, _, files in os.walk(pasta):
        for nome in files:
            if nome.endswith(".java"):
                caminho = os.path.join(raiz, nome)
                relativo = os.path.relpath(caminho, pasta)
                arquivos[relativo] = caminho
    return arquivos

def ler_linhas_filtradas(caminho):
    try:
        with open(caminho, encoding='utf-8', errors='ignore') as f:
            return [
                linha.rstrip('\n') for linha in f.readlines()
                if not any(tag in linha for tag in IGNORAR_TAGS)
            ]
    except Exception as e:
        return [f"<Erro ao ler {caminho}: {e}>"]

class App:
    def __init__(self, root):
        self.root = root
        self.root.title("Comparador Visual de Arquivos Java")
        self.pasta1 = ""
        self.pasta2 = ""
        self.arquivo_atual = ""
        self.setup_interface()

    def setup_interface(self):
        topo = ttk.Frame(self.root)
        topo.pack(pady=5, fill=tk.X)

        ttk.Button(topo, text="Selecionar Pasta Original", command=self.selecionar_pasta1).pack(side=tk.LEFT, padx=5)
        ttk.Button(topo, text="Selecionar Pasta Modificada", command=self.selecionar_pasta2).pack(side=tk.LEFT, padx=5)
        ttk.Button(topo, text="Exportar Diferença Atual", command=self.exportar_diff).pack(side=tk.LEFT, padx=5)
        ttk.Button(topo, text="Exportar TODAS Diferenças", command=self.exportar_tudo).pack(side=tk.LEFT, padx=5)

        self.status = ttk.Label(self.root, text="Selecione duas pastas.")
        self.status.pack()

        self.lista = tk.Listbox(self.root, width=80)
        self.lista.pack(pady=5)
        self.lista.bind("<<ListboxSelect>>", self.visualizar_diff)

        self.frame_top = ttk.Frame(self.root)
        self.frame_top.pack(fill=tk.X)

        self.lbl_modificadas = tk.Text(self.frame_top, height=8, wrap=tk.NONE, bg="#222", fg="#eee", font=("Consolas", 10, "bold"))
        self.lbl_modificadas.pack(fill=tk.X, padx=5, pady=5)

        painel = ttk.Frame(self.root)
        painel.pack(fill=tk.BOTH, expand=True)

        self.txt_esq = tk.Text(painel, wrap=tk.NONE, bg="#1e1e1e", fg="#d4d4d4", font=("Consolas", 11))
        self.txt_esq.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.txt_dir = tk.Text(painel, wrap=tk.NONE, bg="#1e1e1e", fg="#d4d4d4", font=("Consolas", 11))
        self.txt_dir.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.txt_esq.tag_config('removido', background="#ff4444", foreground="#ffffff", font=("Consolas", 11, "bold"))
        self.txt_dir.tag_config('adicionado', background="#44ff44", foreground="#000000", font=("Consolas", 11, "bold"))
        self.txt_esq.tag_config('alterado', background="#ffff66", foreground="#000000", font=("Consolas", 11, "bold"))
        self.txt_dir.tag_config('alterado', background="#ffff66", foreground="#000000", font=("Consolas", 11, "bold"))

        self.lbl_modificadas.tag_config('diff_add', background="#44ff44", foreground="#000000", font=("Consolas", 10, "bold"))
        self.lbl_modificadas.tag_config('diff_remove', background="#ff4444", foreground="#ffffff", font=("Consolas", 10, "bold"))

    def selecionar_pasta1(self):
        self.pasta1 = filedialog.askdirectory(title="Pasta Original")
        self.comparar()

    def selecionar_pasta2(self):
        self.pasta2 = filedialog.askdirectory(title="Pasta Modificada")
        self.comparar()

    def comparar(self):
        if not (self.pasta1 and self.pasta2):
            return

        self.arqs1 = listar_arquivos(self.pasta1)
        self.arqs2 = listar_arquivos(self.pasta2)

        comuns = sorted(set(self.arqs1) & set(self.arqs2))
        adicionados = sorted(set(self.arqs2) - set(self.arqs1))
        removidos = sorted(set(self.arqs1) - set(self.arqs2))

        self.lista.delete(0, tk.END)
        self.modificados = []

        for nome in comuns:
            l1 = ler_linhas_filtradas(self.arqs1[nome])
            l2 = ler_linhas_filtradas(self.arqs2[nome])
            if l1 != l2:
                self.lista.insert(tk.END, f"[MODIFICADO] {nome}")
                self.modificados.append(nome)

        for nome in adicionados:
            self.lista.insert(tk.END, f"[ADICIONADO] {nome}")
            self.modificados.append(nome)

        for nome in removidos:
            self.lista.insert(tk.END, f"[REMOVIDO]   {nome}")
            self.modificados.append(nome)

        self.status.config(text="Arquivos modificados listados.")
        self.lbl_modificadas.delete("1.0", tk.END)
        self.txt_esq.delete("1.0", tk.END)
        self.txt_dir.delete("1.0", tk.END)

    def visualizar_diff(self, event):
        sel = self.lista.curselection()
        if not sel:
            return

        linha = self.lista.get(sel[0])
        tipo, nome = linha.split("]", 1)
        tipo = tipo.strip("[ ")
        nome = nome.strip()
        self.arquivo_atual = nome

        self.lbl_modificadas.delete("1.0", tk.END)
        self.txt_esq.delete("1.0", tk.END)
        self.txt_dir.delete("1.0", tk.END)

        l1 = ler_linhas_filtradas(self.arqs1.get(nome)) if self.arqs1.get(nome) else []
        l2 = ler_linhas_filtradas(self.arqs2.get(nome)) if self.arqs2.get(nome) else []

        matcher = difflib.SequenceMatcher(None, l1, l2)

        linha_esq = 1
        linha_dir = 1
        removidas = []
        adicionadas = []

        for tag, i1, i2, j1, j2 in matcher.get_opcodes():
            if tag == "equal":
                for i in range(i2 - i1):
                    self.txt_esq.insert(tk.END, f"{linha_esq:4}: {l1[i1 + i]}\n")
                    self.txt_dir.insert(tk.END, f"{linha_dir:4}: {l2[j1 + i]}\n")
                    linha_esq += 1
                    linha_dir += 1
            elif tag == "replace":
                for i in range(i2 - i1):
                    linha = l1[i1 + i]
                    self.txt_esq.insert(tk.END, f"{linha_esq:4}: {linha}\n", 'alterado')
                    removidas.append(f"Linha {linha_esq}: {linha}")
                    linha_esq += 1
                for i in range(j2 - j1):
                    linha = l2[j1 + i]
                    self.txt_dir.insert(tk.END, f"{linha_dir:4}: {linha}\n", 'alterado')
                    adicionadas.append(f"Linha {linha_dir}: {linha}")
                    linha_dir += 1
            elif tag == "delete":
                for i in range(i2 - i1):
                    linha = l1[i1 + i]
                    self.txt_esq.insert(tk.END, f"{linha_esq:4}: {linha}\n", 'removido')
                    removidas.append(f"Linha {linha_esq}: {linha}")
                    linha_esq += 1
                for _ in range(i2 - i1):
                    self.txt_dir.insert(tk.END, "\n")
                    linha_dir += 1
            elif tag == "insert":
                for _ in range(j2 - j1):
                    self.txt_esq.insert(tk.END, "\n")
                    linha_esq += 1
                for i in range(j2 - j1):
                    linha = l2[j1 + i]
                    self.txt_dir.insert(tk.END, f"{linha_dir:4}: {linha}\n", 'adicionado')
                    adicionadas.append(f"Linha {linha_dir}: {linha}")
                    linha_dir += 1

        if removidas:
            self.lbl_modificadas.insert(tk.END, "Linhas REMOVIDAS:\n", 'diff_remove')
            self.lbl_modificadas.insert(tk.END, "\n".join(removidas) + "\n\n", 'diff_remove')
        if adicionadas:
            self.lbl_modificadas.insert(tk.END, "Linhas ADICIONADAS:\n", 'diff_add')
            self.lbl_modificadas.insert(tk.END, "\n".join(adicionadas), 'diff_add')

    def exportar_diff(self):
        if not self.arquivo_atual:
            self.status.config(text="Nenhum arquivo selecionado.")
            return

        nome = self.arquivo_atual
        caminho1 = self.arqs1.get(nome)
        caminho2 = self.arqs2.get(nome)
        l1 = ler_linhas_filtradas(caminho1) if caminho1 else []
        l2 = ler_linhas_filtradas(caminho2) if caminho2 else []

        diff = difflib.unified_diff(l1, l2, fromfile='original/' + nome, tofile='modificado/' + nome, lineterm='')
        with open(f"diff_{os.path.basename(nome)}.txt", 'w', encoding='utf-8') as f:
            f.write("\n".join(diff))
        self.status.config(text=f"Exportado: diff_{os.path.basename(nome)}.txt")

    def exportar_tudo(self):
        with open("diff_all.txt", 'w', encoding='utf-8') as saida:
            for nome in self.modificados:
                caminho1 = self.arqs1.get(nome)
                caminho2 = self.arqs2.get(nome)
                l1 = ler_linhas_filtradas(caminho1) if caminho1 else []
                l2 = ler_linhas_filtradas(caminho2) if caminho2 else []

                diff = difflib.unified_diff(l1, l2, fromfile='original/' + nome, tofile='modificado/' + nome, lineterm='')
                saida.write("\n".join(diff) + "\n\n")
        self.status.config(text="Exportado: diff_all.txt")

if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()