Coder Social home page Coder Social logo

ratatouille's People

Contributors

albertored avatar fitblip avatar iboard avatar jvantuyl avatar kianmeng avatar ndreynolds avatar trescenzi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ratatouille's Issues

ElixirLS: error starting Ratatouille.Window

Hello!

As I started up a project in VSCode I found that the ElixirLS plugin marks a copy-paste of the Counter example with this error:

** (exit) shutdown: failed to start child: Ratatouille.Window
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, :already_running}
            lib/ratatouille/window.ex:104: Ratatouille.Window.init/1
            (stdlib) gen_server.erl:374: :gen_server.init_it/2
            (stdlib) gen_server.erl:342: :gen_server.init_it/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

ratatouille-error

It seems like ElixirLS is compiling/running the example in the background to check for any errors, but it's not expecting the Ratatouille.run(Counter) at the end. This error goes away if I remove Ratatouille.run or import Ratatouille.View.

I'm not sure if this is due to this library, ElixirLS, or a misconfiguration on my end ๐Ÿคทโ€โ™‚๏ธ. Let me know if I should redirect this to ElixirLS!

Colored borders

Hi,
thanks a lot for this awesome piece of software. I am impressed by command line tools like "lazygit" and started migrating my own invoicing software from Ruby on Rails to Elixir at first with a terminal UI.
Ratatouille comes in handy here. There is one thing I'm missing (maybe it's there and I did not see it ): it would be great to have a color option for the borders of a panel.
With this option you could have "active" panels like in lazygit or lazydocker.
I tried to add the color to the panel borders, but could find out how to get them from the panel call to the rendering.

Best regards,

Christian

Move the cursor on the interface

Hi,
Thanks for this tool.
Can we (or could we) handle cursor movement? In the documentation_browser for instance, using the arrow keys do update the right panel and the selected module appears with a right arrow sign (>) before it, but the cursor itself is at the very bottom of the window. Perhaps I'm more sensitive than others to it, because as a screen reader user, it's necessary to move line by line toward the top of the window each time a key is pressed (the screen reader focuses on the system cursor by default). In this kind of situation, it would be great if the system cursor were placed on the selected element.
I see several ways to do it. A "focused" option could be added to the DSL elements, though it might be misleading as only one visible element on the current screen could be focused and this wouldn't allow to handle all situations (for instance, in the documentation browser, we might want the cursor to be on the first character of the title and not on the > sign, nor at the end of the line).
Is that already implemented and I missed it somehow? Is there a current workaround even if it's not implemented?
Thanks again,
Vincent

[BUG] Sparkline issue when series max and min are the same

Hi.

Thanks for this library, was able to create a small monitoring tool for my requirement. Just want to report on a possible bug where a Sparkline series has the same min and max value possibly causing an arithmetic error. The snippet below has this error:

panel title: "LINES:" do
  sparkline(series: [1])     # Error
  sparkline(series: [1,1])  # Error
  sparkline(series: [1,2])  # Works
end

I noticed in Ratatouille.Renderer.Element.Sparkline.normalize has this possibility:

defp normalize({min, max}, value) do
  x = (value - min) / (max - min)
  round(x * @range)
end

Not sure if this is intended or is better handled by another component. Thanks again.

Testing Update

I'd like to be able to write tests for the update function of my Ratatouille app, but I'm having some trouble.

Based off the counter example:

  describe "update" do
    test "when + is pressed, the model is incremented by 1" do
      assert Counter.App.update(0, {:event, %{ch: ?+}}) == 1
    end
  end

Fails with (UndefinedFunctionError) function Counter.App.update/2 is undefined or private

Mo' colors, mo' problems (extended color support?)

Howdy!

First off I want to say thanks for all your hard work on Ratatouille and ExTermbox! They're both really awesome and useful packages.

Now onto my issue ๐Ÿ˜ˆ

I was wondering if we this could be relaxed to accept any valid color in the extended color palette instead of only the eight, definitely supported everywhere but limited, colors:

@valid_color_codes Constants.colors() |> Map.values()
@valid_attribute_codes Constants.attributes() |> Map.values()
def to_terminal_color(code)
when is_integer(code) and code in @valid_color_codes do
code
end

I noticed ExTermbox doesn't have any problem with accepting valid integers outside of the eight named colors, but because of that validation Ratatouille does not. I'm imagining something like this would remedy it:

diff --git a/lib/ratatouille/renderer/attributes.ex b/lib/ratatouille/renderer/attributes.ex
index ee1f26b..18403c3 100644
--- a/lib/ratatouille/renderer/attributes.ex
+++ b/lib/ratatouille/renderer/attributes.ex
@@ -6,10 +6,11 @@ defmodule Ratatouille.Renderer.Attributes do
   alias Ratatouille.Constants

   @valid_color_codes Constants.colors() |> Map.values()
+  @valid_color_codes_extended 0x11..0xe8
   @valid_attribute_codes Constants.attributes() |> Map.values()

   def to_terminal_color(code)
-      when is_integer(code) and code in @valid_color_codes do
+  when is_integer(code) and code in @valid_color_codes or @valid_color_codes_extended do
     code
   end

I'd be happy to push a PR if you're interested in this approach, or if you can give some guidance on an approach you'd like (or even why this is all a terrrrrrible idea).

Thank you for your time.

Redesigning the terminal event messages

Current Situtation

Currently, the runtime / event manager sends terminal events as messages to the application in the following format:

{:event, 
  %ExTermbox.Event{
    ch: 0, 
    h: 38, 
    key: 0, 
    mod: 0, 
    type: 2, 
    w: 147, 
    x: 0, 
    y: 0
  }}

The ExTermbox.Event struct is directly based off of the C struct from the termbox API, which uses integer codes for the event type, key (based on terminfo), modifier, character, etc.:

https://github.com/nsf/termbox/blob/master/src/termbox.h

So far, the recommended way to match events on a certain key has been to match the integer for that key by first looking up the constant, e.g.:

@ctrl_c Constants.key(:ctrl_c)

case message do
  {:event, %{key: @ctrl_c}} ->
    # handle ctrl-c keypress

This project evolved out of the termbox bindings library, so this event API made sense in the beginning.

Problems

Constants are clumsy

Looking up constants and storing them in an attribute just to pattern match on them feels clumsy and is an unnecessary extra step. It should be possible to simplify the above as such:

case message do
  {:event, %{key: :ctrl_c}} ->
    # handle ctrl-c keypress

However, one issue with this is that some keys are defined to have the same integer value. For example, ctrl-~ (tilde) and ctrl-2 are both set to 0x00. Which begs the question: which one do we set as key above?

Not integrated with views and rendering

So far, events are completely separate from the views. The "target" of an event is always the terminal. I think we can do better than that. If I click on a label element, it should be possible for the rendering library to figure out that I've clicked that element and expose that information via the event.

Union type has a lot of extraneous information

The event struct is really a sort of union type---each event has a different type and depending on the type, certain information will be filled in and other information will be blank. From a usability standpoint, this can be very confusing.

Ideas

Extended events

In order to (mostly) maintain backwards compatibility, we could take the existing ExTermbox.Event{} struct and extend it with computed information, e.g.:

%Ratatouille.Event{
  ch: 0,
  key: 1,
  key_name: :ctrl_a,
  mod: 0,
  mod_name: :alt,
  type: 1,
  type_name: :key,
  w: 0,
  h: 0,
  x: 0,
  y: 0,
  target: nil
}

AFAIK, it would only break code that explicitly matches the struct (vs. matching any map with the keys).

New event structs by type (Breaking change)

Another approach would be to totally overhaul it in a new major version:

%Ratatouille.KeyEvent{
  key: :a,
  mod: :alt,
  key_code: 0,
  character_code: 97
}

%Ratatouille.MouseEvent{
  x: 21,
  y: 56,
  target: %Element{tag: :label, ...}
}

%Ratatouille.ResizeEvent{
  width: 45,
  height: 21
}

These would also be sent as messages in a new style:

{:key, %KeyEvent{}}
{:mouse, %MouseEvent{}}
{:resize, %ResizeEvent{}}

Other Ideas ???

Next Steps

I'd like to get some feedback and let the problem simmer for a while before I start changing anything.

Error getting dependency ex_termbox

Hi,
I could not get the dependency ex_termbox when running mix deps.get

mix.exs

defp deps do
    [
      {:ratatouille, "~> 0.5.0"}
    ]
end
$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
  asciichart 1.0.0
  elixir_make 0.6.0
  ex_termbox 1.0.1
  ratatouille 0.5.0
* Updating ex_termbox (Hex package)
Request failed (403)
** (Mix) Package fetch failed and no cached copy available (https://repo.hex.pm/tarballs/ex_termbox-1.0.1.tar)

I've checked the dependency in master and is set to {:ex_termbox, "~> 1.0"}
https://github.com/ndreynolds/ratatouille/blob/master/mix.exs#L33

In hex.pm the last ex_termbox release is 1.0.0
https://hex.pm/packages/ex_termbox/1.0.0

Any idea why is trying to install ex_termbox 1.0.1?

Thanks
The library looks amazing

Key events stall on linux

Often when rapidly pressing keys, it seems some events get eaten/stall (without hitting the recv loop). This is on ubuntu 20.04 with OTP23 Elixir 1.11.

Dynamically retrieve dimensions of elements

As mentioned in #9 and the commit that closed the issue, the viewport element helps greatly with scrolling content, but it's limited by the lack of an ability to obtain child element dimensions.

This means that while we can render a table element with some amount of nested table_cells, we can't compare the rendered height of the table with the amount of table_cells.

This means that one can't easily do such a thing as calculating that with 100 table_cell elements in a 99 row high table, adjusting the viewport offset_y should only possibly go one down, and then only one up.

I'm aware there's a fair amount of corner cases that need to be accounted for (not to mention the general work necessary to implement the idea), so I'm opening this issue mostly to have a place to discuss what the requirements are for implementing this kind of feature.

Support aligning text content within block-level elements

Similar to the CSS text-align property, it should be possible to specify the alignment of text content inside of block-level elements in Ratatouille.

For example:

label(text_align: :center, content: "Centered content")

In the above example, the renderer should try to create even margins to the left and right of the content within the current render box.

label doesn't accept color attribute?

19:42:59.285 [error] Error in application loop:
  ** (MatchError) no match of right hand side value: 
{:error, "Invalid attributes: 'label' does not accept attributes [:color]"}
    (ratatouille) lib/ratatouille/runtime.ex:123: Ratatouille.Runtime.loop/1
    (ratatouille) lib/ratatouille/runtime.ex:109: Ratatouille.Runtime.run/1
    (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

My code:

defmodule Counter do
  @behaviour Ratatouille.App

  import Ratatouille.View

  def init(_context), do: 0  

  def update(model, msg) do
    case msg do
      {:event, %{ch: ?+}} -> model + 1
      {:event, %{ch: ?-}} -> model - 1
      _ -> model
    end
  end

  def render(model) do
    view do
      label(content: "Counter is #{model} (+/-)", color: :green)
    end
  end

end

Ratatouille.run(Counter)

From the docs:

# Labels are block-level, so this makes text within the whole block red.
label(content: "Red text", color: :red)

Okay...so let's copy and paste the exact line from the docs into my code:

defmodule Counter do
  @behaviour Ratatouille.App

  import Ratatouille.View

  def init(_context), do: 0  

  def update(model, msg) do
    case msg do
      {:event, %{ch: ?+}} -> model + 1
      {:event, %{ch: ?-}} -> model - 1
      _ -> model
    end
  end

  def render(_model) do
    view do
      label(content: "Red text", color: :red)
    end
  end

end

Ratatouille.run(Counter)

Same error:

9:54:27.071 [error] Error in application loop:
  ** (MatchError) no match of right hand side value: 
{:error, "Invalid attributes: 'label' does not accept attributes [:color]"}
    (ratatouille) lib/ratatouille/runtime.ex:123: Ratatouille.Runtime.loop/1
    (ratatouille) lib/ratatouille/runtime.ex:109: Ratatouille.Runtime.run/1
    (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

mix.exs:

  ...
  ...
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
      {:ratatouille, "~> 0.4.0"}
    ]
  end

I get the same error when I try a background style:

  def render(_model) do
    view do
      #label(content: "Counter is #{model} (+/-)")
      label(content: "Red text", background: :green)
    end
  end

error:

0:39:51.208 [error] Error in application loop:
  ** (MatchError) no match of right hand side value: 
{:error, "Invalid attributes: 'label' does not accept attributes [:background]"}
    (ratatouille) lib/ratatouille/runtime.ex:123: Ratatouille.Runtime.loop/1
    (ratatouille) lib/ratatouille/runtime.ex:109: Ratatouille.Runtime.run/1
    (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Same thing with a panel instead of a label:

defmodule Counter do
  @behaviour Ratatouille.App

  import Ratatouille.View

  def init(_context), do: 0  #like initial state of gen_server
                              #becomes initial value of `model`

  def update(model, msg) do
    case msg do
      {:event, %{ch: ?+}} -> model + 1
      {:event, %{ch: ?-}} -> model - 1
      _ -> model
    end
  end

  def render(_model) do
    view do
      #label(content: "Counter is #{model} (+/-)")
      
      panel(content: "Red text", background: :green)
    end
  end

end

Ratatouille.run(Counter)

error:

20:45:27.522 [error] Error in application loop:
  ** (MatchError) no match of right hand side value: 
{:error, "Invalid attributes: 'panel' does not accept attributes [:background, :content]"}
    (ratatouille) lib/ratatouille/runtime.ex:123: Ratatouille.Runtime.loop/1
    (ratatouille) lib/ratatouille/runtime.ex:109: Ratatouille.Runtime.run/1
    (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Two rows child of the root view share X axis

Code:

view do
  row do
    column(size: 4) do
      panel(title: "Journey's Logs", color: :red, height: :fill) do
        # for l in Enum.take(history, )
      end
    end
  end
  row do
    column(size: 6) do
      panel(title: "Your orders, mi'lord?", color: :red, height: :fill) do

      end
    end
  end
end

Result:

Env: Ubuntu 18.04 via WSL2

Screenshots in readme

There are none.

I will run the examples today anyway, I could just quickly make some screenshots and add them to the readme.

Would you be interested or do you think that stuff will change soon and it would be a burden?

Segfault

I'm using the current master and built an app using Toby as an example. Its quite far along now, but I'm frequently running into segfauls. I attached gdb to a running session and got this backtrace.

I was also wondering if you are using a patched version of termbox in ex_termbox. [Just checked] This sort of thing could be a fun project to look at Rust-based NIFs again.

[Insert] โฏโฏโฏ gdb
GNU gdb (GDB) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) attach 16638
Attaching to process 16638
[New LWP 16655]
[New LWP 16656]
[New LWP 16657]
[New LWP 16659]
[New LWP 16660]
[New LWP 16661]
[New LWP 16662]
[New LWP 16663]
[New LWP 16664]
[New LWP 16665]
[New LWP 16666]
[New LWP 16667]
[New LWP 16668]
[New LWP 16669]
[New LWP 16670]
[New LWP 16671]
[New LWP 16672]
[New LWP 16673]
[New LWP 16674]
[New LWP 16675]
[New LWP 16676]
[New LWP 16677]
[New LWP 16678]
[New LWP 16679]
[New LWP 16680]
[New LWP 16681]
[New LWP 16682]
[New LWP 16683]
[New LWP 16684]
[New LWP 16685]
[New LWP 16686]
[New LWP 16697]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/nix/store/7gx4kiv5m0i7d7qkixq2cwzbr10lvxwc-glibc-2.27/lib/libthread_db.so.1".
0x00007f8a0877e49f in __GI___select (nfds=0, readfds=0x0, writefds=0x0, exceptfds=0x0, timeout=0x0)
    at ../sysdeps/unix/sysv/linux/select.c:41
41      ../sysdeps/unix/sysv/linux/select.c: No such file or directory.
(gdb) continue
Continuing.
[Thread 0x7f89ac9b5700 (LWP 16697) exited]
[New Thread 0x7f894b97f700 (LWP 17391)]

Thread 11 "7_scheduler" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7f89c482d700 (LWP 16665)]
0x00000000004a5db1 in mbc_free ()
(gdb) bt
#0  0x00000000004a5db1 in mbc_free ()
#1  0x00000000004acaaa in erts_alcu_free_thr_spec ()
#2  0x00000000005f46e2 in load_nif_2 ()
#3  0x00000000004611c7 in process_main ()
#4  0x000000000045713b in sched_thread_func ()
#5  0x000000000068da20 in thr_wrapper ()
#6  0x00007f8a08c575a7 in start_thread (arg=0x7f89c482d700) at pthread_create.c:463
#7  0x00007f8a0878622f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Trouble composing several elements

Not sure If I am just misusing ratatouille or it is not possible but anyway I would like to clarify some behaviour

If I have the following for rendering two panels

def render_first do
  column(size: 12) do
    panel(title: "First", height: 4)
  end
end

def render_second do
  column(size: 12) do
    panel(title: "Second", height: 4)
  end
end

I can do it like this and both rows show up as expected:

def render(model) do
  view do
    row do render_first end
    row do render_second end
  end
end

However, if I do it like this, only the last defined row will ever show up:

def render_both do
  row do render_first end
  row do render_second end
end

def render(model) do
  view do
    render_both
  end
end

It works if I change it to the following:

def render_both do
  panel do
    row do render_first end
    row do render_second end
  end
end

def render(model) do
  view do
    render_both
  end
end

but then I have a panel and the border, which I don't want.

Cursor functionality

Hi, I'm exploring the idea of writing terminal editor using your library.

Thank you for all the great work.

I've tried editor.exs but the issue here is the cursor symbol has an additional space and when you move it, the text will be jumping by that symbol. The cursor itself should not contain any width.

The solution I came to is using ExTermbox.Bindings.set_cursor/2 but it limits implementation to only one cursor and I still need spaceless symbol for multi-cursor feature.

It would be great to extend a view DSL to provide an ability to render cursors within a pane. I could look into how to implement that.

What do you think?

Thank you

[bug] Ratatouille.Window failed to start

Hi folks,

@kessejones and I have been working on a personal project for a while.
Since yesterday, he can't run the project because of the error below.
On my computer, it's working fine.

We tried solving it with #11, but it didn't work.

** (EXIT from #PID<0.94.0>) shutdown: failed to start child: Ratatouille.Window
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, -1}
            (ratatouille 0.5.1) lib/ratatouille/window.ex:104: Ratatouille.Window.init/1
            (stdlib 3.16.1) gen_server.erl:423: :gen_server.init_it/2
            (stdlib 3.16.1) gen_server.erl:390: :gen_server.init_it/6
            (stdlib 3.16.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

Support offsetting content

In order to implement scrolling in a UI, it should be possible to render content such that it's offset by some number of rows and/or columns.

Examples

Horizontal scrolling inside a table

The major use case I have in mind is horizontal scrolling within a table. For example, take the following table:

โ”‚ PID           Name or Initial Func                       Reds    Memory  MsgQ  Current Functio โ”‚
โ”‚ #PID<0.0.0>   init                                       3730    21680   0     init:boot_loop/ โ”‚
โ”‚ #PID<0.1.0>   erts_code_purger                           22934   26864   0     erts_code_purge โ”‚
โ”‚ #PID<0.2.0>   erts_literal_area_collector:start/0        22076   2688    0     erts_literal_ar โ”‚
โ”‚ #PID<0.3.0>   erts_dirty_process_signal_handler:start/0  575     2688    0     erts_dirty_proc โ”‚
โ”‚ #PID<0.4.0>   erts_dirty_process_signal_handler:start/0  46      2688    0     erts_dirty_proc โ”‚

The table will be further truncated whenever the screen width does not accomodate rendering additional columns, e.g.:

โ”‚ PID           Name or Initial Func                       R |
โ”‚ #PID<0.0.0>   init                                       3 |
โ”‚ #PID<0.1.0>   erts_code_purger                           2 |
โ”‚ #PID<0.2.0>   erts_literal_area_collector:start/0        2 |
โ”‚ #PID<0.3.0>   erts_dirty_process_signal_handler:start/0  5 |
โ”‚ #PID<0.4.0>   erts_dirty_process_signal_handler:start/0  4 |

It should be possible to change where the rendering starts by adding an offset on the x-axis. Here the offset is two columns of the terminal, meaning we've scrolled two columns to the right:

โ”‚ D           Name or Initial Func                       Red |
โ”‚ ID<0.0.0>   init                                       373 |
โ”‚ ID<0.1.0>   erts_code_purger                           229 |
โ”‚ ID<0.2.0>   erts_literal_area_collector:start/0        220 |
โ”‚ ID<0.3.0>   erts_dirty_process_signal_handler:start/0  575 |
โ”‚ ID<0.4.0>   erts_dirty_process_signal_handler:start/0  46  |

We can continue scrolling right by increasing the x offset to four columns:

โ”‚           Name or Initial Func                       Reds  โ”‚
โ”‚ <0.0.0>   init                                       3730  โ”‚
โ”‚ <0.1.0>   erts_code_purger                           22934 โ”‚
โ”‚ <0.2.0>   erts_literal_area_collector:start/0        22076 โ”‚
โ”‚ <0.3.0>   erts_dirty_process_signal_handler:start/0  575   โ”‚
โ”‚ <0.4.0>   erts_dirty_process_signal_handler:start/0  46    โ”‚

less/more-style pager

Similar to how we've applied an x-axis offset above, it can be helpful to apply an offset to the y-axis:

GREP(1)                 General Commands Manual                 GREP(1)

NAME
       grep, egrep, fgrep - print lines matching a pattern

SYNOPSIS
       grep [OPTIONS] PATTERN [FILE...]
       grep [OPTIONS] -e PATTERN ... [FILE...]
       grep [OPTIONS] -f FILE ... [FILE...]

DESCRIPTION
       grep  searches  for  PATTERN in each FILE.  A FILE of โ€œ-โ€ stands
       for standard input.  If no FILE  is  given,  recursive  searches
       examine  the  working  directory, and nonrecursive searches read
       standard input.  By default, grep prints the matching lines.

With 2 rows of offset applied:

NAME
       grep, egrep, fgrep - print lines matching a pattern

SYNOPSIS
       grep [OPTIONS] PATTERN [FILE...]
       grep [OPTIONS] -e PATTERN ... [FILE...]
       grep [OPTIONS] -f FILE ... [FILE...]

DESCRIPTION
       grep  searches  for  PATTERN in each FILE.  A FILE of โ€œ-โ€ stands
       for standard input.  If no FILE  is  given,  recursive  searches
       examine  the  working  directory, and nonrecursive searches read
       standard input.  By default, grep prints the matching lines.

       In addition, the variant programs egrep and fgrep are  the  same
       as  grep -E  and  grep -F,  respectively.   These  variants  are

Contrasting with Scrolling on the Web

While HTML defines a structured document, it makes few prescriptions as to how the content is actually rendered. In the case of a scrollbar, it's the web browser that ultimately decides to show a scrollbar based on directives from CSS and whether or not the content would overflow the allotted rendering region. How the browser's viewport looks at any given moment depends on the HTML, the CSS, running scripts, and the browser's own internal state and environment.

Ratatouille provides both a language for defining such a structured document and a rendering engine for rendering the document. Rendering in Ratatouille is a pure function. Rendering the same document onto the same canvas should always produce an identical resulting canvas. Whether the resulting canvas is then output to a terminal or as a string is just a small implementation detail. Unlike the browser, Ratatouille never directly changes what's rendered in response to user input. Rather, it only provides a pattern for it---an application needs to define a new document in response to the input event, and Ratatouille will render this new document. In this way, rendering and event handling are decoupled in Ratatouille.

This also ties back into scrolling. Since rendering in Ratatouille is just a pure function of a canvas and a document, any rendering directives (height, width, color, etc.) would need to be stored within the canvas (which stores dimensions and cells) or the document. Unlike the document, the canvas isn't hierarchical---it's only top-level information. That means we could store a scroll offset on the top-level, but not for nested elements such as a table within a tab pane.

Implementation

I think this means we need to somehow encode the offsets in the document itself, e.g.

table(offset_x: 5) do
  table_row do
    table_cell(content: "foo")
  end
end

A more elegant approach could be to define a container to handle offseting arbitrary child elements:

viewport(offset_x: 5, offset_y: 3)
  table do
    table_row do
      table_cell(content: "foo")
    end
  end
end

The rendering logic for the viewport element could initially be relatively simple. E.g., given a canvas with a rendering region of 100 columns by 40 rows, and a desired x-offset of 5 and y-offset of 3:

  1. Make a copy of the canvas with no cells and an adjusted rendering region of 105 columns and 43 rows.
  2. Render the viewport's content onto this copy.
  3. Shift all of the rendered cells in the copy by -5 columns and -3 rows.
  4. Merge the copy with the original canvas and return the result.

Support text wrapping / multiline content

To display multiline content like a man page, markdown document, function documentation or a web page, it needs to be possible to provide a large string of text that may or may not contain line breaks, and have this text rendered such that it is wrapped to fit within the bounding rendering box (and not cut off).

Ratatouille should render multiline text in this way when directed. It should respect explicit line breaks and add additional breaks as necessary to prevent content from being cut off.

To showcase this, it would be interesting to build a small application to display Elixir documentation. Maybe a searchable version of IEx.Helpers.h/1.

Examples

man (via less) performs this sort of text reflowing:


DESCRIPTION
       grep  searches for PATTERN in each FILE.  A FILE of โ€œ-โ€
       stands for  standard  input.   If  no  FILE  is  given,
       recursive  searches  examine the working directory, and
       nonrecursive searches read standard input.  By default,
       grep prints the matching lines.

       In  addition,  the variant programs egrep and fgrep are
       the same as grep -E and grep -F,  respectively.   These
       variants  are deprecated, but are provided for backward
       compatibility.

64 columns


DESCRIPTION
       grep  searches  for PATTERN in
       each  FILE.   A  FILE  of  โ€œ-โ€
       stands for standard input.  If
       no FILE  is  given,  recursive
       searches  examine  the working
       directory,  and   nonrecursive
       searches  read standard input.
       By default,  grep  prints  the
       matching lines.

       In   addition,   the   variant
       programs egrep and  fgrep  are
       the   same   as   grep -E  and
       grep -F, respectively.   These
       variants  are  deprecated, but
       are  provided   for   backward
       compatibility.

39 columns

Ignore state which raises errors

I'm looking through the code and fund the loop cycle.

loop(%State{state | model: model, subscriptions: subscriptions})

Right now on any Exception it'll abort the window. I think user wants to get a message that something is wrong but probably we could avoid aborting the whole application and loosing all state here via loading the previous "working" state back.

Or at least allow to configure custom handler for this event.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.