use std::io;
use std::sync::{Arc, Mutex};
use gpui_kit::base::async_util;
use crate::terminal::core::{Core, Effects};
use crate::terminal::engine::{InputRejected, TerminalSnapshot};
use crate::terminal::frame::{CellSelection, CursorShape, TerminalColors, Viewport};
use crate::terminal::input::TerminalInput;
use crate::terminal::options::EngineOptions;
#[derive(Clone)]
pub struct FrameSink {
latest: Arc<Mutex<Option<Arc<TerminalSnapshot>>>>,
wake: async_util::Sender<()>,
}
impl std::fmt::Debug for FrameSink {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FrameSink").finish_non_exhaustive()
}
}
impl FrameSink {
#[must_use]
pub fn new() -> (Self, async_util::Receiver<()>) {
let (wake, rx) = async_util::unbounded();
(
Self {
latest: Arc::new(Mutex::new(None)),
wake,
},
rx,
)
}
pub fn publish(&self, snapshot: Arc<TerminalSnapshot>) {
*self
.latest
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(snapshot);
let _ = self.wake.try_send(());
}
pub fn take(&self) -> Option<Arc<TerminalSnapshot>> {
self.latest
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct StartOptions {
pub(crate) viewport: Viewport,
pub(crate) colors: TerminalColors,
pub(crate) visible: bool,
pub(crate) cursor: Option<(CursorShape, bool)>,
}
impl Default for StartOptions {
fn default() -> Self {
Self {
viewport: Viewport::default(),
colors: TerminalColors::default(),
visible: true,
cursor: None,
}
}
}
impl StartOptions {
#[must_use]
pub fn with_viewport(mut self, viewport: Viewport) -> Self {
self.viewport = viewport;
self
}
#[must_use]
pub fn with_colors(mut self, colors: TerminalColors) -> Self {
self.colors = colors;
self
}
#[must_use]
pub fn with_visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
#[must_use]
pub fn with_cursor(mut self, shape: CursorShape, blinking: bool) -> Self {
self.cursor = Some((shape, blinking));
self
}
pub fn viewport(&self) -> Viewport {
self.viewport
}
pub fn colors(&self) -> &TerminalColors {
&self.colors
}
pub fn visible(&self) -> bool {
self.visible
}
pub fn cursor(&self) -> Option<(CursorShape, bool)> {
self.cursor
}
}
pub trait FrameSource: 'static {
fn start(
self: Box<Self>,
sink: FrameSink,
options: StartOptions,
) -> io::Result<Box<dyn FrameHandle>>;
}
pub trait FrameHandle: 'static {
fn input(&self, input: TerminalInput) -> Result<(), InputRejected>;
fn input_backlog(&self) -> usize {
0
}
fn resize(&self, viewport: Viewport);
fn set_visible(&self, visible: bool);
fn request_frame(&self);
fn set_colors(&self, colors: TerminalColors) {
let _ = colors;
}
fn set_cursor(&self, shape: CursorShape, blinking: bool) {
let _ = (shape, blinking);
}
fn copy(&self, selection: CellSelection) -> Option<async_util::Receiver<io::Result<String>>> {
let _ = selection;
None
}
fn close(&self);
}
#[derive(Debug)]
pub struct FixtureSource {
bytes: Vec<u8>,
options: EngineOptions,
inputs: Arc<Mutex<Vec<TerminalInput>>>,
}
impl FixtureSource {
#[must_use]
pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
Self {
bytes: bytes.into(),
options: EngineOptions::default(),
inputs: Arc::new(Mutex::new(Vec::new())),
}
}
#[must_use]
pub fn with_options(mut self, options: EngineOptions) -> Self {
self.options = options;
self
}
#[must_use]
pub fn inputs(&self) -> Arc<Mutex<Vec<TerminalInput>>> {
self.inputs.clone()
}
}
struct FixtureHandle {
bytes: Vec<u8>,
options: EngineOptions,
colors: Mutex<TerminalColors>,
cursor: Mutex<Option<(CursorShape, bool)>>,
inputs: Arc<Mutex<Vec<TerminalInput>>>,
sink: FrameSink,
}
impl FixtureHandle {
fn publish(&self, viewport: Viewport) -> io::Result<()> {
let colors = self
.colors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let cursor = *self
.cursor
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut core = Core::new(viewport, &self.options, colors)?;
if let Some((shape, blinking)) = cursor {
core.set_cursor(shape, blinking)?;
}
let mut effects = Effects::default();
core.write(&self.bytes, &mut effects);
let frame = core
.frame(true)?
.ok_or_else(|| io::Error::other("fixture frame missing"))?;
let mut snapshot = TerminalSnapshot::for_frame(frame);
core.title().clone_into(&mut snapshot.title);
snapshot.cwd = core.cwd().cloned();
snapshot.bell_count = effects.bells;
snapshot.at_prompt = core.at_prompt();
self.sink.publish(Arc::new(snapshot));
Ok(())
}
}
impl FrameSource for FixtureSource {
fn start(
self: Box<Self>,
sink: FrameSink,
options: StartOptions,
) -> io::Result<Box<dyn FrameHandle>> {
let handle = FixtureHandle {
bytes: self.bytes,
options: self.options,
colors: Mutex::new(options.colors),
cursor: Mutex::new(options.cursor),
inputs: self.inputs,
sink,
};
handle.publish(options.viewport)?;
Ok(Box::new(handle))
}
}
impl FrameHandle for FixtureHandle {
fn input(&self, input: TerminalInput) -> Result<(), InputRejected> {
self.inputs
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(input);
Ok(())
}
fn resize(&self, viewport: Viewport) {
let _ = self.publish(viewport);
}
fn set_visible(&self, _visible: bool) {}
fn request_frame(&self) {}
fn set_colors(&self, colors: TerminalColors) {
*self
.colors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = colors;
}
fn set_cursor(&self, shape: CursorShape, blinking: bool) {
*self
.cursor
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some((shape, blinking));
}
fn close(&self) {}
}