248 lines
7.2 KiB
Rust
248 lines
7.2 KiB
Rust
use std::{cell::RefCell, fmt::Write as FmtWrite, path::Path, rc::Rc};
|
|
|
|
use libterm::{Clear, Color, Term, TermKey};
|
|
use lysp::vm::value::convert::AnyFunction;
|
|
|
|
use crate::{
|
|
TopMode,
|
|
buffer::{Buffer, Mode, SetMode},
|
|
config::EditorConfig,
|
|
error::Error,
|
|
highlight::Highlighter,
|
|
script::Movement,
|
|
};
|
|
|
|
pub struct State {
|
|
pub(super) term: Term,
|
|
buffer: Buffer,
|
|
top_mode: TopMode,
|
|
config: Rc<RefCell<EditorConfig>>,
|
|
running: bool,
|
|
number_width: usize,
|
|
pub(super) highlight: Highlighter,
|
|
|
|
// Scripting hooks
|
|
pub(super) key_hook: Option<AnyFunction>,
|
|
pub(super) post_render_hook: Option<AnyFunction>,
|
|
}
|
|
|
|
impl State {
|
|
pub fn open<P: AsRef<Path>>(
|
|
path: Option<P>,
|
|
config: Rc<RefCell<EditorConfig>>,
|
|
) -> Result<Self, Error> {
|
|
let mut buffer = match path {
|
|
Some(path) => Buffer::open(path).unwrap(),
|
|
None => Buffer::empty(),
|
|
};
|
|
let term = Term::open().map_err(Error::TerminalError)?;
|
|
|
|
Ok(Self {
|
|
number_width: buffer.number_width(),
|
|
top_mode: TopMode::Normal,
|
|
running: true,
|
|
buffer,
|
|
term,
|
|
config,
|
|
highlight: Highlighter::default(),
|
|
|
|
key_hook: None,
|
|
post_render_hook: None,
|
|
})
|
|
}
|
|
|
|
pub fn buffer(&self) -> &Buffer {
|
|
&self.buffer
|
|
}
|
|
|
|
pub fn buffer_mut(&mut self) -> &mut Buffer {
|
|
&mut self.buffer
|
|
}
|
|
|
|
pub fn mode(&self) -> (TopMode, Mode) {
|
|
(self.top_mode, self.buffer.mode())
|
|
}
|
|
|
|
pub fn buffer_terminal_cursor(&self) -> Option<(usize, usize)> {
|
|
if self.top_mode == TopMode::Command {
|
|
return None;
|
|
}
|
|
Some(self.buffer.get_terminal_cursor(&self.config.borrow()))
|
|
}
|
|
|
|
pub fn exit(&mut self, force: bool) -> Result<(), Error> {
|
|
if self.buffer.is_modified() && !force {
|
|
Err(Error::UnsavedBuffer("Use :q! to force-exit"))
|
|
} else {
|
|
self.running = false;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn exited(&self) -> bool {
|
|
!self.running
|
|
}
|
|
|
|
fn display_number(&mut self) -> Result<(), Error> {
|
|
let start = self.buffer.row_offset();
|
|
let end = self.buffer.len();
|
|
|
|
for i in 0.. {
|
|
self.term
|
|
.set_cursor_position(i, 0)
|
|
.map_err(Error::TerminalError)?;
|
|
|
|
if i + start == self.buffer.cursor_row() {
|
|
self.term.set_bright(true).map_err(Error::TerminalError)?;
|
|
self.term
|
|
.set_foreground(Color::Yellow)
|
|
.map_err(Error::TerminalError)?;
|
|
}
|
|
|
|
if i + start < end {
|
|
write!(self.term, " {0:1$} ", i + start + 1, self.number_width)
|
|
.map_err(Error::TerminalFmtError)?;
|
|
}
|
|
|
|
if i == self.buffer.height() {
|
|
break;
|
|
}
|
|
|
|
if i + start == self.buffer.cursor_row() {
|
|
self.term.reset_style().map_err(Error::TerminalError)?;
|
|
}
|
|
}
|
|
|
|
self.term.reset_style().map_err(Error::TerminalError)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cursor_to_buffer(&mut self) -> Result<(), Error> {
|
|
let config = self.config.borrow();
|
|
self.buffer.set_terminal_cursor(&config, &mut self.term)
|
|
}
|
|
|
|
pub fn finish_display(&mut self) -> Result<(), Error> {
|
|
self.term.flush().map_err(Error::TerminalError)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn display(&mut self) -> Result<(usize, usize), Error> {
|
|
let config = self.config.clone();
|
|
let mut config = config.borrow_mut();
|
|
|
|
if self.buffer.is_dirty() {
|
|
self.term.clear(Clear::All).map_err(Error::TerminalError)?;
|
|
}
|
|
|
|
let (w, h) = self.term.size().map_err(Error::TerminalError)?;
|
|
|
|
if config.number.clean() || config.bottom_margin.clean() {
|
|
if *config.number {
|
|
let nw = self.buffer.number_width() + 3;
|
|
self.buffer
|
|
.resize(&config, nw, w - nw - 1, h - (*config.bottom_margin + 1));
|
|
} else {
|
|
self.buffer
|
|
.resize(&config, 0, w - 1, h - (*config.bottom_margin + 1));
|
|
}
|
|
}
|
|
|
|
if *config.number && self.buffer.is_dirty() {
|
|
self.display_number()?;
|
|
}
|
|
|
|
self.buffer
|
|
.display(&config, &mut self.term, &self.highlight)?;
|
|
|
|
Ok((w, h))
|
|
}
|
|
|
|
pub fn set_mode(&mut self, target: SetMode) {
|
|
self.top_mode = TopMode::Normal;
|
|
self.buffer.set_mode(&self.config.borrow(), target);
|
|
}
|
|
|
|
pub fn set_top_mode(&mut self, target: TopMode) {
|
|
if self.top_mode == target {
|
|
return;
|
|
}
|
|
|
|
self.top_mode = target;
|
|
self.buffer.set_mode(&self.config.borrow(), SetMode::Normal);
|
|
}
|
|
|
|
pub fn write_text(&mut self, text: &str) {
|
|
let config = self.config.borrow();
|
|
for char in text.chars() {
|
|
self.buffer.insert(&config, char);
|
|
}
|
|
}
|
|
|
|
pub fn move_cursor(&mut self, movement: Movement) {
|
|
let config = self.config.borrow();
|
|
match movement {
|
|
Movement::LineStart => self.buffer.set_column(&config, 0),
|
|
Movement::LineEnd => self.buffer.to_line_end(&config),
|
|
Movement::NextLine => self.buffer.move_cursor(&config, 0, 1),
|
|
Movement::PrevLine => self.buffer.move_cursor(&config, 0, -1),
|
|
Movement::NextPage => self.buffer.move_cursor(&config, 0, 25),
|
|
Movement::PrevPage => self.buffer.move_cursor(&config, 0, -25),
|
|
Movement::FirstLine => self.buffer.to_first_line(&config),
|
|
Movement::LastLine => self.buffer.to_last_line(&config),
|
|
Movement::NextChar => self.buffer.move_cursor(&config, 1, 0),
|
|
Movement::PrevChar => self.buffer.move_cursor(&config, -1, 0),
|
|
}
|
|
}
|
|
|
|
pub fn kill_current_line(&mut self) {
|
|
let config = self.config.borrow();
|
|
self.buffer.kill_line(&config);
|
|
}
|
|
|
|
pub fn erase_backward(&mut self) {
|
|
let config = self.config.borrow();
|
|
self.buffer.erase_backward(&config);
|
|
}
|
|
|
|
pub fn open_buffer<P: AsRef<Path>>(&mut self, path: P, force: bool) -> Result<(), Error> {
|
|
if self.buffer.is_modified() && !force {
|
|
return Err(Error::UnsavedBuffer("Use :e! [FILE] to open another file"));
|
|
}
|
|
|
|
self.buffer.reopen(path).map_err(Error::OpenError)
|
|
}
|
|
|
|
pub fn write_buffer<P: AsRef<Path>>(
|
|
&mut self,
|
|
path: Option<P>,
|
|
) -> Result<Option<String>, Error> {
|
|
if path.is_some() && self.buffer.is_modified() && self.buffer.path().is_some() {
|
|
return Err(Error::UnsavedBuffer(
|
|
"Use :w! FILE to force write to another file",
|
|
));
|
|
}
|
|
|
|
if let Some(path) = path {
|
|
self.buffer.set_path(path);
|
|
}
|
|
self.buffer.save()?;
|
|
|
|
if let Some(name) = self.buffer.name() {
|
|
let status = format!("{name:?} written");
|
|
Ok(Some(status))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub fn wait_for_events(&mut self) -> Result<TermKey, Error> {
|
|
self.term.read_key().map_err(Error::TerminalError)
|
|
}
|
|
|
|
pub fn cleanup(&mut self) {
|
|
self.term.clear(Clear::All).ok();
|
|
}
|
|
}
|