Skip to content

Commit

Permalink
Merge pull request #1774 from actonlang/add-term-stuff
Browse files Browse the repository at this point in the history
Add term stuff
  • Loading branch information
plajjan authored Apr 5, 2024
2 parents e26fa33 + df4ab27 commit 091cf64
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 6 deletions.
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,20 @@
to wait for the process to finish running before starting to parse its
output as you'll get a single invokation and the full output rather than
receive it piecemeal
- `env.is_tty()` to check if stdout is a TTY
- `acton.rts.start_gc_performance_measurement()` to start a GC perf measurement
- `acton.rts.get_gc_time()` to get the GC timings
- More grey shades in `term`
- `env.is_tty()` to check if stdout is a TTY
- `env.set_stdin(canonical: bool, echo: bool)` to set stdin options
- `canonical` is the default mode which is line buffered where the OS /
terminal offers line editing capabilities and we only receive the input ones
carriage return is hit
- setting `env.set_stdin(canonical=False)` turns stdin into non-canonical mode
where each character as entered is immediately forwarded to the stdin
callback so we can react to it, great for interactive applications!
- `echo` enables (the default) or disables echoing of characters
- `term` improvements:
- More grey shades
- `term.clear` && `term.top` to clear and move cursor to top

### Changed
- The work dir and environment arguments of `process.Process` have been moved to
Expand Down
29 changes: 28 additions & 1 deletion base/builtin/env.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
#define GC_THREADS 1
#include <gc.h>

#include <termios.h>
#include <unistd.h>
#include <uv.h>

#include "env.h"

#include "../rts/io.h"
Expand All @@ -38,6 +41,30 @@ extern int return_val;
return $R_CONT(c$cont, B_None);
}

$R B_EnvD_set_stdinG_local (B_Env self, $Cont c$cont, B_bool canonical, B_bool echo) {
struct termios attr;
tcgetattr(STDIN_FILENO, &attr);

if (canonical != NULL) {
if (fromB_bool(canonical) == true) {
attr.c_lflag |= ICANON; // Set ICANON flag
} else {
attr.c_lflag &= ~ICANON; // Remove ICANON flag
}
}

if (echo != NULL) {
if (fromB_bool(echo) == true) {
attr.c_lflag |= ECHO;
} else {
attr.c_lflag &= ~ECHO;
}
}

tcsetattr(STDIN_FILENO, TCSANOW, &attr);
return $R_CONT(c$cont, B_None);
}

void read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
if (nread < 0){
if (nread == UV_EOF) {
Expand All @@ -56,7 +83,7 @@ void read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
// pin affinity here (and not earlier)..
pin_actor_affinity();
uv_tty_t *tty = acton_malloc(sizeof(uv_tty_t));
uv_tty_init(get_uv_loop(), tty, 0, 1);
uv_tty_init(get_uv_loop(), tty, STDIN_FILENO, 1);
tty->data = cb;
uv_read_start((uv_stream_t*)tty, alloc_buffer, read_stdin);
return $R_CONT(c$cont, B_None);
Expand Down
7 changes: 7 additions & 0 deletions base/rts/rts.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#define GC_THREADS 1
#include <gc.h>

#include <termios.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
Expand Down Expand Up @@ -249,6 +250,8 @@ int64_t get_next_key() {
remote_db_t * db = NULL;
#endif

struct termios old_stdin_attr;

////////////////////////////////////////////////////////////////////////////////////////

/*
Expand Down Expand Up @@ -2088,6 +2091,8 @@ void *$mon_socket_loop() {
}

void rts_shutdown() {
tcsetattr(STDIN_FILENO, TCSANOW, &old_stdin_attr);

rts_exit = 1;
// 0 = main thread, rest is wthreads, thus +1
for (int i = 0; i < num_wthreads+1; i++) {
Expand Down Expand Up @@ -2592,6 +2597,8 @@ int main(int argc, char **argv) {
rqs[i].count = 0;
}

tcgetattr(STDIN_FILENO, &old_stdin_attr);

// RTS startup and module is static stuff, in particular module constants
// which are created during module init are static and do not need to be
// scanned. We therefore use the real_malloc (not GC_malloc) so that it is
Expand Down
9 changes: 6 additions & 3 deletions base/src/__builtin__.act
Original file line number Diff line number Diff line change
Expand Up @@ -946,10 +946,10 @@ actor StringDecoder(cb_out: action(str) -> None, encoding: ?str="utf-8", on_erro
# Attempt to decode all of buf. If it fails we are likely in the middle
# of a multi-byte character so we try again by removing the last bytes
# iteratively until we succeed. UTF-8 has up to 4 bytes per character.
for i in range(1, MAX_UNICODE_CHAR_SIZE+1):
for i in range(len(buf), len(buf)-MAX_UNICODE_CHAR_SIZE, -1):
try:
s = buf[:-i].decode()
buf = buf[-i:]
s = buf[:i].decode()
buf = buf[i:]
cb_out(s)
return
except ValueError:
Expand Down Expand Up @@ -1057,6 +1057,9 @@ actor Env (wc: WorldCap, sc: SysCap, args: list[str]):
sd = StringDecoder(on_stdin, encoding, on_error)
_on_stdin_bytes(sd.decode)

action def set_stdin(canonical: ?bool, echo: ?bool) -> None:
NotImplemented

action def is_tty() -> bool:
NotImplemented

Expand Down
3 changes: 3 additions & 0 deletions base/src/term.act
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ bg_magenta = "\x1b[45m"
bg_cyan = "\x1b[46m"
bg_white = "\x1b[47m"

clear = "\x1b[0J"
top = "\x1b[H"

def up(n=1):
"""Move cursor up n lines.
"""
Expand Down
1 change: 1 addition & 0 deletions docs/acton-by-example/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- [Environment](environment.md)
- [Environment variables](environment/variables.md)
- [Reading stdin input](environment/stdin.md)
- [Interactive stdin input](environment/interactive_stdin.md)
- [Standard library](stdlib.md)
- [Regular Expression](stdlib/re.md)

Expand Down
27 changes: 27 additions & 0 deletions docs/acton-by-example/src/environment/interactive_stdin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Interactive stdin

For interactive programs, like a text editor, input is not fed into the program
line by line, rather the program can react on individual key strokes.

The default stdin mode is the *canonical* mode, which implies line buffering and
that there are typically line editing capabilities offered that are implemented
external to the Acton program. By setting stdin in non-canonical mode we can
instead get the raw key strokes directly fed to us.

```python
actor main(env):
def interact(input):
print("Got some input:", input)

# Set non-canonical mode, so we get each key stroke directly
env.set_stdin(canonical=False)
# Turn off terminal echo
env.set_stdin(echo=False)
env.stdin_install(interact)
```

We can also disable the echo mode with the echo option.

The Acton run time system will copy the stdin terminal settings on startup and
restore them on exit, so you do not need to manually restore terminal echo for
example.
37 changes: 37 additions & 0 deletions examples/inter.act
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# An interacive terminal program that increments and decrements a counter based
# on user input. The program will exit when the user types 'q'. There is also a
# periodic incrementation of the counter every 5 seconds.
#
# The actor based asynchronous I/O model of Acton makes it very natural to
# express event-driven reactive programs like this that can react to user input
# while not blocking on I/O, thus allowing for other tasks to be performed, in
# this case the periodic incrementation of the counter.

actor main(env: Env):
var count = 0

def interact(input):
if input == "q":
print("Quitting!")
env.exit(0)
elif input == "i":
count += 1
print("Incrementing! Count is now:", count)
elif input == "d":
count -= 1
print("Decrementing! Count is now:", count)
else:
print("Unknown command:", input)

# Set non-canonical mode, so we get each key stroke directly
env.set_stdin(canonical=False)
# Turn off terminal echo
env.set_stdin(echo=False)
env.stdin_install(interact)
print("Type 'q' to quit, 'i' to increment a counter and 'd' to decrement it.")

def periodic():
count += 1
print("Periodic +1 increment. Count is now:", count)
after 5: periodic()
after 1: periodic()

0 comments on commit 091cf64

Please sign in to comment.