Skip to content

Commit 0b235cc

Browse files
committed
Color-code install preview: red for errors, yellow for warnings, green for ready
Add preview_markup opt-in field to MenuItem with automatic Rich markup escaping for all existing previews. Show missing configs and bootloader errors in red, network warning in yellow, "Ready to install" in green. Move network warning from confirmation dialog to install preview so it is visible earlier.
1 parent 76629ec commit 0b235cc

6 files changed

Lines changed: 38 additions & 26 deletions

File tree

archinstall/lib/configuration.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,10 @@ def as_summary(self) -> str:
124124
label_width = max(len(label) for label, _ in rows) + 2
125125
return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows)
126126

127-
async def confirm_config(self, show_install_warnings: bool = False) -> bool:
127+
async def confirm_config(self) -> bool:
128128
header = f'{tr("The specified configuration will be applied")}. '
129129
header += tr('Would you like to continue?') + '\n'
130130

131-
if show_install_warnings:
132-
header += self._render_install_warnings()
133-
134131
group = MenuItemGroup.yes_no()
135132
group.set_preview_for_all(lambda x: self.user_config_to_json())
136133

@@ -156,14 +153,6 @@ def get_install_warnings(self) -> list[str]:
156153

157154
return warnings
158155

159-
def _render_install_warnings(self) -> str:
160-
warnings = self.get_install_warnings()
161-
162-
if not warnings:
163-
return ''
164-
165-
return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n'
166-
167156
def _is_valid_path(self, dest_path: Path) -> bool:
168157
dest_path_ok = dest_path.exists() and dest_path.is_dir()
169158
if not dest_path_ok:

archinstall/lib/global_menu.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import override
22

3+
from rich.markup import escape as _escape_markup
4+
35
from archinstall.default_profiles.profile import GreeterType
46
from archinstall.lib.applications.application_menu import ApplicationMenu
57
from archinstall.lib.args import ArchConfig
@@ -183,6 +185,7 @@ def _get_menu_options(self) -> list[MenuItem]:
183185
MenuItem(
184186
text=tr('Install'),
185187
preview_action=self._prev_install_invalid_config,
188+
preview_markup=True,
186189
key=SpecialMenuKey.INSTALL.value,
187190
),
188191
MenuItem(
@@ -495,20 +498,30 @@ def _validate_bootloader(self) -> str | None:
495498
return None
496499

497500
def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
501+
self.sync_all_to_config()
502+
config_output = ConfigurationOutput(self._arch_config)
503+
504+
warnings = config_output.get_install_warnings()
505+
warnings_text = ''
506+
if warnings:
507+
warnings_text = f'\n\n[yellow]{_escape_markup(tr("Warnings:"))}\n'
508+
for w in warnings:
509+
warnings_text += f'- {_escape_markup(w)}\n'
510+
warnings_text = warnings_text.rstrip('\n') + '[/yellow]'
511+
498512
if missing := self._missing_configs():
499-
text = tr('Missing configurations:\n')
513+
text = f'[red]{_escape_markup(tr("Missing configurations:"))}\n'
500514
for m in missing:
501-
text += f'- {m}\n'
502-
return text[:-1] # remove last new line
515+
text += f'- {_escape_markup(m)}\n'
516+
return text.rstrip('\n') + '[/red]' + warnings_text
503517

504518
if error := self._validate_bootloader():
505-
return tr(f'Invalid configuration: {error}')
519+
return f'[red]{_escape_markup(tr(f"Invalid configuration: {error}"))}[/red]' + warnings_text
506520

507-
self.sync_all_to_config()
508-
summary = ConfigurationOutput(self._arch_config).as_summary()
521+
summary = config_output.as_summary()
509522
if summary:
510-
return f'{tr("Ready to install")}\n\n{summary}'
511-
return tr('Ready to install')
523+
return f'[green]{_escape_markup(tr("Ready to install"))}[/green]{warnings_text}\n\n{_escape_markup(summary)}'
524+
return f'[green]{_escape_markup(tr("Ready to install"))}[/green]{warnings_text}'
512525

513526
def _prev_profile(self, item: MenuItem) -> str | None:
514527
profile_config: ProfileConfiguration | None = item.value

archinstall/scripts/guided.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
226226

227227
if not arch_config_handler.args.silent:
228228
aborted = False
229-
res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True))
229+
res: bool = tui.run(config.confirm_config)
230230

231231
if not res:
232232
debug('Installation aborted')

archinstall/scripts/minimal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
7777

7878
if not arch_config_handler.args.silent:
7979
aborted = False
80-
res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True))
80+
res: bool = tui.run(config.confirm_config)
8181

8282
if not res:
8383
debug('Installation aborted')

archinstall/tui/ui/components.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from enum import Enum, auto
66
from typing import Any, ClassVar, Literal, TypeVar, cast, override
77

8+
from rich.markup import escape as _escape_markup
89
from textual import work
910
from textual.app import App, ComposeResult
1011
from textual.binding import Binding, BindingsMap
@@ -280,7 +281,7 @@ def compose(self) -> ComposeResult:
280281
with Container():
281282
yield option_list
282283
yield Rule(orientation=rule_orientation)
283-
yield ScrollableContainer(Label('', id='preview_content', markup=False))
284+
yield ScrollableContainer(Label('', id='preview_content', markup=True))
284285

285286
if self._filter:
286287
yield Input(placeholder='/filter', id='filter-input')
@@ -359,6 +360,8 @@ def _set_preview(self, item_id: str) -> None:
359360
maybe_preview = item.preview_action(item)
360361

361362
if maybe_preview is not None:
363+
if not item.preview_markup:
364+
maybe_preview = _escape_markup(maybe_preview)
362365
preview_widget.update(maybe_preview)
363366
return
364367

@@ -510,7 +513,7 @@ def compose(self) -> ComposeResult:
510513
with Container():
511514
yield selection_list
512515
yield Rule(orientation=rule_orientation)
513-
yield ScrollableContainer(Label('', id='preview_content', markup=False))
516+
yield ScrollableContainer(Label('', id='preview_content', markup=True))
514517

515518
if self._filter:
516519
yield Input(placeholder='/filter', id='filter-input')
@@ -601,6 +604,8 @@ def _set_preview(self, item: MenuItem) -> None:
601604
if item.preview_action is not None:
602605
maybe_preview = item.preview_action(item)
603606
if maybe_preview is not None:
607+
if not item.preview_markup:
608+
maybe_preview = _escape_markup(maybe_preview)
604609
preview_widget.update(maybe_preview)
605610
return
606611

@@ -688,7 +693,7 @@ def compose(self) -> ComposeResult:
688693
yield Rule(orientation='horizontal')
689694
if self._preview_header is not None:
690695
yield Label(self._preview_header, classes='preview-header', id='preview_header')
691-
yield ScrollableContainer(Label('', id='preview_content', markup=False))
696+
yield ScrollableContainer(Label('', id='preview_content', markup=True))
692697

693698
yield Footer()
694699

@@ -726,6 +731,8 @@ def _update_selection(self) -> None:
726731
else:
727732
text = focused.preview_action(focused)
728733
if text is not None:
734+
if not focused.preview_markup:
735+
text = _escape_markup(text)
729736
preview.update(text)
730737
else:
731738
button.remove_class('-active')
@@ -1016,7 +1023,7 @@ def compose(self) -> ComposeResult:
10161023
yield Rule(orientation='horizontal')
10171024
if self._preview_header is not None:
10181025
yield Label(self._preview_header, classes='preview-header', id='preview-header')
1019-
yield ScrollableContainer(Label('', id='preview_content', markup=False))
1026+
yield ScrollableContainer(Label('', id='preview_content', markup=True))
10201027

10211028
yield Footer()
10221029

@@ -1126,6 +1133,8 @@ def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None
11261133

11271134
maybe_preview = item.preview_action(item)
11281135
if maybe_preview is not None:
1136+
if not item.preview_markup:
1137+
maybe_preview = _escape_markup(maybe_preview)
11291138
preview_widget.update(maybe_preview)
11301139
return
11311140

archinstall/tui/ui/menu_item.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class MenuItem:
1919
dependencies_not: list[str] = field(default_factory=list)
2020
display_action: Callable[[Any], str] | None = None
2121
preview_action: Callable[[Self], str | None] | None = None
22+
preview_markup: bool = False
2223
key: str | None = None
2324

2425
_id: str = ''

0 commit comments

Comments
 (0)