use std::mem;
use terminfo::errors::*;
use terminfo::fields::*;
use terminfo::strtab::{StrTable, StringTable};
use util::{invalid, read_le_u16, read_le_u32};
#[derive(Debug, Clone)]
pub struct TermInfo<'a> {
long: bool,
names: &'a [u8],
bools: &'a [u8],
numbers: &'a [u8],
strings: &'a [u8],
strtab: StrTable<'a>,
ext: Option<TermInfoExt<'a>>,
}
#[derive(Debug, Clone)]
pub(crate) struct TermInfoExt<'a> {
long: bool,
bools: &'a [u8],
numbers: &'a [u8],
strings: &'a [u8],
names: &'a [u8],
strtab: StrTable<'a>,
nametab_start: usize,
}
fn split_terminfo<'a>(bytes: &'a [u8]) -> Result<TermInfo<'a>> {
let file_size = bytes.len();
if file_size < 12 {
return Err(ErrorKind::IncompleteTermInfoHeader.into());
}
let num_bytes = match read_le_u16(bytes, 0) {
0o542 | 542 => 4,
0o432 => 2,
_ => {
return Err(ErrorKind::InvalidMagicNumber.into());
}
};
let names_size = read_le_u16(bytes, 1) as usize;
let bools_count = read_le_u16(bytes, 2) as usize;
let numbers_count = read_le_u16(bytes, 3) as usize;
let strings_count = read_le_u16(bytes, 4) as usize;
let strtab_size = read_le_u16(bytes, 5) as usize;
let mut expected_filesize =
12 + bools_count + numbers_count * num_bytes + strings_count * 2 + strtab_size + names_size;
if bools_count + names_size % 2 != 0 {
expected_filesize += 1;
}
if expected_filesize > file_size {
return Err(ErrorKind::IncompleteTermInfo.into());
}
let mut slice = &bytes[12..];
let names = &slice[..names_size - 1];
slice = &slice[names_size..];
let bools = &slice[..bools_count];
slice = if (bools_count + names_size) % 2 != 0 {
&slice[bools_count + 1..]
} else {
&slice[bools_count..]
};
let numbers = &slice[..numbers_count * num_bytes];
slice = &slice[numbers_count * num_bytes..];
let strings = &slice[..strings_count * 2];
slice = &slice[strings_count * 2..];
let strtab = &slice[..strtab_size];
let ext = if expected_filesize < file_size {
Some(split_terminfo_ext(
if strtab_size % 2 != 0 {
&slice[strtab_size + 1..]
} else {
&slice[strtab_size..]
},
num_bytes,
)?)
} else {
None
};
Ok(TermInfo {
long: num_bytes > 2,
names: names,
bools: bools,
numbers: numbers,
strings: strings,
strtab: StrTable::new(strtab),
ext: ext,
})
}
fn split_terminfo_ext<'a>(bytes: &'a [u8], num_bytes: usize) -> Result<TermInfoExt<'a>> {
let file_size = bytes.len();
if file_size < 10 {
return Err(ErrorKind::IncompleteExtendedHeader.into());
}
let bools_count = read_le_u16(bytes, 0) as usize;
let numbers_count = read_le_u16(bytes, 1) as usize;
let strings_count = read_le_u16(bytes, 2) as usize;
let strtab_size = read_le_u16(bytes, 3) as usize;
let strtab_last_offset = read_le_u16(bytes, 4) as usize;
let names_count = strings_count + numbers_count + bools_count;
let mut expected_filesize = 10 + bools_count + numbers_count * num_bytes + strings_count * 2
+ names_count * 2 + strtab_size;
if bools_count % 2 != 0 {
expected_filesize += 1;
}
if expected_filesize > file_size {
return Err(ErrorKind::IncompleteExtendedTermInfo.into());
}
let mut slice = &bytes[10..];
let bools = &slice[..bools_count];
slice = if bools_count % 2 != 0 {
&slice[bools_count + 1..]
} else {
&slice[bools_count..]
};
let numbers = &slice[..numbers_count * num_bytes];
slice = &slice[numbers_count * num_bytes..];
let strings = &slice[..strings_count * 2];
slice = &slice[strings_count * 2..];
let names = &slice[..names_count * 2];
slice = &slice[names_count * 2..];
let strtab = &slice[..strtab_last_offset];
let mut x = names_count + 1;
let nametab_offset = strtab_last_offset
- strtab
.iter()
.rev()
.take_while(|&&c| {
if c == 0 {
x -= 1
}
x != 0
})
.count();
Ok(TermInfoExt {
long: num_bytes > 2,
bools: bools,
numbers: numbers,
strings: strings,
strtab: StrTable::new(strtab),
nametab_start: nametab_offset,
names: names,
})
}
impl<'a> TermInfoExt<'a> {
pub(crate) fn get_tables(&self) -> (StringTable, StringTable) {
self.strtab.split(self.nametab_start)
}
pub(crate) fn get_numbers(&self) -> Vec<u32> {
if self.long {
self.numbers.chunks(4).map(|n| read_le_u32(n, 0)).collect()
} else {
self.numbers
.chunks(2)
.map(|n| read_le_u16(n, 0) as u32)
.collect()
}
}
pub(crate) fn get_string_offsets(&self) -> Vec<u16> {
self.strings.chunks(2).map(|n| read_le_u16(n, 0)).collect()
}
pub(crate) fn get_name_offsets(&self) -> Vec<u16> {
self.names.chunks(2).map(|n| read_le_u16(n, 0)).collect()
}
pub(crate) fn get_bools(&self) -> Vec<bool> {
self.bools.iter().map(|&b| b != 0).collect()
}
}
impl<'a> TermInfo<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<TermInfo<'a>> {
split_terminfo(bytes)
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.names
.split(|&c| c == b'|')
.map(|slice| unsafe { mem::transmute::<&[u8], &str>(slice) })
}
pub(crate) fn get_strtab(&self) -> StringTable {
self.strtab.to_string_table()
}
pub(crate) fn get_numbers(&self) -> Vec<u32> {
if self.long {
self.numbers.chunks(4).map(|n| read_le_u32(n, 0)).collect()
} else {
self.numbers
.chunks(2)
.map(|n| read_le_u16(n, 0) as u32)
.collect()
}
}
pub(crate) fn get_string_offsets(&self) -> Vec<u16> {
self.strings.chunks(2).map(|n| read_le_u16(n, 0)).collect()
}
pub(crate) fn get_bools(&self) -> Vec<bool> {
self.bools.iter().map(|&b| b != 0).collect()
}
pub(crate) fn get_ext(&self) -> &Option<TermInfoExt> {
&self.ext
}
pub(crate) fn ext_index<T: AsRef<str>>(&self, s: T) -> Option<usize> {
match &self.ext {
Some(e) => {
let bytes = s.as_ref().as_bytes();
for (i, x) in e.names.chunks(2).enumerate() {
let num = read_le_u16(x, 0) as usize;
if e.strtab.get_iter(num + e.nametab_start).eq(bytes) {
return Some(i);
}
}
None
}
None => None,
}
}
pub fn number(&self, field: NumericField) -> Option<u32> {
let i = field as usize;
if i * 2 < self.numbers.len() {
let number = if self.long {
read_le_u32(self.numbers, i)
} else {
read_le_u16(self.numbers, i) as u32
};
if number != invalid() {
Some(number)
} else {
None
}
} else {
None
}
}
pub fn boolean(&self, field: BooleanField) -> bool {
let i = field as usize;
if i < self.bools.len() {
self.bools[i] != 0
} else {
false
}
}
pub fn string(&self, field: StringField) -> Option<&str> {
let i = field as usize;
if i * 2 < self.strings.len() {
let offset = read_le_u16(self.strings, i);
if offset != invalid() {
return Some(self.strtab.get(offset as usize).unwrap());
}
}
None
}
pub fn has_ext(&self) -> bool {
self.ext.is_some()
}
pub fn ext_boolean<T: AsRef<str>>(&self, field: T) -> bool {
if let Some(ref ext) = self.ext {
if let Some(idx) = self.ext_index(field) {
if idx < ext.bools.len() {
return ext.bools[idx] != 0;
}
}
}
false
}
pub fn ext_number<T: AsRef<str>>(&self, field: T) -> Option<u32> {
if let Some(ref ext) = self.ext {
if let Some(idx) = self.ext_index(field) {
let idx_offset = ext.bools.len();
if idx >= idx_offset && idx - idx_offset < ext.numbers.len() {
let number = if self.long {
read_le_u32(self.numbers, idx)
} else {
read_le_u16(self.numbers, idx) as u32
};
if number != invalid() {
return Some(number);
}
}
}
}
None
}
pub fn ext_string<T: AsRef<str>>(&self, field: T) -> Option<&str> {
if let Some(ref ext) = self.ext {
if let Some(idx) = self.ext_index(field) {
let idx_offset = ext.bools.len() + (ext.numbers.len() / 2);
if idx >= idx_offset && idx - idx_offset < (ext.strings.len() / 2) {
let num = read_le_u16(ext.strings, idx - idx_offset);
if num != invalid() {
return ext.strtab
.get(num as usize)
.map(|x| Some(x))
.unwrap_or(None);
}
}
}
}
None
}
}
#[cfg(test)]
mod test {
const RXVT_INFO: &'static [u8] = include_bytes!("../../test-data/rxvt");
const XTERM_INFO: &'static [u8] = include_bytes!("../../test-data/xterm");
const LINUX_16COLOR_INFO: &'static [u8] = include_bytes!("../../test-data/linux-16color");
use terminfo::*;
#[test]
fn names() {
let rxvt = TermInfo::parse(RXVT_INFO).unwrap();
assert_eq!(rxvt.names().nth(0), Some("rxvt"));
assert_eq!(
rxvt.names().collect::<Vec<&str>>(),
vec!["rxvt", "rxvt terminal emulator (X Window System)"]
);
}
#[test]
fn lookup_string() {
let rxvt = TermInfo::parse(RXVT_INFO).unwrap();
let xterm = TermInfo::parse(XTERM_INFO).unwrap();
let l16c = TermInfo::parse(LINUX_16COLOR_INFO).unwrap();
assert_eq!(rxvt.string(StringField::KeyDown), Some("\u{1b}[B"));
assert_eq!(rxvt.string(StringField::KeyBackspace), Some("\u{8}"));
assert_eq!(rxvt.string(StringField::ZeroMotion), None);
assert_eq!(rxvt.string(StringField::Bell), Some("\u{7}"));
assert_eq!(xterm.string(StringField::Newline), None);
assert_eq!(xterm.string(StringField::MetaOff), Some("\u{1b}[?1034l"));
assert_eq!(xterm.string(StringField::KeyEnter), Some("\u{1b}OM"));
assert_eq!(xterm.string(StringField::LinefeedIfNotLf), None);
assert_eq!(l16c.string(StringField::LabF3), None);
assert_eq!(l16c.string(StringField::InsertLine), Some("\u{1b}[L"));
assert_eq!(l16c.string(StringField::GetMouse), None);
assert_eq!(l16c.string(StringField::LinefeedIfNotLf), None);
}
#[test]
fn lookup_bool() {
let rxvt = TermInfo::parse(RXVT_INFO).unwrap();
let xterm = TermInfo::parse(XTERM_INFO).unwrap();
let l16c = TermInfo::parse(LINUX_16COLOR_INFO).unwrap();
assert_eq!(rxvt.boolean(BooleanField::LinefeedIsNewline), false);
assert_eq!(rxvt.boolean(BooleanField::AutoRightMargin), true);
assert_eq!(xterm.boolean(BooleanField::AutoLeftMargin), false);
assert_eq!(xterm.boolean(BooleanField::LinefeedIsNewline), false);
assert_eq!(l16c.boolean(BooleanField::HardCursor), false);
assert_eq!(l16c.boolean(BooleanField::AutoRightMargin), true);
}
#[test]
fn lookup_number() {
let rxvt = TermInfo::parse(RXVT_INFO).unwrap();
let xterm = TermInfo::parse(XTERM_INFO).unwrap();
let l16c = TermInfo::parse(LINUX_16COLOR_INFO).unwrap();
assert_eq!(rxvt.number(NumericField::Columns), Some(80));
assert_eq!(rxvt.number(NumericField::Buttons), None);
assert_eq!(xterm.number(NumericField::WideCharSize), None);
assert_eq!(xterm.number(NumericField::MaxColors), Some(8));
assert_eq!(l16c.number(NumericField::BitImageEntwining), None);
assert_eq!(l16c.number(NumericField::MaxColors), Some(16));
}
#[test]
fn lookup_ext_string() {
let xterm = TermInfo::parse(XTERM_INFO).unwrap();
assert_eq!(xterm.has_ext(), true);
assert_eq!(xterm.ext_string("kUP7"), Some("\u{1b}[1;7A"));
assert_eq!(xterm.ext_string("kUP8"), None);
}
#[test]
fn lookup_ext_bool() {
let rxvt = TermInfo::parse(RXVT_INFO).unwrap();
assert_eq!(rxvt.has_ext(), true);
assert_eq!(rxvt.ext_boolean("XM"), false);
assert_eq!(rxvt.ext_boolean("G0"), false);
assert_eq!(rxvt.ext_boolean("XT"), true);
assert_eq!(rxvt.ext_boolean("kUP"), false);
}
#[test]
fn lookup_ext_number() {
let l16c = TermInfo::parse(LINUX_16COLOR_INFO).unwrap();
assert_eq!(l16c.has_ext(), true);
assert_eq!(l16c.ext_number("U8"), None);
}
}