mirror of
				https://github.com/chylex/Bark-Browser.git
				synced 2025-11-04 10:40:15 +01:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			wip
			...
			995e0fc8f8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						995e0fc8f8
	
				 | 
					
					
						|||
| 
						
						
							
						
						020439d813
	
				 | 
					
					
						|||
| 
						
						
							
						
						d04fc62033
	
				 | 
					
					
						|||
| 
						
						
							
						
						38edfff874
	
				 | 
					
					
						|||
| 
						
						
							
						
						df04593aab
	
				 | 
					
					
						
							
								
								
									
										73
									
								
								src/app.rs
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								src/app.rs
									
									
									
									
									
								
							@@ -5,6 +5,7 @@ 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<()> {
 | 
				
			||||||
@@ -14,41 +15,55 @@ 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);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	'render: loop {
 | 
						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))?;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		'event: loop {
 | 
							match handle_terminal_event(&mut state, crossterm::event::read()?) {
 | 
				
			||||||
			match handle_event(&mut state, crossterm::event::read()?) {
 | 
								ActionResult::Nothing => {
 | 
				
			||||||
				ActionResult::Nothing => {
 | 
									continue;
 | 
				
			||||||
					continue 'event;
 | 
								}
 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
				ActionResult::Draw => {
 | 
								ActionResult::Draw => {
 | 
				
			||||||
					continue 'render;
 | 
									view.set_dirty(false);
 | 
				
			||||||
				}
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
				ActionResult::Redraw => {
 | 
								ActionResult::Redraw => {
 | 
				
			||||||
					view.clear()?;
 | 
									view.set_dirty(true);
 | 
				
			||||||
					continue 'render;
 | 
									continue;
 | 
				
			||||||
				}
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
				ActionResult::PushLayer(layer) => {
 | 
								ActionResult::PushLayer(layer) => {
 | 
				
			||||||
					state.push_layer(layer);
 | 
									state.push_layer(layer);
 | 
				
			||||||
					continue 'render;
 | 
									view.set_dirty(false);
 | 
				
			||||||
				}
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
				ActionResult::ReplaceLayer(layer) => {
 | 
								ActionResult::ReplaceLayer(layer) => {
 | 
				
			||||||
					state.pop_layer();
 | 
									state.pop_layer();
 | 
				
			||||||
					state.push_layer(layer);
 | 
									state.push_layer(layer);
 | 
				
			||||||
					continue 'render;
 | 
									view.set_dirty(false);
 | 
				
			||||||
				}
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
				ActionResult::PopLayer => {
 | 
								ActionResult::PopLayer => {
 | 
				
			||||||
					if state.pop_layer() {
 | 
									if state.pop_layer() {
 | 
				
			||||||
						break 'render;
 | 
										break;
 | 
				
			||||||
					} else {
 | 
									} else {
 | 
				
			||||||
						continue 'render;
 | 
										view.set_dirty(false);
 | 
				
			||||||
					}
 | 
										continue;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -58,7 +73,7 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[allow(clippy::needless_pass_by_value)]
 | 
					#[allow(clippy::needless_pass_by_value)]
 | 
				
			||||||
fn handle_event(state: &mut State, event: Event) -> ActionResult {
 | 
					fn handle_terminal_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 build(self, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> InputFieldDialogLayer<'a> {
 | 
						pub fn on_confirm<F>(self, confirm_action: F) -> InputFieldDialogLayer<'a> where F: Fn(String) -> ActionResult + 'static {
 | 
				
			||||||
		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,6 +12,7 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,8 +30,9 @@ pub struct InputFieldDialogLayer<'a> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> InputFieldDialogLayer<'a> {
 | 
					impl<'a> InputFieldDialogLayer<'a> {
 | 
				
			||||||
	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 {
 | 
						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 {
 | 
				
			||||||
		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 }
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -58,6 +60,10 @@ 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(yes_action: Box<dyn Fn() -> ActionResult>) -> Self {
 | 
						pub fn yes_no<F>(yes_action: F) -> Self where F: Fn() -> ActionResult + 'static {
 | 
				
			||||||
		let mut map = ActionHashMap::new();
 | 
							let mut map = ActionHashMap::new();
 | 
				
			||||||
		map.insert(KeyBinding::char('y'), yes_action);
 | 
							map.insert(KeyBinding::char('y'), Box::new(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(self, yes_action: Box<dyn Fn() -> ActionResult>) -> MessageDialogLayer<'a> {
 | 
						pub fn yes_no<F>(self, yes_action: F) -> MessageDialogLayer<'a> where F: Fn() -> ActionResult + 'static {
 | 
				
			||||||
		self.actions(MessageDialogActionMap::yes_no(yes_action))
 | 
							self.actions(MessageDialogActionMap::yes_no(yes_action))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +41,7 @@ impl<'a> MessageDialogLayer<'a> {
 | 
				
			|||||||
		MessageDialogBuilder
 | 
							MessageDialogBuilder
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn generic_error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> {
 | 
						pub fn error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> {
 | 
				
			||||||
		Self::build()
 | 
							Self::build()
 | 
				
			||||||
			.y(y)
 | 
								.y(y)
 | 
				
			||||||
			.color(Color::LightRed)
 | 
								.color(Color::LightRed)
 | 
				
			||||||
@@ -55,6 +56,10 @@ 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::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset."))))
 | 
								ActionResult::push_layer(MessageDialogLayer::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,14 +1,12 @@
 | 
				
			|||||||
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};
 | 
					use crate::component::filesystem::action::file::{FileNode, get_selected_file, RefreshParentDirectoryAndSelectFile};
 | 
				
			||||||
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};
 | 
				
			||||||
@@ -71,7 +69,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::PushLayer(Box::new(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id())))
 | 
							ActionResult::push_layer(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id()))
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ActionResult::Nothing
 | 
							ActionResult::Nothing
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -95,7 +93,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::PushLayer(Box::new(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id)))
 | 
							ActionResult::push_layer(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id))
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ActionResult::Nothing
 | 
							ActionResult::Nothing
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -105,9 +103,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, view_node_id_to_refresh: NodeId) -> InputFieldDialogLayer<'b> {
 | 
					fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, parent_view_node_id: NodeId) -> InputFieldDialogLayer<'b> {
 | 
				
			||||||
	let y = layer.dialog_y();
 | 
						let y = layer.dialog_y();
 | 
				
			||||||
	let pending_events = Rc::clone(&layer.pending_events);
 | 
						let events = layer.events();
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	InputFieldDialogLayer::build()
 | 
						InputFieldDialogLayer::build()
 | 
				
			||||||
		.y(y)
 | 
							.y(y)
 | 
				
			||||||
@@ -115,25 +113,24 @@ 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()))
 | 
				
			||||||
		.build(Box::new(move |new_name| {
 | 
							.on_confirm(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::PushLayer(Box::new(MessageDialogLayer::generic_error(y, "Something with this name already exists.")));
 | 
									return ActionResult::push_layer(MessageDialogLayer::error(y, "Something with this name already exists."));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			match T::create(new_path) {
 | 
								match T::create(new_path) {
 | 
				
			||||||
				Ok(_) => {
 | 
									Ok(_) => {
 | 
				
			||||||
					FsLayerEvent::RefreshViewNodeChildren(view_node_id_to_refresh).enqueue(&pending_events);
 | 
										events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
 | 
				
			||||||
					FsLayerEvent::SelectViewNodeChildByFileName(view_node_id_to_refresh, new_name).enqueue(&pending_events);
 | 
					 | 
				
			||||||
					ActionResult::PopLayer
 | 
										ActionResult::PopLayer
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				Err(e) => {
 | 
									Err(e) => {
 | 
				
			||||||
					ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, format!("Could not create {}: {e}", T::kind()))))
 | 
										ActionResult::push_layer(MessageDialogLayer::error(y, format!("Could not create {}: {e}", T::kind())))
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}))
 | 
							})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
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;
 | 
				
			||||||
@@ -9,18 +8,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::PushLayer(Box::new(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned())))
 | 
								ActionResult::push_layer(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned()))
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ActionResult::Nothing
 | 
								ActionResult::Nothing
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -29,7 +28,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 pending_events = Rc::clone(&layer.pending_events);
 | 
						let events = layer.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())
 | 
				
			||||||
@@ -45,17 +44,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(Box::new(move || {
 | 
							.yes_no(move || {
 | 
				
			||||||
			match delete_path_recursively(&path) {
 | 
								match delete_path_recursively(&path) {
 | 
				
			||||||
				Ok(_) => {
 | 
									Ok(_) => {
 | 
				
			||||||
					FsLayerEvent::DeleteViewNode(view_node_id).enqueue(&pending_events);
 | 
										events.enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.delete_node(view_node_id)));
 | 
				
			||||||
					ActionResult::PopLayer
 | 
										ActionResult::PopLayer
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				Err(e) => {
 | 
									Err(e) => {
 | 
				
			||||||
					ActionResult::ReplaceLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), e.to_string())))
 | 
										ActionResult::replace_layer(MessageDialogLayer::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::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy()))));
 | 
							return ActionResult::push_layer(MessageDialogLayer::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());
 | 
				
			||||||
	FsLayerEvent::RefreshViewNodeChildren(node_id_to_refresh).enqueue(&layer.pending_events);
 | 
						layer.events().enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.refresh_children(node_id_to_refresh)));
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	ActionResult::Redraw
 | 
						ActionResult::Redraw
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,13 @@
 | 
				
			|||||||
use std::io;
 | 
					use std::io;
 | 
				
			||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use slab_tree::NodeRef;
 | 
					use slab_tree::{NodeId, 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::*;
 | 
				
			||||||
@@ -18,7 +20,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.selected_node() {
 | 
						if let Some(node) = layer.tree.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 });
 | 
				
			||||||
@@ -54,3 +56,19 @@ 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,15 +1,13 @@
 | 
				
			|||||||
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};
 | 
					use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file, RefreshParentDirectoryAndSelectFile};
 | 
				
			||||||
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;
 | 
				
			||||||
@@ -24,7 +22,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::PushLayer(Box::new(self.create_rename_dialog(layer, &node, entry, path.to_owned())))
 | 
								ActionResult::push_layer(self.create_rename_dialog(layer, &node, entry, path.to_owned()))
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ActionResult::Nothing
 | 
								ActionResult::Nothing
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -35,8 +33,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 parent_node_id = node.parent_id();
 | 
							let events = layer.events();
 | 
				
			||||||
		let pending_events = Rc::clone(&layer.pending_events);
 | 
							let parent_view_node_id = node.parent_id();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		InputFieldDialogLayer::build()
 | 
							InputFieldDialogLayer::build()
 | 
				
			||||||
			.y(y)
 | 
								.y(y)
 | 
				
			||||||
@@ -45,20 +43,19 @@ 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()))
 | 
				
			||||||
			.build(Box::new(move |new_name| {
 | 
								.on_confirm(move |new_name| {
 | 
				
			||||||
				match rename_file(&path, &new_name) {
 | 
									match rename_file(&path, &new_name) {
 | 
				
			||||||
					Ok(_) => {
 | 
										Ok(_) => {
 | 
				
			||||||
						if let Some(parent_node_id) = parent_node_id {
 | 
											if let Some(parent_view_node_id) = parent_view_node_id {
 | 
				
			||||||
							FsLayerEvent::RefreshViewNodeChildren(parent_node_id).enqueue(&pending_events);
 | 
												events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
 | 
				
			||||||
							FsLayerEvent::SelectViewNodeChildByFileName(parent_node_id, new_name).enqueue(&pending_events);
 | 
					 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
						ActionResult::PopLayer
 | 
											ActionResult::PopLayer
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					Err(e) => {
 | 
										Err(e) => {
 | 
				
			||||||
						ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), format_io_error(&e))))
 | 
											ActionResult::push_layer(MessageDialogLayer::error(y.saturating_add(1), format_io_error(&e)))
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}))
 | 
								})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,15 @@
 | 
				
			|||||||
use slab_tree::NodeId;
 | 
					use slab_tree::NodeId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count, SimpleMovementAction};
 | 
					use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count_from_register, 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, FsLayer::expand))
 | 
							Some(perform_action_or_movement::<M, _>(layer, FsTree::expand))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,16 +17,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, FsLayer::collapse))
 | 
							Some(perform_action_or_movement::<M, _>(layer, FsTree::collapse))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> bool {
 | 
					fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> bool {
 | 
				
			||||||
	perform_movement_with_count(layer, layer.registers.count, |layer, node_id| {
 | 
						perform_movement_with_count_from_register(layer, |tree, node_id| {
 | 
				
			||||||
		if action(layer, node_id) {
 | 
							if action(tree, node_id) {
 | 
				
			||||||
			Some(node_id)
 | 
								Some(node_id)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			get_simple_movement_target::<M>(layer, node_id)
 | 
								get_simple_movement_target::<M>(tree, 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, SimpleMovementAction};
 | 
					use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
 | 
				
			||||||
use crate::component::filesystem::FsLayer;
 | 
					use crate::component::filesystem::FsLayer;
 | 
				
			||||||
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
 | 
					use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
 | 
				
			||||||
use crate::state::Environment;
 | 
					use crate::state::Environment;
 | 
				
			||||||
use crate::util::slab_tree::NodeRefExtensions;
 | 
					use crate::util::slab_tree::NodeRefExtensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,19 +46,17 @@ 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(layer, layer.registers.count, Self::get_target))
 | 
							Some(perform_movement_with_count_from_register(layer, Self::get_target))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MoveOrTraverseUpParent {
 | 
					impl MoveOrTraverseUpParent {
 | 
				
			||||||
	fn get_target(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> {
 | 
						fn get_target(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
 | 
				
			||||||
		let view = &layer.tree.view;
 | 
							if let Some(node) = tree.view.get(node_id) {
 | 
				
			||||||
		
 | 
								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) = layer.traverse_up_root() {
 | 
								} else if let Some(target_node_id) = tree.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::{FsTreeView, FsTreeViewNode};
 | 
					use crate::component::filesystem::tree::{FsTree, 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,16 +28,15 @@ 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(layer))
 | 
							Some(Self::get_target(&mut layer.tree))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MoveToFirst {
 | 
					impl MoveToFirst {
 | 
				
			||||||
	fn get_target(layer: &mut FsLayer) -> NodeId where Self: Sized {
 | 
						fn get_target(tree: &mut FsTree) -> NodeId where Self: Sized {
 | 
				
			||||||
		let view = &layer.tree.view;
 | 
							let mut target_node_id = tree.selected_view_node_id;
 | 
				
			||||||
		let mut target_node_id = layer.selected_view_node_id;
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		while let Some(node_id) = view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(view, &node)) {
 | 
							while let Some(node_id) = tree.view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(&tree.view, &node)) {
 | 
				
			||||||
			target_node_id = node_id;
 | 
								target_node_id = node_id;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
@@ -50,7 +49,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(layer);
 | 
							let first_node_id = MoveToFirst::get_target(&mut layer.tree);
 | 
				
			||||||
		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)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -63,9 +62,10 @@ 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(layer);
 | 
								let first_node_id = MoveToFirst::get_target(tree);
 | 
				
			||||||
			Some(perform_movement_with_count_from(layer, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
 | 
								Some(perform_movement_with_count_from(tree, 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::{FsTreeView, FsTreeViewNode};
 | 
					use crate::component::filesystem::tree::{FsTree, 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.selected_view_node_id = target_node_id;
 | 
								layer.tree.selected_view_node_id = target_node_id;
 | 
				
			||||||
			ActionResult::Draw
 | 
								ActionResult::Draw
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ActionResult::Nothing
 | 
								ActionResult::Nothing
 | 
				
			||||||
@@ -33,15 +33,19 @@ impl<T: MovementAction> Action<FsLayer> for T {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn perform_movement_with_count<F>(layer: &mut FsLayer, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> {
 | 
					fn perform_movement_with_count_from_register<F>(layer: &mut FsLayer, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
 | 
				
			||||||
	perform_movement_with_count_from(layer, count, layer.selected_view_node_id, get_target)
 | 
						perform_movement_with_count(&mut layer.tree, layer.registers.count, get_target)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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> {
 | 
					fn perform_movement_with_count<F>(tree: &mut FsTree, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsTree, 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(layer, target_node_id) {
 | 
							if let Some(node_id) = get_target(tree, target_node_id) {
 | 
				
			||||||
			target_node_id = node_id;
 | 
								target_node_id = node_id;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
@@ -57,11 +61,10 @@ 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(layer, layer.registers.count, get_simple_movement_target::<T>))
 | 
							Some(perform_movement_with_count_from_register(layer, get_simple_movement_target::<T>))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_simple_movement_target<T: SimpleMovementAction>(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> {
 | 
					fn get_simple_movement_target<T: SimpleMovementAction>(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
 | 
				
			||||||
	let view = &layer.tree.view;
 | 
						tree.view.get(node_id).and_then(|node| T::get_target(&tree.view, &node))
 | 
				
			||||||
	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(layer, Some(count), get_simple_movement_target::<A>))
 | 
							Some(perform_movement_with_count(&mut layer.tree, Some(count), get_simple_movement_target::<A>))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,19 +20,19 @@ impl Action<FsLayer> for ExpandCollapse {
 | 
				
			|||||||
			return ActionResult::Nothing;
 | 
								return ActionResult::Nothing;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		if layer.expand_or_collapse(layer.selected_view_node_id) {
 | 
							if layer.tree.expand_or_collapse(layer.tree.selected_view_node_id) {
 | 
				
			||||||
			if depth > 1 {
 | 
								if depth > 1 {
 | 
				
			||||||
				if let Some(node) = layer.selected_node() {
 | 
									if let Some(node) = layer.tree.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(layer, child_node_ids, remaining_depth) {
 | 
											if !expand_children_to_depth(&mut layer.tree, child_node_ids, remaining_depth) {
 | 
				
			||||||
							return ActionResult::PushLayer(Box::new(MessageDialogLayer::build()
 | 
												return ActionResult::push_layer(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());
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -46,7 +47,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(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool {
 | 
					fn expand_children_to_depth(tree: &mut FsTree, 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();
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -55,8 +56,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, 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;
 | 
				
			||||||
			layer.tree.expand(node_id);
 | 
								tree.expand(node_id);
 | 
				
			||||||
			get_child_node_ids(layer, node_id, &mut child_node_ids);
 | 
								get_child_node_ids(tree, node_id, &mut child_node_ids);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			if start_time.elapsed() >= MAX_EXPANSION_TIME {
 | 
								if start_time.elapsed() >= MAX_EXPANSION_TIME {
 | 
				
			||||||
				return false;
 | 
									return false;
 | 
				
			||||||
@@ -67,8 +68,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
 | 
				
			|||||||
	true
 | 
						true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_child_node_ids(layer: &FsLayer, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
 | 
					fn get_child_node_ids(tree: &FsTree, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
 | 
				
			||||||
	if let Some(node) = layer.tree.view.get(node_id) {
 | 
						if let Some(node) = tree.get_view_node(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.refresh_children(layer.selected_view_node_id) {
 | 
							if layer.tree.refresh_children(layer.tree.selected_view_node_id) {
 | 
				
			||||||
			ActionResult::Draw
 | 
								ActionResult::Draw
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ActionResult::Nothing
 | 
								ActionResult::Nothing
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,62 +0,0 @@
 | 
				
			|||||||
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,32 +1,27 @@
 | 
				
			|||||||
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, FsTreeViewNode};
 | 
					use crate::component::filesystem::tree::FsTree;
 | 
				
			||||||
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,
 | 
				
			||||||
	pub selected_view_node_id: NodeId,
 | 
						tree_structure_version: u32,
 | 
				
			||||||
	pub registers: FsTreeRegisters,
 | 
						pub registers: FsTreeRegisters,
 | 
				
			||||||
	cursor_y: u16,
 | 
						cursor_y: u16,
 | 
				
			||||||
	pending_keys: Vec<KeyBinding>,
 | 
						pending_keys: Vec<KeyBinding>,
 | 
				
			||||||
	pending_events: FsLayerPendingEvents,
 | 
						event_queue: EventQueue<FsLayer>,
 | 
				
			||||||
	file_owner_name_cache: FileOwnerNameCache,
 | 
						file_owner_name_cache: FileOwnerNameCache,
 | 
				
			||||||
	column_width_cache: Option<ColumnWidths>,
 | 
						column_width_cache: Option<ColumnWidths>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -36,70 +31,25 @@ 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,
 | 
								tree: FsTree::with_root_path(root_path),
 | 
				
			||||||
			selected_view_node_id: root_id,
 | 
								tree_structure_version: 0,
 | 
				
			||||||
			cursor_y: 0,
 | 
								cursor_y: 0,
 | 
				
			||||||
			registers: FsTreeRegisters::new(),
 | 
								registers: FsTreeRegisters::new(),
 | 
				
			||||||
			pending_keys: Vec::new(),
 | 
								pending_keys: Vec::new(),
 | 
				
			||||||
			pending_events: Rc::new(RefCell::new(Vec::new())),
 | 
								event_queue: EventQueue::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 {
 | 
				
			||||||
@@ -133,9 +83,14 @@ 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) {
 | 
				
			||||||
		for event in self.pending_events.take() {
 | 
							if self.tree_structure_version != self.tree.structure_version() {
 | 
				
			||||||
			event.handle(self);
 | 
								self.tree_structure_version = self.tree.structure_version();
 | 
				
			||||||
 | 
								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.selected_view_node_id, size.height as usize);
 | 
						let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.tree.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.view.get(selected_node_id).or_else(|| tree.view.root()) {
 | 
						if let Some(middle_node) = tree.selected_node().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,16 +13,39 @@ mod model;
 | 
				
			|||||||
mod view;
 | 
					mod view;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct FsTree {
 | 
					pub struct FsTree {
 | 
				
			||||||
	pub model: FsTreeModel,
 | 
						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();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		Self { model, view }
 | 
							let mut tree = Self {
 | 
				
			||||||
 | 
								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> {
 | 
				
			||||||
@@ -32,26 +55,79 @@ impl FsTree {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn expand(&mut self, view_node_id: NodeId) -> bool {
 | 
						pub fn expand(&mut self, view_node_id: NodeId) -> bool {
 | 
				
			||||||
		self.view.expand(view_node_id, &mut self.model)
 | 
							let result = 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 {
 | 
				
			||||||
		self.view.collapse(view_node_id)
 | 
							let result = 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 {
 | 
				
			||||||
		self.view.expand_or_collapse(view_node_id, &mut self.model)
 | 
							let result = 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> {
 | 
				
			||||||
		self.view.traverse_up_root(&mut self.model)
 | 
							let new_root_id = 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) {
 | 
				
			||||||
			self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model)
 | 
								let result = 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,12 +1,16 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,12 +21,10 @@ pub struct InputFieldOverlayLayer<'a> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> InputFieldOverlayLayer<'a> {
 | 
					impl<'a> InputFieldOverlayLayer<'a> {
 | 
				
			||||||
	pub fn new(read_only_prefix: &'a str, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self {
 | 
						pub fn new<F>(read_only_prefix: &'a str, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
 | 
				
			||||||
		Self {
 | 
							let field = InputField::new();
 | 
				
			||||||
			field: InputField::new(),
 | 
							let confirm_action = Box::new(confirm_action);
 | 
				
			||||||
			read_only_prefix,
 | 
							Self { field, read_only_prefix, confirm_action }
 | 
				
			||||||
			confirm_action,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,6 +55,10 @@ 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 {
 | 
				
			||||||
@@ -62,17 +68,22 @@ 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_style = Style::new()
 | 
							let prefix_text = Span::from(self.read_only_prefix);
 | 
				
			||||||
			.fg(Color::Black)
 | 
							let prefix_width = min(u16::try_from(prefix_text.width()).unwrap_or(u16::MAX), size.width.saturating_sub(2));
 | 
				
			||||||
			.bg(Color::LightYellow);
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		let prefix_paragraph = Paragraph::new(self.read_only_prefix)
 | 
							if prefix_width > 0 {
 | 
				
			||||||
			.style(prefix_style);
 | 
								let prefix_style = Style::new()
 | 
				
			||||||
 | 
									.fg(Color::Black)
 | 
				
			||||||
 | 
									.bg(Color::LightYellow);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
		frame.render_widget(prefix_paragraph, Rect { x, y, width: 1, height: 1 });
 | 
								let prefix_paragraph = Paragraph::new(self.read_only_prefix)
 | 
				
			||||||
 | 
									.style(prefix_style);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
		if size.width > 1 {
 | 
								frame.render_widget(prefix_paragraph, Rect { x, y, width: prefix_width, height: 1 });
 | 
				
			||||||
			self.field.render(frame, x.saturating_add(1), y, size.width.saturating_sub(1), Color::LightYellow, Color::Yellow);
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if size.width > prefix_width {
 | 
				
			||||||
 | 
								self.field.render(frame, x.saturating_add(prefix_width), y, size.width.saturating_sub(prefix_width), Color::LightYellow, Color::Yellow);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,4 +22,12 @@ 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))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								src/state/event.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/state/event.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					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,9 +1,11 @@
 | 
				
			|||||||
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,6 +3,7 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,6 +11,7 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,8 +28,12 @@ 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(|layer| layer.handle_input(&self.environment, key_binding)).unwrap_or(ActionResult::Nothing)
 | 
							self.layers.last_mut().map_or(ActionResult::Nothing, |layer| layer.handle_input(&self.environment, key_binding))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn handle_resize(&mut self, width: u16, height: u16) {
 | 
						pub fn handle_resize(&mut self, width: u16, height: u16) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ 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 {
 | 
				
			||||||
@@ -22,7 +23,7 @@ impl View {
 | 
				
			|||||||
		term.hide_cursor()?;
 | 
							term.hide_cursor()?;
 | 
				
			||||||
		term.clear()?;
 | 
							term.clear()?;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		Ok(Self { term })
 | 
							Ok(Self { term, render_request: RenderRequest::Draw })
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn restore_terminal_on_panic() {
 | 
						pub fn restore_terminal_on_panic() {
 | 
				
			||||||
@@ -45,11 +46,34 @@ impl View {
 | 
				
			|||||||
		self.term.size()
 | 
							self.term.size()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn clear(&mut self) -> io::Result<()> {
 | 
						pub fn set_dirty(&mut self, full_redraw: bool) {
 | 
				
			||||||
		self.term.clear()
 | 
							let new_request = if full_redraw {
 | 
				
			||||||
 | 
								RenderRequest::Redraw
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								RenderRequest::Draw
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							self.render_request = self.render_request.merge(new_request);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pub fn render<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) {
 | 
						pub fn render<R>(&mut self, renderer: R) -> io::Result<()> 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);
 | 
				
			||||||
@@ -58,6 +82,29 @@ 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)>,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user