Added Textarea Input (#382)

* Add password input

* Save empty string for password in .livemd file

* Update lib/livebook_web/live/session_live/cell_component.ex

* Changed radio button it to select
* Changed radio button it to select
* Moved select it to the top
* Keep the elements in the select ordered

* Update lib/livebook_web/helpers.ex

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>

* Update lib/livebook_web/live/session_live/input_cell_settings_component.ex

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>

* Update lib/livebook_web/live/session_live/input_cell_settings_component.ex

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>

* Add textarea input
* Add textarea input
* Added operation in the io_proxy to IO.getn

* Update lib/livebook/evaluator/io_proxy.ex

Co-authored-by: José Valim <jose.valim@gmail.com>

* Update lib/livebook_web/live/session_live/cell_component.ex

Co-authored-by: José Valim <jose.valim@gmail.com>

* Update lib/livebook_web/live/session_live/cell_component.ex

Co-authored-by: José Valim <jose.valim@gmail.com>

* Support for utf8 characters in getn function
* Support for utf8 characters in getn function
* Perform test with special characters

* Accepting latin1 and unicode values

* Update lib/livebook/evaluator/io_proxy.ex

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>

* Added split_at function for better performance

* Update lib/livebook/evaluator/io_proxy.ex

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
Co-authored-by: José Valim <jose.valim@gmail.com>
This commit is contained in:
Jean Carlos 2021-06-25 05:24:20 -03:00 committed by GitHub
parent 6062b9b5b4
commit fb8c6e695d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 15 deletions

View file

@ -131,12 +131,12 @@ defmodule Livebook.Evaluator.IOProxy do
put_chars(encoding, apply(mod, fun, args), req, state)
end
defp io_request({:get_chars, _prompt, count}, state) when count >= 0 do
{{:error, :enotsup}, state}
defp io_request({:get_chars, prompt, count}, state) when count >= 0 do
get_chars(:latin1, prompt, state, count)
end
defp io_request({:get_chars, _encoding, _prompt, count}, state) when count >= 0 do
{{:error, :enotsup}, state}
defp io_request({:get_chars, encoding, prompt, count}, state) when count >= 0 do
get_chars(encoding, prompt, state, count)
end
defp io_request({:get_line, prompt}, state) do
@ -249,6 +249,21 @@ defmodule Livebook.Evaluator.IOProxy do
end
end
defp get_chars(encoding, prompt, state, count) do
prompt = :unicode.characters_to_binary(prompt, encoding, state.encoding)
case get_input(prompt, state) do
input when is_binary(input) ->
{chars, rest} = chars_from_input(input, encoding, count)
state = put_in(state.input_buffers[prompt], rest)
{chars, state}
error ->
{error, state}
end
end
defp get_input(prompt, state) do
Map.get_lazy(state.input_buffers, prompt, fn ->
request_input(prompt, state)
@ -289,6 +304,55 @@ defmodule Livebook.Evaluator.IOProxy do
end
end
defp chars_from_input("", _, _count), do: {:eof, ""}
defp chars_from_input(input, :unicode, count) do
if byte_size_utf8(input) >= count do
chars_part(input, :unicode, count)
else
{input, ""}
end
end
defp chars_from_input(input, :latin1, count) do
if byte_size(input) >= count do
chars_part(input, :latin1, count)
else
{input, ""}
end
end
defp chars_part(chars, _, 0), do: {"", chars}
defp chars_part(input, :unicode, count) do
with {:ok, count} <- split_at(input, count, 0) do
<<chars::binary-size(count), rest::binary>> = input
{chars, rest}
end
end
defp chars_part(input, :latin1, count) do
<<chars::binary-size(count), rest::binary>> = input
{chars, rest}
end
defp split_at(_, 0, acc), do: {:ok, acc}
defp split_at(<<h::utf8, t::binary>>, count, acc),
do: split_at(t, count - 1, acc + byte_size(<<h::utf8>>))
defp split_at(<<_, _::binary>>, _count, _acc),
do: {:error, :invalid_unicode}
defp split_at(<<>>, _count, acc),
do: {:ok, acc}
defp byte_size_utf8(chars), do: byte_size_utf8(chars, 0)
defp byte_size_utf8(<<>>, size), do: size
defp byte_size_utf8(<<_h::utf8, t::binary>>, size), do: byte_size_utf8(t, size + 1)
defp io_reply(from, reply_as, reply) do
send(from, {:io_reply, reply_as, reply})
end

View file

@ -20,7 +20,7 @@ defmodule Livebook.Notebook.Cell.Input do
reactive: boolean()
}
@type type :: :text | :url | :number | :password
@type type :: :text | :url | :number | :password | :textarea
@doc """
Returns an empty cell.
@ -50,6 +50,8 @@ defmodule Livebook.Notebook.Cell.Input do
defp validate_value(_value, :password), do: :ok
defp validate_value(_value, :textarea), do: :ok
defp validate_value(value, :url) do
if Utils.valid_url?(value) do
:ok

View file

@ -191,15 +191,26 @@ defmodule LivebookWeb.SessionLive.CellComponent do
<div class="input-label">
<%= @cell_view.name %>
</div>
<input type="<%= if(@cell_view.input_type == :password, do: "password", else: "text") %>"
data-element="input"
class="input <%= if(@cell_view.error, do: "input--error") %>"
name="value"
value="<%= @cell_view.value %>"
phx-debounce="300"
spellcheck="false"
autocomplete="off"
tabindex="-1" />
<%= if (@cell_view.input_type == :textarea) do %>
<textarea
data-element="input"
class="input <%= if(@cell_view.error, do: "input--error") %>"
name="value"
spellcheck="false"
tabindex="-1"><%= [?\n, @cell_view.value] %></textarea>
<% else %>
<input type="<%= if(@cell_view.input_type == :password, do: "password", else: "text") %>"
data-element="input"
class="input <%= if(@cell_view.error, do: "input--error") %>"
name="value"
value="<%= @cell_view.value %>"
phx-debounce="300"
spellcheck="false"
autocomplete="off"
tabindex="-1" />
<% end %>
<%= if @cell_view.error do %>
<div class="input-error">
<%= String.capitalize(@cell_view.error) %>

View file

@ -28,7 +28,7 @@ defmodule LivebookWeb.SessionLive.InputCellSettingsComponent do
<div class="flex flex-col space-y-6">
<div>
<div class="input-label">Type</div>
<%= render_select("type", [number: "Number", password: "Password", text: "Text", url: "URL"], @type) %>
<%= render_select("type", [number: "Number", password: "Password", text: "Text", textarea: "Textarea", url: "URL"], @type) %>
</div>
<div>
<div class="input-label">Name</div>

View file

@ -121,6 +121,41 @@ defmodule Livebook.Evaluator.IOProxyTest do
assert IOProxy.flush_widgets(io) == MapSet.new()
end
test "getn/1 return first character", %{io: io} do
pid =
spawn_link(fn ->
reply_to_input_request(:ref, "name: ", {:ok, "🐈 test\n"}, 1)
end)
IOProxy.configure(io, pid, :ref)
assert IO.getn(io, "name: ") == "🐈"
end
test "getn/2 returns the number of defined characters ", %{io: io} do
pid =
spawn_link(fn ->
reply_to_input_request(:ref, "name: ", {:ok, "Jake Peralta\nAmy Santiago\n"}, 1)
end)
IOProxy.configure(io, pid, :ref)
assert IO.getn(io, "name: ", 13) == "Jake Peralta\n"
assert IO.getn(io, "name: ", 13) == "Amy Santiago\n"
assert IO.getn(io, "name: ", 13) == :eof
end
test "getn/2 all characters", %{io: io} do
pid =
spawn_link(fn ->
reply_to_input_request(:ref, "name: ", {:ok, "Jake Peralta\nAmy Santiago\n"}, 1)
end)
IOProxy.configure(io, pid, :ref)
assert IO.getn(io, "name: ", 10_000) == "Jake Peralta\nAmy Santiago\n"
end
# Helpers
defp reply_to_input_request(_ref, _prompt, _reply, 0), do: :ok