]> git.lizzy.rs Git - rust.git/blob - src/libterm/terminfo/parser/compiled.rs
Auto merge of #58341 - alexreg:cosmetic-2-doc-comments, r=steveklabnik
[rust.git] / src / libterm / terminfo / parser / compiled.rs
1 #![allow(non_upper_case_globals, missing_docs)]
2
3 //! ncurses-compatible compiled terminfo format parsing (term(5))
4
5 use std::collections::HashMap;
6 use std::io;
7 use std::io::prelude::*;
8 use super::super::TermInfo;
9
10 // These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
11
12 #[rustfmt_skip]
13 pub static boolfnames: &[&str] = &["auto_left_margin", "auto_right_margin",
14     "no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
15     "hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
16     "memory_below", "move_insert_mode", "move_standout_mode", "over_strike", "status_line_esc_ok",
17     "dest_tabs_magic_smso", "tilde_glitch", "transparent_underline", "xon_xoff", "needs_xon_xoff",
18     "prtr_silent", "hard_cursor", "non_rev_rmcup", "no_pad_char", "non_dest_scroll_region",
19     "can_change", "back_color_erase", "hue_lightness_saturation", "col_addr_glitch",
20     "cr_cancels_micro_mode", "has_print_wheel", "row_addr_glitch", "semi_auto_right_margin",
21     "cpi_changes_res", "lpi_changes_res", "backspaces_with_bs", "crt_no_scrolling",
22     "no_correctly_working_cr", "gnu_has_meta_key", "linefeed_is_newline", "has_hardware_tabs",
23     "return_does_clr_eol"];
24
25 #[rustfmt_skip]
26 pub static boolnames: &[&str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
27     "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
28     "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
29     "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
30
31 #[rustfmt_skip]
32 pub static numfnames: &[&str] = &[ "columns", "init_tabs", "lines",
33     "lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
34     "width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
35     "maximum_windows", "max_colors", "max_pairs", "no_color_video", "buffer_capacity",
36     "dot_vert_spacing", "dot_horz_spacing", "max_micro_address", "max_micro_jump", "micro_col_size",
37     "micro_line_size", "number_of_pins", "output_res_char", "output_res_line",
38     "output_res_horz_inch", "output_res_vert_inch", "print_rate", "wide_char_size", "buttons",
39     "bit_image_entwining", "bit_image_type", "magic_cookie_glitch_ul", "carriage_return_delay",
40     "new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
41
42 #[rustfmt_skip]
43 pub static numnames: &[&str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
44     "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
45     "spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
46     "btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
47
48 #[rustfmt_skip]
49 pub static stringfnames: &[&str] = &[ "back_tab", "bell", "carriage_return",
50     "change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
51     "column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
52     "cursor_invisible", "cursor_left", "cursor_mem_address", "cursor_normal", "cursor_right",
53     "cursor_to_ll", "cursor_up", "cursor_visible", "delete_character", "delete_line",
54     "dis_status_line", "down_half_line", "enter_alt_charset_mode", "enter_blink_mode",
55     "enter_bold_mode", "enter_ca_mode", "enter_delete_mode", "enter_dim_mode", "enter_insert_mode",
56     "enter_secure_mode", "enter_protected_mode", "enter_reverse_mode", "enter_standout_mode",
57     "enter_underline_mode", "erase_chars", "exit_alt_charset_mode", "exit_attribute_mode",
58     "exit_ca_mode", "exit_delete_mode", "exit_insert_mode", "exit_standout_mode",
59     "exit_underline_mode", "flash_screen", "form_feed", "from_status_line", "init_1string",
60     "init_2string", "init_3string", "init_file", "insert_character", "insert_line",
61     "insert_padding", "key_backspace", "key_catab", "key_clear", "key_ctab", "key_dc", "key_dl",
62     "key_down", "key_eic", "key_eol", "key_eos", "key_f0", "key_f1", "key_f10", "key_f2", "key_f3",
63     "key_f4", "key_f5", "key_f6", "key_f7", "key_f8", "key_f9", "key_home", "key_ic", "key_il",
64     "key_left", "key_ll", "key_npage", "key_ppage", "key_right", "key_sf", "key_sr", "key_stab",
65     "key_up", "keypad_local", "keypad_xmit", "lab_f0", "lab_f1", "lab_f10", "lab_f2", "lab_f3",
66     "lab_f4", "lab_f5", "lab_f6", "lab_f7", "lab_f8", "lab_f9", "meta_off", "meta_on", "newline",
67     "pad_char", "parm_dch", "parm_delete_line", "parm_down_cursor", "parm_ich", "parm_index",
68     "parm_insert_line", "parm_left_cursor", "parm_right_cursor", "parm_rindex", "parm_up_cursor",
69     "pkey_key", "pkey_local", "pkey_xmit", "print_screen", "prtr_off", "prtr_on", "repeat_char",
70     "reset_1string", "reset_2string", "reset_3string", "reset_file", "restore_cursor",
71     "row_address", "save_cursor", "scroll_forward", "scroll_reverse", "set_attributes", "set_tab",
72     "set_window", "tab", "to_status_line", "underline_char", "up_half_line", "init_prog", "key_a1",
73     "key_a3", "key_b2", "key_c1", "key_c3", "prtr_non", "char_padding", "acs_chars", "plab_norm",
74     "key_btab", "enter_xon_mode", "exit_xon_mode", "enter_am_mode", "exit_am_mode", "xon_character",
75     "xoff_character", "ena_acs", "label_on", "label_off", "key_beg", "key_cancel", "key_close",
76     "key_command", "key_copy", "key_create", "key_end", "key_enter", "key_exit", "key_find",
77     "key_help", "key_mark", "key_message", "key_move", "key_next", "key_open", "key_options",
78     "key_previous", "key_print", "key_redo", "key_reference", "key_refresh", "key_replace",
79     "key_restart", "key_resume", "key_save", "key_suspend", "key_undo", "key_sbeg", "key_scancel",
80     "key_scommand", "key_scopy", "key_screate", "key_sdc", "key_sdl", "key_select", "key_send",
81     "key_seol", "key_sexit", "key_sfind", "key_shelp", "key_shome", "key_sic", "key_sleft",
82     "key_smessage", "key_smove", "key_snext", "key_soptions", "key_sprevious", "key_sprint",
83     "key_sredo", "key_sreplace", "key_sright", "key_srsume", "key_ssave", "key_ssuspend",
84     "key_sundo", "req_for_input", "key_f11", "key_f12", "key_f13", "key_f14", "key_f15", "key_f16",
85     "key_f17", "key_f18", "key_f19", "key_f20", "key_f21", "key_f22", "key_f23", "key_f24",
86     "key_f25", "key_f26", "key_f27", "key_f28", "key_f29", "key_f30", "key_f31", "key_f32",
87     "key_f33", "key_f34", "key_f35", "key_f36", "key_f37", "key_f38", "key_f39", "key_f40",
88     "key_f41", "key_f42", "key_f43", "key_f44", "key_f45", "key_f46", "key_f47", "key_f48",
89     "key_f49", "key_f50", "key_f51", "key_f52", "key_f53", "key_f54", "key_f55", "key_f56",
90     "key_f57", "key_f58", "key_f59", "key_f60", "key_f61", "key_f62", "key_f63", "clr_bol",
91     "clear_margins", "set_left_margin", "set_right_margin", "label_format", "set_clock",
92     "display_clock", "remove_clock", "create_window", "goto_window", "hangup", "dial_phone",
93     "quick_dial", "tone", "pulse", "flash_hook", "fixed_pause", "wait_tone", "user0", "user1",
94     "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "orig_pair",
95     "orig_colors", "initialize_color", "initialize_pair", "set_color_pair", "set_foreground",
96     "set_background", "change_char_pitch", "change_line_pitch", "change_res_horz",
97     "change_res_vert", "define_char", "enter_doublewide_mode", "enter_draft_quality",
98     "enter_italics_mode", "enter_leftward_mode", "enter_micro_mode", "enter_near_letter_quality",
99     "enter_normal_quality", "enter_shadow_mode", "enter_subscript_mode", "enter_superscript_mode",
100     "enter_upward_mode", "exit_doublewide_mode", "exit_italics_mode", "exit_leftward_mode",
101     "exit_micro_mode", "exit_shadow_mode", "exit_subscript_mode", "exit_superscript_mode",
102     "exit_upward_mode", "micro_column_address", "micro_down", "micro_left", "micro_right",
103     "micro_row_address", "micro_up", "order_of_pins", "parm_down_micro", "parm_left_micro",
104     "parm_right_micro", "parm_up_micro", "select_char_set", "set_bottom_margin",
105     "set_bottom_margin_parm", "set_left_margin_parm", "set_right_margin_parm", "set_top_margin",
106     "set_top_margin_parm", "start_bit_image", "start_char_set_def", "stop_bit_image",
107     "stop_char_set_def", "subscript_characters", "superscript_characters", "these_cause_cr",
108     "zero_motion", "char_set_names", "key_mouse", "mouse_info", "req_mouse_pos", "get_mouse",
109     "set_a_foreground", "set_a_background", "pkey_plab", "device_type", "code_set_init",
110     "set0_des_seq", "set1_des_seq", "set2_des_seq", "set3_des_seq", "set_lr_margin",
111     "set_tb_margin", "bit_image_repeat", "bit_image_newline", "bit_image_carriage_return",
112     "color_names", "define_bit_image_region", "end_bit_image_region", "set_color_band",
113     "set_page_length", "display_pc_char", "enter_pc_charset_mode", "exit_pc_charset_mode",
114     "enter_scancode_mode", "exit_scancode_mode", "pc_term_options", "scancode_escape",
115     "alt_scancode_esc", "enter_horizontal_hl_mode", "enter_left_hl_mode", "enter_low_hl_mode",
116     "enter_right_hl_mode", "enter_top_hl_mode", "enter_vertical_hl_mode", "set_a_attributes",
117     "set_pglen_inch", "termcap_init2", "termcap_reset", "linefeed_if_not_lf", "backspace_if_not_bs",
118     "other_non_function_keys", "arrow_key_map", "acs_ulcorner", "acs_llcorner", "acs_urcorner",
119     "acs_lrcorner", "acs_ltee", "acs_rtee", "acs_btee", "acs_ttee", "acs_hline", "acs_vline",
120     "acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
121
122 #[rustfmt_skip]
123 pub static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
124     "_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
125     "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
126     "dim", "smir", "invis", "prot", "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc",
127     "rmir", "rmso", "rmul", "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip",
128     "kbs", "ktbc", "kclr", "kctab", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
129     "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
130     "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
131     "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
132     "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
133     "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
134     "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
135     "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "kent", "kext", "kfnd", "khlp",
136     "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl",
137     "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "_", "_",
138     "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
139     "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
140     "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
141     "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
142     "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
143     "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
144     "_", "_", "_", "_", "_", "_", "_", "_", "_", "op", "oc", "initc", "initp", "scp", "setf",
145     "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq",
146     "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm",
147     "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub",
148     "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd",
149     "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm",
150     "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb",
151     "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch",
152     "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm",
153     "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2",
154     "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
155     "box1"];
156
157 fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u16> {
158     let mut b = [0; 2];
159     let mut amt = 0;
160     while amt < b.len() {
161         match r.read(&mut b[amt..])? {
162             0 => return Err(io::Error::new(io::ErrorKind::Other, "end of file")),
163             n => amt += n,
164         }
165     }
166     Ok((b[0] as u16) | ((b[1] as u16) << 8))
167 }
168
169 fn read_byte(r: &mut dyn io::Read) -> io::Result<u8> {
170     match r.bytes().next() {
171         Some(s) => s,
172         None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
173     }
174 }
175
176 /// Parse a compiled terminfo entry, using long capability names if `longnames`
177 /// is true
178 pub fn parse(file: &mut dyn io::Read, longnames: bool) -> Result<TermInfo, String> {
179     macro_rules! t( ($e:expr) => (
180         match $e {
181             Ok(e) => e,
182             Err(e) => return Err(e.to_string())
183         }
184     ) );
185
186     let (bnames, snames, nnames) = if longnames {
187         (boolfnames, stringfnames, numfnames)
188     } else {
189         (boolnames, stringnames, numnames)
190     };
191
192     // Check magic number
193     let magic = t!(read_le_u16(file));
194     if magic != 0x011A {
195         return Err(format!("invalid magic number: expected {:x}, found {:x}",
196                            0x011A,
197                            magic));
198     }
199
200     // According to the spec, these fields must be >= -1 where -1 means that the feature is not
201     // supported. Using 0 instead of -1 works because we skip sections with length 0.
202     macro_rules! read_nonneg {
203         () => {{
204             match t!(read_le_u16(file)) as i16 {
205                 n if n >= 0 => n as usize,
206                 -1 => 0,
207                 _ => return Err("incompatible file: length fields must be  >= -1".to_string()),
208             }
209         }}
210     }
211
212     let names_bytes = read_nonneg!();
213     let bools_bytes = read_nonneg!();
214     let numbers_count = read_nonneg!();
215     let string_offsets_count = read_nonneg!();
216     let string_table_bytes = read_nonneg!();
217
218     if names_bytes == 0 {
219         return Err("incompatible file: names field must be at least 1 byte wide".to_string());
220     }
221
222     if bools_bytes > boolnames.len() {
223         return Err("incompatible file: more booleans than expected".to_string());
224     }
225
226     if numbers_count > numnames.len() {
227         return Err("incompatible file: more numbers than expected".to_string());
228     }
229
230     if string_offsets_count > stringnames.len() {
231         return Err("incompatible file: more string offsets than expected".to_string());
232     }
233
234     // don't read NUL
235     let mut bytes = Vec::new();
236     t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
237     let names_str = match String::from_utf8(bytes) {
238         Ok(s) => s,
239         Err(_) => return Err("input not utf-8".to_string()),
240     };
241
242     let term_names: Vec<String> = names_str.split('|')
243                                            .map(|s| s.to_string())
244                                            .collect();
245     // consume NUL
246     if t!(read_byte(file)) != b'\0' {
247         return Err("incompatible file: missing null terminator for names section".to_string());
248     }
249
250     let bools_map: HashMap<String, bool> = t! {
251         (0..bools_bytes).filter_map(|i| match read_byte(file) {
252             Err(e) => Some(Err(e)),
253             Ok(1) => Some(Ok((bnames[i].to_string(), true))),
254             Ok(_) => None
255         }).collect()
256     };
257
258     if (bools_bytes + names_bytes) % 2 == 1 {
259         t!(read_byte(file)); // compensate for padding
260     }
261
262     let numbers_map: HashMap<String, u16> = t! {
263         (0..numbers_count).filter_map(|i| match read_le_u16(file) {
264             Ok(0xFFFF) => None,
265             Ok(n) => Some(Ok((nnames[i].to_string(), n))),
266             Err(e) => Some(Err(e))
267         }).collect()
268     };
269
270     let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
271         let string_offsets: Vec<u16> = t!((0..string_offsets_count)
272                                                 .map(|_| read_le_u16(file))
273                                                 .collect());
274
275         let mut string_table = Vec::new();
276         t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
277
278         t!(string_offsets.into_iter().enumerate().filter(|&(_, offset)| {
279             // non-entry
280             offset != 0xFFFF
281         }).map(|(i, offset)| {
282             let offset = offset as usize;
283
284             let name = if snames[i] == "_" {
285                 stringfnames[i]
286             } else {
287                 snames[i]
288             };
289
290             if offset == 0xFFFE {
291                 // undocumented: FFFE indicates cap@, which means the capability is not present
292                 // unsure if the handling for this is correct
293                 return Ok((name.to_string(), Vec::new()));
294             }
295
296             // Find the offset of the NUL we want to go to
297             let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
298             match nulpos {
299                 Some(len) => Ok((name.to_string(), string_table[offset..offset + len].to_vec())),
300                 None => Err("invalid file: missing NUL in string_table".to_string()),
301             }
302         }).collect())
303     } else {
304         HashMap::new()
305     };
306
307     // And that's all there is to it
308     Ok(TermInfo {
309         names: term_names,
310         bools: bools_map,
311         numbers: numbers_map,
312         strings: string_map,
313     })
314 }
315
316 /// Creates a dummy TermInfo struct for msys terminals
317 pub fn msys_terminfo() -> TermInfo {
318     let mut strings = HashMap::new();
319     strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
320     strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
321     strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
322     strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
323
324     let mut numbers = HashMap::new();
325     numbers.insert("colors".to_string(), 8u16);
326
327     TermInfo {
328         names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
329         bools: HashMap::new(),
330         numbers,
331         strings,
332     }
333 }
334
335 #[cfg(test)]
336 mod test {
337
338     use super::{boolnames, boolfnames, numnames, numfnames, stringnames, stringfnames};
339
340     #[test]
341     fn test_veclens() {
342         assert_eq!(boolfnames.len(), boolnames.len());
343         assert_eq!(numfnames.len(), numnames.len());
344         assert_eq!(stringfnames.len(), stringnames.len());
345     }
346 }