stdio.rs

  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}