| //! Windows specific definitions |
| use std::io::{self, Stdout, Write}; |
| use std::mem; |
| use std::sync::atomic; |
| |
| use unicode_width::UnicodeWidthChar; |
| use winapi::shared::minwindef; |
| use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winnt, winuser}; |
| |
| use config::Config; |
| use consts::{self, KeyPress}; |
| use error; |
| use Result; |
| use line_buffer::LineBuffer; |
| use super::{Position, RawMode, RawReader, Renderer, Term}; |
| |
| const STDIN_FILENO: minwindef::DWORD = winbase::STD_INPUT_HANDLE; |
| const STDOUT_FILENO: minwindef::DWORD = winbase::STD_OUTPUT_HANDLE; |
| |
| fn get_std_handle(fd: minwindef::DWORD) -> Result<winnt::HANDLE> { |
| let handle = unsafe { processenv::GetStdHandle(fd) }; |
| if handle == handleapi::INVALID_HANDLE_VALUE { |
| try!(Err(io::Error::last_os_error())); |
| } else if handle.is_null() { |
| try!(Err(io::Error::new( |
| io::ErrorKind::Other, |
| "no stdio handle available for this process", |
| ),)); |
| } |
| Ok(handle) |
| } |
| |
| #[macro_export] |
| macro_rules! check { |
| ($funcall:expr) => { |
| { |
| let rc = unsafe { $funcall }; |
| if rc == 0 { |
| try!(Err(io::Error::last_os_error())); |
| } |
| rc |
| } |
| }; |
| } |
| |
| fn get_win_size(handle: winnt::HANDLE) -> (usize, usize) { |
| let mut info = unsafe { mem::zeroed() }; |
| match unsafe { wincon::GetConsoleScreenBufferInfo(handle, &mut info) } { |
| 0 => (80, 24), |
| _ => ( |
| info.dwSize.X as usize, |
| (1 + info.srWindow.Bottom - info.srWindow.Top) as usize, |
| ), // (info.srWindow.Right - info.srWindow.Left + 1) |
| } |
| } |
| |
| fn get_console_mode(handle: winnt::HANDLE) -> Result<minwindef::DWORD> { |
| let mut original_mode = 0; |
| check!(consoleapi::GetConsoleMode(handle, &mut original_mode)); |
| Ok(original_mode) |
| } |
| |
| pub type Mode = ConsoleMode; |
| |
| #[derive(Clone, Copy, Debug)] |
| pub struct ConsoleMode { |
| original_mode: minwindef::DWORD, |
| stdin_handle: winnt::HANDLE, |
| } |
| |
| impl RawMode for Mode { |
| /// Disable RAW mode for the terminal. |
| fn disable_raw_mode(&self) -> Result<()> { |
| check!(consoleapi::SetConsoleMode( |
| self.stdin_handle, |
| self.original_mode, |
| )); |
| Ok(()) |
| } |
| } |
| |
| /// Console input reader |
| pub struct ConsoleRawReader { |
| handle: winnt::HANDLE, |
| buf: Option<u16>, |
| } |
| |
| impl ConsoleRawReader { |
| pub fn new() -> Result<ConsoleRawReader> { |
| let handle = try!(get_std_handle(STDIN_FILENO)); |
| Ok(ConsoleRawReader { |
| handle: handle, |
| buf: None, |
| }) |
| } |
| } |
| |
| impl RawReader for ConsoleRawReader { |
| fn next_key(&mut self) -> Result<KeyPress> { |
| use std::char::decode_utf16; |
| use winapi::um::wincon::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; |
| |
| let mut rec: wincon::INPUT_RECORD = unsafe { mem::zeroed() }; |
| let mut count = 0; |
| loop { |
| // TODO GetNumberOfConsoleInputEvents |
| check!(consoleapi::ReadConsoleInputW( |
| self.handle, |
| &mut rec, |
| 1 as minwindef::DWORD, |
| &mut count, |
| )); |
| |
| if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT { |
| SIGWINCH.store(true, atomic::Ordering::SeqCst); |
| debug!(target: "rustyline", "SIGWINCH"); |
| return Err(error::ReadlineError::WindowResize); |
| } else if rec.EventType != wincon::KEY_EVENT { |
| continue; |
| } |
| let key_event = unsafe { rec.Event.KeyEvent() }; |
| // writeln!(io::stderr(), "key_event: {:?}", key_event).unwrap(); |
| if key_event.bKeyDown == 0 |
| && key_event.wVirtualKeyCode != winuser::VK_MENU as minwindef::WORD |
| { |
| continue; |
| } |
| // key_event.wRepeatCount seems to be always set to 1 (maybe because we only |
| // read one character at a time) |
| |
| // let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | |
| // RIGHT_ALT_PRESSED) != 0; |
| let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0; |
| let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0; |
| let meta = alt; |
| |
| let utf16 = unsafe { *key_event.uChar.UnicodeChar() }; |
| if utf16 == 0 { |
| match key_event.wVirtualKeyCode as i32 { |
| winuser::VK_LEFT => { |
| return Ok(if ctrl { |
| KeyPress::ControlLeft |
| } else { |
| KeyPress::Left |
| }) |
| } |
| winuser::VK_RIGHT => { |
| return Ok(if ctrl { |
| KeyPress::ControlRight |
| } else { |
| KeyPress::Right |
| }) |
| } |
| winuser::VK_UP => { |
| return Ok(if ctrl { |
| KeyPress::ControlUp |
| } else { |
| KeyPress::Up |
| }) |
| } |
| winuser::VK_DOWN => { |
| return Ok(if ctrl { |
| KeyPress::ControlDown |
| } else { |
| KeyPress::Down |
| }) |
| } |
| winuser::VK_DELETE => return Ok(KeyPress::Delete), |
| winuser::VK_HOME => return Ok(KeyPress::Home), |
| winuser::VK_END => return Ok(KeyPress::End), |
| winuser::VK_PRIOR => return Ok(KeyPress::PageUp), |
| winuser::VK_NEXT => return Ok(KeyPress::PageDown), |
| winuser::VK_INSERT => return Ok(KeyPress::Insert), |
| winuser::VK_F1 => return Ok(KeyPress::F(1)), |
| winuser::VK_F2 => return Ok(KeyPress::F(2)), |
| winuser::VK_F3 => return Ok(KeyPress::F(3)), |
| winuser::VK_F4 => return Ok(KeyPress::F(4)), |
| winuser::VK_F5 => return Ok(KeyPress::F(5)), |
| winuser::VK_F6 => return Ok(KeyPress::F(6)), |
| winuser::VK_F7 => return Ok(KeyPress::F(7)), |
| winuser::VK_F8 => return Ok(KeyPress::F(8)), |
| winuser::VK_F9 => return Ok(KeyPress::F(9)), |
| winuser::VK_F10 => return Ok(KeyPress::F(10)), |
| winuser::VK_F11 => return Ok(KeyPress::F(11)), |
| winuser::VK_F12 => return Ok(KeyPress::F(12)), |
| // winuser::VK_BACK is correctly handled because the key_event.UnicodeChar is |
| // also |
| // set. |
| _ => continue, |
| }; |
| } else if utf16 == 27 { |
| return Ok(KeyPress::Esc); |
| } else { |
| // TODO How to support surrogate pair ? |
| self.buf = Some(utf16); |
| let orc = decode_utf16(self).next(); |
| if orc.is_none() { |
| return Err(error::ReadlineError::Eof); |
| } |
| let c = try!(orc.unwrap()); |
| if meta { |
| return Ok(KeyPress::Meta(c)); |
| } else { |
| return Ok(consts::char_to_key_press(c)); |
| } |
| } |
| } |
| } |
| } |
| |
| impl Iterator for ConsoleRawReader { |
| type Item = u16; |
| |
| fn next(&mut self) -> Option<u16> { |
| let buf = self.buf; |
| self.buf = None; |
| buf |
| } |
| } |
| |
| pub struct ConsoleRenderer { |
| out: Stdout, |
| handle: winnt::HANDLE, |
| cols: usize, // Number of columns in terminal |
| } |
| |
| impl ConsoleRenderer { |
| fn new(handle: winnt::HANDLE) -> ConsoleRenderer { |
| // Multi line editing is enabled by ENABLE_WRAP_AT_EOL_OUTPUT mode |
| let (cols, _) = get_win_size(handle); |
| ConsoleRenderer { |
| out: io::stdout(), |
| handle: handle, |
| cols: cols, |
| } |
| } |
| |
| fn get_console_screen_buffer_info(&self) -> Result<wincon::CONSOLE_SCREEN_BUFFER_INFO> { |
| let mut info = unsafe { mem::zeroed() }; |
| check!(wincon::GetConsoleScreenBufferInfo(self.handle, &mut info)); |
| Ok(info) |
| } |
| |
| fn set_console_cursor_position(&mut self, pos: wincon::COORD) -> Result<()> { |
| check!(wincon::SetConsoleCursorPosition(self.handle, pos)); |
| Ok(()) |
| } |
| |
| fn fill_console_output_character( |
| &mut self, |
| length: minwindef::DWORD, |
| pos: wincon::COORD, |
| ) -> Result<()> { |
| let mut _count = 0; |
| check!(wincon::FillConsoleOutputCharacterA( |
| self.handle, |
| ' ' as winnt::CHAR, |
| length, |
| pos, |
| &mut _count, |
| )); |
| Ok(()) |
| } |
| } |
| |
| impl Renderer for ConsoleRenderer { |
| fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> { |
| let mut info = try!(self.get_console_screen_buffer_info()); |
| if new.row > old.row { |
| info.dwCursorPosition.Y += (new.row - old.row) as i16; |
| } else { |
| info.dwCursorPosition.Y -= (old.row - new.row) as i16; |
| } |
| if new.col > old.col { |
| info.dwCursorPosition.X += (new.col - old.col) as i16; |
| } else { |
| info.dwCursorPosition.X -= (old.col - new.col) as i16; |
| } |
| self.set_console_cursor_position(info.dwCursorPosition) |
| } |
| |
| fn refresh_line( |
| &mut self, |
| prompt: &str, |
| prompt_size: Position, |
| line: &LineBuffer, |
| hint: Option<String>, |
| current_row: usize, |
| old_rows: usize, |
| ) -> Result<(Position, Position)> { |
| // calculate the position of the end of the input line |
| let end_pos = self.calculate_position(line, prompt_size); |
| // calculate the desired position of the cursor |
| let cursor = self.calculate_position(&line[..line.pos()], prompt_size); |
| |
| // position at the start of the prompt, clear to end of previous input |
| let mut info = try!(self.get_console_screen_buffer_info()); |
| info.dwCursorPosition.X = 0; |
| info.dwCursorPosition.Y -= current_row as i16; |
| try!(self.set_console_cursor_position(info.dwCursorPosition)); |
| let mut _count = 0; |
| try!(self.fill_console_output_character( |
| (info.dwSize.X * (old_rows as i16 + 1)) as u32, |
| info.dwCursorPosition, |
| )); |
| let mut ab = String::new(); |
| // display the prompt |
| ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) |
| // display the input line |
| ab.push_str(&line); |
| try!(self.write_and_flush(ab.as_bytes())); |
| |
| // position the cursor |
| let mut info = try!(self.get_console_screen_buffer_info()); |
| info.dwCursorPosition.X = cursor.col as i16; |
| info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; |
| try!(self.set_console_cursor_position(info.dwCursorPosition)); |
| Ok((cursor, end_pos)) |
| } |
| |
| fn write_and_flush(&mut self, buf: &[u8]) -> Result<()> { |
| try!(self.out.write_all(buf)); |
| try!(self.out.flush()); |
| Ok(()) |
| } |
| |
| /// Characters with 2 column width are correctly handled (not splitted). |
| fn calculate_position(&self, s: &str, orig: Position) -> Position { |
| let mut pos = orig; |
| for c in s.chars() { |
| let cw = if c == '\n' { |
| pos.col = 0; |
| pos.row += 1; |
| None |
| } else { |
| c.width() |
| }; |
| if let Some(cw) = cw { |
| pos.col += cw; |
| if pos.col > self.cols { |
| pos.row += 1; |
| pos.col = cw; |
| } |
| } |
| } |
| if pos.col == self.cols { |
| pos.col = 0; |
| pos.row += 1; |
| } |
| pos |
| } |
| |
| /// Clear the screen. Used to handle ctrl+l |
| fn clear_screen(&mut self) -> Result<()> { |
| let info = try!(self.get_console_screen_buffer_info()); |
| let coord = wincon::COORD { X: 0, Y: 0 }; |
| check!(wincon::SetConsoleCursorPosition(self.handle, coord)); |
| let mut _count = 0; |
| let n = info.dwSize.X as minwindef::DWORD * info.dwSize.Y as minwindef::DWORD; |
| check!(wincon::FillConsoleOutputCharacterA( |
| self.handle, |
| ' ' as winnt::CHAR, |
| n, |
| coord, |
| &mut _count, |
| )); |
| Ok(()) |
| } |
| |
| fn sigwinch(&self) -> bool { |
| SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) |
| } |
| |
| /// Try to get the number of columns in the current terminal, |
| /// or assume 80 if it fails. |
| fn update_size(&mut self) { |
| let (cols, _) = get_win_size(self.handle); |
| self.cols = cols; |
| } |
| |
| fn get_columns(&self) -> usize { |
| self.cols |
| } |
| |
| /// Try to get the number of rows in the current terminal, |
| /// or assume 24 if it fails. |
| fn get_rows(&self) -> usize { |
| let (_, rows) = get_win_size(self.handle); |
| rows |
| } |
| } |
| |
| static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; |
| |
| pub type Terminal = Console; |
| |
| #[derive(Clone, Debug)] |
| pub struct Console { |
| stdin_isatty: bool, |
| stdin_handle: winnt::HANDLE, |
| stdout_handle: winnt::HANDLE, |
| } |
| |
| impl Console {} |
| |
| impl Term for Console { |
| type Reader = ConsoleRawReader; |
| type Writer = ConsoleRenderer; |
| type Mode = Mode; |
| |
| fn new() -> Console { |
| use std::ptr; |
| let stdin_handle = get_std_handle(STDIN_FILENO); |
| let stdin_isatty = match stdin_handle { |
| Ok(handle) => { |
| // If this function doesn't fail then fd is a TTY |
| get_console_mode(handle).is_ok() |
| } |
| Err(_) => false, |
| }; |
| |
| let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); |
| Console { |
| stdin_isatty: stdin_isatty, |
| stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()), |
| stdout_handle: stdout_handle, |
| } |
| } |
| |
| /// Checking for an unsupported TERM in windows is a no-op |
| fn is_unsupported(&self) -> bool { |
| false |
| } |
| |
| fn is_stdin_tty(&self) -> bool { |
| self.stdin_isatty |
| } |
| |
| // pub fn install_sigwinch_handler(&mut self) { |
| // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT |
| // } |
| |
| /// Enable RAW mode for the terminal. |
| fn enable_raw_mode(&self) -> Result<Mode> { |
| if !self.stdin_isatty { |
| try!(Err(io::Error::new( |
| io::ErrorKind::Other, |
| "no stdio handle available for this process", |
| ),)); |
| } |
| let original_mode = try!(get_console_mode(self.stdin_handle)); |
| // Disable these modes |
| let raw = original_mode |
| & !(wincon::ENABLE_LINE_INPUT | wincon::ENABLE_ECHO_INPUT |
| | wincon::ENABLE_PROCESSED_INPUT); |
| // Enable these modes |
| let raw = raw | wincon::ENABLE_EXTENDED_FLAGS; |
| let raw = raw | wincon::ENABLE_INSERT_MODE; |
| let raw = raw | wincon::ENABLE_QUICK_EDIT_MODE; |
| let raw = raw | wincon::ENABLE_WINDOW_INPUT; |
| check!(consoleapi::SetConsoleMode(self.stdin_handle, raw)); |
| Ok(Mode { |
| original_mode: original_mode, |
| stdin_handle: self.stdin_handle, |
| }) |
| } |
| |
| fn create_reader(&self, _: &Config) -> Result<ConsoleRawReader> { |
| ConsoleRawReader::new() |
| } |
| |
| fn create_writer(&self) -> ConsoleRenderer { |
| ConsoleRenderer::new(self.stdout_handle) |
| } |
| } |