 
   ## helpers
 
-  defp state_after_read(state, remainder, prompt, count \\ 1)
-
-  defp state_after_read(%{capture_prompt: false} = s, remainder, _prompt, _count) do
-    %{s | input: remainder}
-  end
-
-  defp state_after_read(%{capture_prompt: true, output: output} = s, remainder, prompt, count) do
-    %{s | input: remainder, output: <<output::binary, :binary.copy(IO.chardata_to_string(prompt), count)::binary>>}
+  defp state_after_read(%{capture_prompt: false} = state, remainder, _prompt, _count) do
+    %{state | input: remainder}
+  end
+
+  defp state_after_read(%{capture_prompt: true, output: output} = state, remainder, prompt, count) do
+    %{state | input: remainder, output: <<output::binary, :binary.copy(IO.chardata_to_string(prompt), count)::binary>>}
   end
 
   defp bytes_until_eol("", _, count), do: {:split, count}


  ## get_line

  defp get_line(encoding, prompt, %{input: input} = s) do
    case bytes_until_eol(input, encoding, 0) do
      {:split, 0} ->
        {:eof, state_after_read(s, "", prompt)}
      {:split, count} ->
        {result, remainder} = :erlang.split_binary(input, count)

        {result, state_after_read(s, remainder, prompt)}
      {:replace_split, count} ->
        {result, remainder} = :erlang.split_binary(input, count)

        {binary_part(result, 0, byte_size(result) - 2) <> "\n", state_after_read(s, remainder, prompt)}
      :error
        -> {{:error, :collect_line}, s}
    end
  end

  ## get_until

  defp get_until(encoding, prompt, mod, fun, args, %{input: input} = s) do
    case do_get_until(input, encoding, mod, fun, args) do
      {result, input, count} ->
        input =
         case input do
           :eof -> ""
           _ -> list_to_binary(input, encoding)
         end

        {get_until_result(result, encoding), state_after_read(s, input, prompt, count)}

      :error ->
        {:error, s}
    end
  end

  defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)

  defp do_get_until("", encoding, mod, fun, args, continuation, count) do
    case apply(mod, fun, [continuation, :eof | args]) do
      {:done, result, rest} ->
        {result, rest, count + 1}
      {:more, next_continuation} ->
        do_get_until("", encoding, mod, fun, args, next_continuation, count + 1)
    end
  end

  defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
    case bytes_until_eol(chars, encoding, 0) do
      {r, c} when r in [:split, :replace_split] ->
        {line, rest} = :erlang.split_binary(chars, c)

        case apply(mod, fun, [continuation, binary_to_list(line, encoding) | args]) do
          {:done, result, :eof} ->

    {:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
  end

  def handle_info({:io_request, from, reply_as, req}, state) do
    state = io_request(from, reply_as, req, state)
    {:noreply, state}
  end

  def handle_info(message, state) do
    super(message, state)
  end

  def handle_call(:contents, _from, %{input: input, output: output} = state) do
    {:reply, {input, output}, state}
  end

  def handle_call(:flush, _from, %{output: output} = state) do
    {:reply, output, %{state | output: ""}}
  end

  def handle_call(:close, _from, %{input: input, output: output} = state) do
    {:stop, :normal, {:ok, {input, output}}, state}
  end

  def handle_call(request, from, state) do
    super(request, from, state)
  end

  defp io_request(from, reply_as, req, state) do
    {reply, state} = io_request(req, state)
    io_reply(from, reply_as, to_reply(reply))
    state
  end

  defp io_request({:put_chars, chars} = req, state) do
    put_chars(:latin1, chars, req, state)
  end

  defp io_request({:put_chars, mod, fun, args} = req, state) do
    put_chars(:latin1, apply(mod, fun, args), req, state)
  end

  defp io_request({:put_chars, encoding, chars} = req, state) do
    put_chars(encoding, chars, req, state)
  end

  defp io_request({:put_chars, encoding, mod, fun, args} = req, state) do
    put_chars(encoding, apply(mod, fun, args), req, state)
  end

  defp io_request({:get_chars, prompt, count}, state) when count >= 0 do
    io_request({:get_chars, :latin1, prompt, count}, state)
  end

  defp io_request({:get_chars, encoding, prompt, count}, state) when count >= 0 do
    get_chars(encoding, prompt, count, state)
  end

  defp io_request({:get_line, prompt}, state) do
    io_request({:get_line, :latin1, prompt}, state)
  end

  defp io_request({:get_line, encoding, prompt}, state) do
    get_line(encoding, prompt, state)
  end

  defp io_request({:get_until, prompt, mod, fun, args}, state) do
    io_request({:get_until, :latin1, prompt, mod, fun, args}, state)
  end

  defp io_request({:get_until, encoding, prompt, mod, fun, args}, state) do
    get_until(encoding, prompt, mod, fun, args, state)
  end

  defp io_request({:get_password, encoding}, state) do
    get_line(encoding, "", state)
  end

  defp io_request({:setopts, _opts}, state) do
    {{:error, :enotsup}, state}
  end

  defp io_request(:getopts, state) do
    {{:ok, [binary: true, encoding: :unicode]}, state}
  end

  defp io_request({:get_geometry, :columns}, state) do
    {{:error, :enotsup}, state}
  end

  defp io_request({:get_geometry, :rows}, state) do
    {{:error, :enotsup}, state}
  end

  defp io_request({:requests, reqs}, state) do
    io_requests(reqs, {:ok, state})
  end

  defp io_request(_, state) do
    {{:error, :request}, state}
  end

  ## put_chars

  defp put_chars(encoding, chars, req, %{output: output} = state) do
    case :unicode.characters_to_binary(chars, encoding, :unicode) do
      string when is_binary(string) ->
        {:ok, %{state | output: output <> string}}
      {_, _, _} ->
        {{:error, req}, state}
    end
  end

  ## get_chars

  defp get_chars(encoding, prompt, count, %{input: input} = state) do
    case get_chars(input, encoding, count) do
      {:error, _} = error ->
        {error, state}
      {result, input} ->
        {result, state_after_read(state, input, prompt, 1)}
    end
  end

  defp get_chars("", _encoding, _count) do
    {:eof, ""}
  end

  defp get_chars(input, :latin1, count) when byte_size(input) < count do
    {input, ""}
  end

  defp get_chars(input, :latin1, count) do
    <<chars::binary-size(count), rest::binary>> = input
    {chars, rest}
  end

  defp get_chars(input, encoding, count) do
    try do
      case :file_io_server.count_and_find(input, count, encoding) do
        {buf_count, split_pos} when buf_count < count or split_pos == :none ->
          {input, ""}
        {_buf_count, split_pos} ->
          <<chars::binary-size(split_pos), rest::binary>> = input
