| 215 | |
| 216 | |
| 217 | def get_render_choices( |
| 218 | plain_module: PlainModule, |
| 219 | plain_module_render_state: PlainModuleRenderState, |
| 220 | force_render: bool = False, |
| 221 | ) -> dict[str, RenderChoice]: |
| 222 | render_choices = list[RenderChoice]() |
| 223 | module_start_points = list[str]() |
| 224 | |
| 225 | # Decide whether the detected change actually requires a decision. A spec change that only |
| 226 | # adds work *after* the last-rendered functionality (e.g. a new functionality appended to |
| 227 | # the end of the last module) does not affect anything already rendered, so it is treated |
| 228 | # like a normal "continue" rather than a change that requires choosing where to restart. |
| 229 | partial_start = None |
| 230 | treat_as_continue = plain_module_render_state.change is None |
| 231 | if plain_module_render_state.change is not None and plain_module_render_state.change_type == "spec_change": |
| 232 | partial_start = determine_partial_render_start(plain_module) |
| 233 | if change_is_only_future_work(plain_module, plain_module_render_state, partial_start): |
| 234 | treat_as_continue = True |
| 235 | |
| 236 | if treat_as_continue: |
| 237 | # Continue / resume from the last-rendered position. The change-driven blocks below decide |
| 238 | # the start otherwise — once a change touches already-rendered work, resuming from the old |
| 239 | # position would build on stale code. |
| 240 | render_choices.append(_resume_render_choice(plain_module, plain_module_render_state, force_render)) |
| 241 | |
| 242 | else: |
| 243 | # change is set here (otherwise treat_as_continue would be True), so change_type is always |
| 244 | # "spec_change" or "code_change" and get_all_affected_modules_from_change is well-defined. |
| 245 | all_affected_modules = get_all_affected_modules_from_change(plain_module, plain_module_render_state) |
| 246 | |
| 247 | if plain_module_render_state.change_type == "spec_change" and partial_start is not None: |
| 248 | # Optimized partial render: render from the changed functionality, keeping the |
| 249 | # module's earlier (unchanged) functionalities. |
| 250 | render_range = plain_spec.get_render_range_from(partial_start.frid, partial_start.module.plain_source) |
| 251 | render_choices.append( |
| 252 | RenderChoice( |
| 253 | module=partial_start.module, |
| 254 | render_range=render_range, |
| 255 | choice_type="render_from_change", |
| 256 | wipe_later_modules=len(all_affected_modules) > 1, |
| 257 | is_destructive=True, |
| 258 | ) |
| 259 | ) |
| 260 | |
| 261 | # Full re-render of the affected module(s) from scratch. For a code change this is always |
| 262 | # the action; for a spec change with no partial start (non-FR sections changed, or only |
| 263 | # trailing FR removals) it is the only safe option, and when a partial start does exist it |
| 264 | # is offered alongside it as the "rebuild this module cleanly" alternative. The "re-render |
| 265 | # from first module" choice below remains the full-chain reset. |
| 266 | if len(all_affected_modules) > 0 and all_affected_modules[0].module_name not in module_start_points: |
| 267 | render_choices.append( |
| 268 | RenderChoice( |
| 269 | module=all_affected_modules[0], |
| 270 | render_range=None, |
| 271 | choice_type="rerender_affected", |
| 272 | wipe_later_modules=True, |
| 273 | is_destructive=True, |
| 274 | ) |