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 {
31 if let Some((w, h)) = terminal_size::terminal_size() {
32 Self {
33 width: w.0 as usize,
34 height: h.0 as usize,
35 }
36 } else {
37 Self {
38 width: 80,
39 height: 25,
40 }
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub enum OverflowMethod {
52 Fold,
54 Crop,
56 Ellipsis,
58 Ignore,
60}
61
62#[derive(Debug, Clone)]
68pub struct ConsoleOptions {
69 pub size: ConsoleDimensions,
71 pub is_terminal: bool,
73 pub encoding: String,
75 pub min_width: usize,
77 pub max_width: usize,
79 pub max_height: usize,
81 pub justify: Option<AlignMethod>,
83 pub overflow: Option<OverflowMethod>,
85 pub no_wrap: bool,
87 pub ascii_only: bool,
89 pub markup: bool,
91 pub highlight: bool,
93 pub height: Option<usize>,
95 pub legacy_windows: bool,
97}
98
99impl Default for ConsoleOptions {
100 fn default() -> Self {
101 Self {
102 size: ConsoleDimensions::detect(),
103 is_terminal: true,
104 encoding: "utf-8".into(),
105 min_width: 1,
106 max_width: 80,
107 max_height: 25,
108 justify: None,
109 overflow: None,
110 no_wrap: false,
111 ascii_only: false,
112 markup: true,
113 highlight: true,
114 height: None,
115 legacy_windows: false,
116 }
117 }
118}
119
120impl ConsoleOptions {
121 pub fn update_width(&self, max_width: usize) -> Self {
123 let mut opts = self.clone();
124 opts.max_width = max_width;
125 opts
126 }
127
128 pub fn update_height(&self, height: usize) -> Self {
130 let mut opts = self.clone();
131 opts.height = Some(height);
132 opts
133 }
134
135 pub fn shrink_width(&self, amount: usize) -> Self {
137 let mut opts = self.clone();
138 opts.max_width = opts.max_width.saturating_sub(amount);
139 opts
140 }
141}
142
143#[derive(Clone)]
152pub enum RenderItem {
153 Segment(Segment),
154 Nested(DynRenderable),
155}
156
157impl fmt::Debug for RenderItem {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 Self::Segment(s) => write!(f, "Segment({})", &s.text),
161 Self::Nested(_) => write!(f, "Nested(...)"),
162 }
163 }
164}
165
166impl From<Segment> for RenderItem {
167 fn from(s: Segment) -> Self { Self::Segment(s) }
168}
169
170impl From<DynRenderable> for RenderItem {
171 fn from(r: DynRenderable) -> Self { Self::Nested(r) }
172}
173
174#[derive(Debug, Clone)]
177pub struct RenderResult {
178 pub lines: Vec<Vec<Segment>>,
180 pub items: Vec<RenderItem>,
183}
184
185impl RenderResult {
186 pub fn new() -> Self {
187 Self { lines: Vec::new(), items: Vec::new() }
188 }
189
190 pub fn from_text(text: &str) -> Self {
191 Self {
192 lines: vec![vec![Segment::new(text)]],
193 items: vec![RenderItem::Segment(Segment::new(text))],
194 }
195 }
196
197 pub fn from_segments(segments: Vec<Segment>) -> Self {
198 let items: Vec<RenderItem> = segments.iter().map(|s| RenderItem::Segment(s.clone())).collect();
199 Self { lines: vec![segments], items }
200 }
201
202 pub fn from_lines(lines: Vec<Vec<Segment>>) -> Self {
203 Self { lines, items: Vec::new() }
204 }
205
206 pub fn from_items(items: Vec<RenderItem>) -> Self {
207 Self { lines: Vec::new(), items }
208 }
209
210 pub fn push_item(&mut self, item: impl Into<RenderItem>) {
212 self.items.push(item.into());
213 }
214
215 pub fn push_renderable(&mut self, r: impl Renderable + Send + Sync + 'static) {
217 self.items.push(RenderItem::Nested(DynRenderable::new(r)));
218 }
219
220 pub fn flatten(&self, options: &ConsoleOptions) -> Vec<Segment> {
223 let mut out: Vec<Segment> = Vec::new();
224 flatten_items(&self.items, options, &mut out);
225 if out.is_empty() {
227 for line in &self.lines {
228 for seg in line {
229 out.push(seg.clone());
230 }
231 }
232 }
233 out
234 }
235
236 pub fn to_ansi(&self) -> String {
238 let mut out = String::new();
239 if !self.items.is_empty() {
241 let flat = self.flatten(&ConsoleOptions::default());
242 for seg in &flat {
243 out.push_str(&seg.to_ansi());
244 }
245 } else {
246 for line in &self.lines {
247 for seg in line {
248 out.push_str(&seg.to_ansi());
249 }
250 }
251 }
252 out
253 }
254}
255
256fn flatten_items(items: &[RenderItem], options: &ConsoleOptions, out: &mut Vec<Segment>) {
258 for item in items {
259 match item {
260 RenderItem::Segment(seg) => out.push(seg.clone()),
261 RenderItem::Nested(renderable) => {
262 let nested = renderable.render(options);
263 flatten_items(&nested.items, options, out);
264 }
265 }
266 }
267}
268
269pub trait Renderable {
273 fn render(&self, options: &ConsoleOptions) -> RenderResult;
274
275 fn measure(&self, _options: &ConsoleOptions) -> Option<crate::measure::Measurement> {
278 None
279 }
280}
281
282impl Renderable for String {
285 fn render(&self, options: &ConsoleOptions) -> RenderResult {
286 self.as_str().render(options)
287 }
288}
289
290impl Renderable for &str {
291 fn render(&self, _options: &ConsoleOptions) -> RenderResult {
292 RenderResult::from_text(self)
293 }
294}
295
296impl Renderable for Text {
297 fn render(&self, _options: &ConsoleOptions) -> RenderResult {
298 let rendered = self.render();
299 let lines: Vec<Vec<Segment>> = rendered
301 .lines()
302 .map(|l| vec![Segment::new(l)])
303 .collect();
304 RenderResult { lines, items: Vec::new() }
305 }
306}
307
308#[derive(Clone)]
310pub struct DynRenderable {
311 inner: Arc<dyn Renderable + Send + Sync>,
312}
313
314impl DynRenderable {
315 pub fn new(r: impl Renderable + Send + Sync + 'static) -> Self {
316 Self { inner: Arc::new(r) }
317 }
318}
319
320impl fmt::Debug for DynRenderable {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 f.debug_struct("DynRenderable").finish()
323 }
324}
325
326impl Renderable for DynRenderable {
327 fn render(&self, options: &ConsoleOptions) -> RenderResult {
328 self.inner.render(options)
329 }
330}
331
332#[derive(Debug, Clone)]
334pub struct Group {
335 pub children: Vec<DynRenderable>,
336}
337
338impl Group {
339 pub fn new() -> Self {
340 Self { children: Vec::new() }
341 }
342
343 pub fn add(&mut self, renderable: impl Renderable + Send + Sync + 'static) {
344 self.children.push(DynRenderable::new(renderable));
345 }
346}
347
348impl Renderable for Group {
349 fn render(&self, options: &ConsoleOptions) -> RenderResult {
350 let mut all_lines: Vec<Vec<Segment>> = Vec::new();
351 for child in &self.children {
352 let result = child.render(options);
353 all_lines.extend(result.lines);
354 }
355 RenderResult { lines: all_lines, items: Vec::new() }
356 }
357}
358
359pub struct Console {
365 pub file: Box<dyn Write + Send>,
367 pub color_system: ColorSystem,
369 pub theme: Theme,
371 pub options: ConsoleOptions,
373 width: Option<usize>,
375 height: Option<usize>,
377 is_terminal: bool,
379 pub quiet: bool,
381 pub soft_wrap: bool,
383}
384
385impl Console {
386 pub fn new() -> Self {
388 let is_terminal = atty::is(atty::Stream::Stdout);
389 let color_system = detect_color_system();
390
391 let size = ConsoleDimensions::detect();
392
393 Self {
394 file: Box::new(io::stdout()) as Box<dyn Write + Send>,
395 color_system,
396 theme: crate::theme::default_theme(),
397 options: ConsoleOptions {
398 size,
399 is_terminal,
400 max_width: size.width,
401 max_height: size.height,
402 ..Default::default()
403 },
404 width: None,
405 height: None,
406 is_terminal,
407 quiet: false,
408 soft_wrap: false,
409 }
410 }
411
412 pub fn with_file(file: Box<dyn Write + Send>) -> Self {
414 let _is_terminal = false;
415 Self {
416 file,
417 color_system: ColorSystem::Standard,
418 theme: crate::theme::default_theme(),
419 options: ConsoleOptions {
420 size: ConsoleDimensions { width: 80, height: 25 },
421 is_terminal: false,
422 max_width: 80,
423 max_height: 25,
424 ..Default::default()
425 },
426 width: None,
427 height: None,
428 is_terminal: false,
429 quiet: false,
430 soft_wrap: false,
431 }
432 }
433
434 pub fn set_width(&mut self, width: usize) {
436 self.width = Some(width);
437 self.options.max_width = width;
438 }
439
440 pub fn set_height(&mut self, height: usize) {
442 self.height = Some(height);
443 self.options.max_height = height;
444 }
445
446 pub fn width(&self) -> usize {
448 self.width.unwrap_or(self.options.size.width)
449 }
450
451 pub fn height(&self) -> usize {
453 self.height.unwrap_or(self.options.size.height)
454 }
455
456 pub fn render_lines(
458 &self,
459 renderable: &dyn Renderable,
460 options: &ConsoleOptions,
461 style: Option<&Style>,
462 _pad: bool,
463 ) -> Vec<Vec<Segment>> {
464 let result = renderable.render(options);
465
466 if let Some(st) = style {
467 result
468 .lines
469 .into_iter()
470 .map(|line| {
471 line.into_iter()
472 .map(|seg| {
473 let new_style = if let Some(ref s) = seg.style {
474 s.combine(st)
475 } else {
476 st.clone()
477 };
478 Segment::styled(seg.text, new_style)
479 })
480 .collect()
481 })
482 .collect()
483 } else {
484 result.lines
485 }
486 }
487
488 pub fn get_style(&self, name: &str, default: &str) -> Option<Style> {
490 self.theme
491 .get(name)
492 .cloned()
493 .or_else(|| {
494 if !default.is_empty() {
495 Some(Style::from_str(default))
496 } else {
497 None
498 }
499 })
500 }
501
502 pub fn render_str(&self, text: &str, style: &str) -> Text {
504 let st = self.get_style(style, "");
505 let mut t = Text::new(text);
506 if let Some(s) = st {
507 t = t.style(s);
508 }
509 t
510 }
511
512 pub fn print(&mut self, objects: &[&dyn Renderable], sep: &str, end: &str) {
519 if self.quiet { return; }
520 let mut first = true;
521 for obj in objects {
522 if !first {
523 let _ = write!(self.file, "{sep}");
524 }
525 first = false;
526 let result = obj.render(&self.options);
527 let ansi = result.to_ansi();
528 let _ = write!(self.file, "{ansi}");
529 }
530 let _ = write!(self.file, "{end}");
531 let _ = self.file.flush();
532 }
533
534 pub fn println(&mut self, renderable: &dyn Renderable) {
536 if self.quiet { return; }
537 let result = renderable.render(&self.options);
538 let ansi = result.to_ansi();
539 let _ = writeln!(self.file, "{ansi}");
540 let _ = self.file.flush();
541 }
542
543 pub fn print_str(&mut self, text: &str) {
546 if self.quiet { return; }
547 let ansi = if self.options.markup {
548 let parsed = crate::markup::render(text);
549 parsed.render()
550 } else {
551 text.to_string()
552 };
553 let _ = write!(self.file, "{ansi}");
554 let _ = self.file.flush();
555 }
556
557 pub fn print_json(&mut self, data: &serde_json::Value) {
559 if self.quiet { return; }
560 let formatted = crate::json::render_json(data);
561 let result = formatted.render(&self.options);
562 let ansi = result.to_ansi();
563 let _ = writeln!(self.file, "{ansi}");
564 let _ = self.file.flush();
565 }
566
567 pub fn clear(&mut self) {
569 if self.quiet { return; }
570 let _ = write!(self.file, "\x1b[2J\x1b[H");
571 let _ = self.file.flush();
572 }
573
574 pub fn show_cursor(&mut self) {
576 let _ = write!(self.file, "\x1b[?25h");
577 let _ = self.file.flush();
578 }
579
580 pub fn hide_cursor(&mut self) {
582 let _ = write!(self.file, "\x1b[?25l");
583 let _ = self.file.flush();
584 }
585
586 pub fn set_window_title(&mut self, title: &str) {
588 let _ = write!(self.file, "\x1b]0;{title}\x07");
589 let _ = self.file.flush();
590 }
591
592 pub fn color_ansi(&self, color: &Color) -> String {
594 let downgraded = color.downgrade(self.color_system);
595 downgraded.to_string()
596 }
597
598 pub fn render(&self, renderable: &dyn Renderable, options: &ConsoleOptions) -> Vec<Segment> {
605 let result = renderable.render(options);
606 result.flatten(options)
607 }
608
609 pub fn measure(&self, renderable: &dyn Renderable, options: &ConsoleOptions) -> crate::measure::Measurement {
612 if let Some(m) = renderable.measure(options) {
613 return m;
614 }
615 let segments = self.render(renderable, options);
616 let max_w = segments.iter()
617 .map(|s| s.cell_length())
618 .max()
619 .unwrap_or(0);
620 crate::measure::Measurement::new(max_w, options.max_width)
621 }
622
623 pub fn rule(
628 &mut self,
629 title: impl Into<String>,
630 characters: Option<&str>,
631 style: Option<Style>,
632 align: Option<AlignMethod>,
633 ) {
634 if self.quiet { return; }
635 let mut rule = crate::rule::Rule::new().title(title);
636 if let Some(chars) = characters { rule = rule.characters(chars); }
637 if let Some(st) = style { rule = rule.style(st); }
638 if let Some(a) = align { rule = rule.align(a); }
639 let result = rule.render(&self.options);
640 let ansi = result.to_ansi();
641 let _ = write!(self.file, "{ansi}");
642 let _ = self.file.flush();
643 }
644
645 pub fn bell(&mut self) {
647 if self.quiet { return; }
648 let _ = write!(self.file, "\x07");
649 let _ = self.file.flush();
650 }
651
652 pub fn line(&mut self, count: usize) {
654 if self.quiet { return; }
655 for _ in 0..count {
656 let _ = writeln!(self.file);
657 }
658 let _ = self.file.flush();
659 }
660
661 pub fn log(&mut self, objects: &[&dyn Renderable]) {
663 if self.quiet { return; }
664 let now = chrono::Local::now();
665 let time_str = format!("[{}]", now.format("%H:%M:%S"));
666 let _ = write!(self.file, "{} ", Style::new().dim(true).to_ansi());
667 let _ = write!(self.file, "{time_str} ");
668 let _ = write!(self.file, "{}", Style::new().reset_ansi());
669 self.print(objects, " ", "\n");
670 }
671
672 pub fn push_theme(&mut self, theme: Theme) {
676 let mut new_theme = theme.clone();
677 new_theme.inherit = Some(Box::new(self.theme.clone()));
678 self.theme = new_theme;
679 }
680
681 pub fn pop_theme(&mut self) {
683 if let Some(ref inherit) = self.theme.inherit {
684 self.theme = *inherit.clone();
685 }
686 }
687
688 pub fn export_html(&self, renderable: &dyn Renderable) -> String {
694 let result = renderable.render(&self.options);
695 let ansi = result.to_ansi();
696 crate::export::export_html(&crate::export::ExportHtmlOptions {
697 code: crate::export::strip_ansi_escapes(&ansi),
698 ..Default::default()
699 })
700 }
701
702 pub fn save_html(&self, path: impl AsRef<std::path::Path>, renderable: &dyn Renderable) -> std::io::Result<()> {
704 let html = self.export_html(renderable);
705 crate::export::save_html(path, &crate::export::ExportHtmlOptions {
706 code: html,
707 ..Default::default()
708 })
709 }
710
711 pub fn export_svg(&self, renderable: &dyn Renderable) -> String {
713 let result = renderable.render(&self.options);
714 let ansi = result.to_ansi();
715 crate::export::export_svg(&crate::export::ExportSvgOptions {
716 code: crate::export::strip_ansi_escapes(&ansi),
717 ..Default::default()
718 })
719 }
720
721 pub fn save_svg(&self, path: impl AsRef<std::path::Path>, renderable: &dyn Renderable) -> std::io::Result<()> {
723 let svg = self.export_svg(renderable);
724 crate::export::save_svg(path, &crate::export::ExportSvgOptions {
725 code: svg,
726 ..Default::default()
727 })
728 }
729
730 pub fn export_text(&self, renderable: &dyn Renderable) -> String {
732 let result = renderable.render(&self.options);
733 let ansi = result.to_ansi();
734 crate::export::export_text(&crate::export::ExportTextOptions {
735 text: ansi,
736 strip_ansi: true,
737 })
738 }
739
740 pub fn save_text(&self, path: impl AsRef<std::path::Path>, renderable: &dyn Renderable) -> std::io::Result<()> {
742 let text = self.export_text(renderable);
743 crate::export::save_text(path, &crate::export::ExportTextOptions {
744 text,
745 strip_ansi: false,
746 })
747 }
748
749 pub fn begin_capture(&mut self) {
754 }
757
758 pub fn end_capture(&mut self) -> String {
760 String::new() }
762
763 pub fn set_quiet(&mut self, quiet: bool) {
767 self.quiet = quiet;
768 }
769
770 pub fn quiet(mut self, quiet: bool) -> Self {
772 self.quiet = quiet;
773 self
774 }
775
776 pub fn set_soft_wrap(&mut self, soft_wrap: bool) {
778 self.soft_wrap = soft_wrap;
779 }
780
781 pub fn soft_wrap(mut self, soft_wrap: bool) -> Self {
783 self.soft_wrap = soft_wrap;
784 self
785 }
786
787 pub fn input(&mut self, prompt: &str, password: bool) -> String {
795 let _ = write!(self.file, "{prompt}");
796 let _ = self.file.flush();
797
798 if password {
799 self.read_password()
800 } else {
801 let mut input = String::new();
802 let _ = io::stdin().read_line(&mut input);
803 input.trim().to_string()
804 }
805 }
806
807 fn read_password(&mut self) -> String {
809 use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
810 use std::io::Read;
811
812 match enable_raw_mode() {
813 Ok(()) => {
814 let stdin = io::stdin();
815 let mut handle = stdin.lock();
816 let mut buf = [0u8; 1];
817 let mut password = String::new();
818
819 loop {
820 match handle.read_exact(&mut buf) {
821 Ok(()) => match buf[0] {
822 b'\r' | b'\n' => {
823 let _ = writeln!(self.file);
824 let _ = self.file.flush();
825 break;
826 }
827 b'\x03' => {
828 let _ = writeln!(self.file);
830 let _ = self.file.flush();
831 break;
832 }
833 b'\x7f' | b'\x08' => {
834 password.pop();
836 }
837 c => {
838 password.push(c as char);
839 let _ = write!(self.file, "*");
840 let _ = self.file.flush();
841 }
842 },
843 Err(_) => break,
844 }
845 }
846 let _ = disable_raw_mode();
847 password
848 }
849 Err(_) => {
850 let mut input = String::new();
852 let _ = io::stdin().read_line(&mut input);
853 input.trim().to_string()
854 }
855 }
856 }
857
858 pub fn screen(&mut self) -> crate::screen::ScreenContext {
864 let mut ctx = crate::screen::ScreenContext::new();
865 ctx.enter();
866 ctx
867 }
868
869 pub fn set_alt_screen(&mut self, enable: bool) {
872 if enable {
873 let _ = write!(self.file, "\x1b[?1049h");
874 } else {
875 let _ = write!(self.file, "\x1b[?1049l");
876 }
877 let _ = self.file.flush();
878 }
879
880 pub fn is_terminal(&self) -> bool {
882 self.is_terminal
883 }
884
885 pub fn set_size(&mut self, width: usize, height: usize) {
887 self.width = Some(width);
888 self.height = Some(height);
889 self.options.max_width = width;
890 self.options.max_height = height;
891 self.options.size = crate::console::ConsoleDimensions { width, height };
892 }
893
894 pub fn on_broken_pipe(&self) {
902 }
906}
907
908impl Default for Console {
909 fn default() -> Self {
910 Self::new()
911 }
912}
913
914impl fmt::Debug for Console {
915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916 f.debug_struct("Console")
917 .field("color_system", &self.color_system)
918 .field("width", &self.width())
919 .field("height", &self.height())
920 .field("is_terminal", &self.is_terminal)
921 .finish()
922 }
923}
924
925fn detect_color_system() -> ColorSystem {
930 if let Ok(val) = std::env::var("COLORTERM") {
932 if val == "truecolor" || val == "24bit" {
933 return ColorSystem::TrueColor;
934 }
935 }
936 if let Ok(term) = std::env::var("TERM") {
937 if term.contains("256color") {
938 return ColorSystem::EightBit;
939 }
940 if term == "xterm-kitty" {
941 return ColorSystem::TrueColor;
942 }
943 }
944 if std::env::var("NO_COLOR").is_ok() {
946 return ColorSystem::Standard;
947 }
948 if atty::is(atty::Stream::Stdout) {
950 ColorSystem::TrueColor
951 } else {
952 ColorSystem::Standard
953 }
954}
955
956use std::sync::Mutex;
961use once_cell::sync::Lazy;
962
963static GLOBAL_CONSOLE: Lazy<Mutex<Console>> = Lazy::new(|| {
964 Mutex::new(Console::new())
965});
966
967pub fn get_console() -> std::sync::MutexGuard<'static, Console> {
969 GLOBAL_CONSOLE.lock().unwrap()
970}
971
972pub fn print_objects(objects: &[&dyn Renderable]) {
978 let mut console = GLOBAL_CONSOLE.lock().unwrap();
979 console.print(objects, " ", "\n");
980}
981
982pub fn print_str(text: &str) {
984 let mut console = GLOBAL_CONSOLE.lock().unwrap();
985 console.print_str(text);
986}
987
988pub fn print_json_val(data: &serde_json::Value) {
990 let mut console = GLOBAL_CONSOLE.lock().unwrap();
991 console.print_json(data);
992}
993
994#[cfg(test)]
995mod tests {
996 use super::*;
997
998 #[test]
999 fn test_render_result_from_text() {
1000 let r = RenderResult::from_text("hello");
1001 assert_eq!(r.lines.len(), 1);
1002 assert_eq!(r.lines[0][0].text, "hello");
1003 }
1004
1005 #[test]
1006 fn test_console_options_default() {
1007 let opts = ConsoleOptions::default();
1008 assert!(opts.markup);
1009 }
1010
1011 #[test]
1012 fn test_console_quiet_default() {
1013 let console = Console::new();
1014 assert!(!console.quiet);
1015 }
1016
1017 #[test]
1018 fn test_console_quiet_setter() {
1019 let mut console = Console::new();
1020 console.set_quiet(true);
1021 assert!(console.quiet);
1022 }
1023
1024 #[test]
1025 fn test_console_quiet_builder() {
1026 let console = Console::new().quiet(true);
1027 assert!(console.quiet);
1028 }
1029
1030 #[test]
1031 fn test_console_quiet_suppresses_print() {
1032 let mut console = Console::new();
1033 console.quiet = true;
1034 console.print(&[], " ", "\n");
1036 console.println(&"test");
1037 console.print_str("test");
1038 }
1039
1040 #[test]
1041 fn test_console_soft_wrap_default() {
1042 let console = Console::new();
1043 assert!(!console.soft_wrap);
1044 }
1045
1046 #[test]
1047 fn test_console_soft_wrap_setter() {
1048 let mut console = Console::new();
1049 console.set_soft_wrap(true);
1050 assert!(console.soft_wrap);
1051 }
1052
1053 #[test]
1054 fn test_console_soft_wrap_builder() {
1055 let console = Console::new().soft_wrap(true);
1056 assert!(console.soft_wrap);
1057 }
1058
1059 #[test]
1060 fn test_console_is_terminal() {
1061 let console = Console::new();
1062 let detected = console.is_terminal();
1064 assert_eq!(detected, atty::is(atty::Stream::Stdout));
1065 }
1066
1067 #[test]
1068 fn test_console_set_size() {
1069 let mut console = Console::new();
1070 console.set_size(120, 30);
1071 assert_eq!(console.width(), 120);
1072 assert_eq!(console.height(), 30);
1073 assert_eq!(console.options.max_width, 120);
1074 assert_eq!(console.options.max_height, 30);
1075 }
1076
1077 #[test]
1078 fn test_console_set_alt_screen() {
1079 let mut console = Console::new();
1080 console.set_alt_screen(true);
1082 console.set_alt_screen(false);
1083 }
1084
1085 #[test]
1086 fn test_console_on_broken_pipe() {
1087 let console = Console::new();
1088 console.on_broken_pipe(); }
1090
1091 #[test]
1092 fn test_console_input_normal() {
1093 let _console = Console::new();
1096 }
1098
1099 #[test]
1100 fn test_console_debug() {
1101 let console = Console::new();
1102 let debug = format!("{:?}", console);
1103 assert!(debug.contains("Console"));
1104 }
1105
1106 #[test]
1107 fn test_console_with_file_has_no_terminal() {
1108 let console = Console::with_file(Box::new(std::io::sink()));
1109 assert!(!console.is_terminal());
1110 }
1111}