Skip to content

Commit 03b37b8

Browse files
committed
Finalise le lot P0 de parité IDE/classic et durcit deps_analyser
Extrait un parseur d'imports testable dans Core/deps_analyser/analyser.py via des helpers dédiés pour l'analyse de source et de fichiers, puis réutilise ce chemin unique dans suggest_missing_dependencies(). Cette refactorisation évite la duplication de logique, simplifie les futurs durcissements de l'analyseur et rend les règles d'extraction d'imports vérifiables isolément. Étend la couverture des tests heuristiques de l'analyseur pour inclure explicitement les imports relatifs et les imports dynamiques (__import__ et importlib.import_module), y compris lorsque la résolution dépend du contexte du workspace et de layouts de projet tels que src/ ou lib/python. Finalise la parité fonctionnelle entre l'interface IDE-like et l'interface classique sur les points encore ouverts côté affordances IDE. Le menu d'actions supplémentaires (...) est désormais retraduit proprement, ses actions sont conservées comme objets réutilisables et ses libellés ainsi que certains tooltips spécifiques à l'IDE sont rafraîchis lors des changements de langue afin de limiter les écarts visibles avec le GUI classique. Ajoute une documentation dédiée de matrice de parité IDE/classique afin de tracer explicitement ce qui est aligné, ce qui reste volontairement spécifique au mode IDE et la stratégie de maintenance recommandée pour les évolutions futures. Met à jour TODO.md en cochant les tâches P0 réalisées, notamment la finalisation de la parité IDE/classic, la documentation de cette parité et le durcissement de l'analyseur de dépendances autour d'un parseur d'imports testable avec meilleure couverture des imports relatifs et dynamiques.
1 parent 0df21ae commit 03b37b8

5 files changed

Lines changed: 249 additions & 64 deletions

File tree

Core/IdeLikeGui/connections.py

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -307,80 +307,88 @@ def _tr(fr: str, en: str) -> str:
307307

308308
try:
309309
menu = QMenu(more_btn)
310+
self._ide_more_tools_menu = menu
310311

311-
act_workspace = QAction(_tr("Selectionner workspace", "Select workspace"), menu)
312+
act_workspace = QAction(menu)
312313
act_workspace.triggered.connect(
313314
lambda: getattr(self, "select_workspace", lambda: None)()
314315
)
315316
menu.addAction(act_workspace)
316317

317-
act_venv = QAction(_tr("Selectionner venv", "Select venv"), menu)
318+
act_venv = QAction(menu)
318319
act_venv.triggered.connect(
319320
lambda: getattr(self, "select_venv_manually", lambda: None)()
320321
)
321322
menu.addAction(act_venv)
322323

323-
act_add_files = QAction(_tr("Ajouter fichiers", "Add files"), menu)
324+
act_add_files = QAction(menu)
324325
act_add_files.triggered.connect(
325326
lambda: getattr(self, "select_files_manually", lambda: None)()
326327
)
327328
menu.addAction(act_add_files)
328329

329-
act_clear_workspace = QAction(_tr("Vider workspace", "Clear workspace"), menu)
330+
act_clear_workspace = QAction(menu)
330331
act_clear_workspace.triggered.connect(
331332
lambda: getattr(self, "clear_workspace", lambda: None)()
332333
)
333334
menu.addAction(act_clear_workspace)
334335

335-
act_stats = QAction(_tr("Statistiques", "Statistics"), menu)
336+
act_stats = QAction(menu)
336337
act_stats.triggered.connect(
337338
lambda: getattr(self, "show_statistics", lambda: None)()
338339
)
339340
menu.addAction(act_stats)
340341

341342
menu.addSeparator()
342343

343-
act_language = QAction(_tr("Langue", "Language"), menu)
344-
act_language.triggered.connect(
345-
lambda: getattr(self, "show_language_dialog", lambda: None)()
346-
)
344+
act_language = QAction(menu)
345+
act_language.triggered.connect(lambda: getattr(self, "show_language_dialog", lambda: None)())
347346
menu.addAction(act_language)
348347

349-
act_theme = QAction(_tr("Theme", "Theme"), menu)
348+
act_theme = QAction(menu)
350349
act_theme.triggered.connect(lambda: _open_theme_dialog(self))
351350
menu.addAction(act_theme)
352351

353352
menu.addSeparator()
354353

355-
act_advanced = QAction(_tr("Config avancee", "Advanced config"), menu)
354+
act_advanced = QAction(menu)
356355
act_advanced.triggered.connect(
357356
lambda: getattr(self, "open_advanced_config_editor", lambda: None)()
358357
)
359358
menu.addAction(act_advanced)
360359

361-
act_export = QAction(_tr("Exporter config", "Export config"), menu)
362-
act_export.triggered.connect(
363-
lambda: getattr(self, "export_config", lambda: None)()
364-
)
360+
act_export = QAction(menu)
361+
act_export.triggered.connect(lambda: getattr(self, "export_config", lambda: None)())
365362
menu.addAction(act_export)
366363

367-
act_import = QAction(_tr("Importer config", "Import config"), menu)
368-
act_import.triggered.connect(
369-
lambda: getattr(self, "import_config", lambda: None)()
370-
)
364+
act_import = QAction(menu)
365+
act_import.triggered.connect(lambda: getattr(self, "import_config", lambda: None)())
371366
menu.addAction(act_import)
372367

373-
act_help = QAction(_tr("Aide", "Help"), menu)
374-
act_help.triggered.connect(
375-
lambda: getattr(self, "show_help_dialog", lambda: None)()
376-
)
368+
act_help = QAction(menu)
369+
act_help.triggered.connect(lambda: getattr(self, "show_help_dialog", lambda: None)())
377370
menu.addAction(act_help)
378371

372+
self._ide_more_menu_actions = {
373+
"workspace": act_workspace,
374+
"venv": act_venv,
375+
"add_files": act_add_files,
376+
"clear_workspace": act_clear_workspace,
377+
"stats": act_stats,
378+
"language": act_language,
379+
"theme": act_theme,
380+
"advanced": act_advanced,
381+
"export": act_export,
382+
"import": act_import,
383+
"help": act_help,
384+
}
385+
379386
more_btn.setMenu(menu)
380387
more_btn.setPopupMode(QToolButton.InstantPopup)
381388
more_btn.setToolTip(_tr("Plus d'actions", "More actions"))
382389
except Exception:
383390
pass
391+
_retranslate_ide_like_actions(self)
384392
_apply_activity_buttons_theme(self)
385393

386394
# Avoid duplicated controls in the side panel.
@@ -488,17 +496,63 @@ def _apply_activity_buttons_theme(self) -> None:
488496
_apply_status_bar_theme(self, dark, fg, border)
489497

490498

491-
def _connect_ide_like_specific_signals(self) -> None:
492-
"""Connect only IDE-specific signals on top of the classic shared wiring."""
499+
def _retranslate_ide_like_actions(self) -> None:
500+
"""Apply translated labels/tooltips to IDE-specific actions and affordances."""
501+
try:
502+
trf = getattr(self, "tr", None)
503+
_tr = trf if callable(trf) else (lambda fr, en: en)
504+
except Exception:
505+
_tr = lambda fr, en: en
506+
507+
actions = getattr(self, "_ide_more_menu_actions", {}) or {}
508+
labels = {
509+
"workspace": _tr("Choisir le Workspace", "Select workspace"),
510+
"venv": _tr("Choisir le Venv", "Select venv"),
511+
"add_files": _tr("Ajouter des fichiers", "Add files"),
512+
"clear_workspace": _tr("Vider le Workspace", "Clear workspace"),
513+
"stats": _tr("Statistiques", "Statistics"),
514+
"language": _tr("Langue", "Language"),
515+
"theme": _tr("Thème", "Theme"),
516+
"advanced": _tr("Configuration avancée", "Advanced config"),
517+
"export": _tr("Exporter la configuration", "Export config"),
518+
"import": _tr("Importer la configuration", "Import config"),
519+
"help": _tr("Aide", "Help"),
520+
}
521+
for key, action in actions.items():
522+
if action is None:
523+
continue
524+
try:
525+
action.setText(labels.get(key, action.text()))
526+
except Exception:
527+
pass
493528

494-
_connect_classic_signals(self)
529+
try:
530+
more_btn = getattr(self, "toolButton_more", None)
531+
if more_btn is not None:
532+
more_btn.setToolTip(_tr("Plus d'actions", "More actions"))
495533
except Exception:
496534
pass
497-
498-
# IDE activity-bar shortcut for dependency analysis.
499535
try:
500-
if getattr(self, "activity_btn_deps", None):
501-
self.activity_btn_deps.clicked.connect(self.suggest_missing_dependencies)
536+
deps_btn = getattr(self, "activity_btn_deps", None)
537+
if deps_btn is not None:
538+
deps_btn.setToolTip(
539+
_tr("Analyser les dependances", "Analyze dependencies")
540+
)
541+
except Exception:
542+
pass
543+
try:
544+
if hasattr(self, "register_language_refresh"):
545+
if not getattr(self, "_ide_menu_i18n_registered", False):
546+
self.register_language_refresh(lambda: _retranslate_ide_like_actions(self))
547+
self._ide_menu_i18n_registered = True
548+
except Exception:
549+
pass
550+
551+
552+
def _connect_ide_like_specific_signals(self) -> None:
553+
"""Connect only IDE-specific signals on top of the classic shared wiring."""
554+
555+
_connect_classic_signals(self)
502556
except Exception:
503557
pass
504558

Core/deps_analyser/analyser.py

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import functools
1717
import configparser
18+
import ast
1819
import json
1920
import os
2021
import platform
@@ -448,6 +449,62 @@ def _resolve_relative_import_root(
448449
return anchor_parts[0] if anchor_parts else ""
449450

450451

452+
def _extract_imported_modules_from_source(
453+
source: str, file_path: str = "", workspace_dir: str | None = None
454+
) -> set[str]:
455+
"""Parse Python source and return normalized top-level imported modules."""
456+
modules: set[str] = set()
457+
try:
458+
tree = ast.parse(source, filename=file_path or "<memory>")
459+
except Exception:
460+
return modules
461+
462+
for node in ast.walk(tree):
463+
if isinstance(node, ast.Import):
464+
for alias in node.names:
465+
top = _top_level_module_name(alias.name)
466+
if top:
467+
modules.add(top)
468+
elif isinstance(node, ast.ImportFrom):
469+
if node.level and node.level > 0:
470+
rel_root = _resolve_relative_import_root(file_path, node.level, workspace_dir)
471+
if rel_root:
472+
modules.add(rel_root)
473+
elif node.module:
474+
top = _top_level_module_name(node.module)
475+
if top:
476+
modules.add(top)
477+
478+
dynamic_imports = re.findall(r"__import__\(['\"]([\w\.]+)['\"]\)", source)
479+
modules.update(
480+
[top for top in (_top_level_module_name(mod) for mod in dynamic_imports) if top]
481+
)
482+
483+
importlib_imports = re.findall(
484+
r"importlib\.import_module\(['\"]([\w\.]+)['\"]\)", source
485+
)
486+
modules.update(
487+
[top for top in (_top_level_module_name(mod) for mod in importlib_imports) if top]
488+
)
489+
return modules
490+
491+
492+
def _extract_imported_modules_from_file(
493+
file_path: str, workspace_dir: str | None = None
494+
) -> set[str]:
495+
"""Read a Python file and return normalized imported modules."""
496+
try:
497+
with open(file_path, encoding="utf-8") as f:
498+
source = f.read()
499+
except Exception:
500+
return set()
501+
return _extract_imported_modules_from_source(
502+
source,
503+
file_path=file_path,
504+
workspace_dir=workspace_dir,
505+
)
506+
507+
451508
def _collect_workspace_module_roots(
452509
filtered_files: list[str], workspace_dir: str | None
453510
) -> set[str]:
@@ -783,38 +840,11 @@ def _t(_key: str, fr: str, en: str) -> str:
783840
if idx - last_pump >= 50:
784841
QApplication.processEvents()
785842
last_pump = idx
786-
787-
with open(file, encoding="utf-8") as f:
788-
source = f.read()
789-
tree = ast.parse(source, filename=file)
790-
# Imports classiques (import ... / from ... import ...)
791-
for node in ast.walk(tree):
792-
if isinstance(node, ast.Import):
793-
for alias in node.names:
794-
top = _top_level_module_name(alias.name)
795-
if top:
796-
modules.add(top)
797-
elif isinstance(node, ast.ImportFrom):
798-
if node.level and node.level > 0:
799-
rel_root = _resolve_relative_import_root(
800-
file, node.level, getattr(self, "workspace_dir", None)
801-
)
802-
if rel_root:
803-
modules.add(rel_root)
804-
elif node.module:
805-
top = _top_level_module_name(node.module)
806-
if top:
807-
modules.add(top)
808-
# Imports dynamiques via __import__ ou importlib.import_module
809-
dynamic_imports = re.findall(r"__import__\(['\"]([\w\.]+)['\"]\)", source)
810843
modules.update(
811-
[top for top in (_top_level_module_name(mod) for mod in dynamic_imports) if top]
812-
)
813-
importlib_imports = re.findall(
814-
r"importlib\.import_module\(['\"]([\w\.]+)['\"]\)", source
815-
)
816-
modules.update(
817-
[top for top in (_top_level_module_name(mod) for mod in importlib_imports) if top]
844+
_extract_imported_modules_from_file(
845+
file,
846+
workspace_dir=getattr(self, "workspace_dir", None),
847+
)
818848
)
819849
except Exception as e:
820850
_log_append(self, f"⚠️ Erreur analyse dépendances dans {file} : {e}")

TODO.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@
4343
- [x] Ajouter une doc architecture Core/Engine/UI.
4444
- [x] Ajouter un guide contributeur (branches, commits, release flow).
4545

46-
### Backlog historique
47-
- [x] Doc engines: preciser que la scroll area n'est pas necessaire en UI monolithique (gestion auto par l'UI).
48-
- [x] Venv manager: confirmer et tester le comportement `--break-system-packages` sous Linux system Python.
46+
- [x] Bcasl doit se lancer avan la compilation.
47+
48+
## i18n
49+
50+
- [x] Des error d'ecirture sont present dans languages/** pour les fichier de i18n json. le probleme vient du fait que certaine chose sont mal orthographier.
51+
52+
## Engines
53+
54+
- [x] les engines sans exception presente tous un probleme de design au niveau du bouton de choix d'icon. En effet le button est un simple button donc on ne sais pas si licon est bien selectionner alors il serait pertinent dajoutetr un label a coter qui montre le chemin de licon selectionner ce qui peut aussi permetrttre un parametrage a la foix ux/ui et manuel.
55+
## Docs
56+
57+
- [ ] il faut preciser dans la docs for engine a la section de monolitics ui que pas la peine de faire le scroll area cr l'ui s'en charge automatiquement dès qu-il voit que le tab est trop large.
58+
59+
## venvmanager
60+
61+
- [ ] il faut que sur linux si on utilise le python of system que les installation utilise le flag break system package pour s'assurer que l-instalation aura lieu coute que coute. sur windows cela nest pas necessaire car windwos n-a pas souvent de problme niveau resolution de package.
62+
63+
## ide gui
64+
- [x] donner toute les capacité gui de classic gui à ide gui.
65+
- [x] pour les installations d'outils des engines ou meme des plugins en generale les action d'installation systeme et de python rentre en conflit le plus souvent alors lors de linstallation on dit dabord prioriser linstaltion systme avant meme de lancer les commande pour l'installation de type python. pour les boite de dialog qui saffiche sepcialemnt pour cela il faut un time out de taping de secret (mot de passe) de l'user genre 120s si le delai est passer on annule linstallation system proprement et selon notrer regle si un processus du genre là est tuer tout les autre non plus le droit de sexecuter il sont comme des process enfant une fois que le processus premier est tuer on anneanti le reste pour securiser le system et surtout les projet des utilisateurs. au dela de cela il faut que l'on ameliore le venvnager car il doit suporter tous les types de venv a savvir .env env venv .venv etc et tout les non bizarre posible dans l'industrie.
66+
67+
## P0
68+
69+
- [x] finaliser la parite IDE/classic et documenter la matrice de parite.
70+
- [x] durcir l'analyseur deps avec un parseur d'imports testable et une meilleure couverture des imports relatifs et dynamiques.

docs/ide_classic_parity.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# IDE-Like vs Classic GUI Parity
2+
3+
This matrix tracks the functional parity between the classic GUI and the IDE-like GUI.
4+
It focuses on shared user-facing behaviors and the remaining IDE-specific deltas.
5+
6+
| Capability | Classic GUI | IDE-like GUI | Status | Notes |
7+
|---|---|---|---|---|
8+
| Shared core signal wiring | Yes | Yes | Done | IDE-like now reuses `UiConnection._connect_signals()`. |
9+
| Workspace / venv / files actions | Yes | Yes | Done | Routed through the same shared handlers. |
10+
| Compilation / cancel actions | Yes | Yes | Done | Uses the common signal connector. |
11+
| Dependencies analysis action | Yes | Yes | Done | Shared button plus IDE activity button. |
12+
| App icon button wiring | Yes | Yes | Done | Explicit IDE binding for `btn_select_icon`. |
13+
| Nuitka icon button wiring | Yes | Yes | Done | Explicit IDE binding for `btn_nuitka_icon`. |
14+
| Entrypoint selector | Yes | Yes | Done | `setup_entrypoint_selector()` is called during IDE init. |
15+
| Theme and language dialogs | Yes | Yes | Done | IDE keeps dedicated affordances on top of shared wiring. |
16+
| More-actions (`...`) menu | No | Yes | Done | IDE-only affordance, now translated and refreshed with language changes. |
17+
| IDE-specific tooltip parity | N/A | Yes | Done | `activity_btn_deps` and `toolButton_more` tooltips are refreshed. |
18+
| Status line | No | Yes | Intentional | IDE-specific enhancement, not a parity gap. |
19+
20+
## Notes
21+
22+
- The goal is behavioral parity, not a pixel-identical UI.
23+
- IDE-only affordances remain acceptable as long as the classic feature set stays available.
24+
- Future parity work should prefer shared wiring/helpers over duplicating signal logic in each UI variant.

0 commit comments

Comments
 (0)