blob: d2fcd2bf198ac10290772b8aa1d725466edaefec [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::colors::ColorScheme;
use anyhow::Error;
use carnelian::color::Color;
use carnelian::Size;
use pty::ServerPty;
use std::cell::RefCell;
use std::fs::File;
use std::io::Write;
use std::rc::Rc;
use term_model::clipboard::Clipboard;
use term_model::config::Config;
use term_model::event::EventListener;
use term_model::grid::Scroll;
use term_model::term::color::Rgb;
use term_model::term::{SizeInfo, TermMode};
use term_model::Term;
/// Empty type for term model config.
#[derive(Default)]
pub struct TermConfig;
pub type TerminalConfig = Config<TermConfig>;
fn make_term_color(color: &Color) -> Rgb {
Rgb { r: color.r, g: color.g, b: color.b }
}
impl From<ColorScheme> for TerminalConfig {
fn from(color_scheme: ColorScheme) -> Self {
let mut config = Self::default();
config.colors.primary.background = make_term_color(&color_scheme.back);
config.colors.primary.foreground = make_term_color(&color_scheme.front);
config
}
}
/// Wrapper around a term model instance and its associated PTY fd.
pub struct Terminal<T> {
term: Rc<RefCell<Term<T>>>,
title: String,
pty: Option<ServerPty>,
/// Lazily initialized if `pty` is set.
pty_fd: Option<File>,
}
impl<T> Terminal<T> {
pub fn new(
event_listener: T,
title: String,
color_scheme: ColorScheme,
scrollback_rows: u32,
pty: Option<ServerPty>,
) -> Self {
// Initial size info used before we know what the real size is.
let cell_size = Size::new(8.0, 16.0);
let size_info = SizeInfo {
width: cell_size.width * 80.0,
height: cell_size.height * 24.0,
cell_width: cell_size.width,
cell_height: cell_size.height,
padding_x: 0.0,
padding_y: 0.0,
dpr: 1.0,
};
let mut config: TerminalConfig = color_scheme.into();
config.scrolling.set_history(scrollback_rows);
let term =
Rc::new(RefCell::new(Term::new(&config, &size_info, Clipboard::new(), event_listener)));
Self { term: Rc::clone(&term), title, pty, pty_fd: None }
}
#[cfg(test)]
fn new_for_test(event_listener: T, pty: ServerPty) -> Self {
Self::new(event_listener, String::new(), ColorScheme::default(), 1024, Some(pty))
}
pub fn clone_term(&self) -> Rc<RefCell<Term<T>>> {
Rc::clone(&self.term)
}
pub fn try_clone(&self) -> Result<Self, Error> {
let term = self.clone_term();
let title = self.title.clone();
let pty = self.pty.clone();
let pty_fd = None;
Ok(Self { term, title, pty, pty_fd })
}
pub fn resize(&mut self, size_info: &SizeInfo) {
let mut term = self.term.borrow_mut();
term.resize(size_info);
}
pub fn title(&self) -> &str {
self.title.as_str()
}
pub fn pty(&self) -> Option<&ServerPty> {
self.pty.as_ref()
}
pub fn scroll(&mut self, scroll: Scroll)
where
T: EventListener,
{
let mut term = self.term.borrow_mut();
term.scroll_display(scroll);
}
pub fn history_size(&self) -> usize {
let term = self.term.borrow();
term.grid().history_size()
}
pub fn display_offset(&self) -> usize {
let term = self.term.borrow();
term.grid().display_offset()
}
pub fn mode(&self) -> TermMode {
let term = self.term.borrow();
*term.mode()
}
fn file(&mut self) -> Result<Option<&mut File>, std::io::Error> {
if self.pty_fd.is_none() {
if let Some(pty) = &self.pty {
let pty_fd = pty.try_clone_fd().map_err(|e| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, format!("{:?}", e))
})?;
self.pty_fd = Some(pty_fd);
}
}
Ok(self.pty_fd.as_mut())
}
}
impl<T> Write for Terminal<T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
let fd = self.file()?;
if let Some(fd) = fd {
fd.write(buf)
} else {
Ok(buf.len())
}
}
fn flush(&mut self) -> Result<(), std::io::Error> {
let fd = self.file()?;
if let Some(fd) = fd {
fd.flush()
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_async as fasync;
use term_model::event::Event;
#[derive(Default)]
struct TestListener;
impl EventListener for TestListener {
fn send_event(&self, _event: Event) {}
}
#[fasync::run_singlethreaded(test)]
async fn can_create_terminal() -> Result<(), Error> {
let pty = ServerPty::new()?;
let _ = Terminal::new_for_test(TestListener::default(), pty);
Ok(())
}
}