mirror of
https://github.com/chylex/Bark-Browser.git
synced 2024-11-26 01:42:51 +01:00
Compare commits
No commits in common. "995e0fc8f8d696195c89dfa8b8bf685a2b305f29" and "95c12d4f61d9969590c0280ece08e266d20d7a48" have entirely different histories.
995e0fc8f8
...
95c12d4f61
41
src/app.rs
41
src/app.rs
@ -5,7 +5,6 @@ use crossterm::event::{Event, KeyEventKind};
|
|||||||
use crate::input::keymap::KeyBinding;
|
use crate::input::keymap::KeyBinding;
|
||||||
use crate::state::{Environment, State};
|
use crate::state::{Environment, State};
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::state::view::View;
|
use crate::state::view::View;
|
||||||
|
|
||||||
pub fn run(start_path: &Path) -> std::io::Result<()> {
|
pub fn run(start_path: &Path) -> std::io::Result<()> {
|
||||||
@ -15,55 +14,41 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
|
|||||||
let environment = Environment::try_from(&view)?;
|
let environment = Environment::try_from(&view)?;
|
||||||
let mut state = State::with_root_path(start_path, environment);
|
let mut state = State::with_root_path(start_path, environment);
|
||||||
|
|
||||||
loop {
|
'render: loop {
|
||||||
match state.handle_events() {
|
|
||||||
EventResult::Nothing => {}
|
|
||||||
|
|
||||||
EventResult::Draw => {
|
|
||||||
view.set_dirty(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
EventResult::Redraw => {
|
|
||||||
view.set_dirty(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.render(|frame| state.render(frame))?;
|
view.render(|frame| state.render(frame))?;
|
||||||
|
|
||||||
match handle_terminal_event(&mut state, crossterm::event::read()?) {
|
'event: loop {
|
||||||
|
match handle_event(&mut state, crossterm::event::read()?) {
|
||||||
ActionResult::Nothing => {
|
ActionResult::Nothing => {
|
||||||
continue;
|
continue 'event;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionResult::Draw => {
|
ActionResult::Draw => {
|
||||||
view.set_dirty(false);
|
continue 'render;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionResult::Redraw => {
|
ActionResult::Redraw => {
|
||||||
view.set_dirty(true);
|
view.clear()?;
|
||||||
continue;
|
continue 'render;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionResult::PushLayer(layer) => {
|
ActionResult::PushLayer(layer) => {
|
||||||
state.push_layer(layer);
|
state.push_layer(layer);
|
||||||
view.set_dirty(false);
|
continue 'render;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionResult::ReplaceLayer(layer) => {
|
ActionResult::ReplaceLayer(layer) => {
|
||||||
state.pop_layer();
|
state.pop_layer();
|
||||||
state.push_layer(layer);
|
state.push_layer(layer);
|
||||||
view.set_dirty(false);
|
continue 'render;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionResult::PopLayer => {
|
ActionResult::PopLayer => {
|
||||||
if state.pop_layer() {
|
if state.pop_layer() {
|
||||||
break;
|
break 'render;
|
||||||
} else {
|
} else {
|
||||||
view.set_dirty(false);
|
continue 'render;
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +58,7 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn handle_terminal_event(state: &mut State, event: Event) -> ActionResult {
|
fn handle_event(state: &mut State, event: Event) -> ActionResult {
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
if key.kind == KeyEventKind::Release {
|
if key.kind == KeyEventKind::Release {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
|
@ -69,7 +69,7 @@ impl<'a> InputFieldDialogBuilder5<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_confirm<F>(self, confirm_action: F) -> InputFieldDialogLayer<'a> where F: Fn(String) -> ActionResult + 'static {
|
pub fn build(self, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> InputFieldDialogLayer<'a> {
|
||||||
let step4 = self.step4;
|
let step4 = self.step4;
|
||||||
let step3 = step4.step3;
|
let step3 = step4.step3;
|
||||||
let step2 = step3.step2;
|
let step2 = step3.step2;
|
||||||
|
@ -12,7 +12,6 @@ use crate::component::input::InputField;
|
|||||||
use crate::input::keymap::KeyBinding;
|
use crate::input::keymap::KeyBinding;
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::state::layer::Layer;
|
use crate::state::layer::Layer;
|
||||||
use crate::state::view::Frame;
|
use crate::state::view::Frame;
|
||||||
|
|
||||||
@ -30,9 +29,8 @@ pub struct InputFieldDialogLayer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InputFieldDialogLayer<'a> {
|
impl<'a> InputFieldDialogLayer<'a> {
|
||||||
fn new<F>(y: u16, min_width: u16, default_color: Color, darker_color: Color, title: Line<'a>, message: Text<'a>, initial_value: Option<String>, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
|
fn new(y: u16, min_width: u16, default_color: Color, darker_color: Color, title: Line<'a>, message: Text<'a>, initial_value: Option<String>, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self {
|
||||||
let field = initial_value.map_or_else(InputField::new, InputField::with_text);
|
let field = initial_value.map_or_else(InputField::new, InputField::with_text);
|
||||||
let confirm_action = Box::new(confirm_action);
|
|
||||||
Self { y, min_width, default_color, darker_color, title, message, field, confirm_action }
|
Self { y, min_width, default_color, darker_color, title, message, field, confirm_action }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +58,6 @@ impl<'a> Layer for InputFieldDialogLayer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
|
|
||||||
EventResult::Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, frame: &mut Frame) {
|
fn render(&mut self, frame: &mut Frame) {
|
||||||
let message_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
|
let message_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
|
||||||
let message_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);
|
let message_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);
|
||||||
|
@ -37,9 +37,9 @@ impl<'a> MessageDialogActionMap<'a> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn yes_no<F>(yes_action: F) -> Self where F: Fn() -> ActionResult + 'static {
|
pub fn yes_no(yes_action: Box<dyn Fn() -> ActionResult>) -> Self {
|
||||||
let mut map = ActionHashMap::new();
|
let mut map = ActionHashMap::new();
|
||||||
map.insert(KeyBinding::char('y'), Box::new(yes_action));
|
map.insert(KeyBinding::char('y'), yes_action);
|
||||||
map.insert(KeyBinding::char('n'), Box::new(|| ActionResult::PopLayer));
|
map.insert(KeyBinding::char('n'), Box::new(|| ActionResult::PopLayer));
|
||||||
|
|
||||||
Self::new(map, vec![
|
Self::new(map, vec![
|
||||||
|
@ -63,7 +63,7 @@ impl<'a> MessageDialogBuilder4<'a> {
|
|||||||
self.actions(MessageDialogActionMap::ok())
|
self.actions(MessageDialogActionMap::ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn yes_no<F>(self, yes_action: F) -> MessageDialogLayer<'a> where F: Fn() -> ActionResult + 'static {
|
pub fn yes_no(self, yes_action: Box<dyn Fn() -> ActionResult>) -> MessageDialogLayer<'a> {
|
||||||
self.actions(MessageDialogActionMap::yes_no(yes_action))
|
self.actions(MessageDialogActionMap::yes_no(yes_action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ use crate::component::dialog::render_dialog_border;
|
|||||||
use crate::input::keymap::KeyBinding;
|
use crate::input::keymap::KeyBinding;
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::state::layer::Layer;
|
use crate::state::layer::Layer;
|
||||||
use crate::state::view::Frame;
|
use crate::state::view::Frame;
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ impl<'a> MessageDialogLayer<'a> {
|
|||||||
MessageDialogBuilder
|
MessageDialogBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> {
|
pub fn generic_error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> {
|
||||||
Self::build()
|
Self::build()
|
||||||
.y(y)
|
.y(y)
|
||||||
.color(Color::LightRed)
|
.color(Color::LightRed)
|
||||||
@ -56,10 +55,6 @@ impl Layer for MessageDialogLayer<'_> {
|
|||||||
self.actions.handle_input(key_binding)
|
self.actions.handle_input(key_binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
|
|
||||||
EventResult::Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, frame: &mut Frame) {
|
fn render(&mut self, frame: &mut Frame) {
|
||||||
let content_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
|
let content_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
|
||||||
let content_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);
|
let content_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);
|
||||||
|
@ -17,7 +17,7 @@ impl Action<FsLayer> for PushCountDigit {
|
|||||||
if new_count > MAX_COUNT {
|
if new_count > MAX_COUNT {
|
||||||
layer.registers.count = None;
|
layer.registers.count = None;
|
||||||
|
|
||||||
ActionResult::push_layer(MessageDialogLayer::error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset.")))
|
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset."))))
|
||||||
} else {
|
} else {
|
||||||
layer.registers.count = Some(new_count);
|
layer.registers.count = Some(new_count);
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
use slab_tree::NodeId;
|
use slab_tree::NodeId;
|
||||||
|
|
||||||
use crate::component::dialog::input::InputFieldDialogLayer;
|
use crate::component::dialog::input::InputFieldDialogLayer;
|
||||||
use crate::component::dialog::message::MessageDialogLayer;
|
use crate::component::dialog::message::MessageDialogLayer;
|
||||||
use crate::component::filesystem::action::file::{FileNode, get_selected_file, RefreshParentDirectoryAndSelectFile};
|
use crate::component::filesystem::action::file::{FileNode, get_selected_file};
|
||||||
|
use crate::component::filesystem::event::FsLayerEvent;
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::file::FileKind;
|
use crate::file::FileKind;
|
||||||
use crate::state::action::{Action, ActionResult};
|
use crate::state::action::{Action, ActionResult};
|
||||||
@ -69,7 +71,7 @@ impl Action<FsLayer> for CreateDirectoryInSelectedDirectory {
|
|||||||
|
|
||||||
fn create_in_selected_directory<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
|
fn create_in_selected_directory<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
|
||||||
if let Some(FileNode { node, path, .. }) = get_selected_file(layer).filter(|n| matches!(n.entry.kind(), FileKind::Directory)) {
|
if let Some(FileNode { node, path, .. }) = get_selected_file(layer).filter(|n| matches!(n.entry.kind(), FileKind::Directory)) {
|
||||||
ActionResult::push_layer(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id()))
|
ActionResult::PushLayer(Box::new(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id())))
|
||||||
} else {
|
} else {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
}
|
}
|
||||||
@ -93,7 +95,7 @@ impl Action<FsLayer> for CreateDirectoryInParentOfSelectedEntry {
|
|||||||
|
|
||||||
fn create_in_parent_of_selected_file<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
|
fn create_in_parent_of_selected_file<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
|
||||||
if let Some((parent_node_id, parent_path)) = get_parent_of_selected_file(layer) {
|
if let Some((parent_node_id, parent_path)) = get_parent_of_selected_file(layer) {
|
||||||
ActionResult::push_layer(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id))
|
ActionResult::PushLayer(Box::new(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id)))
|
||||||
} else {
|
} else {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
}
|
}
|
||||||
@ -103,9 +105,9 @@ fn get_parent_of_selected_file(layer: &FsLayer) -> Option<(NodeId, &Path)> {
|
|||||||
get_selected_file(layer).and_then(|n| { Some((n.node.parent_id()?, n.path.parent()?)) })
|
get_selected_file(layer).and_then(|n| { Some((n.node.parent_id()?, n.path.parent()?)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, parent_view_node_id: NodeId) -> InputFieldDialogLayer<'b> {
|
fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, view_node_id_to_refresh: NodeId) -> InputFieldDialogLayer<'b> {
|
||||||
let y = layer.dialog_y();
|
let y = layer.dialog_y();
|
||||||
let events = layer.events();
|
let pending_events = Rc::clone(&layer.pending_events);
|
||||||
|
|
||||||
InputFieldDialogLayer::build()
|
InputFieldDialogLayer::build()
|
||||||
.y(y)
|
.y(y)
|
||||||
@ -113,24 +115,25 @@ fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: Pa
|
|||||||
.color(Color::LightCyan, Color::Cyan)
|
.color(Color::LightCyan, Color::Cyan)
|
||||||
.title(T::title())
|
.title(T::title())
|
||||||
.message(format!("Creating {} in {}", T::kind(), parent_folder.to_string_lossy()))
|
.message(format!("Creating {} in {}", T::kind(), parent_folder.to_string_lossy()))
|
||||||
.on_confirm(move |new_name| {
|
.build(Box::new(move |new_name| {
|
||||||
if new_name.is_empty() {
|
if new_name.is_empty() {
|
||||||
return ActionResult::Nothing;
|
return ActionResult::Nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_path = parent_folder.join(&new_name);
|
let new_path = parent_folder.join(&new_name);
|
||||||
if new_path.exists() {
|
if new_path.exists() {
|
||||||
return ActionResult::push_layer(MessageDialogLayer::error(y, "Something with this name already exists."));
|
return ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, "Something with this name already exists.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
match T::create(new_path) {
|
match T::create(new_path) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
|
FsLayerEvent::RefreshViewNodeChildren(view_node_id_to_refresh).enqueue(&pending_events);
|
||||||
|
FsLayerEvent::SelectViewNodeChildByFileName(view_node_id_to_refresh, new_name).enqueue(&pending_events);
|
||||||
ActionResult::PopLayer
|
ActionResult::PopLayer
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ActionResult::push_layer(MessageDialogLayer::error(y, format!("Could not create {}: {e}", T::kind())))
|
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, format!("Could not create {}: {e}", T::kind()))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
@ -8,18 +9,18 @@ use slab_tree::NodeId;
|
|||||||
|
|
||||||
use crate::component::dialog::message::MessageDialogLayer;
|
use crate::component::dialog::message::MessageDialogLayer;
|
||||||
use crate::component::filesystem::action::file::{FileNode, get_entry_kind_name, get_selected_file};
|
use crate::component::filesystem::action::file::{FileNode, get_entry_kind_name, get_selected_file};
|
||||||
|
use crate::component::filesystem::event::FsLayerEvent;
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::file::{FileEntry, FileKind};
|
use crate::file::{FileEntry, FileKind};
|
||||||
use crate::state::action::{Action, ActionResult};
|
use crate::state::action::{Action, ActionResult};
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
|
|
||||||
pub struct DeleteSelectedEntry;
|
pub struct DeleteSelectedEntry;
|
||||||
|
|
||||||
impl Action<FsLayer> for DeleteSelectedEntry {
|
impl Action<FsLayer> for DeleteSelectedEntry {
|
||||||
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
||||||
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
|
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
|
||||||
ActionResult::push_layer(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned()))
|
ActionResult::PushLayer(Box::new(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned())))
|
||||||
} else {
|
} else {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ impl Action<FsLayer> for DeleteSelectedEntry {
|
|||||||
|
|
||||||
fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId, entry: &FileEntry, path: PathBuf) -> MessageDialogLayer<'a> {
|
fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId, entry: &FileEntry, path: PathBuf) -> MessageDialogLayer<'a> {
|
||||||
let y = layer.dialog_y();
|
let y = layer.dialog_y();
|
||||||
let events = layer.events();
|
let pending_events = Rc::clone(&layer.pending_events);
|
||||||
|
|
||||||
let total_files = if matches!(entry.kind(), FileKind::Directory) {
|
let total_files = if matches!(entry.kind(), FileKind::Directory) {
|
||||||
count_files(path.clone())
|
count_files(path.clone())
|
||||||
@ -44,17 +45,17 @@ fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId,
|
|||||||
Line::from(format!("Permanently delete {}?", path.to_string_lossy())),
|
Line::from(format!("Permanently delete {}?", path.to_string_lossy())),
|
||||||
Line::from(format!("This will affect {}.", total_files.describe())),
|
Line::from(format!("This will affect {}.", total_files.describe())),
|
||||||
])
|
])
|
||||||
.yes_no(move || {
|
.yes_no(Box::new(move || {
|
||||||
match delete_path_recursively(&path) {
|
match delete_path_recursively(&path) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
events.enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.delete_node(view_node_id)));
|
FsLayerEvent::DeleteViewNode(view_node_id).enqueue(&pending_events);
|
||||||
ActionResult::PopLayer
|
ActionResult::PopLayer
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ActionResult::replace_layer(MessageDialogLayer::error(y.saturating_add(1), e.to_string()))
|
ActionResult::ReplaceLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), e.to_string())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_COUNT_TIME: Duration = Duration::from_secs(5);
|
const MAX_COUNT_TIME: Duration = Duration::from_secs(5);
|
||||||
|
@ -8,11 +8,11 @@ use slab_tree::NodeRef;
|
|||||||
|
|
||||||
use crate::component::dialog::message::MessageDialogLayer;
|
use crate::component::dialog::message::MessageDialogLayer;
|
||||||
use crate::component::filesystem::action::file::{FileNode, get_selected_file};
|
use crate::component::filesystem::action::file::{FileNode, get_selected_file};
|
||||||
|
use crate::component::filesystem::event::FsLayerEvent;
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::FsTreeViewNode;
|
use crate::component::filesystem::tree::FsTreeViewNode;
|
||||||
use crate::state::action::{Action, ActionResult};
|
use crate::state::action::{Action, ActionResult};
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::util::slab_tree::NodeRefExtensions;
|
use crate::util::slab_tree::NodeRefExtensions;
|
||||||
|
|
||||||
pub struct EditSelectedEntry;
|
pub struct EditSelectedEntry;
|
||||||
@ -34,12 +34,12 @@ fn open_default_editor(layer: &FsLayer, node: &NodeRef<FsTreeViewNode>, path: &P
|
|||||||
.status();
|
.status();
|
||||||
|
|
||||||
if status.is_err_and(|e| e.kind() == ErrorKind::NotFound) {
|
if status.is_err_and(|e| e.kind() == ErrorKind::NotFound) {
|
||||||
return ActionResult::push_layer(MessageDialogLayer::error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy())));
|
return ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the parent directory, or the root node if this is the view root.
|
// Refresh the parent directory, or the root node if this is the view root.
|
||||||
let node_id_to_refresh = node.parent_id().unwrap_or_else(|| node.node_id());
|
let node_id_to_refresh = node.parent_id().unwrap_or_else(|| node.node_id());
|
||||||
layer.events().enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.refresh_children(node_id_to_refresh)));
|
FsLayerEvent::RefreshViewNodeChildren(node_id_to_refresh).enqueue(&layer.pending_events);
|
||||||
|
|
||||||
ActionResult::Redraw
|
ActionResult::Redraw
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use slab_tree::{NodeId, NodeRef};
|
use slab_tree::NodeRef;
|
||||||
|
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::FsTreeViewNode;
|
use crate::component::filesystem::tree::FsTreeViewNode;
|
||||||
use crate::file::{FileEntry, FileKind};
|
use crate::file::{FileEntry, FileKind};
|
||||||
use crate::state::Environment;
|
|
||||||
use crate::state::event::{Event, EventResult};
|
|
||||||
|
|
||||||
pub use self::create::*;
|
pub use self::create::*;
|
||||||
pub use self::delete::*;
|
pub use self::delete::*;
|
||||||
@ -20,7 +18,7 @@ mod edit;
|
|||||||
mod rename;
|
mod rename;
|
||||||
|
|
||||||
fn get_selected_file(layer: &FsLayer) -> Option<FileNode> {
|
fn get_selected_file(layer: &FsLayer) -> Option<FileNode> {
|
||||||
if let Some(node) = layer.tree.selected_node() {
|
if let Some(node) = layer.selected_node() {
|
||||||
if let Some(entry) = layer.tree.get_entry(&node) {
|
if let Some(entry) = layer.tree.get_entry(&node) {
|
||||||
if let Some(path) = entry.path() {
|
if let Some(path) = entry.path() {
|
||||||
return Some(FileNode { node, entry, path });
|
return Some(FileNode { node, entry, path });
|
||||||
@ -56,19 +54,3 @@ fn format_io_error(err: &io::Error) -> String {
|
|||||||
str.push('.');
|
str.push('.');
|
||||||
str
|
str
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RefreshParentDirectoryAndSelectFile {
|
|
||||||
parent_view_node_id: NodeId,
|
|
||||||
child_file_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Event<FsLayer> for RefreshParentDirectoryAndSelectFile {
|
|
||||||
fn dispatch(&self, layer: &mut FsLayer, _environment: &Environment) -> EventResult {
|
|
||||||
if layer.tree.refresh_children(self.parent_view_node_id) {
|
|
||||||
layer.tree.select_child_node_by_name(self.parent_view_node_id, &self.child_file_name);
|
|
||||||
EventResult::Draw
|
|
||||||
} else {
|
|
||||||
EventResult::Nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
use io::ErrorKind;
|
use io::ErrorKind;
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
use slab_tree::NodeRef;
|
use slab_tree::NodeRef;
|
||||||
|
|
||||||
use crate::component::dialog::input::InputFieldDialogLayer;
|
use crate::component::dialog::input::InputFieldDialogLayer;
|
||||||
use crate::component::dialog::message::MessageDialogLayer;
|
use crate::component::dialog::message::MessageDialogLayer;
|
||||||
use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file, RefreshParentDirectoryAndSelectFile};
|
use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file};
|
||||||
|
use crate::component::filesystem::event::FsLayerEvent;
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::FsTreeViewNode;
|
use crate::component::filesystem::tree::FsTreeViewNode;
|
||||||
use crate::file::FileEntry;
|
use crate::file::FileEntry;
|
||||||
@ -22,7 +24,7 @@ pub struct RenameSelectedEntry {
|
|||||||
impl Action<FsLayer> for RenameSelectedEntry {
|
impl Action<FsLayer> for RenameSelectedEntry {
|
||||||
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
||||||
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
|
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
|
||||||
ActionResult::push_layer(self.create_rename_dialog(layer, &node, entry, path.to_owned()))
|
ActionResult::PushLayer(Box::new(self.create_rename_dialog(layer, &node, entry, path.to_owned())))
|
||||||
} else {
|
} else {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
}
|
}
|
||||||
@ -33,8 +35,8 @@ impl RenameSelectedEntry {
|
|||||||
#[allow(clippy::wildcard_enum_match_arm)]
|
#[allow(clippy::wildcard_enum_match_arm)]
|
||||||
fn create_rename_dialog<'a, 'b>(&'a self, layer: &'a FsLayer, node: &'a NodeRef<FsTreeViewNode>, entry: &'a FileEntry, path: PathBuf) -> InputFieldDialogLayer<'b> {
|
fn create_rename_dialog<'a, 'b>(&'a self, layer: &'a FsLayer, node: &'a NodeRef<FsTreeViewNode>, entry: &'a FileEntry, path: PathBuf) -> InputFieldDialogLayer<'b> {
|
||||||
let y = layer.dialog_y();
|
let y = layer.dialog_y();
|
||||||
let events = layer.events();
|
let parent_node_id = node.parent_id();
|
||||||
let parent_view_node_id = node.parent_id();
|
let pending_events = Rc::clone(&layer.pending_events);
|
||||||
|
|
||||||
InputFieldDialogLayer::build()
|
InputFieldDialogLayer::build()
|
||||||
.y(y)
|
.y(y)
|
||||||
@ -43,19 +45,20 @@ impl RenameSelectedEntry {
|
|||||||
.title(format!("Rename {}", get_entry_kind_name(entry)))
|
.title(format!("Rename {}", get_entry_kind_name(entry)))
|
||||||
.message(format!("Renaming {}", path.to_string_lossy()))
|
.message(format!("Renaming {}", path.to_string_lossy()))
|
||||||
.initial_value(self.prefill.then(|| entry.name().str().to_owned()))
|
.initial_value(self.prefill.then(|| entry.name().str().to_owned()))
|
||||||
.on_confirm(move |new_name| {
|
.build(Box::new(move |new_name| {
|
||||||
match rename_file(&path, &new_name) {
|
match rename_file(&path, &new_name) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
if let Some(parent_view_node_id) = parent_view_node_id {
|
if let Some(parent_node_id) = parent_node_id {
|
||||||
events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
|
FsLayerEvent::RefreshViewNodeChildren(parent_node_id).enqueue(&pending_events);
|
||||||
|
FsLayerEvent::SelectViewNodeChildByFileName(parent_node_id, new_name).enqueue(&pending_events);
|
||||||
}
|
}
|
||||||
ActionResult::PopLayer
|
ActionResult::PopLayer
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ActionResult::push_layer(MessageDialogLayer::error(y.saturating_add(1), format_io_error(&e)))
|
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), format_io_error(&e))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
use slab_tree::NodeId;
|
use slab_tree::NodeId;
|
||||||
|
|
||||||
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
|
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count, SimpleMovementAction};
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::FsTree;
|
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
|
|
||||||
pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M);
|
pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M);
|
||||||
|
|
||||||
impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> {
|
impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> {
|
||||||
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
Some(perform_action_or_movement::<M, _>(layer, FsTree::expand))
|
Some(perform_action_or_movement::<M, _>(layer, FsLayer::expand))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,16 +16,16 @@ pub struct CollapseSelectedOr<M: SimpleMovementAction>(pub M);
|
|||||||
|
|
||||||
impl<M: SimpleMovementAction> MovementAction for CollapseSelectedOr<M> {
|
impl<M: SimpleMovementAction> MovementAction for CollapseSelectedOr<M> {
|
||||||
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
Some(perform_action_or_movement::<M, _>(layer, FsTree::collapse))
|
Some(perform_action_or_movement::<M, _>(layer, FsLayer::collapse))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> bool {
|
fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> bool {
|
||||||
perform_movement_with_count_from_register(layer, |tree, node_id| {
|
perform_movement_with_count(layer, layer.registers.count, |layer, node_id| {
|
||||||
if action(tree, node_id) {
|
if action(layer, node_id) {
|
||||||
Some(node_id)
|
Some(node_id)
|
||||||
} else {
|
} else {
|
||||||
get_simple_movement_target::<M>(tree, node_id)
|
get_simple_movement_target::<M>(layer, node_id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use slab_tree::{NodeId, NodeRef};
|
use slab_tree::{NodeId, NodeRef};
|
||||||
|
|
||||||
use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
|
use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count, SimpleMovementAction};
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
|
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::util::slab_tree::NodeRefExtensions;
|
use crate::util::slab_tree::NodeRefExtensions;
|
||||||
|
|
||||||
@ -46,17 +46,19 @@ pub struct MoveOrTraverseUpParent;
|
|||||||
|
|
||||||
impl MovementAction for MoveOrTraverseUpParent {
|
impl MovementAction for MoveOrTraverseUpParent {
|
||||||
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
Some(perform_movement_with_count_from_register(layer, Self::get_target))
|
Some(perform_movement_with_count(layer, layer.registers.count, Self::get_target))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveOrTraverseUpParent {
|
impl MoveOrTraverseUpParent {
|
||||||
fn get_target(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
|
fn get_target(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> {
|
||||||
if let Some(node) = tree.view.get(node_id) {
|
let view = &layer.tree.view;
|
||||||
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(&tree.view, &node);
|
|
||||||
|
if let Some(node) = view.get(node_id) {
|
||||||
|
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(view, &node);
|
||||||
if target_node_id.is_some() {
|
if target_node_id.is_some() {
|
||||||
return target_node_id;
|
return target_node_id;
|
||||||
} else if let Some(target_node_id) = tree.traverse_up_root() {
|
} else if let Some(target_node_id) = layer.traverse_up_root() {
|
||||||
return Some(target_node_id)
|
return Some(target_node_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use slab_tree::{NodeId, NodeRef};
|
|||||||
|
|
||||||
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, MoveToParent, perform_movement_with_count_from, SimpleMovementAction};
|
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, MoveToParent, perform_movement_with_count_from, SimpleMovementAction};
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
|
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
|
|
||||||
/// Moves up `count` lines (1 line by default).
|
/// Moves up `count` lines (1 line by default).
|
||||||
@ -28,15 +28,16 @@ pub struct MoveToFirst;
|
|||||||
|
|
||||||
impl MovementAction for MoveToFirst {
|
impl MovementAction for MoveToFirst {
|
||||||
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
Some(Self::get_target(&mut layer.tree))
|
Some(Self::get_target(layer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveToFirst {
|
impl MoveToFirst {
|
||||||
fn get_target(tree: &mut FsTree) -> NodeId where Self: Sized {
|
fn get_target(layer: &mut FsLayer) -> NodeId where Self: Sized {
|
||||||
let mut target_node_id = tree.selected_view_node_id;
|
let view = &layer.tree.view;
|
||||||
|
let mut target_node_id = layer.selected_view_node_id;
|
||||||
|
|
||||||
while let Some(node_id) = tree.view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(&tree.view, &node)) {
|
while let Some(node_id) = view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(view, &node)) {
|
||||||
target_node_id = node_id;
|
target_node_id = node_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ pub struct MoveToLast;
|
|||||||
|
|
||||||
impl MovementAction for MoveToLast {
|
impl MovementAction for MoveToLast {
|
||||||
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
let first_node_id = MoveToFirst::get_target(&mut layer.tree);
|
let first_node_id = MoveToFirst::get_target(layer);
|
||||||
let last_node_id = layer.tree.view.get_last_descendant_or_self(first_node_id);
|
let last_node_id = layer.tree.view.get_last_descendant_or_self(first_node_id);
|
||||||
Some(last_node_id)
|
Some(last_node_id)
|
||||||
}
|
}
|
||||||
@ -62,10 +63,9 @@ pub struct MoveToLineOr<A: MovementAction>(pub A);
|
|||||||
impl<A: MovementAction> MovementAction for MoveToLineOr<A> {
|
impl<A: MovementAction> MovementAction for MoveToLineOr<A> {
|
||||||
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
if let Some(line_number) = layer.registers.count {
|
if let Some(line_number) = layer.registers.count {
|
||||||
let tree = &mut layer.tree;
|
|
||||||
let line_index = Some(line_number.saturating_sub(1));
|
let line_index = Some(line_number.saturating_sub(1));
|
||||||
let first_node_id = MoveToFirst::get_target(tree);
|
let first_node_id = MoveToFirst::get_target(layer);
|
||||||
Some(perform_movement_with_count_from(tree, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
|
Some(perform_movement_with_count_from(layer, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
|
||||||
} else {
|
} else {
|
||||||
self.0.get_target(layer, environment)
|
self.0.get_target(layer, environment)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use slab_tree::{NodeId, NodeRef};
|
use slab_tree::{NodeId, NodeRef};
|
||||||
|
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
|
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
|
||||||
use crate::state::action::{Action, ActionResult};
|
use crate::state::action::{Action, ActionResult};
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ pub trait MovementAction {
|
|||||||
impl<T: MovementAction> Action<FsLayer> for T {
|
impl<T: MovementAction> Action<FsLayer> for T {
|
||||||
fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult {
|
fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult {
|
||||||
if let Some(target_node_id) = self.get_target(layer, environment) {
|
if let Some(target_node_id) = self.get_target(layer, environment) {
|
||||||
layer.tree.selected_view_node_id = target_node_id;
|
layer.selected_view_node_id = target_node_id;
|
||||||
ActionResult::Draw
|
ActionResult::Draw
|
||||||
} else {
|
} else {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
@ -33,19 +33,15 @@ impl<T: MovementAction> Action<FsLayer> for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_movement_with_count_from_register<F>(layer: &mut FsLayer, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
|
fn perform_movement_with_count<F>(layer: &mut FsLayer, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> {
|
||||||
perform_movement_with_count(&mut layer.tree, layer.registers.count, get_target)
|
perform_movement_with_count_from(layer, count, layer.selected_view_node_id, get_target)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_movement_with_count<F>(tree: &mut FsTree, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
|
fn perform_movement_with_count_from<F>(layer: &mut FsLayer, count: Option<usize>, start_node_id: NodeId, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> {
|
||||||
perform_movement_with_count_from(tree, count, tree.selected_view_node_id, get_target)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn perform_movement_with_count_from<F>(tree: &mut FsTree, count: Option<usize>, start_node_id: NodeId, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
|
|
||||||
let mut target_node_id = start_node_id;
|
let mut target_node_id = start_node_id;
|
||||||
|
|
||||||
for _ in 0..count.unwrap_or(1) {
|
for _ in 0..count.unwrap_or(1) {
|
||||||
if let Some(node_id) = get_target(tree, target_node_id) {
|
if let Some(node_id) = get_target(layer, target_node_id) {
|
||||||
target_node_id = node_id;
|
target_node_id = node_id;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@ -61,10 +57,11 @@ pub trait SimpleMovementAction {
|
|||||||
|
|
||||||
impl<T: SimpleMovementAction> MovementAction for T {
|
impl<T: SimpleMovementAction> MovementAction for T {
|
||||||
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
Some(perform_movement_with_count_from_register(layer, get_simple_movement_target::<T>))
|
Some(perform_movement_with_count(layer, layer.registers.count, get_simple_movement_target::<T>))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_simple_movement_target<T: SimpleMovementAction>(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
|
fn get_simple_movement_target<T: SimpleMovementAction>(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> {
|
||||||
tree.view.get(node_id).and_then(|node| T::get_target(&tree.view, &node))
|
let view = &layer.tree.view;
|
||||||
|
view.get(node_id).and_then(|node| T::get_target(view, &node))
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ impl<A: SimpleMovementAction + 'static, C: MovementCount> MovementWithCountFacto
|
|||||||
impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> {
|
impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> {
|
||||||
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
|
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||||
let count = self.0.get_count(layer.registers.count, environment);
|
let count = self.0.get_count(layer.registers.count, environment);
|
||||||
Some(perform_movement_with_count(&mut layer.tree, Some(count), get_simple_movement_target::<A>))
|
Some(perform_movement_with_count(layer, Some(count), get_simple_movement_target::<A>))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ use slab_tree::NodeId;
|
|||||||
|
|
||||||
use crate::component::dialog::message::MessageDialogLayer;
|
use crate::component::dialog::message::MessageDialogLayer;
|
||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::component::filesystem::tree::FsTree;
|
|
||||||
use crate::state::action::{Action, ActionResult};
|
use crate::state::action::{Action, ActionResult};
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
|
|
||||||
@ -20,19 +19,19 @@ impl Action<FsLayer> for ExpandCollapse {
|
|||||||
return ActionResult::Nothing;
|
return ActionResult::Nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if layer.tree.expand_or_collapse(layer.tree.selected_view_node_id) {
|
if layer.expand_or_collapse(layer.selected_view_node_id) {
|
||||||
if depth > 1 {
|
if depth > 1 {
|
||||||
if let Some(node) = layer.tree.selected_node() {
|
if let Some(node) = layer.selected_node() {
|
||||||
if node.data().is_expanded() {
|
if node.data().is_expanded() {
|
||||||
let child_node_ids = node.children().map(|node| node.node_id()).collect();
|
let child_node_ids = node.children().map(|node| node.node_id()).collect();
|
||||||
let remaining_depth = depth.saturating_sub(1);
|
let remaining_depth = depth.saturating_sub(1);
|
||||||
if !expand_children_to_depth(&mut layer.tree, child_node_ids, remaining_depth) {
|
if !expand_children_to_depth(layer, child_node_ids, remaining_depth) {
|
||||||
return ActionResult::push_layer(MessageDialogLayer::build()
|
return ActionResult::PushLayer(Box::new(MessageDialogLayer::build()
|
||||||
.y(layer.dialog_y())
|
.y(layer.dialog_y())
|
||||||
.color(Color::LightYellow)
|
.color(Color::LightYellow)
|
||||||
.title("Expansion Stopped")
|
.title("Expansion Stopped")
|
||||||
.message(format!("Expansion was taking more than {} seconds, stopping now.", MAX_EXPANSION_TIME.as_secs()))
|
.message(format!("Expansion was taking more than {} seconds, stopping now.", MAX_EXPANSION_TIME.as_secs()))
|
||||||
.ok());
|
.ok()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +46,7 @@ impl Action<FsLayer> for ExpandCollapse {
|
|||||||
|
|
||||||
const MAX_EXPANSION_TIME: Duration = Duration::from_secs(10);
|
const MAX_EXPANSION_TIME: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
fn expand_children_to_depth(tree: &mut FsTree, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool {
|
fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let mut current_pass_node_ids = Vec::new();
|
let mut current_pass_node_ids = Vec::new();
|
||||||
|
|
||||||
@ -56,8 +55,8 @@ fn expand_children_to_depth(tree: &mut FsTree, mut child_node_ids: Vec<NodeId>,
|
|||||||
|
|
||||||
for node_id in ¤t_pass_node_ids {
|
for node_id in ¤t_pass_node_ids {
|
||||||
let node_id = *node_id;
|
let node_id = *node_id;
|
||||||
tree.expand(node_id);
|
layer.tree.expand(node_id);
|
||||||
get_child_node_ids(tree, node_id, &mut child_node_ids);
|
get_child_node_ids(layer, node_id, &mut child_node_ids);
|
||||||
|
|
||||||
if start_time.elapsed() >= MAX_EXPANSION_TIME {
|
if start_time.elapsed() >= MAX_EXPANSION_TIME {
|
||||||
return false;
|
return false;
|
||||||
@ -68,8 +67,8 @@ fn expand_children_to_depth(tree: &mut FsTree, mut child_node_ids: Vec<NodeId>,
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_child_node_ids(tree: &FsTree, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
|
fn get_child_node_ids(layer: &FsLayer, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
|
||||||
if let Some(node) = tree.get_view_node(node_id) {
|
if let Some(node) = layer.tree.view.get(node_id) {
|
||||||
for child in node.children() {
|
for child in node.children() {
|
||||||
output_node_ids.push(child.node_id());
|
output_node_ids.push(child.node_id());
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ pub struct RefreshChildrenOfSelected;
|
|||||||
|
|
||||||
impl Action<FsLayer> for RefreshChildrenOfSelected {
|
impl Action<FsLayer> for RefreshChildrenOfSelected {
|
||||||
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
||||||
if layer.tree.refresh_children(layer.tree.selected_view_node_id) {
|
if layer.refresh_children(layer.selected_view_node_id) {
|
||||||
ActionResult::Draw
|
ActionResult::Draw
|
||||||
} else {
|
} else {
|
||||||
ActionResult::Nothing
|
ActionResult::Nothing
|
||||||
|
62
src/component/filesystem/event.rs
Normal file
62
src/component/filesystem/event.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use slab_tree::NodeId;
|
||||||
|
|
||||||
|
use crate::component::filesystem::FsLayer;
|
||||||
|
|
||||||
|
pub type FsLayerPendingEvents = Rc<RefCell<Vec<FsLayerEvent>>>;
|
||||||
|
|
||||||
|
pub enum FsLayerEvent {
|
||||||
|
RefreshViewNodeChildren(NodeId),
|
||||||
|
SelectViewNodeChildByFileName(NodeId, String),
|
||||||
|
DeleteViewNode(NodeId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FsLayerEvent {
|
||||||
|
pub fn enqueue(self, pending_events: &FsLayerPendingEvents) -> bool {
|
||||||
|
if let Ok(mut pending_events) = pending_events.try_borrow_mut() {
|
||||||
|
pending_events.push(self);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(self, layer: &mut FsLayer) {
|
||||||
|
match self {
|
||||||
|
Self::RefreshViewNodeChildren(view_node_id) => handle_refresh_view_node_children(layer, view_node_id),
|
||||||
|
Self::SelectViewNodeChildByFileName(parent_view_node_id, child_file_name) => handle_select_view_node_child_by_name(layer, parent_view_node_id, child_file_name.as_str()),
|
||||||
|
Self::DeleteViewNode(view_node_id) => handle_delete_view_node(layer, view_node_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_refresh_view_node_children(layer: &mut FsLayer, view_node_id: NodeId) {
|
||||||
|
layer.refresh_children(view_node_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_select_view_node_child_by_name(layer: &mut FsLayer, parent_view_node_id: NodeId, child_file_name: &str) {
|
||||||
|
layer.tree.expand(parent_view_node_id);
|
||||||
|
|
||||||
|
if let Some(parent_node) = layer.tree.view.get(parent_view_node_id) {
|
||||||
|
for child_node in parent_node.children() {
|
||||||
|
if layer.tree.get_entry(&child_node).is_some_and(|entry| entry.name().str() == child_file_name) {
|
||||||
|
layer.selected_view_node_id = child_node.node_id();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_delete_view_node(layer: &mut FsLayer, view_node_id: NodeId) {
|
||||||
|
let view = &mut layer.tree.view;
|
||||||
|
|
||||||
|
if layer.selected_view_node_id == view_node_id {
|
||||||
|
layer.selected_view_node_id = view.get_node_below_id(view_node_id).or_else(|| view.get_node_above_id(view_node_id)).unwrap_or_else(|| view.root_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(view_node) = view.remove(view_node_id) {
|
||||||
|
layer.tree.model.remove(view_node.model_node_id());
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,32 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use slab_tree::{NodeId, NodeRef};
|
||||||
|
|
||||||
|
use crate::component::filesystem::event::FsLayerPendingEvents;
|
||||||
use crate::component::filesystem::registers::FsTreeRegisters;
|
use crate::component::filesystem::registers::FsTreeRegisters;
|
||||||
use crate::component::filesystem::tree::FsTree;
|
use crate::component::filesystem::tree::{FsTree, FsTreeViewNode};
|
||||||
use crate::file::FileOwnerNameCache;
|
use crate::file::FileOwnerNameCache;
|
||||||
use crate::input::keymap::{KeyBinding, KeyMapLookupResult};
|
use crate::input::keymap::{KeyBinding, KeyMapLookupResult};
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::{EventQueue, EventResult};
|
|
||||||
use crate::state::layer::Layer;
|
use crate::state::layer::Layer;
|
||||||
use crate::state::view::Frame;
|
use crate::state::view::Frame;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
|
mod event;
|
||||||
mod render;
|
mod render;
|
||||||
mod tree;
|
mod tree;
|
||||||
mod registers;
|
mod registers;
|
||||||
|
|
||||||
pub struct FsLayer {
|
pub struct FsLayer {
|
||||||
pub tree: FsTree,
|
pub tree: FsTree,
|
||||||
tree_structure_version: u32,
|
pub selected_view_node_id: NodeId,
|
||||||
pub registers: FsTreeRegisters,
|
pub registers: FsTreeRegisters,
|
||||||
cursor_y: u16,
|
cursor_y: u16,
|
||||||
pending_keys: Vec<KeyBinding>,
|
pending_keys: Vec<KeyBinding>,
|
||||||
event_queue: EventQueue<FsLayer>,
|
pending_events: FsLayerPendingEvents,
|
||||||
file_owner_name_cache: FileOwnerNameCache,
|
file_owner_name_cache: FileOwnerNameCache,
|
||||||
column_width_cache: Option<ColumnWidths>,
|
column_width_cache: Option<ColumnWidths>,
|
||||||
}
|
}
|
||||||
@ -31,25 +36,70 @@ impl FsLayer {
|
|||||||
// Initialize action map early in case it errors.
|
// Initialize action map early in case it errors.
|
||||||
let _ = *action::ACTION_MAP;
|
let _ = *action::ACTION_MAP;
|
||||||
|
|
||||||
|
let mut tree = FsTree::with_root_path(root_path);
|
||||||
|
let root_id = tree.view.root_id();
|
||||||
|
|
||||||
|
tree.expand(root_id);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
tree: FsTree::with_root_path(root_path),
|
tree,
|
||||||
tree_structure_version: 0,
|
selected_view_node_id: root_id,
|
||||||
cursor_y: 0,
|
cursor_y: 0,
|
||||||
registers: FsTreeRegisters::new(),
|
registers: FsTreeRegisters::new(),
|
||||||
pending_keys: Vec::new(),
|
pending_keys: Vec::new(),
|
||||||
event_queue: EventQueue::new(),
|
pending_events: Rc::new(RefCell::new(Vec::new())),
|
||||||
file_owner_name_cache: FileOwnerNameCache::new(),
|
file_owner_name_cache: FileOwnerNameCache::new(),
|
||||||
column_width_cache: None,
|
column_width_cache: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn events(&self) -> EventQueue<Self> {
|
|
||||||
self.event_queue.rc_clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn dialog_y(&self) -> u16 {
|
pub const fn dialog_y(&self) -> u16 {
|
||||||
self.cursor_y.saturating_add(1)
|
self.cursor_y.saturating_add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
|
||||||
|
return self.tree.view.get(self.selected_view_node_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand(&mut self, view_node_id: NodeId) -> bool {
|
||||||
|
let result = self.tree.expand(view_node_id);
|
||||||
|
tree_structure_changed_if_true(self, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
|
||||||
|
let result = self.tree.collapse(view_node_id);
|
||||||
|
tree_structure_changed_if_true(self, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
|
||||||
|
let result = self.tree.expand_or_collapse(view_node_id);
|
||||||
|
tree_structure_changed_if_true(self, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
|
||||||
|
let result = self.tree.refresh_children(view_node_id);
|
||||||
|
if result && self.selected_node().is_none() {
|
||||||
|
self.selected_view_node_id = view_node_id;
|
||||||
|
}
|
||||||
|
tree_structure_changed_if_true(self, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn traverse_up_root(&mut self) -> Option<NodeId> {
|
||||||
|
let new_root_id = self.tree.traverse_up_root();
|
||||||
|
tree_structure_changed_if_true(self, new_root_id.is_some());
|
||||||
|
new_root_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree_structure_changed(layer: &mut FsLayer) {
|
||||||
|
layer.column_width_cache.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree_structure_changed_if_true(layer: &mut FsLayer, result: bool) -> bool {
|
||||||
|
if result {
|
||||||
|
tree_structure_changed(layer);
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layer for FsLayer {
|
impl Layer for FsLayer {
|
||||||
@ -83,14 +133,9 @@ impl Layer for FsLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self, environment: &Environment) -> EventResult {
|
|
||||||
self.event_queue.take().into_iter().fold(EventResult::Nothing, |result, event| result.merge(event.dispatch(self, environment)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, frame: &mut Frame) {
|
fn render(&mut self, frame: &mut Frame) {
|
||||||
if self.tree_structure_version != self.tree.structure_version() {
|
for event in self.pending_events.take() {
|
||||||
self.tree_structure_version = self.tree.structure_version();
|
event.handle(self);
|
||||||
self.column_width_cache.take();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render::render(self, frame);
|
render::render(self, frame);
|
||||||
|
@ -24,7 +24,7 @@ pub fn render(layer: &mut FsLayer, frame: &mut Frame) {
|
|||||||
let column_widths = get_or_update_column_widths(layer, size.width);
|
let column_widths = get_or_update_column_widths(layer, size.width);
|
||||||
let file_owner_name_cache = &mut layer.file_owner_name_cache;
|
let file_owner_name_cache = &mut layer.file_owner_name_cache;
|
||||||
|
|
||||||
let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.tree.selected_view_node_id, size.height as usize);
|
let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.selected_view_node_id, size.height as usize);
|
||||||
layer.cursor_y = cursor_y;
|
layer.cursor_y = cursor_y;
|
||||||
|
|
||||||
frame.render_widget(Clear, size);
|
frame.render_widget(Clear, size);
|
||||||
@ -63,7 +63,7 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
|
|||||||
let mut displayed_rows = Vec::with_capacity(terminal_rows);
|
let mut displayed_rows = Vec::with_capacity(terminal_rows);
|
||||||
let mut cursor_y: u16 = 0;
|
let mut cursor_y: u16 = 0;
|
||||||
|
|
||||||
if let Some(middle_node) = tree.selected_node().or_else(|| tree.view.root()) {
|
if let Some(middle_node) = tree.view.get(selected_node_id).or_else(|| tree.view.root()) {
|
||||||
let middle_node_id = middle_node.node_id();
|
let middle_node_id = middle_node.node_id();
|
||||||
|
|
||||||
displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id));
|
displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id));
|
||||||
|
@ -13,39 +13,16 @@ mod model;
|
|||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
pub struct FsTree {
|
pub struct FsTree {
|
||||||
model: FsTreeModel,
|
pub model: FsTreeModel,
|
||||||
pub view: FsTreeView,
|
pub view: FsTreeView,
|
||||||
pub selected_view_node_id: NodeId,
|
|
||||||
structure_version: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FsTree {
|
impl FsTree {
|
||||||
pub fn with_root_path(path: &Path) -> Self {
|
pub fn with_root_path(path: &Path) -> Self {
|
||||||
let model = FsTreeModel::with_root_path(path);
|
let model = FsTreeModel::with_root_path(path);
|
||||||
let view = FsTreeView::from_model_root(&model);
|
let view = FsTreeView::from_model_root(&model);
|
||||||
let root_id = view.root_id();
|
|
||||||
|
|
||||||
let mut tree = Self {
|
Self { model, view }
|
||||||
model,
|
|
||||||
view,
|
|
||||||
selected_view_node_id: root_id,
|
|
||||||
structure_version: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
tree.expand(root_id);
|
|
||||||
tree
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn structure_version(&self) -> u32 {
|
|
||||||
self.structure_version
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
|
|
||||||
return self.view.get(self.selected_view_node_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_view_node(&self, view_node_id: NodeId) -> Option<NodeRef<FsTreeViewNode>> {
|
|
||||||
self.view.get(view_node_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_entry(&self, node: &NodeRef<FsTreeViewNode>) -> Option<&FileEntry> {
|
pub fn get_entry(&self, node: &NodeRef<FsTreeViewNode>) -> Option<&FileEntry> {
|
||||||
@ -55,79 +32,26 @@ impl FsTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand(&mut self, view_node_id: NodeId) -> bool {
|
pub fn expand(&mut self, view_node_id: NodeId) -> bool {
|
||||||
let result = self.view.expand(view_node_id, &mut self.model);
|
self.view.expand(view_node_id, &mut self.model)
|
||||||
self.structure_changed_if_true(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
|
pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
|
||||||
let result = self.view.collapse(view_node_id);
|
self.view.collapse(view_node_id)
|
||||||
self.structure_changed_if_true(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
|
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
|
||||||
let result = self.view.expand_or_collapse(view_node_id, &mut self.model);
|
self.view.expand_or_collapse(view_node_id, &mut self.model)
|
||||||
self.structure_changed_if_true(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn traverse_up_root(&mut self) -> Option<NodeId> {
|
pub fn traverse_up_root(&mut self) -> Option<NodeId> {
|
||||||
let new_root_id = self.view.traverse_up_root(&mut self.model);
|
self.view.traverse_up_root(&mut self.model)
|
||||||
self.structure_changed_if(new_root_id, Option::is_some)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
|
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
|
||||||
if let Some(view_node) = self.view.get(view_node_id) {
|
if let Some(view_node) = self.view.get(view_node_id) {
|
||||||
let result = self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model);
|
self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model)
|
||||||
if result && self.selected_node().is_none() {
|
|
||||||
self.selected_view_node_id = view_node_id;
|
|
||||||
}
|
|
||||||
self.structure_changed_if_true(result)
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_child_node_by_name(&mut self, parent_view_node_id: NodeId, child_file_name: &str) -> bool {
|
|
||||||
self.expand(parent_view_node_id);
|
|
||||||
|
|
||||||
if let Some(parent_node) = self.view.get(parent_view_node_id) {
|
|
||||||
for child_node in parent_node.children() {
|
|
||||||
if self.get_entry(&child_node).is_some_and(|entry| entry.name().str() == child_file_name) {
|
|
||||||
self.selected_view_node_id = child_node.node_id();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_node(&mut self, view_node_id: NodeId) -> bool {
|
|
||||||
let view = &mut self.view;
|
|
||||||
|
|
||||||
if self.selected_view_node_id == view_node_id {
|
|
||||||
self.selected_view_node_id = view.get_node_below_id(view_node_id).or_else(|| view.get_node_above_id(view_node_id)).unwrap_or_else(|| view.root_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(view_node) = view.remove(view_node_id) {
|
|
||||||
self.model.remove(view_node.model_node_id());
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn structure_changed(&mut self) {
|
|
||||||
self.structure_version = self.structure_version.wrapping_add(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn structure_changed_if<T, F>(&mut self, result: T, predicate: F) -> T where F: FnOnce(&T) -> bool {
|
|
||||||
if predicate(&result) {
|
|
||||||
self.structure_changed();
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn structure_changed_if_true(&mut self, result: bool) -> bool {
|
|
||||||
self.structure_changed_if(result, |result| *result)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
use std::cmp::min;
|
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use ratatui::style::{Color, Style};
|
use ratatui::style::{Color, Style};
|
||||||
use ratatui::text::Span;
|
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
|
|
||||||
use crate::component::input::InputField;
|
use crate::component::input::InputField;
|
||||||
use crate::input::keymap::KeyBinding;
|
use crate::input::keymap::KeyBinding;
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::state::layer::Layer;
|
use crate::state::layer::Layer;
|
||||||
use crate::state::view::Frame;
|
use crate::state::view::Frame;
|
||||||
|
|
||||||
@ -21,10 +17,12 @@ pub struct InputFieldOverlayLayer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InputFieldOverlayLayer<'a> {
|
impl<'a> InputFieldOverlayLayer<'a> {
|
||||||
pub fn new<F>(read_only_prefix: &'a str, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
|
pub fn new(read_only_prefix: &'a str, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self {
|
||||||
let field = InputField::new();
|
Self {
|
||||||
let confirm_action = Box::new(confirm_action);
|
field: InputField::new(),
|
||||||
Self { field, read_only_prefix, confirm_action }
|
read_only_prefix,
|
||||||
|
confirm_action,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,10 +53,6 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
|
|
||||||
EventResult::Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, frame: &mut Frame) {
|
fn render(&mut self, frame: &mut Frame) {
|
||||||
let size = frame.size();
|
let size = frame.size();
|
||||||
if size.width < 1 || size.height < 1 {
|
if size.width < 1 || size.height < 1 {
|
||||||
@ -68,10 +62,6 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
|
|||||||
let x = size.x;
|
let x = size.x;
|
||||||
let y = size.bottom().saturating_sub(1);
|
let y = size.bottom().saturating_sub(1);
|
||||||
|
|
||||||
let prefix_text = Span::from(self.read_only_prefix);
|
|
||||||
let prefix_width = min(u16::try_from(prefix_text.width()).unwrap_or(u16::MAX), size.width.saturating_sub(2));
|
|
||||||
|
|
||||||
if prefix_width > 0 {
|
|
||||||
let prefix_style = Style::new()
|
let prefix_style = Style::new()
|
||||||
.fg(Color::Black)
|
.fg(Color::Black)
|
||||||
.bg(Color::LightYellow);
|
.bg(Color::LightYellow);
|
||||||
@ -79,11 +69,10 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
|
|||||||
let prefix_paragraph = Paragraph::new(self.read_only_prefix)
|
let prefix_paragraph = Paragraph::new(self.read_only_prefix)
|
||||||
.style(prefix_style);
|
.style(prefix_style);
|
||||||
|
|
||||||
frame.render_widget(prefix_paragraph, Rect { x, y, width: prefix_width, height: 1 });
|
frame.render_widget(prefix_paragraph, Rect { x, y, width: 1, height: 1 });
|
||||||
}
|
|
||||||
|
|
||||||
if size.width > prefix_width {
|
if size.width > 1 {
|
||||||
self.field.render(frame, x.saturating_add(prefix_width), y, size.width.saturating_sub(prefix_width), Color::LightYellow, Color::Yellow);
|
self.field.render(frame, x.saturating_add(1), y, size.width.saturating_sub(1), Color::LightYellow, Color::Yellow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,4 @@ impl ActionResult {
|
|||||||
Self::Nothing
|
Self::Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_layer<T>(layer: T) -> Self where T: Layer + 'static {
|
|
||||||
Self::PushLayer(Box::new(layer))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_layer<T>(layer: T) -> Self where T: Layer + 'static {
|
|
||||||
Self::ReplaceLayer(Box::new(layer))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::state::Environment;
|
|
||||||
|
|
||||||
pub trait Event<L> {
|
|
||||||
fn dispatch(&self, layer: &mut L, environment: &Environment) -> EventResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L, F> Event<L> for F where F: Fn(& mut L, &Environment) -> EventResult {
|
|
||||||
fn dispatch(&self, layer: &mut L, environment: &Environment) -> EventResult {
|
|
||||||
self(layer, environment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventQueue<L> {
|
|
||||||
events: Rc<RefCell<Vec<Box<dyn Event<L>>>>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L> EventQueue<L> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { events: Rc::new(RefCell::new(Vec::new())) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rc_clone(&self) -> Self {
|
|
||||||
Self { events: Rc::clone(&self.events) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enqueue<E: Event<L> + 'static>(&self, event: E) -> bool {
|
|
||||||
if let Ok(mut events) = self.events.try_borrow_mut() {
|
|
||||||
events.push(Box::new(event));
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enqueue_fn<F>(&self, event: F) -> bool where F: Fn(&mut L, &Environment) -> EventResult + 'static {
|
|
||||||
self.enqueue(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take(&self) -> Vec<Box<dyn Event<L>>> {
|
|
||||||
self.events.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum EventResult {
|
|
||||||
Nothing,
|
|
||||||
Draw,
|
|
||||||
Redraw,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventResult {
|
|
||||||
pub const fn draw_if(condition: bool) -> Self {
|
|
||||||
if condition {
|
|
||||||
Self::Draw
|
|
||||||
} else {
|
|
||||||
Self::Nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn merge(self, other: Self) -> Self {
|
|
||||||
if self == Self::Redraw || other == Self::Redraw {
|
|
||||||
Self::Redraw
|
|
||||||
} else if self == Self::Draw || other == Self::Draw {
|
|
||||||
Self::Draw
|
|
||||||
} else {
|
|
||||||
Self::Nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,9 @@
|
|||||||
use crate::input::keymap::KeyBinding;
|
use crate::input::keymap::KeyBinding;
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::Environment;
|
use crate::state::Environment;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::state::view::Frame;
|
use crate::state::view::Frame;
|
||||||
|
|
||||||
pub trait Layer {
|
pub trait Layer {
|
||||||
fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult;
|
fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult;
|
||||||
fn handle_events(&mut self, environment: &Environment) -> EventResult;
|
|
||||||
fn render(&mut self, frame: &mut Frame);
|
fn render(&mut self, frame: &mut Frame);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ use std::path::Path;
|
|||||||
use crate::component::filesystem::FsLayer;
|
use crate::component::filesystem::FsLayer;
|
||||||
use crate::input::keymap::KeyBinding;
|
use crate::input::keymap::KeyBinding;
|
||||||
use crate::state::action::ActionResult;
|
use crate::state::action::ActionResult;
|
||||||
use crate::state::event::EventResult;
|
|
||||||
use crate::state::layer::Layer;
|
use crate::state::layer::Layer;
|
||||||
use crate::state::view::Frame;
|
use crate::state::view::Frame;
|
||||||
|
|
||||||
@ -11,7 +10,6 @@ pub use self::environment::Environment;
|
|||||||
|
|
||||||
mod environment;
|
mod environment;
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod event;
|
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
@ -28,12 +26,8 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_events(&mut self) -> EventResult {
|
|
||||||
self.layers.iter_mut().fold(EventResult::Nothing, |result, layer| result.merge(layer.handle_events(&self.environment)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_input(&mut self, key_binding: KeyBinding) -> ActionResult {
|
pub fn handle_input(&mut self, key_binding: KeyBinding) -> ActionResult {
|
||||||
self.layers.last_mut().map_or(ActionResult::Nothing, |layer| layer.handle_input(&self.environment, key_binding))
|
self.layers.last_mut().map(|layer| layer.handle_input(&self.environment, key_binding)).unwrap_or(ActionResult::Nothing)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_resize(&mut self, width: u16, height: u16) {
|
pub fn handle_resize(&mut self, width: u16, height: u16) {
|
||||||
|
@ -10,7 +10,6 @@ use ratatui::widgets::{StatefulWidget, Widget};
|
|||||||
|
|
||||||
pub struct View {
|
pub struct View {
|
||||||
term: Terminal<CrosstermBackend<Stdout>>,
|
term: Terminal<CrosstermBackend<Stdout>>,
|
||||||
render_request: RenderRequest,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
@ -23,7 +22,7 @@ impl View {
|
|||||||
term.hide_cursor()?;
|
term.hide_cursor()?;
|
||||||
term.clear()?;
|
term.clear()?;
|
||||||
|
|
||||||
Ok(Self { term, render_request: RenderRequest::Draw })
|
Ok(Self { term })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_terminal_on_panic() {
|
pub fn restore_terminal_on_panic() {
|
||||||
@ -46,34 +45,11 @@ impl View {
|
|||||||
self.term.size()
|
self.term.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dirty(&mut self, full_redraw: bool) {
|
pub fn clear(&mut self) -> io::Result<()> {
|
||||||
let new_request = if full_redraw {
|
self.term.clear()
|
||||||
RenderRequest::Redraw
|
|
||||||
} else {
|
|
||||||
RenderRequest::Draw
|
|
||||||
};
|
|
||||||
|
|
||||||
self.render_request = self.render_request.merge(new_request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<R>(&mut self, renderer: R) -> io::Result<()> where R: FnOnce(&mut Frame) {
|
pub fn render<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) {
|
||||||
match self.render_request.consume() {
|
|
||||||
RenderRequest::Skip => {}
|
|
||||||
|
|
||||||
RenderRequest::Draw => {
|
|
||||||
self.draw(renderer)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderRequest::Redraw => {
|
|
||||||
self.term.clear()?;
|
|
||||||
self.draw(renderer)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) {
|
|
||||||
self.term.draw(|frame| {
|
self.term.draw(|frame| {
|
||||||
let mut frame = Frame::new(frame);
|
let mut frame = Frame::new(frame);
|
||||||
renderer(&mut frame);
|
renderer(&mut frame);
|
||||||
@ -82,29 +58,6 @@ impl View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
||||||
enum RenderRequest {
|
|
||||||
Skip,
|
|
||||||
Draw,
|
|
||||||
Redraw,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderRequest {
|
|
||||||
fn merge(self, other: Self) -> Self {
|
|
||||||
if self == Self::Redraw || other == Self::Redraw {
|
|
||||||
Self::Redraw
|
|
||||||
} else if self == Self::Draw || other == Self::Draw {
|
|
||||||
Self::Draw
|
|
||||||
} else {
|
|
||||||
Self::Skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consume(&mut self) -> Self {
|
|
||||||
std::mem::replace(self, Self::Skip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Frame<'a, 'b> {
|
pub struct Frame<'a, 'b> {
|
||||||
inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>,
|
inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>,
|
||||||
cursor: Option<(u16, u16)>,
|
cursor: Option<(u16, u16)>,
|
||||||
|
Loading…
Reference in New Issue
Block a user