The Erlang Shell

I wanted to dig into the sourcecode of the shell helper functions, and I cannot find some of the implementations, for example e(N), v(N), f(X).

I would appreciate a pointer to their whereabouts!

3 Likes

They are implemented here: otp/shell.erl at 0a114f4d06e8c7634722c27788cbf9c13d8fd857 ¡ erlang/otp ¡ GitHub

4 Likes

Per-project history is possible with a combination of direnv with ERL_FLAGS and -kernel shell_history_path ... (already mentioned by @garazdawi). It’s also language agnostic, so Erlang and Elixir can have their respective separate histories.

1 Like

Thank you!

I have reconsidered my use case.
Usually it is like this:

  • I need a new function
  • type the name of a module whose name sounds right, type the colon and
  • select a function which sounds right
  • try it in the shell
  • if error, lookup documentation

So, normally I have the function where I need the doc already in the history.

I wrote the new shell command hh(N), which takes an expression from the shell history,
walks down its abstract format tree and calls h(M,F) for every function call.

I have not found functions to deal with AF, so I wrote my own tree walker.

The implementation is here:

Test functions for ct are still missing.

If you are interested, I could make a PR out of it.
Otherwise if anybody is interested it should be possible to implement it via user_default.

Example:

5> element([1,2,3], 4).
** exception error: bad argument
     in function  element/2
        called as element([1,2,3],4)
        *** argument 1: not an integer
        *** argument 2: not a tuple
6> hh(-1).             

  -spec element(N, Tuple) -> term()
                   when N :: pos_integer(), Tuple :: tuple().

  Returns the Nth element (numbering from 1) of Tuple, for example:

    > element(2, {a, b, c}).
    b

  Allowed in guard tests.
ok
2 Likes

Great idea.

We were thinking of adding a keyboard shortcut for printing the documentation for the function at the cursor in the current prompt.

1> lists:<Key>
lists doc...

1> lists:seq<Key>
lists:seq doc

1> lists:seq(calendar:localtime<Key>
calendar:localtime doc

1> lists:seq(<Key>calendar:localtime
lists:seq doc again

Tabbing for autocompletion and documentation would compete for the same space after the prompt. So that it will not clutter the shell if you press or multiple times.

4 Likes

I come again with the history subject as I re worked a bit on this subject in a custom :ssh shell.

A good capacity in the IO/Protocol of erlang would be to overwrite the history behavior. For instance to allow to disable the history for a certain time (for instance when you do hot loads), or even not put in history the command typed in a :io.get_line.

There is 2 solutions that comes to my mind:

  1. If we consider the shell behavior should be part of the IO protocol. Then may be the solution would be to create a new :io_request and function that proceed these messages in the :io module. (The request could be push into history. Pop into history. Or an option like the autocomplete one)

  2. Or we can consider that the shell protcol is an additionnal layer after the IO protocol. We can add a way to pass the control sequence to the processes. (Re reading your previous replies. I think this could be the direction you want to take)

1 Like

Hello!

Wanted to provide a bit of a summary of what has been done so far.

  1. The Windows and Unix shells now behave exactly the same and share a lot more code. This means that werl.exe is now only a link to erl.exe.
  2. erl -remsh now works in dumb terminals aka “oldshell” and gives better error indications.
  3. Through the new shell:start_interactive/1 is it possible to start the interactive shell after Erlang has started, for example from an escript or when starting Erlang using -noshell.
  4. Define records in shell using normal syntax, i.e. -record(state, {a,b,c}).
  5. Define functions in the shell using normal syntax, i.e. foo() -> bar..
  6. Define types and specs in the shell using normal syntax, i.e. -spec foo() -> atom()..
  7. Autocompletion for:
    • Variables bound in the shell
    • Records read into the shell using rr(Module)
    • Filepaths in any strings, that is "/home/lukas/<TAB> will now show autocompletion for all files in my $HOME.
    • Function arguments based on the information given in the -spec and -type attributes. This works for both functions in modules and those defined in the shell.
  8. Use Ctrl+O to open an external $EDITOR to edit the current line.
  9. The slogans printed when the shell is started can now to customized using stdlib application configuration parameters.

The following bugs have also been fixed:

Soon ™ we will also have:

  • Expansion results shown below cursor as zsh or fish does it (configurable if you don’t like it)
  • Ctrl+L will work as in bash and other shells.

We also attempted to:

  • Allow macros to be defined in shell
  • Add multiline shell history

But they turned out to be far more work than we had expected, so we’ll have to tackle them later.

It would be great if you all could try it out and let us know either in this thread or on our issue tracker if you encounter any bugs or idiosyncrasies.

22 Likes

How about the ability to paste a large list or map etc in the shell and not have to up arrow through each line of whatever you pasted?

3 Likes

It was part of our “Add multiline shell history” attempt that failed as it turned out to be more complex than one would think. The problem is not when pasting terms, but rather when pasting code with comments.

We will make better support for it, but we wanted to merge what we had right and take some time to think about how exactly we want to go about it before continuing.

3 Likes

Any chance for enabling the use of parse transforms in the shell via config?

1 Like

I implemented, as my first elixir project, my own history. One thing I found useful is the ability to save variable bindings between sessions.

1 Like

One of my everyday pain points is the narrow printing of terms in the shell (normal results, and rp()). Despite my normal terminal being around 140 columns the terms still print in less than 80 (60 it seems from looking at the code!).

Looks like it would be quick to change, the pp function (otp/shell.erl at maint ¡ erlang/otp ¡ GitHub) already does lookup the number of columns available, but then hard-code {line_max_chars, ?CHAR_MAX} (where CHAR_MAX is defined to be 60).

There are different options, maybe just make it {line_max_chars, colums() - <new constant ~20>} would perhaps be the natural - but one could think of adding a config option, etc…

6 Likes

For me it would be definitely the multiline editing and history.

Another issue I am facing is that I am unable to cancel the currently typed expression. This happens usually when I am pasting multiline expression that is incorrectly quoted or has unbalanced brackets. I then randomly put periods, quotes, closing brackets to get at least syntax error and free prompt.

Third thing I would like to see is “unoverwritable prompt”. In other words I do not want my prompt to be overwritten when I am in the middle of typing an expression, for example by log output.
For example when following code is running:

F = fun F1() -> logger:error("logged: ~p", [something]), timer:sleep(3000), F1() end.
spawn(fun() -> F() end).

and I am typing longer than 3 seconds, my prompt is overwritten and I am on new line, unable to see where I am, especially when I need to backspace.

6 Likes

Oh yeahhh this would be great. Also there are a whole class of things kinda-like this where it freaks out in the middle of a line, arrowing up through history leaves “ghost” chars at the end, that sort of thing. I always assumed that was just readline or terminal fudgery issues that couldn’t really be avoided, am I off track there?

1 Like

@garazdawi this is wonderful, and all these changes are so much better beyond what I could imagine is possible in such a short period of time.

quickly though (at least for me) there’s no URL associated with the link to our issue tracker. maybe that’s some security issue with Discourse, or maybe something simply didn’t get properly attached in your post.

besides tagging along with some of the issues others here have raised (particularly syntax-highlighting the returned values in the shell and the the mangling of the input line in the shell in certain circumstances, such as when interacting with more complicated Unicode sequences), I wonder if one idea could cover a number of wants: a status line as emacs, VIM, BASH, and other shells have.

something like what powerline provides could give people a lot of flexibility in customizing their shell and in communicating information back to the user:

  • some status icon showing if the shell is busy, vs. waiting for input or inside a multi-line input
  • a place to potentially show auto-complete suggestions
  • a place to potentially auto-complete keyboard shortcuts when holding down one or more modifiers
  • a customizable banner or identification message

I’m guessing that would also require a lot of work, but hey, while we’re all making our wishlists…

there’s another thing I hadn’t seen suggested, which is a sometimes-handy feature Node introduced a while back; for some expressions they show in a muted color under the current line the current evaluation of that expression. that is, as I’m typing some code, it prints out the result as if I had hit enter and evaluated it. I’m not sure how they determine what code they can eagerly evaluate and which would trigger side-effects, but it’s a neat feature.

pre-evaluation-in-node.mov

5 Likes

I assume @garazdawi might mean this Issues ¡ erlang/otp ¡ GitHub with our issue tracker.

1 Like

Thanks @kuba. Seeing your response it now seems obvious, though I realize I misunderstood the link anyway. I thought it was a link to the beta release so I could test vs. actually intending on being a link to the issue tracker.

Would still be nice to update the link to add the URL, but thanks for clearing up my misunderstanding.

2 Likes

I fixed the link - I guess it was “https://” missing in post you referred to.

AFAIK, there is no beta release yet and you need to build from master on your own - until there will be RC1 for OTP-26 (probably in February next year).

2 Likes

When working on this PR, I realized that I would like a shell to have some limitations as safeguards in terms of memory consumption, and maybe other resources as well.

Long story: To see if there really was a noticeable difference in terms of runtime between lists:merge(L, []) and lists:merge([], L), I wanted a large list, so I did L=lists:seq(1, 100_000_000). Which crashed my shell :face_exhaling: By crashing the node :flushed:

If I did that through a remote shell on a production system, for debugging purposes, this "Oops, typo, one zero too many :woman_shrugging: " would instead be “OMG, I just crashed that very important node that has never gone down once in history. Well, yet :scream:”

Another thing I noticed when following up on that line (did I mention that I like breaking things? :grin:): If I repeatedly do something like L=lists:seq(1, 10_000_000) (one zero less than above, doesn’t crash right away) at a leisurely pace, all is fine, I can go on for eternity. If I go faster, literally hitting Arrow-Up + Enter as fast as I can, the shell (and node) crashes after a few times. To be clear, the commands are still executed sequentially, one after another, just without any pause in between :thinking:

3 Likes

With great power, comes great risk! Not saying your suggestion lacks merit, but I think this is a specific example of the generic case of remote shell access (be it Erlang or Linux console) being able to do harm to a running system. In your example, you are doing a performance comparison; performance tests are not something you’d typically want to do on a live system.

(I’ve had past experience of a - sadly - non-Erlang High Availability system where logs being copied off a production server adversely impacted runtime behaviour)

4 Likes