fix: issue #20 cmd-J panel tab strip visibility and output panel parity

This commit is contained in:
2026-07-17 20:51:42 +02:00
parent 6c9dd9605b
commit dd53ca3fbc
28 changed files with 1451 additions and 666 deletions

View File

@@ -6,16 +6,20 @@ defmodule BDS.Scripting.Lua do
and opt-in.
"""
require Logger
@type source :: String.t()
@type entrypoint :: String.t()
@type args :: [term()]
@type progress_event :: map()
@type progress_callback :: (progress_event() -> any())
@type output_callback :: (String.t() -> any())
@type execution_option ::
{:timeout, non_neg_integer() | :infinity}
| {:max_reductions, pos_integer() | :none}
| {:spawn_opts, [term()]}
| {:on_progress, progress_callback()}
| {:on_output, output_callback()}
| {:capabilities, map()}
@callback validate(source()) :: :ok | {:error, term()}
@@ -48,12 +52,65 @@ defmodule BDS.Scripting.Lua do
end
end
@doc """
Returns the sink script output (`print`, `bds.app.log`) is routed to: the
`:on_output` callback when given, otherwise a Logger-based default so script
output never leaks onto the console (where it would corrupt the TUI).
"""
@spec output_sink(keyword()) :: output_callback()
def output_sink(opts) when is_list(opts) do
case Keyword.get(opts, :on_output) do
callback when is_function(callback, 1) ->
callback
_other ->
fn text -> Logger.debug("script output: #{text}") end
end
end
@doc """
Builds the Lua-callable function shared by the global `print` override and
the `bds.app.log` capability: joins all arguments with spaces and forwards
the line to the output sink.
"""
@spec output_function(keyword(), [term()]) :: (list(), lua_state() -> {list(), lua_state()})
def output_function(opts, return_values \\ []) when is_list(opts) and is_list(return_values) do
sink = output_sink(opts)
fn args, current_state ->
line =
args
|> :luerl.decode_list(current_state)
|> Enum.map_join(" ", &format_lua_value/1)
sink.(line)
:luerl.encode_list(return_values, current_state)
end
end
@doc "Formats a decoded Lua value the way Lua's `tostring` would present it."
@spec format_lua_value(term()) :: String.t()
def format_lua_value(nil), do: "nil"
def format_lua_value(true), do: "true"
def format_lua_value(false), do: "false"
def format_lua_value(value) when is_binary(value), do: value
def format_lua_value(value) when is_float(value) do
if Float.round(value, 0) == value,
do: "#{trunc(value)}.0",
else: to_string(value)
end
def format_lua_value(value) when is_integer(value), do: Integer.to_string(value)
def format_lua_value(value), do: inspect(value)
defp initial_state(opts) do
state = :luerl_sandbox.init()
capabilities = Keyword.get(opts, :capabilities, %{})
with {:ok, state} <- :luerl.set_table_keys_dec(["bds"], %{}, state),
{:ok, state} <- install_progress_callback(state, Keyword.get(opts, :on_progress)),
{:ok, state} <- install_print_override(state, opts),
{:ok, state} <- install_capabilities(state, capabilities) do
{:ok, state}
end
@@ -85,6 +142,15 @@ defmodule BDS.Scripting.Lua do
defp install_progress_callback(_state, callback),
do: {:error, {:invalid_progress_callback, callback}}
# Replaces the built-in `print` (which would write straight to the BEAM
# console) with a function routed to the output sink.
defp install_print_override(state, opts) do
case :luerl.set_table_keys_dec(["print"], output_function(opts), state) do
{:ok, next_state} -> {:ok, next_state}
error -> {:error, {:print_override_install_failed, error}}
end
end
defp install_capabilities(state, capabilities) when capabilities in [%{}, []], do: {:ok, state}
defp install_capabilities(state, capabilities) when is_map(capabilities) do