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