1use std::fmt;
8use std::io::{self, Write};
9use std::sync::Arc;
10
11use crate::align::AlignMethod;
12use crate::color::{Color, ColorSystem};
13use crate::segment::Segment;
14use crate::style::Style;
15use crate::text::Text;
16use crate::theme::Theme;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct ConsoleDimensions {
25 pub width: usize,
26 pub height: usize,
27}
28
29impl ConsoleDimensions {
30 pub fn detect() -> Self {
32 if let Some((w, h)) = terminal_size::terminal_size() {
33 Self {
34 width: w.0 as usize,
35 height: h.0 as usize,
36 }
37 } else {
38 Self {
39 width: 80,
40 height: 25,
41 }
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum OverflowMethod {
53 Fold,
55 Crop,
57 Ellipsis,
59 Ignore,
61}
62
63#[derive(Debug, Clone)]
69pub struct ConsoleOptions {
70 pub size: ConsoleDimensions,
72 pub is_terminal: bool,
74 pub encoding: String,
76 pub min_width: usize,
78 pub max_width: usize,
80 pub max_height: usize,
82 pub justify: Option<AlignMethod>,
84 pub overflow: Option<OverflowMethod>,
86 pub no_wrap: bool,
88 pub ascii_only: bool,
90 pub markup: bool,
92 pub highlight: bool,
94 pub height: Option<usize>,
96 pub legacy_windows: bool,
98}
99
100impl Default for ConsoleOptions {
101 fn default() -> Self {
102 Self {
103 size: ConsoleDimensions::detect(),
104 is_terminal: true,
105 encoding: "utf-8".into(),
106 min_width: 1,
107 max_width: 80,
108 max_height: 25,
109 justify: None,
110 overflow: None,
111 no_wrap: false,
112 ascii_only: false,
113 markup: true,
114 highlight: true,
115 height: None,
116 legacy_windows: false,
117 }
118 }
119}
120
121impl ConsoleOptions {
122 pub fn update_width(&self, max_width: usize) -> Self {
124 let mut opts = self.clone();
125 opts.max_width = max_width;
126 opts
127 }
128
129 pub fn update_height(&self, height: usize) -> Self {
131 let mut opts = self.clone();
132 opts.height = Some(height);
133 opts
134 }
135
136 pub fn shrink_width(&self, amount: usize) -> Self {
138 let mut opts = self.clone();
139 opts.max_width = opts.max_width.saturating_sub(amount);
140 opts
141 }
142}
143
144#[derive(Clone)]
153pub enum RenderItem {
154 Segment(Segment),
156 Nested(DynRenderable),
158}
159
160impl fmt::Debug for RenderItem {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 match self {
163 Self::Segment(s) => write!(f, "Segment({})", &s.text),
164 Self::Nested(_) => write!(f, "Nested(...)"),
165 }
166 }
167}
168
169impl From<Segment> for RenderItem {
170 fn from(s: Segment) -> Self { Self::Segment(s) }
171}
172
173impl From<DynRenderable> for RenderItem {
174 fn from(r: DynRenderable) -> Self { Self::Nested(r) }
175}
176
177#[derive(Debug, Clone)]
180pub struct RenderResult {
181 pub lines: Vec<Vec<Segment>>,
183 pub items: Vec<RenderItem>,
186}
187
188impl RenderResult {
189 pub fn new() -> Self {
191 Self { lines: Vec::new(), items: Vec::new() }
192 }
193
194 pub fn from_text(text: &str) -> Self {
198 Self {
199 lines: vec![vec![Segment::new(text)]],
200 items: vec![RenderItem::Segment(Segment::new(text))],
201 }
202 }
203
204 pub fn from_segments(segments: Vec<Segment>) -> Self {
206 let items: Vec<RenderItem> = segments.iter().map(|s| RenderItem::Segment(s.clone())).collect();
207 Self { lines: vec![segments], items }
208 }
209
210 pub fn from_lines(lines: Vec<Vec<Segment>>) -> Self {
212 Self { lines, items: Vec::new() }
213 }
214
215 pub fn from_items(items: Vec<RenderItem>) -> Self {
217 Self { lines: Vec::new(), items }
218 }
219
220 pub fn push_item(&mut self, item: impl Into<RenderItem>) {
222 self.items.push(item.into());
223 }
224
225 pub fn push_renderable(&mut self, r: impl Renderable + Send + Sync + 'static) {
227 self.items.push(RenderItem::Nested(DynRenderable::new(r)));
228 }
229
230 pub fn flatten(&self, options: &ConsoleOptions) -> Vec<Segment> {
233 let mut out: Vec<Segment> = Vec::new();
234 flatten_items(&self.items, options, &mut out);
235 if out.is_empty() {
237 for line in &self.lines {
238 for seg in line {
239 out.push(seg.clone());
240 }
241 }
242 }
243 out
244 }
245
246 pub fn to_ansi(&self) -> String {
248 let mut out = String::new();
249 if !self.items.is_empty() {
251 let flat = self.flatten(&ConsoleOptions::default());
252 for seg in &flat {
253 out.push_str(&seg.to_ansi());
254 }
255 } else {
256 for line in &self.lines {
257 for seg in line {
258 out.push_str(&seg.to_ansi());
259 }
260 }
261 }
262 out
263 }
264}
265
266fn flatten_items(items: &[RenderItem], options: &ConsoleOptions, out: &mut Vec<Segment>) {
268 for item in items {
269 match item {
270 RenderItem::Segment(seg) => out.push(seg.clone()),
271 RenderItem::Nested(renderable) => {
272 let nested = renderable.render(options);
273 flatten_items(&nested.items, options, out);
274 }
275 }
276 }
277}
278
279pub trait Renderable {
283 fn render(&self, options: &ConsoleOptions) -> RenderResult;
288
289 fn measure(&self, _options: &ConsoleOptions) -> Option<crate::measure::Measurement> {
292 None
293 }
294}
295
296impl Renderable for String {
300 fn render(&self, options: &ConsoleOptions) -> RenderResult {
301 self.as_str().render(options)
302 }
303}
304
305impl Renderable for &str {
307 fn render(&self, _options: &ConsoleOptions) -> RenderResult {
308 RenderResult::from_text(self)
309 }
310}
311
312impl Renderable for Text {
314 fn render(&self, _options: &ConsoleOptions) -> RenderResult {
315 let rendered = self.render();
316 let lines: Vec<Vec<Segment>> = rendered
318 .lines()
319 .map(|l| vec![Segment::new(l)])
320 .collect();
321 RenderResult { lines, items: Vec::new() }
322 }
323}
324
325#[derive(Clone)]
330pub struct DynRenderable {
331 inner: Arc<dyn Renderable + Send + Sync>,
332}
333
334impl DynRenderable {
335 pub fn new(r: impl Renderable + Send + Sync + 'static) -> Self {
337 Self { inner: Arc::new(r) }
338 }
339}
340
341impl fmt::Debug for DynRenderable {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 f.debug_struct("DynRenderable").finish()
344 }
345}
346
347impl Renderable for DynRenderable {
349 fn render(&self, options: &ConsoleOptions) -> RenderResult {
350 self.inner.render(options)
351 }
352}
353
354#[derive(Debug, Clone)]
358pub struct Group {
359 pub children: Vec<DynRenderable>,
361}
362
363impl Group {
364 pub fn new() -> Self {
366 Self { children: Vec::new() }
367 }
368
369 pub fn add(&mut self, renderable: impl Renderable + Send + Sync + 'static) {
371 self.children.push(DynRenderable::new(renderable));
372 }
373}
374
375impl Renderable for Group {
377 fn render(&self, options: &ConsoleOptions) -> RenderResult {
378 let mut all_lines: Vec<Vec<Segment>> = Vec::new();
379 for child in &self.children {
380 let result = child.render(options);
381 all_lines.extend(result.lines);
382 }
383 RenderResult { lines: all_lines, items: Vec::new() }
384 }
385}
386
387pub struct Console {
393 pub file: Box<dyn Write + Send>,
395 pub color_system: ColorSystem,
397 pub theme: Theme,
399 pub options: ConsoleOptions,
401 width: Option<usize>,
403 height: Option<usize>,
405 is_terminal: bool,
407 pub quiet: bool,
409 pub soft_wrap: bool,
411}
412
413impl Console {
414 pub fn new() -> Self {
416 let is_terminal = atty::is(atty::Stream::Stdout);
417 let color_system = detect_color_system();
418
419 let size = ConsoleDimensions::detect();
420
421 Self {
422 file: Box::new(io::stdout()) as Box<dyn Write + Send>,
423 color_system,
424 theme: crate::theme::default_theme(),
425 options: ConsoleOptions {
426 size,
427 is_terminal,
428 max_width: size.width,
429 max_height: size.height,
430 ..Default::default()
431 },
432 width: None,
433 height: None,
434 is_terminal,
435 quiet: false,
436 soft_wrap: false,
437 }
438 }
439
440 pub fn with_file(file: Box<dyn Write + Send>) -> Self {
442 let _is_terminal = false;
443 Self {
444 file,
445 color_system: ColorSystem::Standard,
446 theme: crate::theme::default_theme(),
447 options: ConsoleOptions {
448 size: ConsoleDimensions { width: 80, height: 25 },
449 is_terminal: false,
450 max_width: 80,
451 max_height: 25,
452 ..Default::default()
453 },
454 width: None,
455 height: None,
456 is_terminal: false,
457 quiet: false,
458 soft_wrap: false,
459 }
460 }
461
462 pub fn set_width(&mut self, width: usize) {
464 self.width = Some(width);
465 self.options.max_width = width;
466 }
467
468 pub fn set_height(&mut self, height: usize) {
470 self.height = Some(height);
471 self.options.max_height = height;
472 }
473
474 pub fn width(&self) -> usize {
476 self.width.unwrap_or(self.options.size.width)
477 }
478
479 pub fn height(&self) -> usize {
481 self.height.unwrap_or(self.options.size.height)
482 }
483
484 pub fn render_lines(
486 &self,
487 renderable: &dyn Renderable,
488 options: &ConsoleOptions,
489 style: Option<&Style>,
490 _pad: bool,
491 ) -> Vec<Vec<Segment>> {
492 let result = renderable.render(options);
493
494 if let Some(st) = style {
495 result
496 .lines
497 .into_iter()
498 .map(|line| {
499 line.into_iter()
500 .map(|seg| {
501 let new_style = if let Some(ref s) = seg.style {
502 s.combine(st)
503 } else {
504 st.clone()
505 };
506 Segment::styled(seg.text, new_style)
507 })
508 .collect()
509 })
510 .collect()
511 } else {
512 result.lines
513 }
514 }
515
516 pub fn get_style(&self, name: &str, default: &str) -> Option<Style> {
518 self.theme
519 .get(name)
520 .cloned()
521 .or_else(|| {
522 if !default.is_empty() {
523 Some(Style::from_str(default))
524 } else {
525 None
526 }
527 })
528 }
529
530 pub fn render_str(&self, text: &str, style: &str) -> Text {
532 let st = self.get_style(style, "");
533 let mut t = Text::new(text);
534 if let Some(s) = st {
535 t = t.style(s);
536 }
537 t
538 }
539
540 pub fn print(&mut self, objects: &[&dyn Renderable], sep: &str, end: &str) {
547 if self.quiet { return; }
548 let mut first = true;
549 for obj in objects {
550 if !first {
551 let _ = write!(self.file, "{sep}");
552 }
553 first = false;
554 let result = obj.render(&self.options);
555 let ansi = result.to_ansi();
556 let _ = write!(self.file, "{ansi}");
557 }
558 let _ = write!(self.file, "{end}");
559 let _ = self.file.flush();
560 }
561
562 pub fn println(&mut self, renderable: &dyn Renderable) {
564 if self.quiet { return; }
565 let result = renderable.render(&self.options);
566 let ansi = result.to_ansi();
567 let _ = writeln!(self.file, "{ansi}");
568 let _ = self.file.flush();
569 }
570
571 pub fn print_str(&mut self, text: &str) {
574 if self.quiet { return; }
575 let ansi = if self.options.markup {
576 let parsed = crate::markup::render(text);
577 parsed.render()
578 } else {
579 text.to_string()
580 };
581 let _ = write!(self.file, "{ansi}");
582 let _ = self.file.flush();
583 }
584
585 pub fn print_json(&mut self, data: &serde_json::Value) {
587 if self.quiet { return; }
588 let formatted = crate::json::render_json(data);
589 let result = formatted.render(&self.options);
590 let ansi = result.to_ansi();
591 let _ = writeln!(self.file, "{ansi}");
592 let _ = self.file.flush();
593 }
594
595 pub fn clear(&mut self) {
597 if self.quiet { return; }
598 let _ = write!(self.file, "\x1b[2J\x1b[H");
599 let _ = self.file.flush();
600 }
601
602 pub fn show_cursor(&mut self) {
604 let _ = write!(self.file, "\x1b[?25h");
605 let _ = self.file.flush();
606 }
607
608 pub fn hide_cursor(&mut self) {
610 let _ = write!(self.file, "\x1b[?25l");
611 let _ = self.file.flush();
612 }
613
614 pub fn set_window_title(&mut self, title: &str) {
616 let _ = write!(self.file, "\x1b]0;{title}\x07");
617 let _ = self.file.flush();
618 }
619
620 pub fn color_ansi(&self, color: &Color) -> String {
622 let downgraded = color.downgrade(self.color_system);
623 downgraded.to_string()
624 }
625
626 pub fn render(&self, renderable: &dyn Renderable, options: &ConsoleOptions) -> Vec<Segment> {
633 let result = renderable.render(options);
634 result.flatten(options)
635 }
636
637 pub fn measure(&self, renderable: &dyn Renderable, options: &ConsoleOptions) -> crate::measure::Measurement {
640 if let Some(m) = renderable.measure(options) {
641 return m;
642 }
643 let segments = self.render(renderable, options);
644 let max_w = segments.iter()
645 .map(|s| s.cell_length())
646 .max()
647 .unwrap_or(0);
648 crate::measure::Measurement::new(max_w, options.max_width)
649 }
650
651 pub fn rule(
656 &mut self,
657 title: impl Into<String>,
658 characters: Option<&str>,
659 style: Option<Style>,
660 align: Option<AlignMethod>,
661 ) {
662 if self.quiet { return; }
663 let mut rule = crate::rule::Rule::new().title(title);
664 if let Some(chars) = characters { rule = rule.characters(chars); }
665 if let Some(st) = style { rule = rule.style(st); }
666 if let Some(a) = align { rule = rule.align(a); }
667 let result = rule.render(&self.options);
668 let ansi = result.to_ansi();
669 let _ = write!(self.file, "{ansi}");
670 let _ = self.file.flush();
671 }
672
673 pub fn bell(&mut self) {
675 if self.quiet { return; }
676 let _ = write!(self.file, "\x07");
677 let _ = self.file.flush();
678 }
679
680 pub fn line(&mut self, count: usize) {
682 if self.quiet { return; }
683 for _ in 0..count {
684 let _ = writeln!(self.file);
685 }
686 let _ = self.file.flush();
687 }
688
689 pub fn log(&mut self, objects: &[&dyn Renderable]) {
691 if self.quiet { return; }
692 let now = chrono::Local::now();
693 let time_str = format!("[{}]", now.format("%H:%M:%S"));
694 let _ = write!(self.file, "{} ", Style::new().dim(true).to_ansi());
695 let _ = write!(self.file, "{time_str} ");
696 let _ = write!(self.file, "{}", Style::new().reset_ansi());
697 self.print(objects, " ", "\n");
698 }
699
700 pub fn push_theme(&mut self, theme: Theme) {
704 let mut new_theme = theme.clone();
705 new_theme.inherit = Some(Box::new(self.theme.clone()));
706 self.theme = new_theme;
707 }
708
709 pub fn pop_theme(&mut self) {
711 if let Some(ref inherit) = self.theme.inherit {
712 self.theme = *inherit.clone();
713 }
714 }
715
716 pub fn export_html(&self, renderable: &dyn Renderable) -> String {
722 let result = renderable.render(&self.options);
723 let ansi = result.to_ansi();
724 crate::export::export_html(&crate::export::ExportHtmlOptions {
725 code: crate::export::strip_ansi_escapes(&ansi),
726 ..Default::default()
727 })
728 }
729
730 pub fn save_html(&self, path: impl AsRef<std::path::Path>, renderable: &dyn Renderable) -> std::io::Result<()> {
732 let html = self.export_html(renderable);
733 crate::export::save_html(path, &crate::export::ExportHtmlOptions {
734 code: html,
735 ..Default::default()
736 })
737 }
738
739 pub fn export_svg(&self, renderable: &dyn Renderable) -> String {
741 let result = renderable.render(&self.options);
742 let ansi = result.to_ansi();
743 crate::export::export_svg(&crate::export::ExportSvgOptions {
744 code: crate::export::strip_ansi_escapes(&ansi),
745 ..Default::default()
746 })
747 }
748
749 pub fn save_svg(&self, path: impl AsRef<std::path::Path>, renderable: &dyn Renderable) -> std::io::Result<()> {
751 let svg = self.export_svg(renderable);
752 crate::export::save_svg(path, &crate::export::ExportSvgOptions {
753 code: svg,
754 ..Default::default()
755 })
756 }
757
758 pub fn export_text(&self, renderable: &dyn Renderable) -> String {
760 let result = renderable.render(&self.options);
761 let ansi = result.to_ansi();
762 crate::export::export_text(&crate::export::ExportTextOptions {
763 text: ansi,
764 strip_ansi: true,
765 })
766 }
767
768 pub fn save_text(&self, path: impl AsRef<std::path::Path>, renderable: &dyn Renderable) -> std::io::Result<()> {
770 let text = self.export_text(renderable);
771 crate::export::save_text(path, &crate::export::ExportTextOptions {
772 text,
773 strip_ansi: false,
774 })
775 }
776
777 pub fn begin_capture(&mut self) {
782 }
785
786 pub fn end_capture(&mut self) -> String {
788 String::new() }
790
791 pub fn set_quiet(&mut self, quiet: bool) {
795 self.quiet = quiet;
796 }
797
798 pub fn quiet(mut self, quiet: bool) -> Self {
800 self.quiet = quiet;
801 self
802 }
803
804 pub fn set_soft_wrap(&mut self, soft_wrap: bool) {
806 self.soft_wrap = soft_wrap;
807 }
808
809 pub fn soft_wrap(mut self, soft_wrap: bool) -> Self {
811 self.soft_wrap = soft_wrap;
812 self
813 }
814
815 pub fn input(&mut self, prompt: &str, password: bool) -> String {
823 let _ = write!(self.file, "{prompt}");
824 let _ = self.file.flush();
825
826 if password {
827 self.read_password()
828 } else {
829 let mut input = String::new();
830 let _ = io::stdin().read_line(&mut input);
831 input.trim().to_string()
832 }
833 }
834
835 fn read_password(&mut self) -> String {
837 use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
838 use std::io::Read;
839
840 match enable_raw_mode() {
841 Ok(()) => {
842 let stdin = io::stdin();
843 let mut handle = stdin.lock();
844 let mut buf = [0u8; 1];
845 let mut password = String::new();
846
847 loop {
848 match handle.read_exact(&mut buf) {
849 Ok(()) => match buf[0] {
850 b'\r' | b'\n' => {
851 let _ = writeln!(self.file);
852 let _ = self.file.flush();
853 break;
854 }
855 b'\x03' => {
856 let _ = writeln!(self.file);
858 let _ = self.file.flush();
859 break;
860 }
861 b'\x7f' | b'\x08' => {
862 password.pop();
864 }
865 c => {
866 password.push(c as char);
867 let _ = write!(self.file, "*");
868 let _ = self.file.flush();
869 }
870 },
871 Err(_) => break,
872 }
873 }
874 let _ = disable_raw_mode();
875 password
876 }
877 Err(_) => {
878 let mut input = String::new();
880 let _ = io::stdin().read_line(&mut input);
881 input.trim().to_string()
882 }
883 }
884 }
885
886 pub fn screen(&mut self) -> crate::screen::ScreenContext {
892 let mut ctx = crate::screen::ScreenContext::new();
893 ctx.enter();
894 ctx
895 }
896
897 pub fn set_alt_screen(&mut self, enable: bool) {
900 if enable {
901 let _ = write!(self.file, "\x1b[?1049h");
902 } else {
903 let _ = write!(self.file, "\x1b[?1049l");
904 }
905 let _ = self.file.flush();
906 }
907
908 pub fn is_terminal(&self) -> bool {
910 self.is_terminal
911 }
912
913 pub fn set_size(&mut self, width: usize, height: usize) {
915 self.width = Some(width);
916 self.height = Some(height);
917 self.options.max_width = width;
918 self.options.max_height = height;
919 self.options.size = crate::console::ConsoleDimensions { width, height };
920 }
921
922 pub fn on_broken_pipe(&self) {
930 }
934}
935
936impl Default for Console {
937 fn default() -> Self {
938 Self::new()
939 }
940}
941
942impl fmt::Debug for Console {
943 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
944 f.debug_struct("Console")
945 .field("color_system", &self.color_system)
946 .field("width", &self.width())
947 .field("height", &self.height())
948 .field("is_terminal", &self.is_terminal)
949 .finish()
950 }
951}
952
953fn detect_color_system() -> ColorSystem {
962 if let Ok(val) = std::env::var("COLORTERM") {
964 if val == "truecolor" || val == "24bit" {
965 return ColorSystem::TrueColor;
966 }
967 }
968 if let Ok(term) = std::env::var("TERM") {
969 if term.contains("256color") {
970 return ColorSystem::EightBit;
971 }
972 if term == "xterm-kitty" {
973 return ColorSystem::TrueColor;
974 }
975 }
976 if std::env::var("NO_COLOR").is_ok() {
978 return ColorSystem::Standard;
979 }
980 if atty::is(atty::Stream::Stdout) {
982 ColorSystem::TrueColor
983 } else {
984 ColorSystem::Standard
985 }
986}
987
988use std::sync::Mutex;
993use once_cell::sync::Lazy;
994
995static GLOBAL_CONSOLE: Lazy<Mutex<Console>> = Lazy::new(|| {
996 Mutex::new(Console::new())
997});
998
999pub fn get_console() -> std::sync::MutexGuard<'static, Console> {
1001 GLOBAL_CONSOLE.lock().unwrap()
1002}
1003
1004pub fn print_objects(objects: &[&dyn Renderable]) {
1010 let mut console = GLOBAL_CONSOLE.lock().unwrap();
1011 console.print(objects, " ", "\n");
1012}
1013
1014pub fn print_str(text: &str) {
1016 let mut console = GLOBAL_CONSOLE.lock().unwrap();
1017 console.print_str(text);
1018}
1019
1020pub fn print_json_val(data: &serde_json::Value) {
1022 let mut console = GLOBAL_CONSOLE.lock().unwrap();
1023 console.print_json(data);
1024}
1025
1026#[cfg(test)]
1027mod tests {
1028 use super::*;
1029
1030 #[test]
1031 fn test_render_result_from_text() {
1032 let r = RenderResult::from_text("hello");
1033 assert_eq!(r.lines.len(), 1);
1034 assert_eq!(r.lines[0][0].text, "hello");
1035 }
1036
1037 #[test]
1038 fn test_console_options_default() {
1039 let opts = ConsoleOptions::default();
1040 assert!(opts.markup);
1041 }
1042
1043 #[test]
1044 fn test_console_quiet_default() {
1045 let console = Console::new();
1046 assert!(!console.quiet);
1047 }
1048
1049 #[test]
1050 fn test_console_quiet_setter() {
1051 let mut console = Console::new();
1052 console.set_quiet(true);
1053 assert!(console.quiet);
1054 }
1055
1056 #[test]
1057 fn test_console_quiet_builder() {
1058 let console = Console::new().quiet(true);
1059 assert!(console.quiet);
1060 }
1061
1062 #[test]
1063 fn test_console_quiet_suppresses_print() {
1064 let mut console = Console::new();
1065 console.quiet = true;
1066 console.print(&[], " ", "\n");
1068 console.println(&"test");
1069 console.print_str("test");
1070 }
1071
1072 #[test]
1073 fn test_console_soft_wrap_default() {
1074 let console = Console::new();
1075 assert!(!console.soft_wrap);
1076 }
1077
1078 #[test]
1079 fn test_console_soft_wrap_setter() {
1080 let mut console = Console::new();
1081 console.set_soft_wrap(true);
1082 assert!(console.soft_wrap);
1083 }
1084
1085 #[test]
1086 fn test_console_soft_wrap_builder() {
1087 let console = Console::new().soft_wrap(true);
1088 assert!(console.soft_wrap);
1089 }
1090
1091 #[test]
1092 fn test_console_is_terminal() {
1093 let console = Console::new();
1094 let detected = console.is_terminal();
1096 assert_eq!(detected, atty::is(atty::Stream::Stdout));
1097 }
1098
1099 #[test]
1100 fn test_console_set_size() {
1101 let mut console = Console::new();
1102 console.set_size(120, 30);
1103 assert_eq!(console.width(), 120);
1104 assert_eq!(console.height(), 30);
1105 assert_eq!(console.options.max_width, 120);
1106 assert_eq!(console.options.max_height, 30);
1107 }
1108
1109 #[test]
1110 fn test_console_set_alt_screen() {
1111 let mut console = Console::new();
1112 console.set_alt_screen(true);
1114 console.set_alt_screen(false);
1115 }
1116
1117 #[test]
1118 fn test_console_on_broken_pipe() {
1119 let console = Console::new();
1120 console.on_broken_pipe(); }
1122
1123 #[test]
1124 fn test_console_input_normal() {
1125 let _console = Console::new();
1128 }
1130
1131 #[test]
1132 fn test_console_debug() {
1133 let console = Console::new();
1134 let debug = format!("{:?}", console);
1135 assert!(debug.contains("Console"));
1136 }
1137
1138 #[test]
1139 fn test_console_with_file_has_no_terminal() {
1140 let console = Console::with_file(Box::new(std::io::sink()));
1141 assert!(!console.is_terminal());
1142 }
1143}