import etch/event.{ type Event, type EventError, type KeyboardEnhancementFlag, FailedToParseEvent, parse_cursor_position, parse_events, parse_keyboard_enhancement_flags, } import etch/internal/consts.{csi} import gleam/io import gleam/option.{type Option} import gleam/string /// 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)) @external(erlang, "io", "get_chars") fn get_chars(prompt: String, n: Int) -> String @external(erlang, "io", "get_line") fn erlang_read(prompt: String) -> String @external(erlang, "input_ffi", "push") fn push(event: Result(Event, EventError)) -> Nil @internal pub fn input_loop(is_raw_mode: Bool) { let str = case is_raw_mode { True -> get_chars("", 128) False -> erlang_read("") } let str = string.to_graphemes(str) let events = parse_events(str, "", [], False) push_events(events) input_loop(is_raw_mode) } fn push_events(events: List(Result(Event, EventError))) { case events { [] -> Nil [e, ..rest] -> { push(e) push_events(rest) } } } /// Get keyboard enhancement flags. See /// 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")) } }