Coverage for vibefpmgui/window/complexity.py: 93%
77 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-01 20:51 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-01 20:51 +0000
1"""The Complexity panel: measuring the project's Fortran files (on a pool
2thread, while the panel is on view), again when one (or a file one
3includes) is saved; a file that hasn't changed since it was last measured,
4nor have the files it includes, isn't read again.
5"""
7from __future__ import annotations
9import time
10from pathlib import Path
12from .. import complexity as cx
13from ..background import call_in_background
14from ..complexity_panel import ComplexityPanel
15from ..project_search import Query, iter_files, search_roots
16from ..project_tree import fpm_folders
18FORTRAN_GLOBS = ", ".join(f"*{ext}" for ext in sorted(cx.FORTRAN))
21def _stamp(path: Path) -> tuple[int, int] | None:
22 """A file's (modification time, size); None if it's gone."""
23 try:
24 stat = path.stat()
25 except OSError:
26 return None
27 return stat.st_mtime_ns, stat.st_size
30def _key(path: Path, metrics: cx.FileMetrics) -> tuple:
31 return _stamp(path), tuple(_stamp(p) for p in metrics.includes)
34def measure(roots, dependencies: bool, cache: dict) -> tuple[list[cx.FileMetrics], dict]:
35 """Every Fortran file's metrics, and the cache for next time ({path:
36 (key, metrics)}: a file isn't read again while it and the files it
37 includes are unchanged, their modification times and sizes the same)."""
38 files, kept = [], {}
39 query = Query("", include=FORTRAN_GLOBS, dependencies=dependencies)
40 for _root, path in iter_files(roots, query):
41 if _stamp(path) is None: 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true
42 continue
43 old = cache.get(path)
44 if old is not None and old[0] == _key(path, old[1]):
45 metrics = old[1]
46 else:
47 metrics = cx.analyze_file(path)
48 kept[path] = (_key(path, metrics), metrics)
49 files.append(metrics)
50 return files, kept
53class ComplexityMixin:
54 """The Complexity panel (a part of `vibefpmgui.main_window.MainWindow`)."""
56 def _build_complexity_panel(self):
57 """The Complexity panel."""
58 self.complexity_panel = panel = ComplexityPanel()
59 panel.icon_for = self.project_tree.icon_for
60 panel.set_code_font(self._mono_font(editor=True))
61 panel.tests_check.setChecked(self.settings.value("complexity_tests", False, type=bool))
62 panel.tests_check.toggled.connect(lambda on: self.settings.setValue("complexity_tests", on))
63 panel.dependencies_check.setChecked(self.settings.value("complexity_dependencies", False, type=bool))
64 panel.dependencies_check.toggled.connect(lambda on: self.settings.setValue("complexity_dependencies", on))
65 panel.refresh_requested.connect(self.refresh_complexity)
66 panel.open_requested.connect(lambda path, line: self.navigate_to(path, line, 0, preview=True))
67 self._complexity_cache: dict = {}
68 self._complexity_request = 0
69 self._complexity_stale = True
71 def refresh_complexity(self):
72 """Measure the project's code again (or once the panel is on view)."""
73 panel = self.complexity_panel
74 if self.manifest_path is None: 74 ↛ 75line 74 didn't jump to line 75 because the condition on line 74 was never true
75 panel.show_message("Open a project to see the complexity of its Fortran code here.")
76 return
77 if not self.panel_in_front(panel):
78 self._complexity_stale = True
79 return
80 self._complexity_stale = False
81 self._complexity_request += 1
82 request = self._complexity_request
83 project = self.manifest_path.parent
84 roots = search_roots(self.graph, project)
85 dependencies = panel.dependencies_check.isChecked()
86 cache = dict(self._complexity_cache)
87 if not panel.files:
88 panel.show_message("Measuring the code…")
89 call_in_background(lambda: measure(roots, dependencies, cache),
90 lambda result: self._complexity_measured(request, roots, *result), self)
92 def _complexity_measured(self, request: int, roots, files: list[cx.FileMetrics], cache: dict):
93 if request != self._complexity_request or self.manifest_path is None:
94 return
95 self._complexity_cache = cache
96 project = self.manifest_path.parent
97 tests = [Path(folder) for folder, role in fpm_folders(self.manifest_path).items() if role == "test"]
98 dependency_dirs = [r.directory for r in roots if r.dependency]
99 self.complexity_panel.show_metrics(files, project, tests, dependency_dirs, time.strftime("%H:%M"))
101 def _complexity_panel_shown(self, shown: bool):
102 if shown and self._complexity_stale:
103 self.refresh_complexity()
105 def _complexity_file_saved(self, path: str):
106 saved = Path(path)
107 if cx.is_fortran(saved) or any(saved.resolve() == p.resolve()
108 for f in self.complexity_panel.files for p in f.includes):
109 self.refresh_complexity()