diff --git a/archinstall/lib/models/packages.py b/archinstall/lib/models/packages.py index 6ba6e134..1adc527e 100644 --- a/archinstall/lib/models/packages.py +++ b/archinstall/lib/models/packages.py @@ -1,3 +1,5 @@ +import os +import textwrap from dataclasses import dataclass, field from enum import Enum from functools import cached_property @@ -134,13 +136,32 @@ class AvailablePackage(BaseModel): # return all package info line by line def info(self) -> str: - output = '' - for key, value in self.model_dump().items(): - key = key.replace('_', ' ').capitalize() - key = key.ljust(self.longest_key) - output += f'{key} : {value}\n' + # Preview pane occupies roughly half the terminal width when shown + # alongside the option list. Wrap each value to fit so long fields + # (Description, Optional Deps, etc.) do not produce horizontal scroll. + try: + cols = os.get_terminal_size().columns + except OSError: + cols = 80 + preview_width = max(40, cols // 2 - 5) + indent = ' ' * (self.longest_key + 3) - return output + lines: list[str] = [] + for key, value in self.model_dump().items(): + key_label = key.replace('_', ' ').capitalize().ljust(self.longest_key) + prefix = f'{key_label} : ' + lines.append( + textwrap.fill( + str(value), + width=preview_width, + initial_indent=prefix, + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ), + ) + + return '\n'.join(lines) + '\n' @cached_property def get_depends_on(self) -> list[str]: diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index 3572c6c4..6c7de390 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -9,6 +9,12 @@ from archinstall.lib.translationhandler import tr from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.ui.result import ResultType +# Force pacman to emit untruncated single-line field values. With the default +# pty width of 80 columns, pacman wraps long fields like Description across +# multiple lines, and our line-based parser ends up dropping the continuation +# lines after they get pre-stripped of their leading whitespace. See #3580. +_WIDE_PACMAN_ENV = {'COLUMNS': '500'} + def installed_package(package: str) -> LocalPackage | None: try: @@ -52,7 +58,7 @@ def package_group_info(package: str) -> PackageGroup | None: def available_package(package: str) -> AvailablePackage | None: try: package_info: list[str] = [] - for line in Pacman.run(f'-S --info {package}'): + for line in Pacman.run(f'-S --info {package}', environment_vars=_WIDE_PACMAN_ENV): package_info.append(line.decode().strip()) return _parse_package_output(package_info, AvailablePackage) @@ -78,7 +84,7 @@ def list_available_packages( except Exception as e: debug(f'Failed to sync Arch Linux package database: {e}') - for line in Pacman.run('-S --info'): + for line in Pacman.run('-S --info', environment_vars=_WIDE_PACMAN_ENV): dec_line = line.decode().strip() current_package.append(dec_line) diff --git a/archinstall/lib/pacman/pacman.py b/archinstall/lib/pacman/pacman.py index 83f8b40d..4f0a1e82 100644 --- a/archinstall/lib/pacman/pacman.py +++ b/archinstall/lib/pacman/pacman.py @@ -18,7 +18,11 @@ class Pacman: self.target = target @staticmethod - def run(args: str, default_cmd: str = 'pacman') -> SysCommand: + def run( + args: str, + default_cmd: str = 'pacman', + environment_vars: dict[str, str] | None = None, + ) -> SysCommand: """ A centralized function to call `pacman` from. It also protects us from colliding with other running pacman sessions (if used locally). @@ -37,7 +41,7 @@ class Pacman: error(tr('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.')) sys.exit(1) - return SysCommand(f'{default_cmd} {args}') + return SysCommand(f'{default_cmd} {args}', environment_vars=environment_vars) def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs) -> None: # type: ignore[no-untyped-def, type-arg] while True: