- IPrintWithWriter protocol + CljElixir.Printer module with pr-str, print-str, pr, prn for all BEAM types (EDN-like output) - Source-mapped error messages: line/col metadata from reader now propagated through transformer into Elixir AST for accurate error locations in .clje files - Interactive REPL (mix clje.repl) with multi-line input detection, history, bindings persistence, and pr-str formatted output - nREPL server (mix clje.nrepl) with TCP transport, Bencode wire protocol, session management, and core operations (clone, close, eval, describe, ls-sessions, load-file, interrupt, completions). Writes .nrepl-port for editor auto-discovery. 92 new tests (699 total, 0 failures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
111 lines
3.4 KiB
Elixir
111 lines
3.4 KiB
Elixir
defmodule CljElixir.NRepl.Handler do
|
|
@moduledoc "Handles nREPL protocol messages."
|
|
|
|
alias CljElixir.NRepl.SessionManager
|
|
|
|
def handle(msg, session_manager) do
|
|
op = Map.get(msg, "op")
|
|
id = Map.get(msg, "id", "unknown")
|
|
session = Map.get(msg, "session")
|
|
|
|
case op do
|
|
"clone" -> handle_clone(id, session, session_manager)
|
|
"close" -> handle_close(id, session, session_manager)
|
|
"eval" -> handle_eval(msg, id, session, session_manager)
|
|
"describe" -> handle_describe(id, session)
|
|
"ls-sessions" -> handle_ls_sessions(id, session_manager)
|
|
"load-file" -> handle_load_file(msg, id, session, session_manager)
|
|
"interrupt" -> handle_interrupt(id, session)
|
|
"completions" -> handle_completions(msg, id, session)
|
|
_ -> [%{"id" => id, "session" => session || "", "status" => ["done", "error", "unknown-op"]}]
|
|
end
|
|
end
|
|
|
|
defp handle_clone(id, _session, manager) do
|
|
new_id = SessionManager.create_session(manager)
|
|
[%{"id" => id, "new-session" => new_id, "status" => ["done"]}]
|
|
end
|
|
|
|
defp handle_close(id, session, manager) do
|
|
SessionManager.close_session(manager, session)
|
|
[%{"id" => id, "session" => session, "status" => ["done"]}]
|
|
end
|
|
|
|
defp handle_eval(msg, id, session, manager) do
|
|
code = Map.get(msg, "code", "")
|
|
|
|
# Capture stdout inside the Agent process where eval actually runs
|
|
{output, result} = SessionManager.eval_with_capture(manager, session, code)
|
|
|
|
responses = []
|
|
|
|
# Send stdout if any
|
|
responses =
|
|
if output != "" do
|
|
responses ++ [%{"id" => id, "session" => session, "out" => output}]
|
|
else
|
|
responses
|
|
end
|
|
|
|
# Send value or error
|
|
responses =
|
|
case result do
|
|
{:ok, value} ->
|
|
responses ++ [%{"id" => id, "session" => session, "value" => value, "ns" => "user"}]
|
|
|
|
{:error, error} ->
|
|
responses ++
|
|
[%{"id" => id, "session" => session, "err" => error, "status" => ["eval-error"]}]
|
|
end
|
|
|
|
# Send done
|
|
responses ++ [%{"id" => id, "session" => session, "status" => ["done"]}]
|
|
end
|
|
|
|
defp handle_describe(id, session) do
|
|
[
|
|
%{
|
|
"id" => id,
|
|
"session" => session || "",
|
|
"status" => ["done"],
|
|
"ops" => %{
|
|
"clone" => %{},
|
|
"close" => %{},
|
|
"eval" => %{},
|
|
"describe" => %{},
|
|
"ls-sessions" => %{},
|
|
"load-file" => %{},
|
|
"interrupt" => %{},
|
|
"completions" => %{}
|
|
},
|
|
"versions" => %{
|
|
"clj-elixir" => %{"major" => 0, "minor" => 1, "incremental" => 0},
|
|
"nrepl" => %{"major" => 1, "minor" => 0, "incremental" => 0}
|
|
}
|
|
}
|
|
]
|
|
end
|
|
|
|
defp handle_ls_sessions(id, manager) do
|
|
sessions = SessionManager.list_sessions(manager)
|
|
[%{"id" => id, "sessions" => sessions, "status" => ["done"]}]
|
|
end
|
|
|
|
defp handle_load_file(msg, id, session, manager) do
|
|
code = Map.get(msg, "file", "")
|
|
handle_eval(Map.put(msg, "code", code), id, session, manager)
|
|
end
|
|
|
|
defp handle_interrupt(id, session) do
|
|
# Not truly implemented -- just acknowledge
|
|
[%{"id" => id, "session" => session, "status" => ["done"]}]
|
|
end
|
|
|
|
defp handle_completions(msg, id, session) do
|
|
_prefix = Map.get(msg, "prefix", "")
|
|
# Basic completions -- return empty for now, can be extended later
|
|
[%{"id" => id, "session" => session || "", "completions" => [], "status" => ["done"]}]
|
|
end
|
|
|
|
end
|