use crate::core::geometry::Rect;
use crate::core::event::{Event, EventType, KB_ESC};
use crate::core::state::StateFlags;
use crate::core::command::{CM_CANCEL, CommandId};
use crate::terminal::Terminal;
use super::view::View;
use super::window::Window;
use super::help_viewer::HelpViewer;
use super::help_file::HelpFile;
use std::rc::Rc;
use std::cell::RefCell;
pub struct HelpWindow {
window: Window,
viewer: HelpViewer,
help_file: Rc<RefCell<HelpFile>>,
history: Vec<String>,
history_pos: usize,
}
impl HelpWindow {
pub fn new(bounds: Rect, title: &str, help_file: Rc<RefCell<HelpFile>>) -> Self {
let window = Window::new(bounds, title);
let viewer_bounds = Rect::new(1, 1, bounds.width() - 2, bounds.height() - 2);
let viewer = HelpViewer::new(viewer_bounds).with_scrollbar();
Self {
window,
viewer,
help_file,
history: Vec::new(),
history_pos: 0,
}
}
pub fn show_topic(&mut self, topic_id: &str) -> bool {
let help = self.help_file.borrow();
if let Some(topic) = help.get_topic(topic_id) {
self.viewer.set_topic(topic);
true
} else {
false
}
}
pub fn show_default_topic(&mut self) {
let help = self.help_file.borrow();
if let Some(topic) = help.get_default_topic() {
self.viewer.set_topic(topic);
}
}
pub fn current_topic(&self) -> Option<String> {
self.viewer.current_topic().map(|s| s.to_string())
}
pub fn viewer_mut(&mut self) -> &mut HelpViewer {
&mut self.viewer
}
pub fn viewer(&self) -> &HelpViewer {
&self.viewer
}
pub fn help_file(&self) -> &Rc<RefCell<HelpFile>> {
&self.help_file
}
pub fn switch_to_topic(&mut self, topic_id: &str) -> bool {
let help = self.help_file.borrow();
if help.get_topic(topic_id).is_none() {
return false;
}
drop(help);
if self.history_pos < self.history.len() {
self.history.truncate(self.history_pos);
}
if let Some(current) = self.viewer.current_topic() {
self.history.push(current.to_string());
}
let success = self.show_topic(topic_id);
if success {
self.history_pos = self.history.len();
}
success
}
pub fn go_back(&mut self) -> bool {
if self.history_pos > 0 {
self.history_pos -= 1;
let topic_id = self.history[self.history_pos].clone();
self.show_topic(&topic_id);
true
} else {
false
}
}
pub fn go_forward(&mut self) -> bool {
if self.history_pos < self.history.len() {
let topic_id = self.history[self.history_pos].clone();
self.history_pos += 1;
self.show_topic(&topic_id);
true
} else {
false
}
}
pub fn can_go_back(&self) -> bool {
self.history_pos > 0
}
pub fn can_go_forward(&self) -> bool {
self.history_pos < self.history.len()
}
pub fn make_select_topic(&self) -> Option<String> {
let help = self.help_file.borrow();
let topics = help.get_topic_ids();
if topics.is_empty() {
return None;
}
Some(topics[0].clone())
}
pub fn execute(&mut self, app: &mut crate::app::Application) -> CommandId {
self.window.execute(app)
}
pub fn end_modal(&mut self, command: CommandId) {
self.window.end_modal(command);
}
}
impl View for HelpWindow {
fn bounds(&self) -> Rect {
self.window.bounds()
}
fn set_bounds(&mut self, bounds: Rect) {
self.window.set_bounds(bounds);
let viewer_bounds = Rect::new(1, 1, bounds.width() - 2, bounds.height() - 2);
self.viewer.set_bounds(viewer_bounds);
}
fn draw(&mut self, terminal: &mut Terminal) {
self.window.draw(terminal);
self.viewer.draw(terminal);
}
fn handle_event(&mut self, event: &mut Event) {
if event.what == EventType::Keyboard && event.key_code == KB_ESC {
self.window.end_modal(CM_CANCEL);
event.clear();
return;
}
self.viewer.handle_event(event);
self.window.handle_event(event);
}
fn can_focus(&self) -> bool {
true
}
fn state(&self) -> StateFlags {
self.window.state()
}
fn set_state(&mut self, state: StateFlags) {
self.window.set_state(state);
self.viewer.set_state(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
fn create_test_help_file() -> (NamedTempFile, Rc<RefCell<HelpFile>>) {
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "# Test Topic {{#test}}").unwrap();
writeln!(file, "").unwrap();
writeln!(file, "This is test content.").unwrap();
file.flush().unwrap();
let help = HelpFile::new(file.path().to_str().unwrap()).unwrap();
(file, Rc::new(RefCell::new(help)))
}
#[test]
fn test_help_window_creation() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let window = HelpWindow::new(bounds, "Help", help);
assert_eq!(window.bounds(), bounds);
}
#[test]
fn test_show_topic() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let mut window = HelpWindow::new(bounds, "Help", help);
assert!(window.show_topic("test"));
assert_eq!(window.current_topic(), Some("test".to_string()));
}
#[test]
fn test_show_default_topic() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let mut window = HelpWindow::new(bounds, "Help", help);
window.show_default_topic();
assert_eq!(window.current_topic(), Some("test".to_string()));
}
#[test]
fn test_show_nonexistent_topic() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let mut window = HelpWindow::new(bounds, "Help", help);
assert!(!window.show_topic("nonexistent"));
}
}