1 //! Windows specific definitions
2 #![allow(clippy::try_err)] // suggested fix does not work (cannot infer...)
3
4 use std::io::{self, Write};
5 use std::mem;
6 use std::sync::atomic::{AtomicBool, Ordering};
7
8 use log::{debug, warn};
9 use unicode_segmentation::UnicodeSegmentation;
10 use unicode_width::UnicodeWidthStr;
11 use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE, WORD};
12 use winapi::shared::winerror;
13 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
14 use winapi::um::wincon::{self, CONSOLE_SCREEN_BUFFER_INFO, COORD};
15 use winapi::um::winnt::{CHAR, HANDLE};
16 use winapi::um::{consoleapi, processenv, winbase, winuser};
17
18 use super::{width, RawMode, RawReader, Renderer, Term};
19 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
20 use crate::error;
21 use crate::highlight::Highlighter;
22 use crate::keys::{KeyCode as K, KeyEvent, Modifiers as M};
23 use crate::layout::{Layout, Position};
24 use crate::line_buffer::LineBuffer;
25 use crate::Result;
26
27 const STDIN_FILENO: DWORD = winbase::STD_INPUT_HANDLE;
28 const STDOUT_FILENO: DWORD = winbase::STD_OUTPUT_HANDLE;
29 const STDERR_FILENO: DWORD = winbase::STD_ERROR_HANDLE;
30
get_std_handle(fd: DWORD) -> Result<HANDLE>31 fn get_std_handle(fd: DWORD) -> Result<HANDLE> {
32 let handle = unsafe { processenv::GetStdHandle(fd) };
33 if handle == INVALID_HANDLE_VALUE {
34 Err(io::Error::last_os_error())?;
35 } else if handle.is_null() {
36 Err(io::Error::new(
37 io::ErrorKind::Other,
38 "no stdio handle available for this process",
39 ))?;
40 }
41 Ok(handle)
42 }
43
check(rc: BOOL) -> Result<()>44 fn check(rc: BOOL) -> Result<()> {
45 if rc == FALSE {
46 Err(io::Error::last_os_error())?
47 } else {
48 Ok(())
49 }
50 }
51
get_win_size(handle: HANDLE) -> (usize, usize)52 fn get_win_size(handle: HANDLE) -> (usize, usize) {
53 let mut info = unsafe { mem::zeroed() };
54 match unsafe { wincon::GetConsoleScreenBufferInfo(handle, &mut info) } {
55 FALSE => (80, 24),
56 _ => (
57 info.dwSize.X as usize,
58 (1 + info.srWindow.Bottom - info.srWindow.Top) as usize,
59 ), // (info.srWindow.Right - info.srWindow.Left + 1)
60 }
61 }
62
get_console_mode(handle: HANDLE) -> Result<DWORD>63 fn get_console_mode(handle: HANDLE) -> Result<DWORD> {
64 let mut original_mode = 0;
65 check(unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) })?;
66 Ok(original_mode)
67 }
68
69 #[must_use = "You must restore default mode (disable_raw_mode)"]
70 #[cfg(not(test))]
71 pub type Mode = ConsoleMode;
72
73 #[derive(Clone, Copy, Debug)]
74 pub struct ConsoleMode {
75 original_stdin_mode: DWORD,
76 stdin_handle: HANDLE,
77 original_stdstream_mode: Option<DWORD>,
78 stdstream_handle: HANDLE,
79 }
80
81 impl RawMode for ConsoleMode {
82 /// Disable RAW mode for the terminal.
disable_raw_mode(&self) -> Result<()>83 fn disable_raw_mode(&self) -> Result<()> {
84 check(unsafe { consoleapi::SetConsoleMode(self.stdin_handle, self.original_stdin_mode) })?;
85 if let Some(original_stdstream_mode) = self.original_stdstream_mode {
86 check(unsafe {
87 consoleapi::SetConsoleMode(self.stdstream_handle, original_stdstream_mode)
88 })?;
89 }
90 Ok(())
91 }
92 }
93
94 /// Console input reader
95 pub struct ConsoleRawReader {
96 handle: HANDLE,
97 }
98
99 impl ConsoleRawReader {
create() -> Result<ConsoleRawReader>100 pub fn create() -> Result<ConsoleRawReader> {
101 let handle = get_std_handle(STDIN_FILENO)?;
102 Ok(ConsoleRawReader { handle })
103 }
104 }
105
106 impl RawReader for ConsoleRawReader {
next_key(&mut self, _: bool) -> Result<KeyEvent>107 fn next_key(&mut self, _: bool) -> Result<KeyEvent> {
108 use std::char::decode_utf16;
109 use winapi::um::wincon::{
110 LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
111 SHIFT_PRESSED,
112 };
113
114 let mut rec: wincon::INPUT_RECORD = unsafe { mem::zeroed() };
115 let mut count = 0;
116 let mut surrogate = 0;
117 loop {
118 // TODO GetNumberOfConsoleInputEvents
119 check(unsafe {
120 consoleapi::ReadConsoleInputW(self.handle, &mut rec, 1 as DWORD, &mut count)
121 })?;
122
123 if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT {
124 SIGWINCH.store(true, Ordering::SeqCst);
125 debug!(target: "rustyline", "SIGWINCH");
126 return Err(error::ReadlineError::WindowResize); // sigwinch +
127 // err => err
128 // ignored
129 } else if rec.EventType != wincon::KEY_EVENT {
130 continue;
131 }
132 let key_event = unsafe { rec.Event.KeyEvent() };
133 // writeln!(io::stderr(), "key_event: {:?}", key_event).unwrap();
134 if key_event.bKeyDown == 0 && key_event.wVirtualKeyCode != winuser::VK_MENU as WORD {
135 continue;
136 }
137 // key_event.wRepeatCount seems to be always set to 1 (maybe because we only
138 // read one character at a time)
139
140 let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)
141 == (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
142 let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0;
143 let mut mods = M::NONE;
144 if !alt_gr
145 && key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0
146 {
147 mods |= M::CTRL;
148 }
149 if alt && !alt_gr {
150 mods |= M::ALT;
151 }
152 if key_event.dwControlKeyState & SHIFT_PRESSED != 0 {
153 mods |= M::SHIFT;
154 }
155
156 let utf16 = unsafe { *key_event.uChar.UnicodeChar() };
157 let key = if utf16 == 0 {
158 KeyEvent(
159 match i32::from(key_event.wVirtualKeyCode) {
160 winuser::VK_LEFT => K::Left,
161 winuser::VK_RIGHT => K::Right,
162 winuser::VK_UP => K::Up,
163 winuser::VK_DOWN => K::Down,
164 winuser::VK_DELETE => K::Delete,
165 winuser::VK_HOME => K::Home,
166 winuser::VK_END => K::End,
167 winuser::VK_PRIOR => K::PageUp,
168 winuser::VK_NEXT => K::PageDown,
169 winuser::VK_INSERT => K::Insert,
170 winuser::VK_F1 => K::F(1),
171 winuser::VK_F2 => K::F(2),
172 winuser::VK_F3 => K::F(3),
173 winuser::VK_F4 => K::F(4),
174 winuser::VK_F5 => K::F(5),
175 winuser::VK_F6 => K::F(6),
176 winuser::VK_F7 => K::F(7),
177 winuser::VK_F8 => K::F(8),
178 winuser::VK_F9 => K::F(9),
179 winuser::VK_F10 => K::F(10),
180 winuser::VK_F11 => K::F(11),
181 winuser::VK_F12 => K::F(12),
182 // winuser::VK_BACK is correctly handled because the key_event.UnicodeChar
183 // is also set.
184 _ => continue,
185 },
186 mods,
187 )
188 } else if utf16 == 27 {
189 KeyEvent(K::Esc, mods)
190 } else {
191 if utf16 >= 0xD800 && utf16 < 0xDC00 {
192 surrogate = utf16;
193 continue;
194 }
195 let orc = if surrogate == 0 {
196 decode_utf16(Some(utf16)).next()
197 } else {
198 decode_utf16([surrogate, utf16].iter().cloned()).next()
199 };
200 let rc = if let Some(rc) = orc {
201 rc
202 } else {
203 return Err(error::ReadlineError::Eof);
204 };
205 let c = rc?;
206 KeyEvent::new(c, mods)
207 };
208 debug!(target: "rustyline", "key: {:?}", key);
209 return Ok(key);
210 }
211 }
212
read_pasted_text(&mut self) -> Result<String>213 fn read_pasted_text(&mut self) -> Result<String> {
214 unimplemented!()
215 }
216 }
217
218 pub struct ConsoleRenderer {
219 out: OutputStreamType,
220 handle: HANDLE,
221 cols: usize, // Number of columns in terminal
222 buffer: String,
223 colors_enabled: bool,
224 bell_style: BellStyle,
225 }
226
227 impl ConsoleRenderer {
new( handle: HANDLE, out: OutputStreamType, colors_enabled: bool, bell_style: BellStyle, ) -> ConsoleRenderer228 fn new(
229 handle: HANDLE,
230 out: OutputStreamType,
231 colors_enabled: bool,
232 bell_style: BellStyle,
233 ) -> ConsoleRenderer {
234 // Multi line editing is enabled by ENABLE_WRAP_AT_EOL_OUTPUT mode
235 let (cols, _) = get_win_size(handle);
236 ConsoleRenderer {
237 out,
238 handle,
239 cols,
240 buffer: String::with_capacity(1024),
241 colors_enabled,
242 bell_style,
243 }
244 }
245
get_console_screen_buffer_info(&self) -> Result<CONSOLE_SCREEN_BUFFER_INFO>246 fn get_console_screen_buffer_info(&self) -> Result<CONSOLE_SCREEN_BUFFER_INFO> {
247 let mut info = unsafe { mem::zeroed() };
248 check(unsafe { wincon::GetConsoleScreenBufferInfo(self.handle, &mut info) })?;
249 Ok(info)
250 }
251
set_console_cursor_position(&mut self, pos: COORD) -> Result<()>252 fn set_console_cursor_position(&mut self, pos: COORD) -> Result<()> {
253 check(unsafe { wincon::SetConsoleCursorPosition(self.handle, pos) })
254 }
255
clear(&mut self, length: DWORD, pos: COORD, attr: WORD) -> Result<()>256 fn clear(&mut self, length: DWORD, pos: COORD, attr: WORD) -> Result<()> {
257 let mut _count = 0;
258 check(unsafe {
259 wincon::FillConsoleOutputCharacterA(self.handle, ' ' as CHAR, length, pos, &mut _count)
260 })?;
261 check(unsafe {
262 wincon::FillConsoleOutputAttribute(self.handle, attr, length, pos, &mut _count)
263 })
264 }
265
set_cursor_visible(&mut self, visible: BOOL) -> Result<()>266 fn set_cursor_visible(&mut self, visible: BOOL) -> Result<()> {
267 set_cursor_visible(self.handle, visible)
268 }
269
270 // You can't have both ENABLE_WRAP_AT_EOL_OUTPUT and
271 // ENABLE_VIRTUAL_TERMINAL_PROCESSING. So we need to wrap manually.
wrap_at_eol(&mut self, s: &str, mut col: usize) -> usize272 fn wrap_at_eol(&mut self, s: &str, mut col: usize) -> usize {
273 let mut esc_seq = 0;
274 for c in s.graphemes(true) {
275 if c == "\n" {
276 col = 0;
277 self.buffer.push_str(c);
278 } else {
279 let cw = width(c, &mut esc_seq);
280 col += cw;
281 if col > self.cols {
282 self.buffer.push('\n');
283 col = cw;
284 }
285 self.buffer.push_str(c);
286 }
287 }
288 if col == self.cols {
289 self.buffer.push('\n');
290 col = 0;
291 }
292 col
293 }
294
295 // position at the start of the prompt, clear to end of previous input
clear_old_rows(&mut self, info: &CONSOLE_SCREEN_BUFFER_INFO, layout: &Layout) -> Result<()>296 fn clear_old_rows(&mut self, info: &CONSOLE_SCREEN_BUFFER_INFO, layout: &Layout) -> Result<()> {
297 let current_row = layout.cursor.row;
298 let old_rows = layout.end.row;
299 let mut coord = info.dwCursorPosition;
300 coord.X = 0;
301 coord.Y -= current_row as i16;
302 self.set_console_cursor_position(coord)?;
303 self.clear(
304 (info.dwSize.X * (old_rows as i16 + 1)) as DWORD,
305 coord,
306 info.wAttributes,
307 )
308 }
309 }
310
set_cursor_visible(handle: HANDLE, visible: BOOL) -> Result<()>311 fn set_cursor_visible(handle: HANDLE, visible: BOOL) -> Result<()> {
312 let mut info = unsafe { mem::zeroed() };
313 check(unsafe { wincon::GetConsoleCursorInfo(handle, &mut info) })?;
314 if info.bVisible == visible {
315 return Ok(());
316 }
317 info.bVisible = visible;
318 check(unsafe { wincon::SetConsoleCursorInfo(handle, &info) })
319 }
320
321 impl Renderer for ConsoleRenderer {
322 type Reader = ConsoleRawReader;
323
move_cursor(&mut self, old: Position, new: Position) -> Result<()>324 fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
325 let mut cursor = self.get_console_screen_buffer_info()?.dwCursorPosition;
326 if new.row > old.row {
327 cursor.Y += (new.row - old.row) as i16;
328 } else {
329 cursor.Y -= (old.row - new.row) as i16;
330 }
331 if new.col > old.col {
332 cursor.X += (new.col - old.col) as i16;
333 } else {
334 cursor.X -= (old.col - new.col) as i16;
335 }
336 self.set_console_cursor_position(cursor)
337 }
338
refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, highlighter: Option<&dyn Highlighter>, ) -> Result<()>339 fn refresh_line(
340 &mut self,
341 prompt: &str,
342 line: &LineBuffer,
343 hint: Option<&str>,
344 old_layout: &Layout,
345 new_layout: &Layout,
346 highlighter: Option<&dyn Highlighter>,
347 ) -> Result<()> {
348 let default_prompt = new_layout.default_prompt;
349 let cursor = new_layout.cursor;
350 let end_pos = new_layout.end;
351
352 self.buffer.clear();
353 let mut col = 0;
354 if let Some(highlighter) = highlighter {
355 // TODO handle ansi escape code (SetConsoleTextAttribute)
356 // append the prompt
357 col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col);
358 // append the input line
359 col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col);
360 } else {
361 // append the prompt
362 self.buffer.push_str(prompt);
363 // append the input line
364 self.buffer.push_str(line);
365 }
366 // append hint
367 if let Some(hint) = hint {
368 if let Some(highlighter) = highlighter {
369 self.wrap_at_eol(&highlighter.highlight_hint(hint), col);
370 } else {
371 self.buffer.push_str(hint);
372 }
373 }
374 let info = self.get_console_screen_buffer_info()?;
375 self.set_cursor_visible(FALSE)?; // just to avoid flickering
376 let handle = self.handle;
377 scopeguard::defer! {
378 let _ = set_cursor_visible(handle, TRUE);
379 }
380 // position at the start of the prompt, clear to end of previous input
381 self.clear_old_rows(&info, old_layout)?;
382 // display prompt, input line and hint
383 self.write_and_flush(self.buffer.as_bytes())?;
384
385 // position the cursor
386 let mut coord = self.get_console_screen_buffer_info()?.dwCursorPosition;
387 coord.X = cursor.col as i16;
388 coord.Y -= (end_pos.row - cursor.row) as i16;
389 self.set_console_cursor_position(coord)?;
390
391 Ok(())
392 }
393
write_and_flush(&self, buf: &[u8]) -> Result<()>394 fn write_and_flush(&self, buf: &[u8]) -> Result<()> {
395 match self.out {
396 OutputStreamType::Stdout => {
397 io::stdout().write_all(buf)?;
398 io::stdout().flush()?;
399 }
400 OutputStreamType::Stderr => {
401 io::stderr().write_all(buf)?;
402 io::stderr().flush()?;
403 }
404 }
405 Ok(())
406 }
407
408 /// Characters with 2 column width are correctly handled (not split).
calculate_position(&self, s: &str, orig: Position) -> Position409 fn calculate_position(&self, s: &str, orig: Position) -> Position {
410 let mut pos = orig;
411 for c in s.graphemes(true) {
412 if c == "\n" {
413 pos.col = 0;
414 pos.row += 1;
415 } else {
416 let cw = c.width();
417 pos.col += cw;
418 if pos.col > self.cols {
419 pos.row += 1;
420 pos.col = cw;
421 }
422 }
423 }
424 if pos.col == self.cols {
425 pos.col = 0;
426 pos.row += 1;
427 }
428 pos
429 }
430
beep(&mut self) -> Result<()>431 fn beep(&mut self) -> Result<()> {
432 match self.bell_style {
433 BellStyle::Audible => {
434 io::stderr().write_all(b"\x07")?;
435 io::stderr().flush()?;
436 Ok(())
437 }
438 _ => Ok(()),
439 }
440 }
441
442 /// Clear the screen. Used to handle ctrl+l
clear_screen(&mut self) -> Result<()>443 fn clear_screen(&mut self) -> Result<()> {
444 let info = self.get_console_screen_buffer_info()?;
445 let coord = COORD { X: 0, Y: 0 };
446 check(unsafe { wincon::SetConsoleCursorPosition(self.handle, coord) })?;
447 let n = info.dwSize.X as DWORD * info.dwSize.Y as DWORD;
448 self.clear(n, coord, info.wAttributes)
449 }
450
sigwinch(&self) -> bool451 fn sigwinch(&self) -> bool {
452 SIGWINCH.compare_and_swap(true, false, Ordering::SeqCst)
453 }
454
455 /// Try to get the number of columns in the current terminal,
456 /// or assume 80 if it fails.
update_size(&mut self)457 fn update_size(&mut self) {
458 let (cols, _) = get_win_size(self.handle);
459 self.cols = cols;
460 }
461
get_columns(&self) -> usize462 fn get_columns(&self) -> usize {
463 self.cols
464 }
465
466 /// Try to get the number of rows in the current terminal,
467 /// or assume 24 if it fails.
get_rows(&self) -> usize468 fn get_rows(&self) -> usize {
469 let (_, rows) = get_win_size(self.handle);
470 rows
471 }
472
colors_enabled(&self) -> bool473 fn colors_enabled(&self) -> bool {
474 self.colors_enabled
475 }
476
move_cursor_at_leftmost(&mut self, _: &mut ConsoleRawReader) -> Result<()>477 fn move_cursor_at_leftmost(&mut self, _: &mut ConsoleRawReader) -> Result<()> {
478 self.write_and_flush(b"")?; // we must do this otherwise the cursor position is not reported correctly
479 let mut info = self.get_console_screen_buffer_info()?;
480 if info.dwCursorPosition.X == 0 {
481 return Ok(());
482 }
483 debug!(target: "rustyline", "initial cursor location: {:?}, {:?}", info.dwCursorPosition.X, info.dwCursorPosition.Y);
484 info.dwCursorPosition.X = 0;
485 info.dwCursorPosition.Y += 1;
486 let res = self.set_console_cursor_position(info.dwCursorPosition);
487 if let Err(error::ReadlineError::Io(ref e)) = res {
488 if e.raw_os_error() == Some(winerror::ERROR_INVALID_PARAMETER as i32) {
489 warn!(target: "rustyline", "invalid cursor position: ({:?}, {:?}) in ({:?}, {:?})", info.dwCursorPosition.X, info.dwCursorPosition.Y, info.dwSize.X, info.dwSize.Y);
490 println!();
491 return Ok(());
492 }
493 }
494 res
495 }
496 }
497
498 static SIGWINCH: AtomicBool = AtomicBool::new(false);
499
500 #[cfg(not(test))]
501 pub type Terminal = Console;
502
503 #[derive(Clone, Debug)]
504 pub struct Console {
505 stdin_isatty: bool,
506 stdin_handle: HANDLE,
507 stdstream_isatty: bool,
508 stdstream_handle: HANDLE,
509 pub(crate) color_mode: ColorMode,
510 ansi_colors_supported: bool,
511 stream_type: OutputStreamType,
512 bell_style: BellStyle,
513 }
514
515 impl Console {
colors_enabled(&self) -> bool516 fn colors_enabled(&self) -> bool {
517 // TODO ANSI Colors & Windows <10
518 match self.color_mode {
519 ColorMode::Enabled => self.stdstream_isatty && self.ansi_colors_supported,
520 ColorMode::Forced => true,
521 ColorMode::Disabled => false,
522 }
523 }
524 }
525
526 impl Term for Console {
527 type Mode = ConsoleMode;
528 type Reader = ConsoleRawReader;
529 type Writer = ConsoleRenderer;
530
new( color_mode: ColorMode, stream_type: OutputStreamType, _tab_stop: usize, bell_style: BellStyle, ) -> Console531 fn new(
532 color_mode: ColorMode,
533 stream_type: OutputStreamType,
534 _tab_stop: usize,
535 bell_style: BellStyle,
536 ) -> Console {
537 use std::ptr;
538 let stdin_handle = get_std_handle(STDIN_FILENO);
539 let stdin_isatty = match stdin_handle {
540 Ok(handle) => {
541 // If this function doesn't fail then fd is a TTY
542 get_console_mode(handle).is_ok()
543 }
544 Err(_) => false,
545 };
546
547 let stdstream_handle = get_std_handle(if stream_type == OutputStreamType::Stdout {
548 STDOUT_FILENO
549 } else {
550 STDERR_FILENO
551 });
552 let stdstream_isatty = match stdstream_handle {
553 Ok(handle) => {
554 // If this function doesn't fail then fd is a TTY
555 get_console_mode(handle).is_ok()
556 }
557 Err(_) => false,
558 };
559
560 Console {
561 stdin_isatty,
562 stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()),
563 stdstream_isatty,
564 stdstream_handle: stdstream_handle.unwrap_or(ptr::null_mut()),
565 color_mode,
566 ansi_colors_supported: false,
567 stream_type,
568 bell_style,
569 }
570 }
571
572 /// Checking for an unsupported TERM in windows is a no-op
is_unsupported(&self) -> bool573 fn is_unsupported(&self) -> bool {
574 false
575 }
576
is_stdin_tty(&self) -> bool577 fn is_stdin_tty(&self) -> bool {
578 self.stdin_isatty
579 }
580
is_output_tty(&self) -> bool581 fn is_output_tty(&self) -> bool {
582 self.stdstream_isatty
583 }
584
585 // pub fn install_sigwinch_handler(&mut self) {
586 // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT
587 // }
588
589 /// Enable RAW mode for the terminal.
enable_raw_mode(&mut self) -> Result<Self::Mode>590 fn enable_raw_mode(&mut self) -> Result<Self::Mode> {
591 if !self.stdin_isatty {
592 Err(io::Error::new(
593 io::ErrorKind::Other,
594 "no stdio handle available for this process",
595 ))?;
596 }
597 let original_stdin_mode = get_console_mode(self.stdin_handle)?;
598 // Disable these modes
599 let mut raw = original_stdin_mode
600 & !(wincon::ENABLE_LINE_INPUT
601 | wincon::ENABLE_ECHO_INPUT
602 | wincon::ENABLE_PROCESSED_INPUT);
603 // Enable these modes
604 raw |= wincon::ENABLE_EXTENDED_FLAGS;
605 raw |= wincon::ENABLE_INSERT_MODE;
606 raw |= wincon::ENABLE_QUICK_EDIT_MODE;
607 raw |= wincon::ENABLE_WINDOW_INPUT;
608 check(unsafe { consoleapi::SetConsoleMode(self.stdin_handle, raw) })?;
609
610 let original_stdstream_mode = if self.stdstream_isatty {
611 let original_stdstream_mode = get_console_mode(self.stdstream_handle)?;
612
613 let mut mode = original_stdstream_mode;
614 if mode & wincon::ENABLE_WRAP_AT_EOL_OUTPUT == 0 {
615 mode |= wincon::ENABLE_WRAP_AT_EOL_OUTPUT;
616 debug!(target: "rustyline", "activate ENABLE_WRAP_AT_EOL_OUTPUT");
617 unsafe {
618 assert!(consoleapi::SetConsoleMode(self.stdstream_handle, mode) != 0);
619 }
620 }
621 // To enable ANSI colors (Windows 10 only):
622 // https://docs.microsoft.com/en-us/windows/console/setconsolemode
623 self.ansi_colors_supported = mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0;
624 if self.ansi_colors_supported {
625 if self.color_mode == ColorMode::Disabled {
626 mode &= !wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
627 debug!(target: "rustyline", "deactivate ENABLE_VIRTUAL_TERMINAL_PROCESSING");
628 unsafe {
629 assert!(consoleapi::SetConsoleMode(self.stdstream_handle, mode) != 0);
630 }
631 } else {
632 debug!(target: "rustyline", "ANSI colors already enabled");
633 }
634 } else if self.color_mode != ColorMode::Disabled {
635 mode |= wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
636 self.ansi_colors_supported =
637 unsafe { consoleapi::SetConsoleMode(self.stdstream_handle, mode) != 0 };
638 debug!(target: "rustyline", "ansi_colors_supported: {}", self.ansi_colors_supported);
639 }
640 Some(original_stdstream_mode)
641 } else {
642 None
643 };
644
645 Ok(ConsoleMode {
646 original_stdin_mode,
647 stdin_handle: self.stdin_handle,
648 original_stdstream_mode,
649 stdstream_handle: self.stdstream_handle,
650 })
651 }
652
create_reader(&self, _: &Config) -> Result<ConsoleRawReader>653 fn create_reader(&self, _: &Config) -> Result<ConsoleRawReader> {
654 ConsoleRawReader::create()
655 }
656
create_writer(&self) -> ConsoleRenderer657 fn create_writer(&self) -> ConsoleRenderer {
658 ConsoleRenderer::new(
659 self.stdstream_handle,
660 self.stream_type,
661 self.colors_enabled(),
662 self.bell_style,
663 )
664 }
665 }
666
667 unsafe impl Send for Console {}
668 unsafe impl Sync for Console {}
669
670 #[cfg(test)]
671 mod test {
672 use super::Console;
673
674 #[test]
test_send()675 fn test_send() {
676 fn assert_send<T: Send>() {}
677 assert_send::<Console>();
678 }
679
680 #[test]
test_sync()681 fn test_sync() {
682 fn assert_sync<T: Sync>() {}
683 assert_sync::<Console>();
684 }
685 }
686