girt-core 2.2.1

Core modules for git-interactive-rebase-tool
Documentation
use std::collections::HashMap;

use input::{EventHandler, Sender as EventSender};
use todo_file::TodoFile;
use view::{RenderContext, ViewData, ViewSender};

use super::{Module, ProcessResult, State};
use crate::events::{AppKeyBindings, MetaEvent};

pub(crate) struct Modules<'modules> {
	event_handler: EventHandler<AppKeyBindings, MetaEvent>,
	modules: HashMap<State, Box<dyn Module + 'modules>>,
}

impl<'modules> Modules<'modules> {
	pub(crate) fn new(event_handler: EventHandler<AppKeyBindings, MetaEvent>) -> Self {
		Self {
			event_handler,
			modules: HashMap::new(),
		}
	}

	pub(crate) fn register_module<T: Module + 'modules>(&mut self, state: State, module: T) {
		let _previous = self.modules.insert(state, Box::new(module));
	}

	#[allow(clippy::panic)]
	fn get_mut_module(&mut self, state: State) -> &mut Box<dyn Module + 'modules> {
		self.modules
			.get_mut(&state)
			.unwrap_or_else(|| panic!("Invalid module for provided state: {:?}. Please report.", state))
	}

	#[allow(clippy::borrowed_box, clippy::panic)]
	fn get_module(&self, state: State) -> &Box<dyn Module + 'modules> {
		self.modules
			.get(&state)
			.unwrap_or_else(|| panic!("Invalid module for provided state: {:?}", state))
	}

	pub(crate) fn activate(&mut self, state: State, rebase_todo: &TodoFile, previous_state: State) -> ProcessResult {
		self.get_mut_module(state).activate(rebase_todo, previous_state)
	}

	pub(crate) fn deactivate(&mut self, state: State) {
		self.get_mut_module(state).deactivate();
	}

	pub(crate) fn build_view_data(
		&mut self,
		state: State,
		render_context: &RenderContext,
		rebase_todo: &TodoFile,
	) -> &ViewData {
		self.get_mut_module(state).build_view_data(render_context, rebase_todo)
	}

	pub(crate) fn handle_event(
		&mut self,
		state: State,
		event_sender: &mut EventSender<MetaEvent>,
		view_sender: &ViewSender,
		rebase_todo: &mut TodoFile,
	) -> ProcessResult {
		let module = self.get_module(state);
		let input_options = module.input_options();
		let event = self
			.event_handler
			.read_event(event_sender.read_event(), input_options, |event, key_bindings| {
				module.read_event(event, key_bindings)
			});
		self.get_mut_module(state).handle_event(event, view_sender, rebase_todo)
	}

	pub(crate) fn error(&mut self, state: State, error: &anyhow::Error) {
		self.get_mut_module(state).handle_error(error);
	}
}

#[cfg(test)]
mod tests {
	use std::sync::Arc;

	use anyhow::{anyhow, Error};
	use input::StandardEvent;
	use parking_lot::Mutex;

	use super::*;
	use crate::{events::Event, testutil::module_test};

	#[derive(Debug, Clone)]
	struct TestModule {
		view_data: Arc<ViewData>,
		trace: Arc<Mutex<Vec<String>>>,
	}

	impl TestModule {
		fn new() -> Self {
			Self {
				view_data: Arc::new(ViewData::new(|_| {})),
				trace: Arc::new(Mutex::new(vec![])),
			}
		}

		fn trace(&self) -> String {
			self.trace.lock().join(",")
		}
	}

	impl Module for TestModule {
		fn activate(&mut self, _rebase_todo: &TodoFile, _previous_state: State) -> ProcessResult {
			self.trace.lock().push(String::from("Activate"));
			ProcessResult::new()
		}

		fn deactivate(&mut self) {
			self.trace.lock().push(String::from("Deactivate"));
		}

		fn build_view_data(&mut self, _render_context: &RenderContext, _rebase_todo: &TodoFile) -> &ViewData {
			self.trace.lock().push(String::from("Build View Data"));
			&self.view_data
		}

		fn handle_event(&mut self, _: Event, _: &ViewSender, _: &mut TodoFile) -> ProcessResult {
			self.trace.lock().push(String::from("Handle Events"));
			ProcessResult::new()
		}

		fn handle_error(&mut self, error: &Error) {
			self.trace.lock().push(error.to_string());
		}
	}

	#[test]
	fn module_lifecycle() {
		module_test(
			&["pick aaa comment"],
			&[Event::Standard(StandardEvent::Exit)],
			|mut context| {
				let mut modules = Modules::new(context.event_handler_context.event_handler);
				let test_module = TestModule::new();
				modules.register_module(State::List, test_module.clone());

				let _ = modules.activate(State::List, &context.rebase_todo_file, State::Insert);
				let _ = modules.handle_event(
					State::List,
					&mut context.event_handler_context.sender,
					&context.view_sender_context.sender,
					&mut context.rebase_todo_file,
				);
				let _ = modules.build_view_data(State::List, &RenderContext::new(100, 100), &context.rebase_todo_file);
				modules.deactivate(State::List);
				assert_eq!(test_module.trace(), "Activate,Handle Events,Build View Data,Deactivate");
			},
		);
	}

	#[test]
	fn error() {
		module_test(
			&["pick aaa comment"],
			&[Event::Standard(StandardEvent::Exit)],
			|context| {
				let mut modules = Modules::new(context.event_handler_context.event_handler);
				let test_module = TestModule::new();
				modules.register_module(State::Error, test_module.clone());
				modules.error(State::Error, &anyhow!("Test Error"));
				assert_eq!(test_module.trace(), "Test Error");
			},
		);
	}
}