diff --git a/partial_rendering.py b/partial_rendering.py index cbb1ba59..8e864efd 100644 --- a/partial_rendering.py +++ b/partial_rendering.py @@ -60,7 +60,6 @@ def module_comes_before_or_equal( module1: PlainModule, module2: PlainModule, ) -> bool: - for module in all_required_modules: if module.module_name == module1.module_name: return True @@ -120,7 +119,7 @@ def get_all_affected_modules_from_change( plain_module: PlainModule, plain_module_render_state: PlainModuleRenderState, ) -> list[PlainModule]: - all_affected_modules = list[PlainModule]() + all_affected_modules = dict[str, PlainModule]() if plain_module_render_state.change_type == "spec_change": start_module = plain_module_render_state.change @@ -138,10 +137,10 @@ def get_all_affected_modules_from_change( if module.module_name == start_module.module_name: affected_module = True - if affected_module: - all_affected_modules.append(module) + if affected_module and module.module_name not in all_affected_modules: + all_affected_modules[module.module_name] = module - return all_affected_modules + return list(all_affected_modules.values()) def get_render_choices( @@ -151,6 +150,7 @@ def get_render_choices( ) -> dict[str, RenderChoice]: choices = dict[str, RenderChoice]() choice_idx = 1 + module_start_points = list[str]() if plain_module_render_state.last_render_module.is_initial_module(): choices[str(choice_idx)] = RenderChoice( @@ -190,12 +190,13 @@ def get_render_choices( is_destructive=plain_module_render_state.last_render_module.module_name == plain_module.module_name, choice_type="module_start", ) + module_start_points.append(next_module.module_name) choice_idx += 1 if plain_module_render_state.change: all_affected_modules = get_all_affected_modules_from_change(plain_module, plain_module_render_state) - if len(all_affected_modules) > 0: + if len(all_affected_modules) > 0 and all_affected_modules[0].module_name not in module_start_points: choices[str(choice_idx)] = RenderChoice( module=all_affected_modules[0], render_range=None, @@ -203,6 +204,7 @@ def get_render_choices( wipe_later_modules=True, is_destructive=True, ) + module_start_points.append(all_affected_modules[0].module_name) choice_idx += 1 if len(plain_module.all_required_modules) > 0: @@ -210,6 +212,7 @@ def get_render_choices( if first_module.module_name != plain_module_render_state.last_render_module.module_name and ( plain_module_render_state.change is not None and first_module.module_name != plain_module_render_state.change.module_name + and first_module.module_name not in module_start_points ): choices[str(choice_idx)] = RenderChoice( module=first_module, @@ -218,6 +221,7 @@ def get_render_choices( wipe_later_modules=True, is_destructive=True, ) + module_start_points.append(first_module.module_name) choice_idx += 1 choices[str(choice_idx)] = RenderChoice(module=None, render_range=None, choice_type="quit") diff --git a/plain2code.py b/plain2code.py index 68acdb9d..336cc72f 100644 --- a/plain2code.py +++ b/plain2code.py @@ -193,7 +193,7 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901 render_choice = render_choices[list(render_choices.keys())[0]] ask_user = render_choice.is_destructive - if ask_user: + if ask_user and not args.headless: app = PlainModuleRenderChoiceTUI( plain_module, plain_module_render_state, @@ -209,6 +209,11 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901 and render_choice.choice_type == "quit" ): sys.exit(0) + elif ask_user and args.headless: + # ignore the default choice if it requires user input due to headless mode + # fallback to --render-from + # default choice is only used only when the action is not destructive + render_choice = None if render_choice is not None and render_range is not None: raise Exception("Partial rendering and render range cannot be used together") diff --git a/plain_modules.py b/plain_modules.py index aa702edf..c300ec87 100644 --- a/plain_modules.py +++ b/plain_modules.py @@ -71,15 +71,14 @@ def get_codeplain_folder(self): return os.path.join(self.module_build_folder, CODEPLAIN_METADATA_FOLDER) def get_module_render_status(self) -> tuple[str | None, str | None]: - if len(self.required_modules) == 0: - return git_utils.get_last_rendered_functionality(self.module_build_folder) - module_name, frid = git_utils.get_last_rendered_functionality(self.module_build_folder) if module_name is not None and module_name == self.module_name: return module_name, frid - for module in reversed(self.required_modules): - last_rendered_module_name, last_rendered_frid = module.get_module_render_status() + for module in reversed(self.all_required_modules): + last_rendered_module_name, last_rendered_frid = git_utils.get_last_rendered_functionality( + module.module_build_folder + ) if last_rendered_module_name is not None: return last_rendered_module_name, last_rendered_frid @@ -315,7 +314,7 @@ def is_module_fully_rendered(self) -> bool: last_rendered_module_name is not None and last_rendered_module_name == self.module_name and last_rendered_frid is not None - and last_rendered_frid == frids[-1] + and int(last_rendered_frid) >= int(frids[-1]) ): return True diff --git a/tui/plain_module_render_choice_tui.py b/tui/plain_module_render_choice_tui.py index 6d8ee3e4..58350eb7 100644 --- a/tui/plain_module_render_choice_tui.py +++ b/tui/plain_module_render_choice_tui.py @@ -36,7 +36,13 @@ def __init__( def get_msg_from_choice(self, render_choice: RenderChoice) -> str: if render_choice.choice_type == "module_start": - return f"Start from module [#5593FF]{render_choice.module.module_name}[/]" + if render_choice.module.module_name == self.plain_module.module_name: + if render_choice.module.is_module_fully_rendered(): + return f"Re-render the current module ([#5593FF]{render_choice.module.module_name}[/])" + else: + return f"Start rendering the current module ([#5593FF]{render_choice.module.module_name}[/])" + else: + return f"Start from module [#5593FF]{render_choice.module.module_name}[/]" elif render_choice.choice_type == "rerender_affected" and self.plain_module_render_state.change is not None: all_affected_modules = get_all_affected_modules_from_change( self.plain_module, self.plain_module_render_state @@ -96,21 +102,23 @@ def on_mount(self) -> None: if pr.change: title_start = "Spec changes" if pr.change_type == "spec_change" else "Code changes" + is_required_module = pr.change.module_name != self.plain_module.module_name change_box.mount( Label( - f"--- {title_start} detected in required module [#5593FF]{pr.change.module_name}[/] ---", + f"--- {title_start} detected in {'required ' if is_required_module else 'current '}module [#5593FF]{pr.change.module_name}[/] ---", classes="rendering-info-row", ) ) - change_box.mount( - Label( - f"{title_start} in a required module may affect the current module", classes="rendering-info-title" + if is_required_module: + change_box.mount( + Label( + f"{title_start} in a required module may affect the current module", + classes="rendering-info-title", + ) ) - ) elif pr.last_render_module.is_module_fully_rendered(): - change_box.mount(Label("--- Rendering finished ---", classes="rendering-info-row")) - change_box.mount(Label("The current module was fully rendered.", classes="rendering-info-title")) + change_box.mount(Label("The current module is fully rendered.", classes="rendering-info-title")) else: interrupted_frid = "1" interrupted_module = pr.last_render_module @@ -147,7 +155,7 @@ def on_mount(self) -> None: # Populate the ListView lv = self.query_one("#choice-list", ListView) - self.mount(Label("How would you like to continue?", classes="partial-render-question"), before=lv) + self.mount(Label("How would you like to proceed?", classes="partial-render-question"), before=lv) for key, choice in self.render_choices.items(): lv.append(ListItem(Label(f"[bold]{key}.[/bold] {self.get_msg_from_choice(choice)}"), id=f"choice-{key}")) lv.focus() @@ -170,6 +178,16 @@ def on_choice_selected(self, event: ListView.Selected) -> None: if not item_id or not item_id.startswith("choice-"): raise ValueError(f"Invalid item ID: {item_id}") key = item_id.split("-", 1)[1] + self._select_choice(key) + + def on_key(self, event) -> None: + """Allow selecting a choice by typing its number directly.""" + if event.key in self.render_choices: + event.stop() + event.prevent_default() + self._select_choice(event.key) + + def _select_choice(self, key: str) -> None: self.selected_choice = self.render_choices[key] self.exit(self.selected_choice)