From 1fe0875a5a244b52cba165aae54f874d5432507a Mon Sep 17 00:00:00 2001 From: gwen Date: Mon, 29 Jun 2026 22:19:13 +0200 Subject: [PATCH] docs(crepe-carottage): essai d'ui avec une variable et sa description --- doc/crepe.py | 468 +++++++++++++++++++++++++++++++++++++++++++++++ doc/crepe.py.ori | 389 +++++++++++++++++++++++++++++++++++++++ doc/crepe3.py | 460 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1317 insertions(+) create mode 100644 doc/crepe.py create mode 100644 doc/crepe.py.ori create mode 100644 doc/crepe3.py diff --git a/doc/crepe.py b/doc/crepe.py new file mode 100644 index 0000000..2f15234 --- /dev/null +++ b/doc/crepe.py @@ -0,0 +1,468 @@ +from textual.app import App, ComposeResult +from textual.containers import Container +from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static +from textual import events +from typing import List + +class CustomListItem(ListItem): + """ListItem personnalisé pour stocker le terme et sa définition""" + def __init__(self, term: str, definition: str) -> None: + super().__init__(Label(term)) + self.term = term + self.definition = definition + +class DictionaryApp(App): + """Application de dictionnaire avec autocomplétion et aperçu en temps réel""" + + CSS = """ + Screen { + background: #1e1e2e; + } + + #main-container { + height: 100%; + width: 100%; + padding: 1; + background: #1e1e2e; + } + + #search-container { + height: auto; + min-height: 5; + max-height: 8; + margin-bottom: 1; + background: #2d2d44; + padding: 1; + } + + #results-container { + height: 100%; + background: #2d2d44; + padding: 1; + overflow-y: auto; + } + + #search-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + Input { + background: #3d3d5c; + color: #cdd6f4; + border: solid #585b70; + padding: 1; + width: 100%; + margin-top: 1; + } + + Input:focus { + border: solid #89b4fa; + } + + #results-list { + background: #3d3d5c; + color: #cdd6f4; + height: 100%; + min-height: 10; + border: solid #585b70; + padding: 0; + } + + ListItem { + padding: 1; + background: #3d3d5c; + color: #cdd6f4; + width: 100%; + } + + ListItem:hover { + background: #45476a; + } + + ListItem:focus { + background: #89b4fa; + color: #1e1e2e; + } + + ListItem > Label { + width: 100%; + } + + #results-label { + color: #a6e3a1; + padding-bottom: 1; + width: 100%; + } + + #preview-container { + background: #2d2d44; + padding: 1; + margin-top: 1; + min-height: 3; + max-height: 5; + border: solid #585b70; + color: #cdd6f4; + } + + #preview-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + #preview-content { + color: #f9e2af; + padding: 1; + background: #3d3d5c; + min-height: 2; + width: 100%; + } + + #definition-box { + background: #2d2d44; + padding: 1; + margin-top: 1; + min-height: 8; + max-height: 30; + border: solid #585b70; + color: #cdd6f4; + } + + #definition-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + #definition-content { + color: #cdd6f4; + padding: 1; + background: #3d3d5c; + height: 100%; + min-height: 5; + overflow-y: scroll; + width: 100%; + } + + .highlight-term { + color: #f9e2af; + text-style: bold; + background: #45476a; + padding: 1; + } + """ + + def __init__(self): + super().__init__() + # Dictionnaire de termes avec leurs définitions + self.dictionary = { + "Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité", + "Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées", + "Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique", + "API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel", + "Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées", + "Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données", + "Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds", + "Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions", + "Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données", + "Cloud Computing": "Fourniture de services informatiques via internet à la demande", + "DevOps": "Pratique combinant développement et opérations informatiques en continu", + "Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain", + "Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs", + "Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production", + "Docker": "Plateforme de conteneurisation pour applications distribuées", + "Git": "Système de contrôle de version distribué pour le suivi de code", + "GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration", + "VS Code": "Éditeur de code source développé par Microsoft avec extensions", + "PyCharm": "IDE Python développé par JetBrains avec intégration complète", + "Jupyter": "Application web pour créer des notebooks interactifs en direct", + } + + # Liste triée des termes pour la recherche + self.terms = sorted(self.dictionary.keys()) + self.filtered_terms = self.terms.copy() + self.current_input = "" + self.last_selected_term = None + + def compose(self) -> ComposeResult: + """Compose l'interface utilisateur""" + yield Header() + yield Container( + Container( + # Zone de recherche + Container( + Label("🔍 Rechercher un terme :", id="search-label"), + Input(placeholder="Tapez un terme...", id="search-input"), + id="search-container" + ), + # Zone d'aperçu du texte en cours + Container( + Label("✏️ Texte saisi :", id="preview-label"), + Static("En attente de saisie...", id="preview-content"), + id="preview-container" + ), + # Résultats et définition + Container( + Label("📋 0 terme trouvé", id="results-label"), + ListView(id="results-list"), + Container( + Label("📖 Définition", id="definition-label"), + Static("Sélectionnez un terme pour voir sa définition", id="definition-content"), + id="definition-box" + ), + id="results-container" + ), + id="main-container" + ), + ) + yield Footer() + + def on_mount(self) -> None: + """Initialisation après le montage""" + search_input = self.query_one("#search-input") + search_input.focus() + self.update_results() + self.apply_responsive_styles() + + def on_resize(self, event: events.Resize) -> None: + """Gère le redimensionnement du terminal""" + self.apply_responsive_styles() + self.update_list_height() + + def apply_responsive_styles(self) -> None: + """Applique les styles responsifs en fonction de la taille du terminal""" + width = self.size.width + height = self.size.height + + # Ajuster les paddings selon la largeur + main_container = self.query_one("#main-container") + search_container = self.query_one("#search-container") + results_container = self.query_one("#results-container") + preview_container = self.query_one("#preview-container") + definition_box = self.query_one("#definition-box") + definition_content = self.query_one("#definition-content") + + if width < 80: + # Petit terminal + main_container.styles.padding = 1 + search_container.styles.padding = 1 + results_container.styles.padding = 1 + preview_container.styles.padding = 1 + definition_box.styles.padding = 1 + definition_content.styles.padding = 1 + definition_box.styles.min_height = 6 + definition_box.styles.max_height = 12 + preview_container.styles.min_height = 2 + preview_container.styles.max_height = 3 + + search_container.styles.min_height = 4 + search_container.styles.max_height = 6 + + elif width > 120 and height > 40: + # Grand terminal + main_container.styles.padding = 2 + search_container.styles.padding = 2 + results_container.styles.padding = 2 + preview_container.styles.padding = 2 + definition_box.styles.padding = 2 + definition_content.styles.padding = 2 + definition_box.styles.min_height = 12 + definition_box.styles.max_height = 30 + preview_container.styles.min_height = 4 + preview_container.styles.max_height = 6 + + search_container.styles.min_height = 6 + search_container.styles.max_height = 10 + + else: + # Terminal moyen (par défaut) + main_container.styles.padding = 1 + search_container.styles.padding = 1 + results_container.styles.padding = 1 + preview_container.styles.padding = 1 + definition_box.styles.padding = 1 + definition_content.styles.padding = 1 + definition_box.styles.min_height = 8 + definition_box.styles.max_height = 20 + preview_container.styles.min_height = 3 + preview_container.styles.max_height = 5 + + search_container.styles.min_height = 5 + search_container.styles.max_height = 8 + + # Ajuster la hauteur de la définition selon la hauteur du terminal + if height < 30: + definition_box.styles.min_height = 4 + definition_box.styles.max_height = 8 + preview_container.styles.min_height = 2 + preview_container.styles.max_height = 3 + elif height > 50: + definition_box.styles.min_height = 10 + definition_box.styles.max_height = 25 + preview_container.styles.min_height = 4 + preview_container.styles.max_height = 6 + + def update_list_height(self) -> None: + """Met à jour la hauteur de la liste en fonction de l'espace disponible""" + try: + results_list = self.query_one("#results-list") + available_height = self.size.height - self.calculate_fixed_height() + + # Ajuster la hauteur de la liste + if available_height > 15: + results_list.styles.height = available_height - 2 + elif available_height > 10: + results_list.styles.height = available_height + else: + results_list.styles.height = 8 + + except Exception: + pass + + def calculate_fixed_height(self) -> int: + """Calcule la hauteur fixe des éléments (header, footer, search, preview, definition)""" + try: + search_container = self.query_one("#search-container") + preview_container = self.query_one("#preview-container") + definition_box = self.query_one("#definition-box") + + # Récupérer les hauteurs actuelles + search_height = search_container.styles.min_height or 5 + preview_height = preview_container.styles.min_height or 3 + definition_height = definition_box.styles.min_height or 8 + + # Estimer la hauteur fixe totale + if isinstance(search_height, (int, float)): + search_h = search_height + else: + search_h = 5 + + if isinstance(preview_height, (int, float)): + preview_h = preview_height + else: + preview_h = 3 + + if isinstance(definition_height, (int, float)): + definition_h = definition_height + else: + definition_h = 8 + + # Header + Footer + margins + padding + fixed_height = 2 + search_h + preview_h + definition_h + 6 + + return int(fixed_height) + except Exception: + return 25 # Valeur par défaut en cas d'erreur + + def on_input_changed(self, event: Input.Changed) -> None: + """Gère les changements dans le champ de recherche""" + if event.input.id == "search-input": + self.current_input = event.value + self.update_preview() + self.update_results() + + def update_preview(self) -> None: + """Met à jour l'aperçu du texte saisi""" + preview_content = self.query_one("#preview-content") + + if self.current_input: + # Afficher le texte avec mise en forme + preview_text = f"📝 {self.current_input}" + + # Si le texte correspond à un terme connu, le mettre en évidence + matching_terms = [term for term in self.terms if term.lower() == self.current_input.lower()] + if matching_terms: + preview_text = f"✅ {self.current_input} (terme trouvé !)" + + preview_content.update(preview_text) + else: + preview_content.update("En attente de saisie...") + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Gère la soumission du champ de recherche (touche Entrée)""" + if event.input.id == "search-input": + results_list = self.query_one("#results-list") + if results_list.children: + first_item = results_list.children[0] + if hasattr(first_item, 'term') and hasattr(first_item, 'definition'): + self.show_definition(first_item.term, first_item.definition) + + def update_results(self) -> None: + """Met à jour la liste des résultats""" + search_text = self.current_input.lower().strip() + + # Filtrer les termes + if search_text: + self.filtered_terms = [ + term for term in self.terms + if search_text in term.lower() + ] + else: + self.filtered_terms = self.terms.copy() + + # Mettre à jour la ListView + results_list = self.query_one("#results-list") + results_list.clear() + + for term in self.filtered_terms: + definition = self.dictionary[term] + item = CustomListItem(term, definition) + results_list.append(item) + + # Mettre à jour le compteur + results_label = self.query_one("#results-label") + count = len(self.filtered_terms) + if count == 0: + results_label.update("❌ Aucun résultat trouvé") + else: + results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}") + + def on_list_view_selected(self, event: ListView.Selected) -> None: + """Gère la sélection d'un terme dans la liste""" + if event.item and hasattr(event.item, 'term') and hasattr(event.item, 'definition'): + self.show_definition(event.item.term, event.item.definition) + + def show_definition(self, term: str, definition: str) -> None: + """Affiche la définition du terme sélectionné""" + definition_content = self.query_one("#definition-content") + + # Mettre en valeur le terme dans la définition + definition_text = f"[b]{term}[/b]\n\n{definition}" + definition_content.update(definition_text) + + # Mettre à jour le champ de recherche avec le terme sélectionné + search_input = self.query_one("#search-input") + search_input.value = term + self.current_input = term + self.update_preview() + self.update_results() + + self.last_selected_term = term + + def on_key(self, event: events.Key) -> None: + """Gère les touches spéciales""" + if event.key == "escape": + # Effacer la recherche + search_input = self.query_one("#search-input") + search_input.value = "" + self.current_input = "" + self.update_preview() + self.update_results() + search_input.focus() + elif event.key == "ctrl+f": + # Focus sur la recherche + self.query_one("#search-input").focus() + elif event.key == "ctrl+d": + # Effacer la définition + definition_content = self.query_one("#definition-content") + definition_content.update("Sélectionnez un terme pour voir sa définition") + +# Point d'entrée de l'application +if __name__ == "__main__": + app = DictionaryApp() + app.run() diff --git a/doc/crepe.py.ori b/doc/crepe.py.ori new file mode 100644 index 0000000..36e98f7 --- /dev/null +++ b/doc/crepe.py.ori @@ -0,0 +1,389 @@ +from textual.app import App, ComposeResult +from textual.containers import Container +from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static +from textual import events +from textual.message import Message +from typing import List, Optional + +class CustomListItem(ListItem): + """ListItem personnalisé pour stocker le terme et sa définition""" + def __init__(self, term: str, definition: str) -> None: + super().__init__(Label(term)) + self.term = term + self.definition = definition + +class DictionaryApp(App): + """Application de dictionnaire avec autocomplétion et responsive design""" + + CSS = """ + Screen { + background: #1e1e2e; + } + + #main-container { + height: 100%; + width: 100%; + padding: 1; + background: #1e1e2e; + } + + #search-container { + height: auto; + min-height: 5; + max-height: 8; + margin-bottom: 1; + background: #2d2d44; + padding: 1; + } + + #results-container { + height: 100%; + background: #2d2d44; + padding: 1; + overflow-y: auto; + } + + #search-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + Input { + background: #3d3d5c; + color: #cdd6f4; + border: solid #585b70; + padding: 1; + width: 100%; + margin-top: 1; + } + + Input:focus { + border: solid #89b4fa; + } + + #results-list { + background: #3d3d5c; + color: #cdd6f4; + height: 100%; + min-height: 10; + border: solid #585b70; + padding: 0; + } + + ListItem { + padding: 1; + background: #3d3d5c; + color: #cdd6f4; + width: 100%; + } + + ListItem:hover { + background: #45476a; + } + + ListItem:focus { + background: #89b4fa; + color: #1e1e2e; + } + + ListItem > Label { + width: 100%; + } + + #results-label { + color: #a6e3a1; + padding-bottom: 1; + width: 100%; + } + + #definition-box { + background: #2d2d44; + padding: 1; + margin-top: 1; + min-height: 8; + max-height: 30; + border: solid #585b70; + color: #cdd6f4; + } + + #definition-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + #definition-content { + color: #cdd6f4; + padding: 1; + background: #3d3d5c; + height: 100%; + min-height: 5; + overflow-y: scroll; + width: 100%; + } + """ + + def __init__(self): + super().__init__() + # Dictionnaire de termes avec leurs définitions + self.dictionary = { + "Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité", + "Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées", + "Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique", + "API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel", + "Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées", + "Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données", + "Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds", + "Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions", + "Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données", + "Cloud Computing": "Fourniture de services informatiques via internet à la demande", + "DevOps": "Pratique combinant développement et opérations informatiques en continu", + "Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain", + "Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs", + "Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production", + "Docker": "Plateforme de conteneurisation pour applications distribuées", + "Git": "Système de contrôle de version distribué pour le suivi de code", + "GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration", + "VS Code": "Éditeur de code source développé par Microsoft avec extensions", + "PyCharm": "IDE Python développé par JetBrains avec intégration complète", + "Jupyter": "Application web pour créer des notebooks interactifs en direct", + } + + # Liste triée des termes pour la recherche + self.terms = sorted(self.dictionary.keys()) + self.filtered_terms = self.terms.copy() + self.current_input = "" + self.last_selected_term = None + + def compose(self) -> ComposeResult: + """Compose l'interface utilisateur""" + yield Header() + yield Container( + Container( + Container( + Label("🔍 Rechercher un terme :", id="search-label"), + Input(placeholder="Tapez un terme...", id="search-input"), + id="search-container" + ), + Container( + Label("📋 0 terme trouvé", id="results-label"), + ListView(id="results-list"), + Container( + Label("📖 Définition", id="definition-label"), + Static("Sélectionnez un terme pour voir sa définition", id="definition-content"), + id="definition-box" + ), + id="results-container" + ), + id="main-container" + ), + ) + yield Footer() + + def on_mount(self) -> None: + """Initialisation après le montage""" + search_input = self.query_one("#search-input") + search_input.focus() + self.update_results() + self.apply_responsive_styles() + + def on_resize(self, event: events.Resize) -> None: + """Gère le redimensionnement du terminal""" + self.apply_responsive_styles() + self.update_list_height() + + def apply_responsive_styles(self) -> None: + """Applique les styles responsifs en fonction de la taille du terminal""" + width = self.size.width + height = self.size.height + + # Ajuster les paddings selon la largeur + main_container = self.query_one("#main-container") + search_container = self.query_one("#search-container") + results_container = self.query_one("#results-container") + definition_box = self.query_one("#definition-box") + definition_content = self.query_one("#definition-content") + + if width < 80: + # Petit terminal + main_container.styles.padding = 0.5 + search_container.styles.padding = 0.5 + results_container.styles.padding = 0.5 + definition_box.styles.padding = 0.5 + definition_content.styles.padding = 0.5 + definition_box.styles.min_height = 6 + definition_box.styles.max_height = 12 + + # Réduire la police ou l'espacement + search_container.styles.min_height = 4 + search_container.styles.max_height = 6 + + elif width > 120 and height > 40: + # Grand terminal + main_container.styles.padding = 2 + search_container.styles.padding = 2 + results_container.styles.padding = 2 + definition_box.styles.padding = 2 + definition_content.styles.padding = 2 + definition_box.styles.min_height = 12 + definition_box.styles.max_height = 30 + + search_container.styles.min_height = 6 + search_container.styles.max_height = 10 + + else: + # Terminal moyen (par défaut) + main_container.styles.padding = 1 + search_container.styles.padding = 1 + results_container.styles.padding = 1 + definition_box.styles.padding = 1 + definition_content.styles.padding = 1 + definition_box.styles.min_height = 8 + definition_box.styles.max_height = 20 + + search_container.styles.min_height = 5 + search_container.styles.max_height = 8 + + # Ajuster la hauteur de la définition selon la hauteur du terminal + if height < 30: + definition_box.styles.min_height = 4 + definition_box.styles.max_height = 8 + elif height > 50: + definition_box.styles.min_height = 10 + definition_box.styles.max_height = 25 + + def update_list_height(self) -> None: + """Met à jour la hauteur de la liste en fonction de l'espace disponible""" + try: + results_list = self.query_one("#results-list") + available_height = self.size.height - self.calculate_fixed_height() + + # Ajuster la hauteur de la liste + if available_height > 15: + results_list.styles.height = available_height - 2 + elif available_height > 10: + results_list.styles.height = available_height + else: + results_list.styles.height = 8 + + except Exception: + pass + + def calculate_fixed_height(self) -> int: + """Calcule la hauteur fixe des éléments (header, footer, search, definition)""" + try: + search_container = self.query_one("#search-container") + definition_box = self.query_one("#definition-box") + + # Récupérer les hauteurs actuelles + search_height = search_container.styles.min_height or 5 + definition_height = definition_box.styles.min_height or 8 + + # Estimer la hauteur fixe totale + if isinstance(search_height, (int, float)): + search_h = search_height + else: + search_h = 5 + + if isinstance(definition_height, (int, float)): + definition_h = definition_height + else: + definition_h = 8 + + # Header + Footer + margins + padding + fixed_height = 2 + search_h + definition_h + 4 + + return int(fixed_height) + except Exception: + return 20 # Valeur par défaut en cas d'erreur + + def on_input_changed(self, event: Input.Changed) -> None: + """Gère les changements dans le champ de recherche""" + if event.input.id == "search-input": + self.current_input = event.value + self.update_results() + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Gère la soumission du champ de recherche (touche Entrée)""" + if event.input.id == "search-input": + results_list = self.query_one("#results-list") + if results_list.children: + first_item = results_list.children[0] + if hasattr(first_item, 'term') and hasattr(first_item, 'definition'): + self.show_definition(first_item.term, first_item.definition) + + def update_results(self) -> None: + """Met à jour la liste des résultats""" + search_text = self.current_input.lower().strip() + + # Filtrer les termes + if search_text: + self.filtered_terms = [ + term for term in self.terms + if search_text in term.lower() + ] + else: + self.filtered_terms = self.terms.copy() + + # Mettre à jour la ListView + results_list = self.query_one("#results-list") + results_list.clear() + + for term in self.filtered_terms: + definition = self.dictionary[term] + item = CustomListItem(term, definition) + results_list.append(item) + + # Mettre à jour le compteur + results_label = self.query_one("#results-label") + count = len(self.filtered_terms) + if count == 0: + results_label.update("❌ Aucun résultat trouvé") + else: + results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}") + + def on_list_view_selected(self, event: ListView.Selected) -> None: + """Gère la sélection d'un terme dans la liste""" + if event.item and hasattr(event.item, 'term') and hasattr(event.item, 'definition'): + self.show_definition(event.item.term, event.item.definition) + + def show_definition(self, term: str, definition: str) -> None: + """Affiche la définition du terme sélectionné""" + definition_content = self.query_one("#definition-content") + + # Mettre en valeur le terme dans la définition + definition_text = f"[b]{term}[/b]\n\n{definition}" + definition_content.update(definition_text) + + # Mettre à jour le champ de recherche avec le terme sélectionné + search_input = self.query_one("#search-input") + search_input.value = term + self.current_input = term + self.update_results() + + self.last_selected_term = term + + def on_key(self, event: events.Key) -> None: + """Gère les touches spéciales""" + if event.key == "escape": + # Effacer la recherche + search_input = self.query_one("#search-input") + search_input.value = "" + self.current_input = "" + self.update_results() + search_input.focus() + elif event.key == "ctrl+f": + # Focus sur la recherche + self.query_one("#search-input").focus() + elif event.key == "ctrl+d": + # Effacer la définition + definition_content = self.query_one("#definition-content") + definition_content.update("Sélectionnez un terme pour voir sa définition") + +# Point d'entrée de l'application +if __name__ == "__main__": + app = DictionaryApp() + app.run() diff --git a/doc/crepe3.py b/doc/crepe3.py new file mode 100644 index 0000000..657595a --- /dev/null +++ b/doc/crepe3.py @@ -0,0 +1,460 @@ +from textual.app import App, ComposeResult +from textual.containers import Container, Vertical +from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static +from textual import events +from typing import List, Optional + +class TermListItem(ListItem): + """ListItem personnalisé pour afficher un terme et sa définition""" + def __init__(self, term: str, definition: str, expanded: bool = False) -> None: + self.term = term + self.definition = definition + self.expanded = expanded + + # Créer le contenu avec le terme et éventuellement la définition + content = self._create_content() + super().__init__(content) + + def _create_content(self): + """Crée le contenu du ListItem avec ou sans définition""" + if self.expanded: + return Vertical( + Label(f"📌 {self.term}", classes="term-title"), + Label(f" {self.definition}", classes="term-definition"), + classes="term-expanded" + ) + else: + return Label(f"📌 {self.term}", classes="term-title") + + def toggle_expand(self): + """Bascule l'état d'expansion du terme""" + self.expanded = not self.expanded + # Mettre à jour le contenu + self._update_content() + + def _update_content(self): + """Met à jour le contenu du ListItem""" + new_content = self._create_content() + # Remplacer l'ancien contenu + self.remove_children() + self.mount(new_content) + +class DictionaryApp(App): + """Application de dictionnaire avec affichage de la définition sous le terme""" + + CSS = """ + Screen { + background: #1e1e2e; + } + + #main-container { + height: 100%; + width: 100%; + padding: 1; + background: #1e1e2e; + } + + #search-container { + height: auto; + min-height: 5; + max-height: 8; + margin-bottom: 1; + background: #2d2d44; + padding: 1; + } + + #results-container { + height: 100%; + background: #2d2d44; + padding: 1; + overflow-y: auto; + } + + #search-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + Input { + background: #3d3d5c; + color: #cdd6f4; + border: solid #585b70; + padding: 1; + width: 100%; + margin-top: 1; + } + + Input:focus { + border: solid #89b4fa; + } + + #results-list { + background: #3d3d5c; + color: #cdd6f4; + height: 100%; + min-height: 10; + border: solid #585b70; + padding: 0; + } + + ListItem { + padding: 1; + background: #3d3d5c; + color: #cdd6f4; + width: 100%; + border-bottom: solid #45476a; + } + + ListItem:hover { + background: #45476a; + } + + ListItem:focus { + background: #45476a; + } + + .term-title { + color: #89b4fa; + text-style: bold; + width: 100%; + padding: 1; + } + + .term-definition { + color: #cdd6f4; + padding: 1; + padding-left: 2; + background: #3d3d5c; + width: 100%; + border-left: solid #89b4fa; + margin-top: 1; + } + + .term-expanded { + background: #2d2d44; + padding: 0; + } + + #results-label { + color: #a6e3a1; + padding-bottom: 1; + width: 100%; + } + + #preview-container { + background: #2d2d44; + padding: 1; + margin-top: 1; + min-height: 3; + max-height: 5; + border: solid #585b70; + color: #cdd6f4; + } + + #preview-label { + color: #89b4fa; + text-style: bold; + padding-bottom: 1; + width: 100%; + } + + #preview-content { + color: #f9e2af; + padding: 1; + background: #3d3d5c; + min-height: 2; + width: 100%; + } + """ + + def __init__(self): + super().__init__() + # Dictionnaire de termes avec leurs définitions + self.dictionary = { + "Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité", + "Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées", + "Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique", + "API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel", + "Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées", + "Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données", + "Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds", + "Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions", + "Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données", + "Cloud Computing": "Fourniture de services informatiques via internet à la demande", + "DevOps": "Pratique combinant développement et opérations informatiques en continu", + "Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain", + "Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs", + "Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production", + "Docker": "Plateforme de conteneurisation pour applications distribuées", + "Git": "Système de contrôle de version distribué pour le suivi de code", + "GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration", + "VS Code": "Éditeur de code source développé par Microsoft avec extensions", + "PyCharm": "IDE Python développé par JetBrains avec intégration complète", + "Jupyter": "Application web pour créer des notebooks interactifs en direct", + } + + # Liste triée des termes pour la recherche + self.terms = sorted(self.dictionary.keys()) + self.filtered_terms = self.terms.copy() + self.current_input = "" + self.last_selected_term = None + self.expanded_terms = set() # Ensemble des termes actuellement expansés + + def compose(self) -> ComposeResult: + """Compose l'interface utilisateur""" + yield Header() + yield Container( + Container( + # Zone de recherche + Container( + Label("🔍 Rechercher un terme :", id="search-label"), + Input(placeholder="Tapez un terme...", id="search-input"), + id="search-container" + ), + # Zone d'aperçu du texte en cours + Container( + Label("✏️ Texte saisi :", id="preview-label"), + Static("En attente de saisie...", id="preview-content"), + id="preview-container" + ), + # Résultats + Container( + Label("📋 0 terme trouvé", id="results-label"), + ListView(id="results-list"), + id="results-container" + ), + id="main-container" + ), + ) + yield Footer() + + def on_mount(self) -> None: + """Initialisation après le montage""" + search_input = self.query_one("#search-input") + search_input.focus() + self.update_results() + self.apply_responsive_styles() + + def on_resize(self, event: events.Resize) -> None: + """Gère le redimensionnement du terminal""" + self.apply_responsive_styles() + self.update_list_height() + + def apply_responsive_styles(self) -> None: + """Applique les styles responsifs en fonction de la taille du terminal""" + width = self.size.width + height = self.size.height + + # Ajuster les paddings selon la largeur + main_container = self.query_one("#main-container") + search_container = self.query_one("#search-container") + results_container = self.query_one("#results-container") + preview_container = self.query_one("#preview-container") + + if width < 80: + # Petit terminal + main_container.styles.padding = 1 + search_container.styles.padding = 1 + results_container.styles.padding = 1 + preview_container.styles.padding = 1 + preview_container.styles.min_height = 2 + preview_container.styles.max_height = 3 + search_container.styles.min_height = 4 + search_container.styles.max_height = 6 + + elif width > 120 and height > 40: + # Grand terminal + main_container.styles.padding = 2 + search_container.styles.padding = 2 + results_container.styles.padding = 2 + preview_container.styles.padding = 2 + preview_container.styles.min_height = 4 + preview_container.styles.max_height = 6 + search_container.styles.min_height = 6 + search_container.styles.max_height = 10 + + else: + # Terminal moyen (par défaut) + main_container.styles.padding = 1 + search_container.styles.padding = 1 + results_container.styles.padding = 1 + preview_container.styles.padding = 1 + preview_container.styles.min_height = 3 + preview_container.styles.max_height = 5 + search_container.styles.min_height = 5 + search_container.styles.max_height = 8 + + # Ajuster la hauteur de la preview selon la hauteur du terminal + if height < 30: + preview_container.styles.min_height = 2 + preview_container.styles.max_height = 3 + elif height > 50: + preview_container.styles.min_height = 4 + preview_container.styles.max_height = 6 + + def update_list_height(self) -> None: + """Met à jour la hauteur de la liste en fonction de l'espace disponible""" + try: + results_list = self.query_one("#results-list") + available_height = self.size.height - self.calculate_fixed_height() + + # Ajuster la hauteur de la liste + if available_height > 15: + results_list.styles.height = available_height - 2 + elif available_height > 10: + results_list.styles.height = available_height + else: + results_list.styles.height = 8 + + except Exception: + pass + + def calculate_fixed_height(self) -> int: + """Calcule la hauteur fixe des éléments (header, footer, search, preview)""" + try: + search_container = self.query_one("#search-container") + preview_container = self.query_one("#preview-container") + + # Récupérer les hauteurs actuelles + search_height = search_container.styles.min_height or 5 + preview_height = preview_container.styles.min_height or 3 + + # Estimer la hauteur fixe totale + if isinstance(search_height, (int, float)): + search_h = search_height + else: + search_h = 5 + + if isinstance(preview_height, (int, float)): + preview_h = preview_height + else: + preview_h = 3 + + # Header + Footer + margins + padding + fixed_height = 2 + search_h + preview_h + 4 + + return int(fixed_height) + except Exception: + return 18 # Valeur par défaut en cas d'erreur + + def on_input_changed(self, event: Input.Changed) -> None: + """Gère les changements dans le champ de recherche""" + if event.input.id == "search-input": + self.current_input = event.value + self.update_preview() + self.update_results() + + def update_preview(self) -> None: + """Met à jour l'aperçu du texte saisi""" + preview_content = self.query_one("#preview-content") + + if self.current_input: + preview_text = f"📝 {self.current_input}" + + # Si le texte correspond à un terme connu + matching_terms = [term for term in self.terms if term.lower() == self.current_input.lower()] + if matching_terms: + preview_text = f"✅ {self.current_input} (terme trouvé !)" + + preview_content.update(preview_text) + else: + preview_content.update("En attente de saisie...") + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Gère la soumission du champ de recherche (touche Entrée)""" + if event.input.id == "search-input": + # Sélectionner le premier résultat + results_list = self.query_one("#results-list") + if results_list.children: + first_item = results_list.children[0] + if isinstance(first_item, TermListItem): + self.toggle_term_expansion(first_item.term) + + def update_results(self) -> None: + """Met à jour la liste des résultats""" + search_text = self.current_input.lower().strip() + + # Filtrer les termes + if search_text: + self.filtered_terms = [ + term for term in self.terms + if search_text in term.lower() + ] + else: + self.filtered_terms = self.terms.copy() + + # Mettre à jour la ListView + results_list = self.query_one("#results-list") + results_list.clear() + + for term in self.filtered_terms: + definition = self.dictionary[term] + # Vérifier si le terme est expansé + expanded = term in self.expanded_terms + item = TermListItem(term, definition, expanded) + results_list.append(item) + + # Mettre à jour le compteur + results_label = self.query_one("#results-label") + count = len(self.filtered_terms) + if count == 0: + results_label.update("❌ Aucun résultat trouvé") + else: + results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}") + + def on_list_view_selected(self, event: ListView.Selected) -> None: + """Gère la sélection d'un terme dans la liste""" + if event.item and isinstance(event.item, TermListItem): + # Basculer l'expansion du terme sélectionné + self.toggle_term_expansion(event.item.term) + + def toggle_term_expansion(self, term: str) -> None: + """Bascule l'état d'expansion d'un terme""" + if term in self.expanded_terms: + self.expanded_terms.remove(term) + else: + # Si un autre terme est expansé, le réduire + if len(self.expanded_terms) > 0: + # Réduire tous les autres termes + for other_term in list(self.expanded_terms): + if other_term != term: + self.expanded_terms.remove(other_term) + self.expanded_terms.add(term) + + # Mettre à jour l'affichage + self.update_results() + + # Mettre à jour le champ de recherche + search_input = self.query_one("#search-input") + search_input.value = term + self.current_input = term + self.update_preview() + + def on_key(self, event: events.Key) -> None: + """Gère les touches spéciales""" + if event.key == "escape": + # Effacer la recherche + search_input = self.query_one("#search-input") + search_input.value = "" + self.current_input = "" + self.expanded_terms.clear() + self.update_preview() + self.update_results() + search_input.focus() + elif event.key == "ctrl+f": + # Focus sur la recherche + self.query_one("#search-input").focus() + elif event.key == "enter": + # Si un item est sélectionné, basculer son expansion + results_list = self.query_one("#results-list") + if results_list.children and hasattr(results_list, 'highlighted_child'): + highlighted = results_list.highlighted_child + if highlighted and isinstance(highlighted, TermListItem): + self.toggle_term_expansion(highlighted.term) + +# Point d'entrée de l'application +if __name__ == "__main__": + app = DictionaryApp() + app.run()