Skip to content

Commit

Permalink
fix: terminal corruption when running in iEX (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
francois-codes authored Dec 4, 2024
1 parent cf070e6 commit 07b310f
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ node-*.tar
/node_modules/
package-lock.json

app
41 changes: 27 additions & 14 deletions lib/nodejs/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,28 @@ defmodule NodeJS.Worker do
port =
Port.open(
{:spawn_executable, node},
line: @read_chunk_size,
env: [
{~c"NODE_PATH", node_path(module_path)},
{~c"WRITE_CHUNK_SIZE", String.to_charlist("#{@read_chunk_size}")}
],
args: [node_service_path()]
[
{:line, @read_chunk_size},
{:env, get_env_vars(module_path)},
{:args, [node_service_path()]},
:exit_status,
:stderr_to_stdout
]
)

{:ok, [node_service_path(), port]}
end

defp get_env_vars(module_path) do
[
{~c"NODE_PATH", node_path(module_path)},
{~c"WRITE_CHUNK_SIZE", String.to_charlist("#{@read_chunk_size}")}
]
end

defp get_response(data, timeout) do
receive do
{_, {:data, {flag, chunk}}} ->
{_port, {:data, {flag, chunk}}} ->
data = data ++ chunk

case flag do
Expand All @@ -72,16 +80,15 @@ defmodule NodeJS.Worker do

:eol ->
case data do
@prefix ++ protocol_data ->
{:ok, protocol_data}

_ ->
get_response(~c"", timeout)
@prefix ++ protocol_data -> {:ok, protocol_data}
_ -> get_response(~c"", timeout)
end
end

{_port, {:exit_status, status}} when status != 0 ->
{:error, {:exit, status}}
after
timeout ->
{:error, :timeout}
timeout -> {:error, :timeout}
end
end

Expand Down Expand Up @@ -126,8 +133,14 @@ defmodule NodeJS.Worker do
end
end

defp reset_terminal(port) do
Port.command(port, "\x1b[0m\x1b[?7h\x1b[?25h\x1b[H\x1b[2J")
Port.command(port, "\x1b[!p\x1b[?47l")
end

@doc false
def terminate(_reason, [_, port]) do
reset_terminal(port)
send(port, {self(), :close})
end
end
33 changes: 33 additions & 0 deletions test/js/terminal-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Test various ANSI sequences and terminal control characters
module.exports = {
outputWithANSI: () => {
// Color and formatting
process.stdout.write('\u001b[31mred text\u001b[0m\n');
process.stdout.write('\u001b[1mbold text\u001b[0m\n');

// Cursor movement
process.stdout.write('\u001b[2Amove up\n');
process.stdout.write('\u001b[2Bmove down\n');

// Screen control
process.stdout.write('\u001b[2Jclear screen\n');
process.stdout.write('\u001b[?25linvisible cursor\n');

// Return a clean string to verify protocol handling
return "clean output";
},

// Test function that outputs complex ANSI sequences
complexOutput: () => {
// Nested and compound sequences
process.stdout.write('\u001b[1m\u001b[31m\u001b[4mcomplex formatting\u001b[0m\n');

// OSC sequences (window title, etc)
process.stdout.write('\u001b]0;Window Title\u0007');

// Alternative screen buffer
process.stdout.write('\u001b[?1049h\u001b[Halternate screen\u001b[?1049l');

return "complex test passed";
}
}
20 changes: 20 additions & 0 deletions test/nodejs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,24 @@ defmodule NodeJS.Test do
assert js_error_message(msg) =~ "ReferenceError: require is not defined in ES module scope"
end
end

describe "terminal handling" do
test "handles ANSI sequences without corrupting protocol" do
# Test basic ANSI handling - protocol messages should work
assert {:ok, "clean output"} = NodeJS.call({"terminal-test", "outputWithANSI"})

# Test complex ANSI sequences - protocol messages should work
assert {:ok, "complex test passed"} = NodeJS.call({"terminal-test", "complexOutput"})

# Test multiple processes don't interfere with each other
tasks = for _ <- 1..4 do
Task.async(fn ->
NodeJS.call({"terminal-test", "outputWithANSI"})
end)
end

results = Task.await_many(tasks)
assert Enum.all?(results, &match?({:ok, "clean output"}, &1))
end
end
end

0 comments on commit 07b310f

Please sign in to comment.