Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save eros18123/1d38190ac5e560065c73311cea755277 to your computer and use it in GitHub Desktop.
Save eros18123/1d38190ac5e560065c73311cea755277 to your computer and use it in GitHub Desktop.
Pesquisa no editor (Search in editor) 2
# -*- coding: utf-8 -*-
from aqt import gui_hooks
from aqt.editor import Editor
from aqt.qt import (
QWidget,
QLineEdit,
QShortcut,
QKeySequence,
QHBoxLayout,
QPushButton,
QFont,
QSplitter,
QTimer,
QLabel, ### NOVO/MODIFICADO ### - Importar QLabel
)
import re
# --- Constante para o Destaque ---
HIGHLIGHT_SPAN_START = '<span style="background-color: yellow;">'
HIGHLIGHT_SPAN_END = '</span>'
# Padrão de regex para encontrar e remover o span de destaque
CLEANUP_PATTERN = re.compile(re.escape(HIGHLIGHT_SPAN_START) + r'(.*?)' + re.escape(HIGHLIGHT_SPAN_END), re.DOTALL | re.IGNORECASE)
# --- Funções de Lógica ---
def _build_accent_insensitive_pattern(text: str) -> str:
"""
Cria um padrão de regex que trata vogais acentuadas e não acentuadas
como equivalentes e escapa outros caracteres para a busca.
"""
if not text:
return ""
char_map = {
'a': '[aáàâã]', 'e': '[eéê]', 'i': '[ií]', 'o': '[oóôõ]', 'u': '[uúü]', 'c': '[cç]',
}
pattern_str = ""
for char in text:
pattern_str += char_map.get(char.lower(), re.escape(char))
return pattern_str
def _on_search_after_save(editor, text):
"""
Callback que executa DEPOIS que o editor salvou o texto.
Agora também conta as ocorrências.
"""
# 1. Limpa qualquer destaque amarelo antigo.
for i in range(len(editor.note.fields)):
editor.note.fields[i] = CLEANUP_PATTERN.sub(r'\1', editor.note.fields[i])
if not text:
editor.loadNote()
editor._search_bar.setStyleSheet("height: 35px; background-color: lightblue;")
editor._results_label.setText("Resultados: 0") ### NOVO/MODIFICADO ### - Limpa o contador
return
# 2. Faz a nova busca de forma segura e conta as ocorrências.
total_matches = 0 ### NOVO/MODIFICADO ### - Inicializa o contador total
pattern_str = _build_accent_insensitive_pattern(text)
search_pattern = re.compile(pattern_str, re.IGNORECASE)
def highlight_repl(match):
return f"{HIGHLIGHT_SPAN_START}{match.group(0)}{HIGHLIGHT_SPAN_END}"
for i in range(len(editor.note.fields)):
original_field = editor.note.fields[i]
parts = re.split(r'(<[^>]+>)', original_field)
new_parts = []
for part in parts:
if not re.match(r'<[^>]+>', part):
# Usa subn() que retorna (novo_texto, numero_de_substituicoes)
new_part, count = search_pattern.subn(highlight_repl, part) ### NOVO/MODIFICADO ###
if count > 0:
total_matches += count ### NOVO/MODIFICADO ### - Adiciona ao contador
new_parts.append(new_part)
else:
new_parts.append(part)
editor.note.fields[i] = "".join(new_parts)
# 3. Atualiza a cor da barra, o label do contador e recarrega a nota.
editor._results_label.setText(f"Resultados: {total_matches}") ### NOVO/MODIFICADO ###
if total_matches > 0:
editor._search_bar.setStyleSheet("height: 35px; background-color: lightblue;")
else:
editor._search_bar.setStyleSheet("height: 35px; background-color: pink;")
editor.loadNote()
def perform_search_and_highlight(editor):
"""
Função chamada pelo timer para iniciar a busca.
"""
search_text = editor._search_bar.text()
editor.saveNow(lambda: _on_search_after_save(editor, search_text))
def _on_replace_after_save(editor):
"""Callback para a função de substituir."""
search_text = editor._search_bar.text()
replace_text = editor._replace_box.text()
if not search_text:
return
# Limpa o destaque antes de substituir
for i in range(len(editor.note.fields)):
editor.note.fields[i] = CLEANUP_PATTERN.sub(r'\1', editor.note.fields[i])
pattern_str = _build_accent_insensitive_pattern(search_text)
full_pattern_str = r'\b' + pattern_str + r'\b'
replace_pattern = re.compile(full_pattern_str, re.IGNORECASE)
for i in range(len(editor.note.fields)):
original_field = editor.note.fields[i]
parts = re.split(r'(<[^>]+>)', original_field)
new_parts = []
for part in parts:
if not re.match(r'<[^>]+>', part):
new_parts.append(replace_pattern.sub(replace_text, part))
else:
new_parts.append(part)
editor.note.fields[i] = "".join(new_parts)
editor._search_bar.clear()
editor._replace_box.clear()
editor.loadNote()
def replace_word(editor):
"""Inicia o processo de salvar-e-substituir."""
editor.saveNow(lambda: _on_replace_after_save(editor))
# --- Função Principal que adiciona a UI ---
def add_search_bar_to_editor(editor: Editor):
"""
Adiciona a barra de pesquisa/substituição na interface do editor.
"""
if hasattr(editor, "_search_replace_bar"):
return
editor._search_timer = QTimer(editor.widget)
editor._search_timer.setSingleShot(True)
editor._search_timer.setInterval(250)
editor._search_timer.timeout.connect(lambda e=editor: perform_search_and_highlight(e))
search_widget = QWidget(editor.widget)
search_layout = QHBoxLayout(search_widget)
search_layout.setContentsMargins(0, 10, 0, 5)
search_layout.setSpacing(10)
editor._search_bar = QLineEdit()
editor._search_bar.setPlaceholderText("Pesquisar (Ctrl+F)")
editor._search_bar.textChanged.connect(editor._search_timer.start)
search_layout.addWidget(editor._search_bar)
### NOVO/MODIFICADO ### - Criação e adição do Label de resultados
editor._results_label = QLabel("Resultados: 0")
search_layout.addWidget(editor._results_label)
editor._replace_box = QLineEdit()
editor._replace_box.setPlaceholderText("Substituir por (Ctrl+H)")
search_layout.addWidget(editor._replace_box)
editor._replace_button = QPushButton("Substituir (Ctrl+J)")
editor._replace_button.clicked.connect(lambda _, e=editor: replace_word(e))
search_layout.addWidget(editor._replace_button)
font = QFont("Arial", 14)
editor._search_bar.setFont(font)
editor._search_bar.setStyleSheet("height: 35px; background-color: lightblue;")
editor._results_label.setFont(font) ### NOVO/MODIFICADO ### - Aplica a fonte ao label
editor._replace_box.setFont(font)
editor._replace_box.setStyleSheet("height: 35px; background-color: lightgreen;")
editor._replace_button.setFont(font)
editor._replace_button.setStyleSheet("height: 35px; background-color: lightgray;")
QShortcut(QKeySequence("Ctrl+F"), editor.widget, editor._search_bar.setFocus)
QShortcut(QKeySequence("Ctrl+H"), editor.widget, editor._replace_box.setFocus)
QShortcut(QKeySequence("Ctrl+J"), editor.widget, editor._replace_button.click)
splitter = editor.widget.findChild(QSplitter)
if splitter:
fields_widget = splitter.widget(0)
if fields_widget and fields_widget.layout():
fields_widget.layout().addWidget(search_widget)
else:
editor.widget.layout().addWidget(search_widget)
editor._search_replace_bar = search_widget
# *** FUNÇÃO DE LIMPEZA COM A ASSINATURA CORRIGIDA ***
def cleanup_html_before_save(html: str, editor: Editor) -> str:
"""
Esta função é chamada para CADA campo individualmente antes de salvar.
Ela limpa o destaque amarelo e retorna o HTML limpo.
"""
cleaned_html = CLEANUP_PATTERN.sub(r'\1', html)
return cleaned_html
# --- Hooks do Anki ---
gui_hooks.editor_did_load_note.append(add_search_bar_to_editor)
gui_hooks.editor_will_munge_html.append(cleanup_html_before_save)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment