use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use crossterm::{cursor, queue, style::Print, terminal};
use super::editor::{Editor, PROMPT};
use super::program_status::Report;
static ALTERNATE: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Written {
Shown,
Lost,
}
#[derive(Debug, Default)]
pub(crate) struct Screen {
lost: bool,
rows: u16,
}
impl Screen {
pub(crate) fn take(&mut self) -> Written {
if terminal::enable_raw_mode().is_err() {
self.lost = true;
return Written::Lost;
}
restore_on_panic();
self.write(|out| {
queue!(
out,
terminal::SetTitle("sprawling · closing this window stops it")
)
})
}
pub(crate) fn give_back(&mut self) -> Written {
let left = self.leave_quiet();
let ended = self.write(|out| queue!(out, Print("\r\n")));
drop(terminal::disable_raw_mode());
worst(left, ended)
}
pub(crate) fn is_quiet(&self) -> bool {
ALTERNATE.load(Ordering::SeqCst)
}
pub(crate) fn cut(&mut self) {
self.lost = true;
}
pub(crate) fn above(&mut self, line: &str, editor: Option<&Editor>) -> Written {
let erased = self.erase_input();
let text = line.replace('\n', "\r\n");
let shown = self.write(|out| queue!(out, Print(&text), Print("\r\n")));
let drawn = editor.map_or(Written::Shown, |editor| self.draw_input(editor));
worst(worst(erased, shown), drawn)
}
pub(crate) fn submitted(&mut self) -> Written {
self.rows = 0;
self.write(|out| queue!(out, Print("\r\n")))
}
pub(crate) fn erase_input(&mut self) -> Written {
let rows = std::mem::take(&mut self.rows);
self.write(|out| {
if rows > 0 {
queue!(out, cursor::MoveUp(rows))?;
}
queue!(
out,
cursor::MoveToColumn(0),
terminal::Clear(terminal::ClearType::FromCursorDown)
)
})
}
pub(crate) fn draw_input(&mut self, editor: &Editor) -> Written {
let width = terminal::size().map_or(80, |(columns, _)| columns);
let layout = editor.layout(width);
let text = editor.text().replace('\n', "\r\n ");
let written = self.write(|out| {
queue!(out, Print(PROMPT), Print(&text))?;
let up = layout.end_row.saturating_sub(layout.cursor_row);
if up > 0 {
queue!(out, cursor::MoveUp(up))?;
}
queue!(out, cursor::MoveToColumn(layout.cursor_column))
});
self.rows = layout.cursor_row;
written
}
pub(crate) fn quiet(&mut self, lines: &[String]) -> Written {
let entered = if ALTERNATE.swap(true, Ordering::SeqCst) {
Written::Shown
} else {
let erased = self.erase_input();
worst(
erased,
self.write(|out| queue!(out, terminal::EnterAlternateScreen)),
)
};
let text = lines.join("\r\n");
let shown = self.write(|out| {
queue!(
out,
terminal::Clear(terminal::ClearType::All),
cursor::MoveTo(0, 0),
Print(&text)
)
});
worst(entered, shown)
}
pub(crate) fn report(&mut self, report: Report) -> Written {
self.write(|out| queue!(out, report))
}
pub(crate) fn leave_quiet(&mut self) -> Written {
if !ALTERNATE.swap(false, Ordering::SeqCst) {
return Written::Shown;
}
self.write(|out| {
queue!(
out,
terminal::Clear(terminal::ClearType::All),
terminal::LeaveAlternateScreen
)
})
}
fn write(&mut self, draw: impl FnOnce(&mut std::io::Stdout) -> std::io::Result<()>) -> Written {
if self.lost {
return Written::Shown;
}
let mut out = std::io::stdout();
match draw(&mut out).and_then(|()| out.flush()) {
Ok(()) => Written::Shown,
Err(_) => {
self.lost = true;
Written::Lost
}
}
}
}
fn worst(first: Written, second: Written) -> Written {
match (first, second) {
(Written::Shown, Written::Shown) => Written::Shown,
(Written::Lost, _) | (_, Written::Lost) => Written::Lost,
}
}
fn restore_on_panic() {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let mut out = std::io::stdout();
if ALTERNATE.swap(false, Ordering::SeqCst) {
drop(crossterm::execute!(
out,
terminal::Clear(terminal::ClearType::All),
terminal::LeaveAlternateScreen
));
}
drop(terminal::disable_raw_mode());
previous(info);
}));
}