diff --git a/lib/bds/tui.ex b/lib/bds/tui.ex index 2bf71f8..87ca3af 100644 --- a/lib/bds/tui.ex +++ b/lib/bds/tui.ex @@ -487,7 +487,7 @@ defmodule BDS.TUI do [ {%List{ items: items, - selected: state.selected, + selected: list_selected(items, state.selected), highlight_style: if(focused?, do: %Style{fg: :black, bg: :cyan}, @@ -498,6 +498,11 @@ defmodule BDS.TUI do ] end + # The List widget requires selected: nil when the collection is empty + # and raises otherwise — clamp every dynamic list through this. + defp list_selected([], _selected), do: nil + defp list_selected(items, selected), do: min(selected, length(items) - 1) + defp main_widgets(%{editor: nil} = state, rect) do text = case selected_item(state) do @@ -621,7 +626,7 @@ defmodule BDS.TUI do {%Clear{}, overlay}, {%List{ items: items, - selected: if(command.help?, do: nil, else: command.selected), + selected: if(command.help?, do: nil, else: list_selected(items, command.selected)), highlight_style: %Style{fg: :black, bg: :cyan}, block: %Block{title: title, borders: [:all]} }, overlay} @@ -642,7 +647,7 @@ defmodule BDS.TUI do {%Clear{}, overlay}, {%List{ items: report_lines(report), - selected: report.scroll, + selected: list_selected(report_lines(report), report.scroll), scroll_padding: 2, highlight_style: %Style{modifiers: [:bold]}, block: %Block{title: report_title(report), borders: [:all]} diff --git a/test/bds/tui_test.exs b/test/bds/tui_test.exs index b24cf7b..0d1dbbb 100644 --- a/test/bds/tui_test.exs +++ b/test/bds/tui_test.exs @@ -230,6 +230,15 @@ defmodule BDS.TUITest do assert is_binary(action) end + test "input that matches no command renders without errors" do + state = mount_with_executor!() |> press(":") |> press("j") + + # Filtering down to zero commands must not blow up the render + # (List panics on a selected index into an empty item list). + text = screen_text(state) + assert text =~ ":j" + end + test "no match reports an unknown command" do state = mount_with_executor!()