Coverage for vibefpmgui/window/configs.py: 86%

355 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-11-01 20:51 +0000

1"""Build configurations (see `vibefpmgui.build_configs`): the Configuration 

2list at the top of Build options (its ⋯ menu opens another configuration 

3file), and File → Save Build Configuration…. (A project opened with File → 

4Open Project… or Recent Projects comes with its vibefpm.json: see 

5vibefpmgui.window.project.) 

6 

7"New configuration" is the Build options as before: editable, kept in the 

8app's settings. Choosing a saved configuration shows its options, read-only 

9(the fpm executable stays editable: it isn't part of one); choosing New 

10configuration again brings back what was there. Save… adds what's shown to 

11the project's configuration file, as a configuration chosen from then on. 

12Edit makes a saved configuration's options editable, and Save puts them back 

13in the file under its name; leaving it (choosing another, opening another 

14project or file, quitting) with changes not saved asks first. 

15""" 

16 

17from __future__ import annotations 

18 

19from pathlib import Path 

20 

21from qtpy.QtCore import QSize, Qt 

22from qtpy.QtGui import QPalette 

23from qtpy.QtWidgets import ( 

24 QApplication, 

25 QComboBox, 

26 QFileDialog, 

27 QHBoxLayout, 

28 QInputDialog, 

29 QLabel, 

30 QMenu, 

31 QMessageBox, 

32 QToolButton, 

33) 

34 

35from .. import build_configs as bc 

36from ..icons import make_icon 

37from .options import project_key 

38 

39NEW_CONFIGURATION = "New configuration" 

40 

41 

42class ConfigsMixin: 

43 """Build configurations (a part of `vibefpmgui.main_window.MainWindow`).""" 

44 

45 _free_options: bc.Configuration | None = None # New configuration's options, while a saved one is shown 

46 _editing = "" # the saved configuration being edited (its options editable), if any 

47 _listed_file: bc.ConfigFile | None = None # the file the list is of 

48 

49 # (The project's configuration file: config_file, see vibefpmgui.window.options.) 

50 @property 

51 def configurations(self) -> list[bc.Configuration]: 

52 return self.config_file.configurations if self.config_file is not None else [] 

53 

54 @property 

55 def _config_error(self) -> str: 

56 """Why the configuration file can't be read ("" if it can).""" 

57 return self.config_file.error if self.config_file is not None else "" 

58 

59 def _build_config_row(self, form): 

60 """The Configuration row (the first of Build options) and the 

61 read-only note under it.""" 

62 self.config_combo = QComboBox() 

63 self.config_combo.addItem(NEW_CONFIGURATION) 

64 # (As narrow as the panel gets: not as wide as the longest name.) 

65 self.config_combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) 

66 self.config_combo.setMinimumContentsLength(6) 

67 self.config_combo.currentIndexChanged.connect(self._config_chosen) 

68 self.config_edit_button = QToolButton() 

69 self.config_edit_button.setText("Edit") 

70 self.config_edit_button.clicked.connect(self._edit_clicked) 

71 self.config_save_button = QToolButton() 

72 self.config_save_button.setText("Save…") 

73 self.config_save_button.clicked.connect(self.save_configuration) 

74 more = QToolButton() 

75 more.setText("⋯") 

76 more.setToolTip("More: save as, edit a copy, delete, open a configuration file") 

77 menu = QMenu(more) 

78 self.config_save_as_action = menu.addAction("Save As…", self.save_configuration_as) 

79 self.config_save_as_action.setToolTip("Save the options shown as a configuration under another name") 

80 self.config_edit_action = menu.addAction("Edit as New Configuration", self.edit_configuration_as_new) 

81 self.config_edit_action.setToolTip("Copy this configuration's options to New configuration, to change " 

82 "them (and Save…, under its name or another)") 

83 self.config_delete_action = menu.addAction("Delete Configuration…", self.delete_configuration) 

84 menu.addSeparator() 

85 menu.addAction("Open Configuration File…", self.browse_configuration) 

86 self.config_show_action = menu.addAction("Show Configuration File in Editor", self._show_config_file) 

87 menu.setToolTipsVisible(True) 

88 # (Opened by hand: a button's own menu adds an arrow beside the icon.) 

89 more.clicked.connect(lambda: menu.popup(more.mapToGlobal(more.rect().bottomLeft()))) 

90 self._config_more_menu = menu 

91 self.config_more_button = more 

92 # Icon buttons, as the Debug and Git panels' (the texts kept, for the tooltips' and 

93 # screen readers' sake; the icons: see _draw_config_icons). 

94 for button in (self.config_edit_button, self.config_save_button, more): 

95 button.setToolButtonStyle(Qt.ToolButtonIconOnly) 

96 button.setIconSize(QSize(18, 18)) 

97 button.setAutoRaise(True) 

98 row = QHBoxLayout() 

99 row.addWidget(self.config_combo, 1) 

100 row.addWidget(self.config_edit_button) 

101 row.addWidget(self.config_save_button) 

102 row.addWidget(more) 

103 form.addRow(row) # (the panel's whole width: with a label, it made the panel wider than its fields) 

104 self.config_note = QLabel() 

105 self.config_note.setWordWrap(True) 

106 self.config_note.hide() 

107 form.addRow(self.config_note) 

108 self._update_config_ui() 

109 

110 # ------------------------------------------------------------ options 

111 def _option_fields(self) -> list: 

112 """The fields a configuration sets (not the fpm executable).""" 

113 return [self.profile_combo, self.compiler_edit, self.flag_edit, self.target_edit, self.extra_edit, 

114 self.runner_args_edit, self.test_jobs_spin, self.coverage_check, self.verbose_check] 

115 

116 def _watch_option_fields(self): 

117 """(After the fields are made:) a change shows whether there are unsaved ones.""" 

118 for widget in self._option_fields(): 

119 for signal in ("textChanged", "valueChanged", "toggled", "currentTextChanged"): 119 ↛ 118line 119 didn't jump to line 118 because the loop on line 119 didn't complete

120 if hasattr(widget, signal): 

121 getattr(widget, signal).connect(self._options_changed) 

122 break 

123 

124 def _options_changed(self, *_): 

125 if self._editing: 

126 self._update_config_ui() 

127 

128 def config_shown(self) -> bool: 

129 """A saved configuration is shown (read-only, or being edited).""" 

130 return self.config_combo.currentIndex() > 0 

131 

132 def options_locked(self) -> bool: 

133 """A saved configuration is shown, read-only (not being edited).""" 

134 return self.config_shown() and not self._editing 

135 

136 def has_unsaved_configuration(self) -> bool: 

137 """The configuration being edited has changes not in the file.""" 

138 return bool(self._editing) and self.current_options(self._editing) != self._configuration(self._editing) 

139 

140 def current_options(self, name: str = "") -> bc.Configuration: 

141 """The Build options shown, as a configuration named ``name``.""" 

142 return bc.Configuration( 

143 name, profile=self.profile_combo.currentText() or bc.DEFAULT_PROFILE, 

144 features=list(self.selected_features), compiler=self.compiler_edit.text().strip(), 

145 flags=self.flag_edit.text().strip(), target=self.target_edit.text().strip(), 

146 extra_args=self.extra_edit.text().strip(), program_args=self.runner_args_edit.text().strip(), 

147 parallel_tests=self.test_jobs_spin.value(), coverage=self.coverage_check.isChecked(), 

148 verbose=self.verbose_check.isChecked()) 

149 

150 def free_options(self) -> bc.Configuration: 

151 """New configuration's options (those to remember), even while a saved one is shown.""" 

152 return self._free_options if self._free_options is not None else self.current_options() 

153 

154 def _show_options(self, config: bc.Configuration): 

155 """Fill the fields from ``config``.""" 

156 combo = self.profile_combo 

157 if combo.findText(config.profile) < 0: # (a profile fpm.toml no longer has: shown as asked for) 157 ↛ 158line 157 didn't jump to line 158 because the condition on line 157 was never true

158 combo.addItem(config.profile) 

159 combo.setCurrentText(config.profile) 

160 self.compiler_edit.setText(config.compiler) 

161 self.flag_edit.setText(config.flags) 

162 self.target_edit.setText(config.target) 

163 self.extra_edit.setText(config.extra_args) 

164 self.runner_args_edit.setText(config.program_args) 

165 self.test_jobs_spin.setValue(config.parallel_tests) 

166 self.coverage_check.setChecked(config.coverage) 

167 self.verbose_check.setChecked(config.verbose) 

168 self.selected_features = list(config.features) 

169 self._update_features_ui() 

170 

171 def _set_options_locked(self, locked: bool): 

172 """Greyed out (read-only fields looked the same as the others).""" 

173 for widget in self._option_fields(): 

174 if widget is not self.profile_combo: # (its state: see _update_features_ui) 

175 widget.setEnabled(not locked) 

176 self._update_features_ui() 

177 

178 # ------------------------------------------------------------ choosing 

179 def _config_chosen(self, index: int): 

180 if self._editing: 

181 editing = self._editing 

182 if not self.maybe_save_configuration(): 

183 self.config_combo.blockSignals(True) # (Cancel: still on it, its changes kept) 

184 self.config_combo.setCurrentIndex(self._index_of(editing)) 

185 self.config_combo.blockSignals(False) 

186 return 

187 self._editing = "" 

188 if index <= 0: 

189 if self._free_options is not None: 

190 free, self._free_options = self._free_options, None 

191 self._show_options(free) 

192 else: 

193 config = self._configuration(self.config_combo.itemText(index)) 

194 if config is None: 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true

195 return 

196 if self._free_options is None: 

197 self._free_options = self.current_options() 

198 self._show_options(config) 

199 self._set_options_locked(index > 0) 

200 if self.manifest_path is not None: 200 ↛ 202line 200 didn't jump to line 202 because the condition on line 200 was always true

201 self.settings.setValue("build_configuration", self.config_combo.currentText() if index > 0 else "") 

202 self._update_config_ui() 

203 

204 def _configuration(self, name: str) -> bc.Configuration | None: 

205 return next((c for c in self.configurations if c.name == name), None) 

206 

207 def _index_of(self, name: str) -> int: 

208 """The list's item of the saved configuration named ``name`` (0: New 

209 configuration, if there's none; one may be named like it).""" 

210 combo = self.config_combo 

211 return next((i for i in range(1, combo.count()) if name and combo.itemText(i) == name), 0) 

212 

213 def select_configuration(self, name: str): 

214 """Show the configuration named ``name`` ("": New configuration).""" 

215 self.config_combo.setCurrentIndex(self._index_of(name)) 

216 

217 # ------------------------------------------------------------ the file 

218 def _load_configurations(self, reread: bool = True): 

219 """The list of the project's configurations (the project was opened 

220 or reloaded): its configurations read again (not for a file just 

221 opened, or ``reread`` False: just read), the one shown kept (for a 

222 file just opened, the one shown last time); one being edited keeps 

223 its changes.""" 

224 file = self.config_file 

225 if file is None or self.manifest_path is None: 225 ↛ 226line 225 didn't jump to line 226 because the condition on line 225 was never true

226 return 

227 first = file is not self._listed_file 

228 if reread and not first: 

229 file.reread_configurations() # (not its options: they're the window's until written) 

230 self._listed_file = file 

231 if self._editing and not first: 

232 self._refill_items(self._editing) 

233 return 

234 if first: 

235 self._editing = "" # (asked about before the project changed: see maybe_save_configuration) 

236 wanted = self.settings.value("build_configuration", "") or "" 

237 else: 

238 wanted = self.config_combo.currentText() if self.options_locked() else "" 

239 if self._config_error: 

240 self.statusBar().showMessage(f"Build configurations: {self._config_error}", 8000) 

241 self._fill_config_combo(wanted) 

242 

243 def reload_configurations(self): 

244 """The configuration file was saved in the editor: its options, if 

245 they were changed there, applied, and its configurations listed again 

246 (the one shown kept: with its options as the file has them now, 

247 unless it's being edited: its changes are kept).""" 

248 if self.config_file is None or self.manifest_path is None: 248 ↛ 249line 248 didn't jump to line 249 because the condition on line 248 was never true

249 return 

250 self.reload_config_file() # (it reads it all) 

251 self._load_configurations(reread=False) 

252 if self._config_error: 

253 return # (the list says why; it's empty until the file is fixed) 

254 count = len(self.configurations) 

255 self.statusBar().showMessage(f"Build configurations read again from {self.config_path.name}: {count} " 

256 f"configuration{'s' * (count != 1)}", 5000) 

257 

258 def _fill_config_combo(self, wanted: str = ""): 

259 """The list again, showing ``wanted`` (New configuration if it's gone): 

260 back to New configuration first (its options restored), then to 

261 ``wanted`` (its options read again: the file may have changed).""" 

262 self.select_configuration("") 

263 self._refill_items() 

264 if wanted and self._configuration(wanted) is not None: 

265 self.select_configuration(wanted) 

266 self._update_config_ui() 

267 

268 def _refill_items(self, shown: str = ""): 

269 """The list's names again, ``shown`` chosen (no options changed).""" 

270 combo = self.config_combo 

271 combo.blockSignals(True) 

272 while combo.count() > 1: 

273 combo.removeItem(1) 

274 names = [config.name for config in self.configurations] 

275 combo.addItems(names + ([shown] if shown and shown not in names else [])) # (being edited: kept) 

276 combo.setCurrentIndex(self._index_of(shown)) 

277 combo.blockSignals(False) 

278 self._update_config_ui() 

279 

280 def _draw_config_icons(self): 

281 """The Configuration row's icons in the theme's text color (again when 

282 it changes: see window.view): Edit a pencil (Cancel, while editing, an 

283 ×), Save a disk, ⋯ three lines.""" 

284 color = QApplication.palette().color(QPalette.WindowText) 

285 self.config_edit_button.setIcon(make_icon("close" if self._editing else "pencil", color)) 

286 self.config_save_button.setIcon(make_icon("save", color)) 

287 self.config_more_button.setIcon(make_icon("menu", color)) 

288 

289 def _update_config_ui(self): 

290 locked, editing = self.options_locked(), bool(self._editing) 

291 unsaved = self.has_unsaved_configuration() 

292 has_project = getattr(self, "manifest_path", None) is not None 

293 self.config_edit_button.setText("Cancel" if editing else "Edit") 

294 self._draw_config_icons() 

295 self.config_edit_button.setEnabled(locked or editing) 

296 self.config_edit_button.setToolTip( 

297 "Stop editing: the configuration's options as saved" if editing else 

298 "Change this configuration's options (then Save them under its name)" if locked else 

299 "Choose a saved configuration to edit it") 

300 self.config_save_button.setText("Save" if editing else "Save…") 

301 self.config_save_button.setEnabled(has_project and (unsaved if editing else not locked)) 

302 self.config_save_button.setToolTip( 

303 f"Save the changes to “{self._editing}”" if editing else 

304 f"Save these options as a configuration in the project's {bc.FILE_NAME} (beside fpm.toml)") 

305 self.config_save_as_action.setEnabled(has_project) 

306 self.config_edit_action.setEnabled(self.config_shown()) 

307 self.config_delete_action.setEnabled(self.config_shown()) 

308 self.config_show_action.setEnabled(bool(self.config_path and self.config_path.is_file())) 

309 where = self.config_path.name if self.config_path else bc.FILE_NAME 

310 if self._config_error: 

311 tip = f"⚠ {self._config_error}" 

312 if self.options_in_use(): # (read when the project was opened: kept, not written over) 

313 tip += "\nThe project's options aren't saved in it until it's fixed." 

314 elif self.config_path and self.configurations: 

315 tip = f"The configurations in {self.config_path}" 

316 else: 

317 tip = f"No saved configurations yet: Save… adds the options shown to {where}, beside fpm.toml" 

318 self.config_combo.setToolTip(tip) 

319 if locked: 

320 self.config_note.setText(f"<span style='color:gray'>Read-only, from {where}.</span>") 

321 self.config_note.setToolTip("Edit to change its options (then Save), or choose " 

322 f"{NEW_CONFIGURATION}.") 

323 elif editing: 

324 self.config_note.setText(f"<span style='color:#e8590c'>Editing; changes not saved in {where}.</span>" 

325 if unsaved else f"<span style='color:gray'>Editing (from {where}).</span>") 

326 self.config_note.setToolTip("Save puts the changes in the file; Cancel goes back to it as saved.") 

327 self.config_note.setVisible(locked or editing) 

328 self.update_window_title() # (the configuration chosen is in it) 

329 

330 def _remember_config_file(self, manifest: Path, path: Path): 

331 """The configuration file the project uses from now on.""" 

332 self.settings.setValue(project_key(manifest, "build_config_file"), 

333 "" if path == bc.default_path(manifest) else str(path)) 

334 

335 # ------------------------------------------------------------ commands 

336 def save_configuration(self) -> bool: 

337 """Build options → Save (File → Save Build Configuration…): the 

338 configuration being edited, under its name; otherwise Save As….""" 

339 if not self._editing: 

340 return self.save_configuration_as() 

341 name = self._editing 

342 if not self._write_configuration(self.current_options(name)): 342 ↛ 343line 342 didn't jump to line 343 because the condition on line 342 was never true

343 return False 

344 self._editing = "" 

345 self._fill_config_combo(name) 

346 return True 

347 

348 def _config_file_for_saving(self) -> bc.ConfigFile | None: 

349 """The file to save in (None: it can't be read, so isn't written over).""" 

350 file = self.config_file 

351 if file is None: 351 ↛ 352line 351 didn't jump to line 352 because the condition on line 351 was never true

352 return None 

353 if file.error and file.exists(): 

354 QMessageBox.warning(self, "Save Build Configuration", 

355 f"{file.error}\n\nFix the file (or move it away) to save configurations in it.") 

356 return None 

357 return file 

358 

359 def _write_configuration(self, config: bc.Configuration) -> bool: 

360 file = self._config_file_for_saving() 

361 if file is None: 361 ↛ 362line 361 didn't jump to line 362 because the condition on line 361 was never true

362 return False 

363 try: 

364 file.save(config) 

365 except (OSError, bc.ConfigError) as exc: 

366 QMessageBox.warning(self, "Save Build Configuration", f"Couldn't save {file.path}:\n\n{exc}") 

367 return False 

368 self._remember_config_file(self.manifest_path, file.path) 

369 self._config_file_saved() # (a file just made: the project's options go in it from now on) 

370 self.statusBar().showMessage(f"Saved build configuration “{config.name}” in {file.path.name}", 5000) 

371 return True 

372 

373 def save_configuration_as(self) -> bool: 

374 """Save… / ⋯ → Save As…: what's shown, as a configuration in the 

375 project's file under a name asked for, chosen from then on.""" 

376 if self.manifest_path is None: 376 ↛ 377line 376 didn't jump to line 377 because the condition on line 376 was never true

377 return False 

378 file = self._config_file_for_saving() 

379 if file is None: 

380 return False 

381 suggestion = self.config_combo.currentText() if self.config_shown() else "" 

382 name, ok = QInputDialog.getText(self, "Save Build Configuration", 

383 f"Save these Build options in {file.path.name} as:", text=suggestion) 

384 name = name.strip() 

385 if not ok or not name: 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true

386 return False 

387 if name == NEW_CONFIGURATION: 387 ↛ 388line 387 didn't jump to line 388 because the condition on line 387 was never true

388 QMessageBox.warning(self, "Save Build Configuration", f"“{NEW_CONFIGURATION}” is taken: choose " 

389 "another name.") 

390 return False 

391 if self._configuration(name) is not None and name != self._editing and QMessageBox.question( 391 ↛ 394line 391 didn't jump to line 394 because the condition on line 391 was never true

392 self, "Save Build Configuration", 

393 f"{file.path.name} already has a configuration named “{name}”. Replace it?") != QMessageBox.Yes: 

394 return False 

395 if not self._write_configuration(self.current_options(name)): 395 ↛ 396line 395 didn't jump to line 396 because the condition on line 395 was never true

396 return False 

397 self._editing = "" # (saved: under another name, the one edited is left as it was in the file) 

398 self._fill_config_combo(name) 

399 return True 

400 

401 def _edit_clicked(self): 

402 if self._editing: 

403 self.cancel_editing() 

404 else: 

405 self.edit_configuration() 

406 

407 def edit_configuration(self): 

408 """Edit: the saved configuration's options, editable (Save puts them back).""" 

409 if not self.options_locked(): 409 ↛ 410line 409 didn't jump to line 410 because the condition on line 409 was never true

410 return 

411 self._editing = self.config_combo.currentText() 

412 self._set_options_locked(False) 

413 self._update_config_ui() 

414 

415 def cancel_editing(self): 

416 """Cancel: the configuration as saved, read-only again (changes dropped).""" 

417 if not self._editing: 417 ↛ 418line 417 didn't jump to line 418 because the condition on line 417 was never true

418 return 

419 name, self._editing = self._editing, "" 

420 config = self._configuration(name) 

421 if config is None: # (gone from the file while it was edited) 421 ↛ 422line 421 didn't jump to line 422 because the condition on line 421 was never true

422 self._fill_config_combo() 

423 return 

424 self._show_options(config) 

425 self._set_options_locked(True) 

426 self._update_config_ui() 

427 

428 def maybe_save_configuration(self) -> bool: 

429 """Before leaving a configuration being edited with unsaved changes 

430 (choosing another, opening another project or file, quitting): Save, 

431 Discard (it's shown as saved again: what follows may not happen) or 

432 Cancel. False: Cancel (or saving failed): stay.""" 

433 if not self.has_unsaved_configuration(): 

434 return True 

435 answer = QMessageBox.question( 

436 self, "Unsaved Build Configuration", 

437 f"Save the changes to the build configuration “{self._editing}” in {self.config_path.name}?", 

438 QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, QMessageBox.Save) 

439 if answer == QMessageBox.Cancel: 

440 return False 

441 if answer == QMessageBox.Save: 

442 name = self._editing 

443 if not self._write_configuration(self.current_options(name)): 443 ↛ 444line 443 didn't jump to line 444 because the condition on line 443 was never true

444 return False 

445 else: 

446 self.cancel_editing() 

447 return True 

448 

449 def edit_configuration_as_new(self): 

450 """⋯ → Edit as New Configuration: the configuration's options (as 

451 shown, with any changes being made), to change, as New 

452 configuration's (they replace what it had).""" 

453 if not self.config_shown(): 453 ↛ 454line 453 didn't jump to line 454 because the condition on line 453 was never true

454 return 

455 self._editing = "" # (the changes go to New configuration: nothing to ask) 

456 self._free_options = None # (what's shown stays, editable) 

457 self.select_configuration("") 

458 self._save_features() 

459 

460 def delete_configuration(self): 

461 if not self.config_shown() or self.config_path is None: 461 ↛ 462line 461 didn't jump to line 462 because the condition on line 461 was never true

462 return 

463 name = self.config_combo.currentText() 

464 if QMessageBox.question(self, "Delete Build Configuration", 464 ↛ 467line 464 didn't jump to line 467 because the condition on line 464 was never true

465 f"Delete the configuration “{name}” from {self.config_path.name}?") \ 

466 != QMessageBox.Yes: 

467 return 

468 try: 

469 self.config_file.remove(name) 

470 except (OSError, bc.ConfigError) as exc: 

471 QMessageBox.warning(self, "Delete Build Configuration", f"Couldn't change {self.config_path}:\n\n{exc}") 

472 return 

473 self._editing = "" 

474 self.select_configuration("") 

475 self._fill_config_combo() 

476 

477 def browse_configuration(self): 

478 start = str(self.manifest_path.parent) if self.manifest_path else str(Path.home()) 

479 filename, _ = QFileDialog.getOpenFileName(self, "Open Build Configuration", start, 

480 "VibeFPM configurations (*.json);;All files (*)") 

481 if filename: 

482 self.open_configuration(filename) 

483 

484 def open_configuration(self, filename: str | Path): 

485 """Open a configuration file: its project (the fpm.toml beside it), 

486 if it isn't the one open, and its first configuration.""" 

487 path = Path(filename).resolve() 

488 manifest = bc.manifest_for(path) 

489 if manifest is None: 

490 QMessageBox.warning(self, "Open Build Configuration", 

491 f"There's no fpm.toml beside {path.name}: a configuration file goes in the " 

492 "project's folder, next to its fpm.toml.") 

493 return 

494 opened = bc.ConfigFile(path).read() 

495 if opened.error: 495 ↛ 496line 495 didn't jump to line 496 because the condition on line 495 was never true

496 QMessageBox.warning(self, "Open Build Configuration", opened.error) 

497 return 

498 if not self.maybe_save_configuration(): 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true

499 return 

500 manifest = manifest.resolve() 

501 key = project_key(manifest, "build_config_file") 

502 before = self.settings.app.value(key, "") 

503 self._remember_config_file(manifest, path) # (the file the project is opened with) 

504 if manifest != self.manifest_path: 

505 self.open_manifest(manifest) 

506 if self.manifest_path != manifest: # (not opened: Cancel, or fpm running) 

507 self.settings.app.setValue(key, before) 

508 return 

509 elif path != self.config_path: 509 ↛ 512line 509 didn't jump to line 512 because the condition on line 509 was always true

510 self._switch_config_file(manifest, force=True) 

511 self._load_configurations() 

512 configs = self.configurations 

513 self._fill_config_combo(configs[0].name if configs else "") 

514 self.statusBar().showMessage(f"{path.name}: {len(configs)} configuration" 

515 f"{'s' * (len(configs) != 1)}", 5000) 

516 

517 def _show_config_file(self): 

518 if self.config_path and self.config_path.is_file(): 

519 self.open_file(str(self.config_path), force_editor=True)