Packages

Native desktop GUI framework for Gleam, powered by Iced

Current section

Files

Jump to
plushie_gleam src plushie@dev_server.erl
Raw

src/plushie@dev_server.erl

-module(plushie@dev_server).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/plushie/dev_server.gleam").
-export([start/1, start_supervised/1]).
-export_type([dev_message/0, dev_state/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Dev server: file watcher and recompiler for live reload.\n"
"\n"
" Watches source directories for `.gleam` file changes, runs\n"
" `gleam build`, detects changed BEAM modules, reloads them, and\n"
" tells the runtime to re-render. The UI updates without losing\n"
" application state.\n"
"\n"
" Started automatically when `dev: True` is set in StartOpts.\n"
" Requires the `file_system` Erlang package.\n"
).
-opaque dev_message() :: recompile | {raw_file_event, gleam@dynamic:dynamic_()}.
-type dev_state() :: {dev_state,
gleam@erlang@process:subject(plushie@runtime:runtime_message()),
gleam@dynamic:dynamic_(),
boolean(),
list({gleam@dynamic:dynamic_(), gleam@dynamic:dynamic_()}),
gleam@erlang@process:subject(dev_message())}.
-file("src/plushie/dev_server.gleam", 188).
?DOC(
" Check if a path is inside a top-level build output directory.\n"
" Matches \"/build/dev/\" or \"build/dev/\" but not \"/my_build/\".\n"
).
-spec is_build_output_dir(binary()) -> boolean().
is_build_output_dir(Path) ->
gleam_stdlib:contains_string(Path, <<"/build/dev/"/utf8>>) orelse gleam_stdlib:string_starts_with(
Path,
<<"build/dev/"/utf8>>
).
-file("src/plushie/dev_server.gleam", 179).
?DOC(
" Check if a path is a Gleam source file worth watching.\n"
" Excludes _build/ and build/ only when they appear as directory\n"
" boundaries, not as substrings of other directory names.\n"
).
-spec is_gleam_file(binary()) -> boolean().
is_gleam_file(Path) ->
((gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) andalso not gleam_stdlib:contains_string(
Path,
<<"/_build/"/utf8>>
))
andalso not gleam_stdlib:string_starts_with(Path, <<"_build/"/utf8>>))
andalso not is_build_output_dir(Path).
-file("src/plushie/dev_server.gleam", 112).
-spec handle_message(dev_state(), dev_message()) -> gleam@otp@actor:next(dev_state(), dev_message()).
handle_message(State, Msg) ->
case Msg of
{raw_file_event, Raw} ->
case plushie_dev_server_ffi:extract_file_path(Raw) of
{ok, Path} ->
case is_gleam_file(Path) of
true ->
case erlang:element(4, State) of
true ->
gleam@otp@actor:continue(State);
false ->
gleam@erlang@process:send_after(
erlang:element(6, State),
100,
recompile
),
gleam@otp@actor:continue(
{dev_state,
erlang:element(2, State),
erlang:element(3, State),
true,
erlang:element(5, State),
erlang:element(6, State)}
)
end;
false ->
gleam@otp@actor:continue(State)
end;
{error, _} ->
gleam@otp@actor:continue(State)
end;
recompile ->
State@1 = {dev_state,
erlang:element(2, State),
erlang:element(3, State),
false,
erlang:element(5, State),
erlang:element(6, State)},
gleam_stdlib:println(<<"plushie dev: recompiling..."/utf8>>),
Output = plushie_ffi:gleam_build(),
case gleam_stdlib:contains_string(Output, <<"error"/utf8>>) of
true ->
gleam_stdlib:println(<<"plushie dev: build failed:"/utf8>>),
gleam_stdlib:println(Output),
gleam@otp@actor:continue(State@1);
false ->
New_mtimes = plushie_ffi:list_beam_files(
<<"build/dev/erlang"/utf8>>
),
Changed = plushie_dev_server_ffi:find_changed_modules(
erlang:element(5, State@1),
New_mtimes
),
case Changed of
[] ->
gleam_stdlib:println(
<<"plushie dev: no modules changed"/utf8>>
),
gleam@otp@actor:continue(
{dev_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
New_mtimes,
erlang:element(6, State@1)}
);
Modules ->
plushie_ffi:reload_modules(Modules),
gleam@erlang@process:send(
erlang:element(2, State@1),
force_rerender
),
gleam_stdlib:println(
<<"plushie dev: reload complete"/utf8>>
),
gleam@otp@actor:continue(
{dev_state,
erlang:element(2, State@1),
erlang:element(3, State@1),
erlang:element(4, State@1),
New_mtimes,
erlang:element(6, State@1)}
)
end
end
end.
-file("src/plushie/dev_server.gleam", 74).
-spec start_actor(
gleam@erlang@process:subject(plushie@runtime:runtime_message())
) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(dev_message()))} |
{error, gleam@otp@actor:start_error()}.
start_actor(Runtime) ->
Initial_mtimes = plushie_ffi:list_beam_files(<<"build/dev/erlang"/utf8>>),
Watcher = plushie_ffi:start_file_watcher([<<"src/"/utf8>>]),
plushie_ffi:file_watcher_subscribe(Watcher),
_pipe@5 = gleam@otp@actor:new_with_initialiser(
5000,
fun(Self) ->
Initial_state = {dev_state,
Runtime,
Watcher,
false,
Initial_mtimes,
Self},
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@1 = gleam@erlang@process:select(_pipe, Self),
gleam@erlang@process:select_other(
_pipe@1,
fun(Msg) -> {raw_file_event, Msg} end
)
end,
_pipe@2 = gleam@otp@actor:initialised(Initial_state),
_pipe@3 = gleam@otp@actor:selecting(_pipe@2, Selector),
_pipe@4 = gleam@otp@actor:returning(_pipe@3, Self),
{ok, _pipe@4}
end
),
_pipe@6 = gleam@otp@actor:on_message(_pipe@5, fun handle_message/2),
gleam@otp@actor:start(_pipe@6).
-file("src/plushie/dev_server.gleam", 51).
?DOC(" Start the dev server actor, watching src/ for changes.\n").
-spec start(gleam@erlang@process:subject(plushie@runtime:runtime_message())) -> nil.
start(Runtime) ->
_ = start_actor(Runtime),
gleam_stdlib:println(
<<"plushie dev: watching "/utf8,
(gleam@string:join([<<"src/"/utf8>>], <<", "/utf8>>))/binary>>
),
nil.
-file("src/plushie/dev_server.gleam", 60).
?DOC(
" Start the dev server under a supervisor.\n"
"\n"
" Returns `Started` for use as a supervisor child spec.\n"
).
-spec start_supervised(
gleam@erlang@process:subject(plushie@runtime:runtime_message())
) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(dev_message()))} |
{error, gleam@otp@actor:start_error()}.
start_supervised(Runtime) ->
case start_actor(Runtime) of
{ok, Started} ->
gleam_stdlib:println(
<<"plushie dev: watching "/utf8,
(gleam@string:join([<<"src/"/utf8>>], <<", "/utf8>>))/binary>>
),
{ok, Started};
{error, Err} ->
{error, Err}
end.