#![deny(missing_docs)]
#![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::len_without_is_empty)]
#![allow(clippy::manual_range_contains)]
#![allow(clippy::match_like_matches_macro)]
#![allow(clippy::needless_return)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
use std::io::{stdout, Stdout};
use std::process;
use ratatui::{
backend::CrosstermBackend,
buffer::Buffer,
layout::Rect,
style::{Color, Style},
text::{Line, Span},
widgets::Paragraph,
Frame,
Terminal,
};
use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
};
use modalkit::actions::Action;
use modalkit::editing::{application::ApplicationInfo, completion::CompletionList, store::Store};
use modalkit::errors::{EditResult, UIResult};
use modalkit::prelude::*;
pub mod cmdbar;
pub mod list;
pub mod screen;
pub mod textbox;
pub mod windows;
mod util;
pub type TermOffset = (u16, u16);
pub trait TerminalCursor {
fn get_term_cursor(&self) -> Option<TermOffset>;
}
pub trait ScrollActions<C, S, I>
where
I: ApplicationInfo,
{
fn dirscroll(
&mut self,
dir: MoveDir2D,
size: ScrollSize,
count: &Count,
ctx: &C,
store: &mut S,
) -> EditResult<EditInfo, I>;
fn cursorpos(
&mut self,
pos: MovePosition,
axis: Axis,
ctx: &C,
store: &mut S,
) -> EditResult<EditInfo, I>;
fn linepos(
&mut self,
pos: MovePosition,
count: &Count,
ctx: &C,
store: &mut S,
) -> EditResult<EditInfo, I>;
}
pub trait PromptActions<C, S, I>
where
I: ApplicationInfo,
{
fn submit(&mut self, ctx: &C, store: &mut S) -> EditResult<Vec<(Action<I>, C)>, I>;
fn abort(&mut self, empty: bool, ctx: &C, store: &mut S) -> EditResult<Vec<(Action<I>, C)>, I>;
fn recall(
&mut self,
filter: &RecallFilter,
dir: &MoveDir1D,
count: &Count,
ctx: &C,
store: &mut S,
) -> EditResult<Vec<(Action<I>, C)>, I>;
}
pub trait WindowOps<I: ApplicationInfo>: TerminalCursor {
fn dup(&self, store: &mut Store<I>) -> Self;
fn close(&mut self, flags: CloseFlags, store: &mut Store<I>) -> bool;
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut Store<I>);
fn get_completions(&self) -> Option<CompletionList>;
fn get_cursor_word(&self, style: &WordStyle) -> Option<String>;
fn get_selected_word(&self) -> Option<String>;
fn write(
&mut self,
path: Option<&str>,
flags: WriteFlags,
store: &mut Store<I>,
) -> UIResult<EditInfo, I>;
}
pub trait Window<I: ApplicationInfo>: WindowOps<I> + Sized {
fn id(&self) -> I::WindowId;
fn get_win_title(&self, store: &mut Store<I>) -> Line;
fn get_tab_title(&self, store: &mut Store<I>) -> Line {
self.get_win_title(store)
}
fn open(id: I::WindowId, store: &mut Store<I>) -> UIResult<Self, I>;
fn find(name: String, store: &mut Store<I>) -> UIResult<Self, I>;
fn posn(index: usize, store: &mut Store<I>) -> UIResult<Self, I>;
fn unnamed(store: &mut Store<I>) -> UIResult<Self, I>;
}
pub fn render_cursor<T: TerminalCursor>(f: &mut Frame, widget: &T, cursor: Option<char>) {
if let Some((cx, cy)) = widget.get_term_cursor() {
if let Some(c) = cursor {
let style = Style::default().fg(Color::Green);
let span = Span::styled(c.to_string(), style);
let para = Paragraph::new(span);
let inner = Rect::new(cx, cy, 1, 1);
f.render_widget(para, inner)
}
f.set_cursor_position((cx, cy));
}
}
pub trait TerminalExtOps {
type Result;
fn program_suspend(&mut self) -> Self::Result;
}
impl TerminalExtOps for Terminal<CrosstermBackend<Stdout>> {
type Result = Result<EditInfo, std::io::Error>;
fn program_suspend(&mut self) -> Self::Result {
let mut stdout = stdout();
crossterm::terminal::disable_raw_mode()?;
execute!(self.backend_mut(), LeaveAlternateScreen)?;
self.show_cursor()?;
let pid = process::id();
#[cfg(unix)]
unsafe {
libc::kill(pid as i32, libc::SIGTSTP);
}
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(stdout, EnterAlternateScreen)?;
self.clear()?;
Ok(None)
}
}