use std::path::{Path, PathBuf};
use anyhow::Result;
use super::mode::Mode;
use crate::clipboard::Clipboard;
use crate::config::{self, Config};
use crate::editor::buffer::Buffer;
use crate::editor::document::Document;
use crate::filesystem::tree::Tree;
use crate::search::Search;
use crate::theme::Theme;
#[derive(Debug, Clone, Default)]
pub struct Status {
pub text: String,
pub is_error: bool,
}
#[derive(Debug)]
pub struct App {
pub mode: Mode,
pub config: Config,
pub theme: Theme,
pub buffers: Vec<Buffer>,
pub active: usize,
pub command_line: String,
pub status: Status,
pub clipboard: Clipboard,
pub search: Search,
pub tree: Option<Tree>,
pub tree_selected: usize,
pub tree_visible: bool,
pub popup: Option<(String, String)>,
pub viewport_height: u16,
quit: bool,
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
impl App {
#[must_use]
pub fn new() -> Self {
let (config, config_error) = Config::load();
let (theme, theme_error) = Theme::load(&config.theme, &config::themes_dir());
let clipboard = Clipboard::new(config.system_clipboard);
let mut app = Self {
mode: Mode::default(),
config,
theme,
buffers: vec![Buffer::empty()],
active: 0,
command_line: String::new(),
status: Status::default(),
clipboard,
search: Search::default(),
tree: None,
tree_selected: 0,
tree_visible: false,
popup: None,
viewport_height: 1,
quit: false,
};
if let Some(message) = config_error.or(theme_error) {
app.error(message);
}
app
}
#[must_use]
pub fn buffer(&self) -> &Buffer {
&self.buffers[self.active]
}
pub fn buffer_mut(&mut self) -> &mut Buffer {
&mut self.buffers[self.active]
}
pub fn buffer_and_config(&mut self) -> (&mut Buffer, &Config) {
let index = self.active;
(&mut self.buffers[index], &self.config)
}
pub fn open(&mut self, path: PathBuf) -> Result<()> {
if let Some(index) = self.index_of(&path) {
self.active = index;
return Ok(());
}
let buffer = Buffer::new(Document::open(path)?);
if self.buffers.len() == 1 && self.is_scratch(0) {
self.buffers[0] = buffer;
self.active = 0;
} else {
self.buffers.push(buffer);
self.active = self.buffers.len() - 1;
}
Ok(())
}
pub fn close_active(&mut self) {
if self.buffers.len() == 1 {
self.buffers[0] = Buffer::empty();
return;
}
self.buffers.remove(self.active);
self.active = self.active.min(self.buffers.len() - 1);
}
pub fn cycle_buffer(&mut self, forward: bool) {
let count = self.buffers.len();
self.active = if forward {
(self.active + 1) % count
} else {
(self.active + count - 1) % count
};
}
#[must_use]
pub fn has_unsaved_changes(&self) -> bool {
self.buffers.iter().any(|b| b.document.is_dirty())
}
pub fn info(&mut self, text: impl Into<String>) {
self.status = Status {
text: text.into(),
is_error: false,
};
}
pub fn error(&mut self, text: impl Into<String>) {
self.status = Status {
text: text.into(),
is_error: true,
};
}
pub fn clear_status(&mut self) {
self.status = Status::default();
}
pub fn show_popup(&mut self, title: impl Into<String>, body: impl Into<String>) {
self.popup = Some((title.into(), body.into()));
}
pub fn tree_root(&self) -> PathBuf {
self.buffer()
.document
.path()
.and_then(Path::parent)
.map(Path::to_path_buf)
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
pub fn quit(&mut self) {
self.quit = true;
}
#[must_use]
pub const fn should_quit(&self) -> bool {
self.quit
}
fn index_of(&self, path: &Path) -> Option<usize> {
self.buffers
.iter()
.position(|b| b.document.path() == Some(path))
}
fn is_scratch(&self, index: usize) -> bool {
let document = &self.buffers[index].document;
document.path().is_none() && !document.is_dirty() && document.len_chars() == 0
}
}