///|
pub fn parse_input(bytes : Bytes) -> Array[Event] {
let (events, _) = parse_available(bytes, false)
events
}
///|
struct InputDecoder {
pending : Bytes
} derive(Eq, Debug)
///|
fn InputDecoder::new() -> InputDecoder {
{ pending: b"" }
}
///|
fn InputDecoder::has_pending(self : InputDecoder) -> Bool {
self.pending.length() > 0
}
///|
fn InputDecoder::pending_is_lone_escape(self : InputDecoder) -> Bool {
self.pending.length() == 1 && self.pending[0].to_int() == 0x1b
}
///|
fn InputDecoder::feed(
self : InputDecoder,
bytes : Bytes,
) -> (Array[Event], InputDecoder) {
let input = if self.pending.length() == 0 {
bytes
} else {
concat_bytes(self.pending, bytes)
}
let (events, consumed) = parse_available(input, true)
let pending = if consumed >= input.length() {
b""
} else {
input[consumed:].to_owned()
}
(events, { pending, })
}
///|
fn InputDecoder::flush(self : InputDecoder) -> (Array[Event], InputDecoder) {
(parse_input(self.pending), { pending: b"" })
}
///|
fn parse_available(
bytes : Bytes,
hold_incomplete : Bool,
) -> (Array[Event], Int) {
let events : Array[Event] = []
let mut index = 0
while index < bytes.length() {
match parse_one(bytes, index, hold_incomplete) {
Parsed(event, next) => {
events.push(event)
index = next
}
IncompleteInput => break
}
}
(events, index)
}
///|
priv enum InputParse {
Parsed(Event, Int)
IncompleteInput
}
///|
fn parse_one(bytes : Bytes, index : Int, hold_incomplete : Bool) -> InputParse {
let byte = bytes[index].to_int()
if byte == 0x1b {
if starts_with_ascii(bytes, index, "\u{1b}[200~") {
parse_paste(bytes, index, hold_incomplete)
} else {
parse_escape(bytes, index, hold_incomplete)
}
} else if byte == 0x0d || byte == 0x0a {
Parsed(Key(Enter), index + 1)
} else if byte == 0x7f || byte == 0x08 {
Parsed(Key(Backspace), index + 1)
} else if byte == 0x09 {
Parsed(Key(Tab), index + 1)
} else if byte > 0 && byte < 0x20 {
Parsed(
Key(Ctrl(String::make(1, Int::unsafe_to_char(byte + 96)))),
index + 1,
)
} else if byte < 0x80 {
Parsed(Key(Char(String::make(1, Int::unsafe_to_char(byte)))), index + 1)
} else {
let len = utf8_char_len(byte)
if hold_incomplete && index + len > bytes.length() {
IncompleteInput
} else {
let (text, next) = take_utf8_char(bytes, index)
Parsed(Key(Char(text)), next)
}
}
}
///|
fn parse_escape(
bytes : Bytes,
index : Int,
hold_incomplete : Bool,
) -> InputParse {
if index + 1 >= bytes.length() {
return if hold_incomplete {
IncompleteInput
} else {
Parsed(Key(Escape), index + 1)
}
}
let next = bytes[index + 1].to_int()
if next != '[' && next != 'O'.to_int() {
let len = utf8_char_len(next)
if hold_incomplete && index + 1 + len > bytes.length() {
return IncompleteInput
}
let (text, end) = take_utf8_char(bytes, index + 1)
return Parsed(Key(Alt(text)), end)
}
if next == 'O'.to_int() && index + 2 < bytes.length() {
match bytes[index + 2].to_int().unsafe_to_char() {
'P' => Parsed(Key(Function(1)), index + 3)
'Q' => Parsed(Key(Function(2)), index + 3)
'R' => Parsed(Key(Function(3)), index + 3)
'S' => Parsed(Key(Function(4)), index + 3)
'H' => Parsed(Key(Home), index + 3)
'F' => Parsed(Key(End), index + 3)
_ =>
Parsed(
UnknownEvent(slice_ascii(bytes, index, bytes.length())),
bytes.length(),
)
}
} else {
parse_csi(bytes, index, hold_incomplete)
}
}
///|
fn parse_csi(bytes : Bytes, index : Int, hold_incomplete : Bool) -> InputParse {
let mut end = index + 2
while end < bytes.length() && !is_csi_final(bytes[end]) {
end += 1
}
if end >= bytes.length() {
return if hold_incomplete {
IncompleteInput
} else {
Parsed(
UnknownEvent(slice_ascii(bytes, index, bytes.length())),
bytes.length(),
)
}
}
let terminator = bytes[end].to_int().unsafe_to_char()
let body = slice_ascii(bytes, index + 2, end)
if body.has_prefix("<") && (terminator == 'M' || terminator == 'm') {
Parsed(parse_sgr_mouse(body, terminator), end + 1)
} else {
match parse_csi_key(body, terminator) {
Some(event) => Parsed(event, end + 1)
None => Parsed(UnknownEvent(slice_ascii(bytes, index, end + 1)), end + 1)
}
}
}
///|
fn parse_paste(
bytes : Bytes,
index : Int,
hold_incomplete : Bool,
) -> InputParse {
let start = index + "\u{1b}[200~".length()
let mut end = start
while end < bytes.length() && !starts_with_ascii(bytes, end, "\u{1b}[201~") {
end += 1
}
if end >= bytes.length() {
return if hold_incomplete {
IncompleteInput
} else {
Parsed(
UnknownEvent(slice_ascii(bytes, index, bytes.length())),
bytes.length(),
)
}
}
let pasted = @encoding/utf8.decode(bytes[start:end]) catch {
_ => slice_ascii(bytes, start, end)
}
Parsed(Paste(pasted), end + "\u{1b}[201~".length())
}
///|
fn parse_sgr_mouse(body : String, terminator : Char) -> Event {
let parts = split_char(body[1:].to_owned(), ';')
if parts.length() != 3 {
return UnknownEvent("\u{1b}[\{body}\{terminator}")
}
let code = @string.parse_int(parts[0]) catch {
_ => return UnknownEvent(body)
}
let x = @string.parse_int(parts[1]) catch { _ => return UnknownEvent(body) }
let y = @string.parse_int(parts[2]) catch { _ => return UnknownEvent(body) }
Mouse({
button: mouse_button(code),
action: if terminator == 'm' {
Release
} else if (code & 32) != 0 {
Drag
} else {
Press
},
x,
y,
})
}
///|
fn parse_csi_key(body : String, terminator : Char) -> Event? {
match terminator {
'A' | 'B' | 'C' | 'D' | 'H' | 'F' | 'P' | 'Q' | 'R' | 'S' =>
parse_modified_final_key(body, terminator)
'Z' => if body == "" { Some(Key(BackTab)) } else { None }
'I' => if body == "" { Some(FocusGained) } else { None }
'O' => if body == "" { Some(FocusLost) } else { None }
'~' => parse_tilde_key(body)
'u' => parse_kitty_key(body)
_ => None
}
}
///|
fn parse_modified_final_key(body : String, terminator : Char) -> Event? {
let base = match terminator {
'A' => Up
'B' => Down
'C' => Right
'D' => Left
'H' => Home
'F' => End
'P' => Function(1)
'Q' => Function(2)
'R' => Function(3)
'S' => Function(4)
_ => return None
}
if body == "" {
Some(Key(base))
} else {
let parts = split_char(body, ';')
if parts.length() < 2 {
None
} else {
let modifiers = match parse_modifier_code(parts[parts.length() - 1]) {
Some(value) => value
None => return None
}
Some(Key(apply_modifiers(base, modifiers)))
}
}
}
///|
fn parse_tilde_key(body : String) -> Event? {
let parts = split_char(body, ';')
if parts.length() == 0 {
return None
}
if parts[0] == "27" && parts.length() >= 3 {
let modifiers = match parse_modifier_code(parts[1]) {
Some(value) => value
None => return None
}
let code = @string.parse_int(parts[2]) catch { _ => return None }
return Some(Key(apply_modifiers(key_from_code(code), modifiers)))
}
let code = @string.parse_int(parts[0]) catch { _ => return None }
let base = tilde_base_key(code)
let key = match base {
Some(value) => value
None => return Some(UnknownEvent(body))
}
if parts.length() >= 2 {
let modifiers = match parse_modifier_code(parts[1]) {
Some(value) => value
None => return None
}
Some(Key(apply_modifiers(key, modifiers)))
} else {
Some(Key(key))
}
}
///|
fn parse_kitty_key(body : String) -> Event? {
let parts = split_char(body, ';')
if parts.length() == 0 {
return None
}
let code = @string.parse_int(parts[0]) catch { _ => return None }
let key = key_from_code(code)
if parts.length() >= 2 {
let modifiers = match parse_modifier_code(parts[1]) {
Some(value) => value
None => return None
}
Some(Key(apply_modifiers(key, modifiers)))
} else {
Some(Key(key))
}
}
///|
fn tilde_base_key(code : Int) -> Key? {
match code {
1 | 7 => Some(Home)
2 => Some(Unknown("insert"))
3 => Some(Delete)
4 | 8 => Some(End)
5 => Some(PageUp)
6 => Some(PageDown)
11 => Some(Function(1))
12 => Some(Function(2))
13 => Some(Function(3))
14 => Some(Function(4))
15 => Some(Function(5))
17 => Some(Function(6))
18 => Some(Function(7))
19 => Some(Function(8))
20 => Some(Function(9))
21 => Some(Function(10))
23 => Some(Function(11))
24 => Some(Function(12))
_ => None
}
}
///|
fn key_from_code(code : Int) -> Key {
match code {
9 => Tab
13 => Enter
27 => Escape
127 => Backspace
_ =>
if code >= 32 && code <= 0x10ffff {
Char(String::make(1, Int::unsafe_to_char(code)))
} else {
Unknown(code.to_string())
}
}
}
///|
fn parse_modifier_code(value : String) -> KeyModifiers? {
let code = @string.parse_int(value) catch { _ => return None }
let mask = Int::max(0, code - 1)
Some({
shift: (mask & 1) != 0,
alt: (mask & 2) != 0,
ctrl: (mask & 4) != 0,
super_key: (mask & 8) != 0,
hyper: (mask & 16) != 0,
meta: (mask & 32) != 0,
})
}
///|
fn apply_modifiers(key : Key, modifiers : KeyModifiers) -> Key {
if modifiers == KeyModifiers::none() {
key
} else {
Modified(key, modifiers)
}
}
///|
fn mouse_button(code : Int) -> MouseButton {
if (code & 64) != 0 {
if (code & 1) == 0 {
WheelUp
} else {
WheelDown
}
} else {
match code & 3 {
0 => Primary
1 => Middle
2 => Secondary
n => Other(n)
}
}
}
///|
fn take_utf8_char(bytes : Bytes, index : Int) -> (String, Int) {
let first = bytes[index].to_int()
let len = utf8_char_len(first)
let end = Int::min(bytes.length(), index + len)
let text = @encoding/utf8.decode(bytes[index:end]) catch {
_ => slice_ascii(bytes, index, end)
}
(text, end)
}
///|
fn utf8_char_len(first : Int) -> Int {
if first < 0x80 {
1
} else if first >= 0xc2 && first < 0xe0 {
2
} else if first >= 0xe0 && first < 0xf0 {
3
} else if first >= 0xf0 && first < 0xf5 {
4
} else {
1
}
}
///|
fn is_csi_final(byte : Byte) -> Bool {
let value = byte.to_int()
value >= 0x40 && value <= 0x7e
}
///|
fn split_char(value : String, sep : Char) -> Array[String] {
let out : Array[String] = []
let mut start = 0
let mut index = 0
while index < value.length() {
if value.code_unit_at(index) == Int::to_uint16(sep.to_int()) {
out.push(value[start:index].to_owned())
start = index + 1
}
index += 1
}
out.push(value[start:].to_owned())
out
}
///|
fn starts_with_ascii(bytes : Bytes, index : Int, value : String) -> Bool {
if index + value.length() > bytes.length() {
return false
}
for offset in 0.. String {
let out = StringBuilder::new()
for index in start..= 32 && byte <= 126 {
out.write_char(Int::unsafe_to_char(byte))
} else {
out.write_string("\\x")
out.write_string(byte.to_string(radix=16))
}
}
out.to_string()
}
///|
fn concat_bytes(left : Bytes, right : Bytes) -> Bytes {
let out = FixedArray::make(left.length() + right.length(), b'\x00')
out.blit_from_bytes(0, left, 0, left.length())
out.blit_from_bytes(left.length(), right, 0, right.length())
Bytes::from_array(out)
}