1 | //! # Terminal
|
2 | //!
|
3 | //! The `terminal` module provides functionality to work with the terminal.
|
4 | //!
|
5 | //! This documentation does not contain a lot of examples. The reason is that it's fairly
|
6 | //! obvious how to use this crate. Although, we do provide
|
7 | //! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
|
8 | //! to demonstrate the capabilities.
|
9 | //!
|
10 | //! Most terminal actions can be performed with commands.
|
11 | //! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation.
|
12 | //!
|
13 | //! ## Screen Buffer
|
14 | //!
|
15 | //! A screen buffer is a two-dimensional array of character
|
16 | //! and color data which is displayed in a terminal screen.
|
17 | //!
|
18 | //! The terminal has several of those buffers and is able to switch between them.
|
19 | //! The default screen in which you work is called the 'main screen'.
|
20 | //! The other screens are called the 'alternative screen'.
|
21 | //!
|
22 | //! It is important to understand that crossterm does not yet support creating screens,
|
23 | //! or switch between more than two buffers, and only offers the ability to change
|
24 | //! between the 'alternate' and 'main screen'.
|
25 | //!
|
26 | //! ### Alternate Screen
|
27 | //!
|
28 | //! By default, you will be working on the main screen.
|
29 | //! There is also another screen called the 'alternative' screen.
|
30 | //! This screen is slightly different from the main screen.
|
31 | //! For example, it has the exact dimensions of the terminal window,
|
32 | //! without any scroll-back area.
|
33 | //!
|
34 | //! Crossterm offers the possibility to switch to the 'alternative' screen,
|
35 | //! make some modifications, and move back to the 'main' screen again.
|
36 | //! The main screen will stay intact and will have the original data as we performed all
|
37 | //! operations on the alternative screen.
|
38 | //!
|
39 | //! An good example of this is Vim.
|
40 | //! When it is launched from bash, a whole new buffer is used to modify a file.
|
41 | //! Then, when the modification is finished, it closes again and continues on the main screen.
|
42 | //!
|
43 | //! ### Raw Mode
|
44 | //!
|
45 | //! By default, the terminal functions in a certain way.
|
46 | //! For example, it will move the cursor to the beginning of the next line when the input hits the end of a line.
|
47 | //! Or that the backspace is interpreted for character removal.
|
48 | //!
|
49 | //! Sometimes these default modes are irrelevant,
|
50 | //! and in this case, we can turn them off.
|
51 | //! This is what happens when you enable raw modes.
|
52 | //!
|
53 | //! Those modes will be set when enabling raw modes:
|
54 | //!
|
55 | //! - Input will not be forwarded to screen
|
56 | //! - Input will not be processed on enter press
|
57 | //! - Input will not be line buffered (input sent byte-by-byte to input buffer)
|
58 | //! - Special keys like backspace and CTRL+C will not be processed by terminal driver
|
59 | //! - New line character will not be processed therefore `println!` can't be used, use `write!` instead
|
60 | //!
|
61 | //! Raw mode can be enabled/disabled with the [enable_raw_mode](terminal::enable_raw_mode) and [disable_raw_mode](terminal::disable_raw_mode) functions.
|
62 | //!
|
63 | //! ## Examples
|
64 | //!
|
65 | //! ```no_run
|
66 | //! use std::io::{self, Write};
|
67 | //! use crossterm::{execute, terminal::{ScrollUp, SetSize, size}};
|
68 | //!
|
69 | //! fn main() -> io::Result<()> {
|
70 | //! let (cols, rows) = size()?;
|
71 | //! // Resize terminal and scroll up.
|
72 | //! execute!(
|
73 | //! io::stdout(),
|
74 | //! SetSize(10, 10),
|
75 | //! ScrollUp(5)
|
76 | //! )?;
|
77 | //!
|
78 | //! // Be a good citizen, cleanup
|
79 | //! execute!(io::stdout(), SetSize(cols, rows))?;
|
80 | //! Ok(())
|
81 | //! }
|
82 | //! ```
|
83 | //!
|
84 | //! For manual execution control check out [crossterm::queue](../macro.queue.html).
|
85 |
|
86 | use std::{fmt, io};
|
87 |
|
88 | #[cfg (windows)]
|
89 | use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer};
|
90 | #[cfg (feature = "serde" )]
|
91 | use serde::{Deserialize, Serialize};
|
92 | #[cfg (windows)]
|
93 | use winapi::um::wincon::ENABLE_WRAP_AT_EOL_OUTPUT;
|
94 |
|
95 | #[doc (no_inline)]
|
96 | use crate::Command;
|
97 | use crate::{csi, impl_display};
|
98 |
|
99 | pub(crate) mod sys;
|
100 |
|
101 | #[cfg (feature = "events" )]
|
102 | pub use sys::supports_keyboard_enhancement;
|
103 |
|
104 | /// Tells whether the raw mode is enabled.
|
105 | ///
|
106 | /// Please have a look at the [raw mode](./index.html#raw-mode) section.
|
107 | pub fn is_raw_mode_enabled() -> io::Result<bool> {
|
108 | #[cfg (unix)]
|
109 | {
|
110 | Ok(sys::is_raw_mode_enabled())
|
111 | }
|
112 |
|
113 | #[cfg (windows)]
|
114 | {
|
115 | sys::is_raw_mode_enabled()
|
116 | }
|
117 | }
|
118 |
|
119 | /// Enables raw mode.
|
120 | ///
|
121 | /// Please have a look at the [raw mode](./index.html#raw-mode) section.
|
122 | pub fn enable_raw_mode() -> io::Result<()> {
|
123 | sys::enable_raw_mode()
|
124 | }
|
125 |
|
126 | /// Disables raw mode.
|
127 | ///
|
128 | /// Please have a look at the [raw mode](./index.html#raw-mode) section.
|
129 | pub fn disable_raw_mode() -> io::Result<()> {
|
130 | sys::disable_raw_mode()
|
131 | }
|
132 |
|
133 | /// Returns the terminal size `(columns, rows)`.
|
134 | ///
|
135 | /// The top left cell is represented `(1, 1)`.
|
136 | pub fn size() -> io::Result<(u16, u16)> {
|
137 | sys::size()
|
138 | }
|
139 |
|
140 | #[derive (Debug)]
|
141 | pub struct WindowSize {
|
142 | pub rows: u16,
|
143 | pub columns: u16,
|
144 | pub width: u16,
|
145 | pub height: u16,
|
146 | }
|
147 |
|
148 | /// Returns the terminal size `[WindowSize]`.
|
149 | ///
|
150 | /// The width and height in pixels may not be reliably implemented or default to 0.
|
151 | /// For unix, https://man7.org/linux/man-pages/man4/tty_ioctl.4.html documents them as "unused".
|
152 | /// For windows it is not implemented.
|
153 | pub fn window_size() -> io::Result<WindowSize> {
|
154 | sys::window_size()
|
155 | }
|
156 |
|
157 | /// Disables line wrapping.
|
158 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
159 | pub struct DisableLineWrap;
|
160 |
|
161 | impl Command for DisableLineWrap {
|
162 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
163 | f.write_str(csi!("?7l" ))
|
164 | }
|
165 |
|
166 | #[cfg (windows)]
|
167 | fn execute_winapi(&self) -> io::Result<()> {
|
168 | let screen_buffer = ScreenBuffer::current()?;
|
169 | let console_mode = ConsoleMode::from(screen_buffer.handle().clone());
|
170 | let new_mode = console_mode.mode()? & !ENABLE_WRAP_AT_EOL_OUTPUT;
|
171 | console_mode.set_mode(new_mode)?;
|
172 | Ok(())
|
173 | }
|
174 | }
|
175 |
|
176 | /// Enable line wrapping.
|
177 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
178 | pub struct EnableLineWrap;
|
179 |
|
180 | impl Command for EnableLineWrap {
|
181 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
182 | f.write_str(csi!("?7h" ))
|
183 | }
|
184 |
|
185 | #[cfg (windows)]
|
186 | fn execute_winapi(&self) -> io::Result<()> {
|
187 | let screen_buffer = ScreenBuffer::current()?;
|
188 | let console_mode = ConsoleMode::from(screen_buffer.handle().clone());
|
189 | let new_mode = console_mode.mode()? | ENABLE_WRAP_AT_EOL_OUTPUT;
|
190 | console_mode.set_mode(new_mode)?;
|
191 | Ok(())
|
192 | }
|
193 | }
|
194 |
|
195 | /// A command that switches to alternate screen.
|
196 | ///
|
197 | /// # Notes
|
198 | ///
|
199 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
200 | /// * Use [LeaveAlternateScreen](./struct.LeaveAlternateScreen.html) command to leave the entered alternate screen.
|
201 | ///
|
202 | /// # Examples
|
203 | ///
|
204 | /// ```no_run
|
205 | /// use std::io::{self, Write};
|
206 | /// use crossterm::{execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
|
207 | ///
|
208 | /// fn main() -> io::Result<()> {
|
209 | /// execute!(io::stdout(), EnterAlternateScreen)?;
|
210 | ///
|
211 | /// // Do anything on the alternate screen
|
212 | ///
|
213 | /// execute!(io::stdout(), LeaveAlternateScreen)
|
214 | /// }
|
215 | /// ```
|
216 | ///
|
217 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
218 | pub struct EnterAlternateScreen;
|
219 |
|
220 | impl Command for EnterAlternateScreen {
|
221 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
222 | f.write_str(csi!("?1049h" ))
|
223 | }
|
224 |
|
225 | #[cfg (windows)]
|
226 | fn execute_winapi(&self) -> io::Result<()> {
|
227 | let alternate_screen = ScreenBuffer::create()?;
|
228 | alternate_screen.show()?;
|
229 | Ok(())
|
230 | }
|
231 | }
|
232 |
|
233 | /// A command that switches back to the main screen.
|
234 | ///
|
235 | /// # Notes
|
236 | ///
|
237 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
238 | /// * Use [EnterAlternateScreen](./struct.EnterAlternateScreen.html) to enter the alternate screen.
|
239 | ///
|
240 | /// # Examples
|
241 | ///
|
242 | /// ```no_run
|
243 | /// use std::io::{self, Write};
|
244 | /// use crossterm::{execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
|
245 | ///
|
246 | /// fn main() -> io::Result<()> {
|
247 | /// execute!(io::stdout(), EnterAlternateScreen)?;
|
248 | ///
|
249 | /// // Do anything on the alternate screen
|
250 | ///
|
251 | /// execute!(io::stdout(), LeaveAlternateScreen)
|
252 | /// }
|
253 | /// ```
|
254 | ///
|
255 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
256 | pub struct LeaveAlternateScreen;
|
257 |
|
258 | impl Command for LeaveAlternateScreen {
|
259 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
260 | f.write_str(csi!("?1049l" ))
|
261 | }
|
262 |
|
263 | #[cfg (windows)]
|
264 | fn execute_winapi(&self) -> io::Result<()> {
|
265 | let screen_buffer = ScreenBuffer::from(Handle::current_out_handle()?);
|
266 | screen_buffer.show()?;
|
267 | Ok(())
|
268 | }
|
269 | }
|
270 |
|
271 | /// Different ways to clear the terminal buffer.
|
272 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))]
|
273 | #[derive (Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
274 | pub enum ClearType {
|
275 | /// All cells.
|
276 | All,
|
277 | /// All plus history
|
278 | Purge,
|
279 | /// All cells from the cursor position downwards.
|
280 | FromCursorDown,
|
281 | /// All cells from the cursor position upwards.
|
282 | FromCursorUp,
|
283 | /// All cells at the cursor row.
|
284 | CurrentLine,
|
285 | /// All cells from the cursor position until the new line.
|
286 | UntilNewLine,
|
287 | }
|
288 |
|
289 | /// A command that scrolls the terminal screen a given number of rows up.
|
290 | ///
|
291 | /// # Notes
|
292 | ///
|
293 | /// Commands must be executed/queued for execution otherwise they do nothing.
|
294 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
295 | pub struct ScrollUp(pub u16);
|
296 |
|
297 | impl Command for ScrollUp {
|
298 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
299 | if self.0 != 0 {
|
300 | write!(f, csi!("{}S" ), self.0)?;
|
301 | }
|
302 | Ok(())
|
303 | }
|
304 |
|
305 | #[cfg (windows)]
|
306 | fn execute_winapi(&self) -> io::Result<()> {
|
307 | sys::scroll_up(self.0)
|
308 | }
|
309 | }
|
310 |
|
311 | /// A command that scrolls the terminal screen a given number of rows down.
|
312 | ///
|
313 | /// # Notes
|
314 | ///
|
315 | /// Commands must be executed/queued for execution otherwise they do nothing.
|
316 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
317 | pub struct ScrollDown(pub u16);
|
318 |
|
319 | impl Command for ScrollDown {
|
320 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
321 | if self.0 != 0 {
|
322 | write!(f, csi!("{}T" ), self.0)?;
|
323 | }
|
324 | Ok(())
|
325 | }
|
326 |
|
327 | #[cfg (windows)]
|
328 | fn execute_winapi(&self) -> io::Result<()> {
|
329 | sys::scroll_down(self.0)
|
330 | }
|
331 | }
|
332 |
|
333 | /// A command that clears the terminal screen buffer.
|
334 | ///
|
335 | /// See the [`ClearType`](enum.ClearType.html) enum.
|
336 | ///
|
337 | /// # Notes
|
338 | ///
|
339 | /// Commands must be executed/queued for execution otherwise they do nothing.
|
340 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
341 | pub struct Clear(pub ClearType);
|
342 |
|
343 | impl Command for Clear {
|
344 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
345 | f.write_str(match self.0 {
|
346 | ClearType::All => csi!("2J" ),
|
347 | ClearType::Purge => csi!("3J" ),
|
348 | ClearType::FromCursorDown => csi!("J" ),
|
349 | ClearType::FromCursorUp => csi!("1J" ),
|
350 | ClearType::CurrentLine => csi!("2K" ),
|
351 | ClearType::UntilNewLine => csi!("K" ),
|
352 | })
|
353 | }
|
354 |
|
355 | #[cfg (windows)]
|
356 | fn execute_winapi(&self) -> io::Result<()> {
|
357 | sys::clear(self.0)
|
358 | }
|
359 | }
|
360 |
|
361 | /// A command that sets the terminal buffer size `(columns, rows)`.
|
362 | ///
|
363 | /// # Notes
|
364 | ///
|
365 | /// Commands must be executed/queued for execution otherwise they do nothing.
|
366 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
367 | pub struct SetSize(pub u16, pub u16);
|
368 |
|
369 | impl Command for SetSize {
|
370 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
371 | write!(f, csi!("8;{};{}t" ), self.1, self.0)
|
372 | }
|
373 |
|
374 | #[cfg (windows)]
|
375 | fn execute_winapi(&self) -> io::Result<()> {
|
376 | sys::set_size(self.0, self.1)
|
377 | }
|
378 | }
|
379 |
|
380 | /// A command that sets the terminal title
|
381 | ///
|
382 | /// # Notes
|
383 | ///
|
384 | /// Commands must be executed/queued for execution otherwise they do nothing.
|
385 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
386 | pub struct SetTitle<T>(pub T);
|
387 |
|
388 | impl<T: fmt::Display> Command for SetTitle<T> {
|
389 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
390 | write!(f, " \x1B]0; {}\x07" , &self.0)
|
391 | }
|
392 |
|
393 | #[cfg (windows)]
|
394 | fn execute_winapi(&self) -> io::Result<()> {
|
395 | sys::set_window_title(&self.0)
|
396 | }
|
397 | }
|
398 |
|
399 | /// A command that instructs the terminal emulator to begin a synchronized frame.
|
400 | ///
|
401 | /// # Notes
|
402 | ///
|
403 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
404 | /// * Use [EndSynchronizedUpdate](./struct.EndSynchronizedUpdate.html) command to leave the entered alternate screen.
|
405 | ///
|
406 | /// When rendering the screen of the terminal, the Emulator usually iterates through each visible grid cell and
|
407 | /// renders its current state. With applications updating the screen a at higher frequency this can cause tearing.
|
408 | ///
|
409 | /// This mode attempts to mitigate that.
|
410 | ///
|
411 | /// When the synchronization mode is enabled following render calls will keep rendering the last rendered state.
|
412 | /// The terminal Emulator keeps processing incoming text and sequences. When the synchronized update mode is disabled
|
413 | /// again the renderer may fetch the latest screen buffer state again, effectively avoiding the tearing effect
|
414 | /// by unintentionally rendering in the middle a of an application screen update.
|
415 | ///
|
416 | /// # Examples
|
417 | ///
|
418 | /// ```no_run
|
419 | /// use std::io::{self, Write};
|
420 | /// use crossterm::{execute, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
421 | ///
|
422 | /// fn main() -> io::Result<()> {
|
423 | /// execute!(io::stdout(), BeginSynchronizedUpdate)?;
|
424 | ///
|
425 | /// // Anything performed here will not be rendered until EndSynchronizedUpdate is called.
|
426 | ///
|
427 | /// execute!(io::stdout(), EndSynchronizedUpdate)?;
|
428 | /// Ok(())
|
429 | /// }
|
430 | /// ```
|
431 | ///
|
432 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
433 | pub struct BeginSynchronizedUpdate;
|
434 |
|
435 | impl Command for BeginSynchronizedUpdate {
|
436 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
437 | f.write_str(csi!("?2026h" ))
|
438 | }
|
439 |
|
440 | #[cfg (windows)]
|
441 | fn execute_winapi(&self) -> io::Result<()> {
|
442 | Ok(())
|
443 | }
|
444 |
|
445 | #[cfg (windows)]
|
446 | #[inline ]
|
447 | fn is_ansi_code_supported(&self) -> bool {
|
448 | true
|
449 | }
|
450 | }
|
451 |
|
452 | /// A command that instructs the terminal to end a synchronized frame.
|
453 | ///
|
454 | /// # Notes
|
455 | ///
|
456 | /// * Commands must be executed/queued for execution otherwise they do nothing.
|
457 | /// * Use [BeginSynchronizedUpdate](./struct.BeginSynchronizedUpdate.html) to enter the alternate screen.
|
458 | ///
|
459 | /// When rendering the screen of the terminal, the Emulator usually iterates through each visible grid cell and
|
460 | /// renders its current state. With applications updating the screen a at higher frequency this can cause tearing.
|
461 | ///
|
462 | /// This mode attempts to mitigate that.
|
463 | ///
|
464 | /// When the synchronization mode is enabled following render calls will keep rendering the last rendered state.
|
465 | /// The terminal Emulator keeps processing incoming text and sequences. When the synchronized update mode is disabled
|
466 | /// again the renderer may fetch the latest screen buffer state again, effectively avoiding the tearing effect
|
467 | /// by unintentionally rendering in the middle a of an application screen update.
|
468 | ///
|
469 | /// # Examples
|
470 | ///
|
471 | /// ```no_run
|
472 | /// use std::io::{self, Write};
|
473 | /// use crossterm::{execute, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
474 | ///
|
475 | /// fn main() -> io::Result<()> {
|
476 | /// execute!(io::stdout(), BeginSynchronizedUpdate)?;
|
477 | ///
|
478 | /// // Anything performed here will not be rendered until EndSynchronizedUpdate is called.
|
479 | ///
|
480 | /// execute!(io::stdout(), EndSynchronizedUpdate)?;
|
481 | /// Ok(())
|
482 | /// }
|
483 | /// ```
|
484 | ///
|
485 | #[derive (Debug, Clone, Copy, PartialEq, Eq)]
|
486 | pub struct EndSynchronizedUpdate;
|
487 |
|
488 | impl Command for EndSynchronizedUpdate {
|
489 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
490 | f.write_str(csi!("?2026l" ))
|
491 | }
|
492 |
|
493 | #[cfg (windows)]
|
494 | fn execute_winapi(&self) -> io::Result<()> {
|
495 | Ok(())
|
496 | }
|
497 |
|
498 | #[cfg (windows)]
|
499 | #[inline ]
|
500 | fn is_ansi_code_supported(&self) -> bool {
|
501 | true
|
502 | }
|
503 | }
|
504 |
|
505 | impl_display!(for ScrollUp);
|
506 | impl_display!(for ScrollDown);
|
507 | impl_display!(for SetSize);
|
508 | impl_display!(for Clear);
|
509 |
|
510 | #[cfg (test)]
|
511 | mod tests {
|
512 | use std::{io::stdout, thread, time};
|
513 |
|
514 | use crate::execute;
|
515 |
|
516 | use super::*;
|
517 |
|
518 | // Test is disabled, because it's failing on Travis CI
|
519 | #[test ]
|
520 | #[ignore ]
|
521 | fn test_resize_ansi() {
|
522 | let (width, height) = size().unwrap();
|
523 |
|
524 | execute!(stdout(), SetSize(35, 35)).unwrap();
|
525 |
|
526 | // see issue: https://github.com/eminence/terminal-size/issues/11
|
527 | thread::sleep(time::Duration::from_millis(30));
|
528 |
|
529 | assert_eq!((35, 35), size().unwrap());
|
530 |
|
531 | // reset to previous size
|
532 | execute!(stdout(), SetSize(width, height)).unwrap();
|
533 |
|
534 | // see issue: https://github.com/eminence/terminal-size/issues/11
|
535 | thread::sleep(time::Duration::from_millis(30));
|
536 |
|
537 | assert_eq!((width, height), size().unwrap());
|
538 | }
|
539 |
|
540 | #[test ]
|
541 | fn test_raw_mode() {
|
542 | // check we start from normal mode (may fail on some test harnesses)
|
543 | assert!(!is_raw_mode_enabled().unwrap());
|
544 |
|
545 | // enable the raw mode
|
546 | if enable_raw_mode().is_err() {
|
547 | // Enabling raw mode doesn't work on the ci
|
548 | // So we just ignore it
|
549 | return;
|
550 | }
|
551 |
|
552 | // check it worked (on unix it doesn't really check the underlying
|
553 | // tty but rather check that the code is consistent)
|
554 | assert!(is_raw_mode_enabled().unwrap());
|
555 |
|
556 | // enable it again, this should not change anything
|
557 | enable_raw_mode().unwrap();
|
558 |
|
559 | // check we're still in raw mode
|
560 | assert!(is_raw_mode_enabled().unwrap());
|
561 |
|
562 | // now let's disable it
|
563 | disable_raw_mode().unwrap();
|
564 |
|
565 | // check we're back to normal mode
|
566 | assert!(!is_raw_mode_enabled().unwrap());
|
567 | }
|
568 | }
|
569 | |