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

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""" 

6 

7from __future__ import annotations 

8 

9import time 

10from pathlib import Path 

11 

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 

17 

18FORTRAN_GLOBS = ", ".join(f"*{ext}" for ext in sorted(cx.FORTRAN)) 

19 

20 

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 

28 

29 

30def _key(path: Path, metrics: cx.FileMetrics) -> tuple: 

31 return _stamp(path), tuple(_stamp(p) for p in metrics.includes) 

32 

33 

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 

51 

52 

53class ComplexityMixin: 

54 """The Complexity panel (a part of `vibefpmgui.main_window.MainWindow`).""" 

55 

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 

70 

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) 

91 

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")) 

100 

101 def _complexity_panel_shown(self, shown: bool): 

102 if shown and self._complexity_stale: 

103 self.refresh_complexity() 

104 

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()