1use crate::outputs::ExecutionView;
2use alacritty_terminal::{term::Config, vte::ansi::Processor};
3use gpui::{canvas, size, AnyElement};
4use std::mem;
5use terminal::ZedListener;
6use terminal_view::terminal_element::TerminalElement;
7use ui::{prelude::*, IntoElement, ViewContext};
8
9/// Implements the most basic of terminal output for use by Jupyter outputs
10/// whether:
11///
12/// * stdout
13/// * stderr
14/// * text/plain
15/// * traceback from an error output
16///
17pub struct TerminalOutput {
18 parser: Processor,
19 handler: alacritty_terminal::Term<ZedListener>,
20}
21
22pub fn terminal_size(cx: &mut WindowContext) -> terminal::TerminalSize {
23 let text_style = cx.text_style();
24 let text_system = cx.text_system();
25
26 let line_height = cx.line_height();
27
28 let font_pixels = text_style.font_size.to_pixels(cx.rem_size());
29 let font_id = text_system.resolve_font(&text_style.font());
30
31 let cell_width = text_system
32 .advance(font_id, font_pixels, 'w')
33 .unwrap()
34 .width;
35
36 let num_lines = 200;
37 let columns = 120;
38
39 // Reversed math from terminal::TerminalSize to get pixel width according to terminal width
40 let width = columns as f32 * cell_width;
41 let height = num_lines as f32 * cx.line_height();
42
43 terminal::TerminalSize {
44 cell_width,
45 line_height,
46 size: size(width, height),
47 }
48}
49
50impl TerminalOutput {
51 pub fn new(cx: &mut WindowContext) -> Self {
52 let (events_tx, events_rx) = futures::channel::mpsc::unbounded();
53 let term = alacritty_terminal::Term::new(
54 Config::default(),
55 &terminal_size(cx),
56 terminal::ZedListener(events_tx.clone()),
57 );
58
59 mem::forget(events_rx);
60 Self {
61 parser: Processor::new(),
62 handler: term,
63 }
64 }
65
66 pub fn from(text: &str, cx: &mut WindowContext) -> Self {
67 let mut output = Self::new(cx);
68 output.append_text(text);
69 output
70 }
71
72 pub fn append_text(&mut self, text: &str) {
73 for byte in text.as_bytes() {
74 if *byte == b'\n' {
75 // Dirty (?) hack to move the cursor down
76 self.parser.advance(&mut self.handler, b'\r');
77 self.parser.advance(&mut self.handler, b'\n');
78 } else {
79 self.parser.advance(&mut self.handler, *byte);
80 }
81
82 // self.parser.advance(&mut self.handler, *byte);
83 }
84 }
85
86 pub fn render(&self, cx: &ViewContext<ExecutionView>) -> AnyElement {
87 let text_style = cx.text_style();
88 let text_system = cx.text_system();
89
90 let grid = self
91 .handler
92 .renderable_content()
93 .display_iter
94 .map(|ic| terminal::IndexedCell {
95 point: ic.point,
96 cell: ic.cell.clone(),
97 });
98 let (cells, rects) = TerminalElement::layout_grid(grid, &text_style, text_system, None, cx);
99
100 // lines are 0-indexed, so we must add 1 to get the number of lines
101 let num_lines = cells.iter().map(|c| c.point.line).max().unwrap_or(0) + 1;
102 let height = num_lines as f32 * cx.line_height();
103
104 let line_height = cx.line_height();
105
106 let font_pixels = text_style.font_size.to_pixels(cx.rem_size());
107 let font_id = text_system.resolve_font(&text_style.font());
108
109 let cell_width = text_system
110 .advance(font_id, font_pixels, 'w')
111 .map(|advance| advance.width)
112 .unwrap_or(Pixels(0.0));
113
114 canvas(
115 // prepaint
116 move |_bounds, _| {},
117 // paint
118 move |bounds, _, cx| {
119 for rect in rects {
120 rect.paint(
121 bounds.origin,
122 &terminal::TerminalSize {
123 cell_width,
124 line_height,
125 size: bounds.size,
126 },
127 cx,
128 );
129 }
130
131 for cell in cells {
132 cell.paint(
133 bounds.origin,
134 &terminal::TerminalSize {
135 cell_width,
136 line_height,
137 size: bounds.size,
138 },
139 bounds,
140 cx,
141 );
142 }
143 },
144 )
145 // We must set the height explicitly for the editor block to size itself correctly
146 .h(height)
147 .into_any_element()
148 }
149}