Current section

Files

Jump to
etch_erlang src etch erlang input.gleam
Raw

src/etch/erlang/input.gleam

import etch/erlang/tty.{start_input_loop}
import etch/event.{
type Event, type EventError, type KeyboardEnhancementFlag, FailedToParseEvent,
parse_cursor_position, parse_keyboard_enhancement_flags,
}
import etch/internal/consts.{csi}
import gleam/io
import gleam/option.{type Option}
import gleam/string
@external(erlang, "io", "get_chars")
fn get_chars(prompt: String, n: Int) -> String
@external(erlang, "input_ffi", "start_link")
fn start_link() -> Nil
@external(erlang, "tty_state", "init")
fn init_tty_state() -> Nil
/// Checks if there is an [`Event`](https://hexdocs.pm/etch/etch/event.html#Event) available.
/// Returns None if no events were received within the timeout.
/// See also [`read`](input.html#read).
@external(erlang, "input_ffi", "poll")
pub fn poll(timeout: Int) -> Option(Result(Event, EventError))
/// Checks if there is an [`Event`](https://hexdocs.pm/etch/etch/event.html#Event) available.
/// Waits forever for an available event.
/// See also [`poll`](input.html#poll).
@external(erlang, "input_ffi", "read")
pub fn read() -> Option(Result(Event, EventError))
/// Initializes the event server responsible for listening for events.
pub fn init_event_server() {
init_tty_state()
start_link()
start_input_loop()
}
/// Get keyboard enhancement flags. See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement>
/// This function shouldn't be called in a tight loop. It's fine to call it when
/// responding to specific user input (e.g., after a key press), but avoid calling
/// it on every loop iteration.
pub fn get_keyboard_enhancement_flags() -> Result(
List(KeyboardEnhancementFlag),
EventError,
) {
io.print(csi <> "?u")
let flags = get_chars("", 32)
case flags {
"\u{001b}[?" <> s -> {
case string.last(s) {
Ok("u") -> Ok(parse_keyboard_enhancement_flags(s))
_ -> Error(FailedToParseEvent("Could not get enhancment flags"))
}
}
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}
/// Returns cursor position.
/// This function shouldn't be called in a tight loop. It's fine to call it when
/// responding to specific user input (e.g., after a key press), but avoid calling
/// it on every loop iteration.
pub fn get_cursor_position() -> Result(#(Int, Int), EventError) {
io.print(csi <> "6n")
let pos = get_chars("", 32)
case pos {
"\u{001b}[" <> s -> {
case string.last(s) {
Ok("R") -> parse_cursor_position(s)
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}
_ -> Error(FailedToParseEvent("Could not get cursor position"))
}
}