mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-17 08:57:28 +08:00
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:
parent
6062b9b5b4
commit
fb8c6e695d
5 changed files with 127 additions and 15 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) %>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue