pub mod commands;
pub mod dispatch;
pub mod mode;
pub mod state;
use std::path::PathBuf;
use std::time::Duration;
use anyhow::Result;
use crossterm::event::{self, Event, KeyEventKind};
pub use state::App;
use crate::filesystem::watcher::Watcher;
use crate::input::Input;
use crate::renderer::Tui;
use crate::ui;
const POLL_INTERVAL: Duration = Duration::from_millis(100);
pub fn run(app: &mut App, tui: &mut Tui) -> Result<()> {
let mut input = Input::default();
let mut watcher = app.config.watch_files.then(Watcher::new).flatten();
while !app.should_quit() {
if let Some(watcher) = watcher.as_mut() {
watch_open_files(app, watcher);
watch_open_directories(app, watcher);
handle_external_changes(app, watcher.drain());
}
tui.set_cursor_shape(app.mode.uses_bar_cursor() || app.has_selection())?;
tui.draw(|frame| ui::draw(frame, app))?;
if !event::poll(POLL_INTERVAL)? {
continue;
}
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => {
let action = input.handle(key, app.mode);
dispatch::apply(app, action)?;
}
Event::Mouse(mouse) => {
let action = input.handle_mouse(mouse);
dispatch::apply(app, action)?;
}
Event::Paste(text) => {
let mut edit = app.edit();
edit.insert_text(&text);
edit.checkpoint();
}
Event::Resize(_, _) => tui.clear()?,
_ => {}
}
}
Ok(())
}
fn watch_open_files(app: &App, watcher: &mut Watcher) {
for buffer in &app.buffers {
if let Some(path) = buffer.document.path() {
watcher.watch(path);
}
}
}
fn watch_open_directories(app: &App, watcher: &mut Watcher) {
let Some(tree) = app.tree.as_ref() else {
return;
};
for directory in tree.open_directories() {
watcher.watch_directory(directory);
}
}
fn handle_external_changes(app: &mut App, changed: Vec<PathBuf>) {
if app
.tree
.as_ref()
.is_some_and(|tree| changed.iter().any(|path| path.starts_with(tree.root())))
{
app.refresh_tree();
}
for path in changed {
let Some(index) = app
.buffers
.iter()
.position(|buffer| buffer.document.path() == Some(path.as_path()))
else {
continue;
};
let Ok(on_disk) = crate::filesystem::read_file(&path) else {
continue;
};
if app.buffers[index].document.matches_disk_text(&on_disk) {
continue;
}
if app.buffers[index].document.is_dirty() {
let name = app.buffers[index].document.display_name().to_string();
app.show_popup(
"file changed on disk",
format!(
"{name} was modified outside the editor, and this buffer has \
unsaved changes.\n\nUse :e! to discard yours and reload, or \
:w to overwrite the file on disk."
),
);
} else if app.buffers[index].document.reload().is_ok() {
app.buffers[index].detect_language();
app.clamp_windows_on(index);
let name = app.buffers[index].document.display_name().to_string();
app.info(format!("{name} reloaded from disk"));
}
}
}