[tui] Add ComponentOf trait.

I think it will help with fetching a component by type from the
Components vector attached to App?
This commit is contained in:
Shaun Reed 2026-01-20 20:43:01 -05:00
parent 7149ad0118
commit 4d81cd51a6

View File

@ -24,6 +24,59 @@ pub enum AppComponents<'a> {
AppComponent(Box<dyn Component>), AppComponent(Box<dyn Component>),
} }
/// Usage: get_component_mut::<Editor>() OR get_component::<Editor>()
///
/// Implementing this trait for each AppComponent allows for easy lookup in the vector.
trait ComponentOf<T> {
fn as_ref(&self) -> Option<&T>;
fn as_mut(&mut self) -> Option<&mut T>;
}
impl<'a> ComponentOf<Logger> for AppComponents<'a> {
fn as_ref(&self) -> Option<&Logger> {
if let AppComponents::AppLogger(ref e) = *self {
return Some(e);
}
None
}
fn as_mut(&mut self) -> Option<&mut Logger> {
if let AppComponents::AppLogger(ref mut e) = *self {
return Some(e);
}
None
}
}
impl<'a> ComponentOf<Editor> for AppComponents<'a> {
fn as_ref(&self) -> Option<&Editor> {
if let AppComponents::AppEditor(ref e) = *self {
return Some(e);
}
None
}
fn as_mut(&mut self) -> Option<&mut Editor> {
if let AppComponents::AppEditor(ref mut e) = *self {
return Some(e);
}
None
}
}
impl<'a> ComponentOf<Explorer<'a>> for AppComponents<'a> {
fn as_ref(&self) -> Option<&Explorer<'a>> {
if let AppComponents::AppExplorer(ref e) = *self {
return Some(e);
}
None
}
fn as_mut(&mut self) -> Option<&mut Explorer<'a>> {
if let AppComponents::AppExplorer(ref mut e) = *self {
return Some(e);
}
None
}
}
pub struct App<'a> { pub struct App<'a> {
components: Vec<AppComponents<'a>>, components: Vec<AppComponents<'a>>,
} }
@ -37,7 +90,7 @@ impl<'a> App<'a> {
AppComponents::AppLogger(Logger::new()), AppComponents::AppLogger(Logger::new()),
], ],
}; };
app.get_editor_mut() app.get_component_mut::<Editor>()
.unwrap() .unwrap()
.set_contents(&root_path.join("src/tui/app.rs")) .set_contents(&root_path.join("src/tui/app.rs"))
.context(format!( .context(format!(
@ -47,44 +100,18 @@ impl<'a> App<'a> {
Ok(app) Ok(app)
} }
fn get_explorer(&self) -> Result<&Explorer<'a>> { fn get_component<T>(&self) -> Option<&T>
for component in &self.components { where
if let AppComponents::AppExplorer(explorer) = component { AppComponents<'a>: ComponentOf<T>,
return Ok(explorer); {
} self.components.iter().find_map(|c| c.as_ref())
}
bail!("Failed to find project explorer widget.")
} }
fn get_explorer_mut(&mut self) -> Result<&mut Explorer<'a>> { fn get_component_mut<T>(&mut self) -> Option<&mut T>
for component in &mut self.components { where
if let AppComponents::AppExplorer(explorer) = component { AppComponents<'a>: ComponentOf<T>,
return Ok(explorer); {
} self.components.iter_mut().find_map(|c| c.as_mut())
}
bail!("Failed to find project explorer widget.")
}
fn get_editor(&self) -> Option<&Editor> {
for component in &self.components {
if let AppComponents::AppEditor(editor) = component {
return Some(editor);
}
}
// There is no editor currently opened.
None
}
fn get_editor_mut(&mut self) -> Option<&mut Editor> {
for component in &mut self.components {
if let AppComponents::AppEditor(editor) = component {
return Some(editor);
}
}
// There is no editor currently opened.
None
} }
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
@ -121,7 +148,7 @@ impl<'a> App<'a> {
fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { fn draw_tabs(&self, area: Rect, buf: &mut Buffer) {
// Determine the tab title from the current file (or use a fallback). // Determine the tab title from the current file (or use a fallback).
let mut title: Option<&str> = None; let mut title: Option<&str> = None;
if let Some(editor) = self.get_editor() { if let Some(editor) = self.get_component::<Editor>() {
title = editor title = editor
.file_path .file_path
.as_ref() .as_ref()
@ -144,12 +171,12 @@ impl<'a> App<'a> {
/// If the selected item is not a file, this does nothing. /// If the selected item is not a file, this does nothing.
fn refresh_editor_contents(&mut self) -> Result<()> { fn refresh_editor_contents(&mut self) -> Result<()> {
// Use the currently selected TreeItem or get an absolute path to this source file. // Use the currently selected TreeItem or get an absolute path to this source file.
let selected_pathbuf = match self.get_explorer()?.selected() { let selected_pathbuf = match self.get_component::<Explorer>().unwrap().selected() {
Ok(path) => PathBuf::from(path), Ok(path) => PathBuf::from(path),
Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()), Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()),
}; };
let editor = self let editor = self
.get_editor_mut() .get_component_mut::<Editor>()
.context("Failed to get active editor while refreshing contents.")?; .context("Failed to get active editor while refreshing contents.")?;
if let Some(current_file_path) = editor.file_path.clone() { if let Some(current_file_path) = editor.file_path.clone() {
if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() {
@ -194,15 +221,15 @@ impl<'a> Widget for &mut App<'a> {
self.draw_status(vertical[0], buf); self.draw_status(vertical[0], buf);
self.draw_tabs(editor_layout[0], buf); self.draw_tabs(editor_layout[0], buf);
let id = self.id().to_string();
for component in &mut self.components { for component in &mut self.components {
match component { match component {
AppComponents::AppEditor(editor) => editor.render(editor_layout[1], buf), AppComponents::AppEditor(editor) => editor.render(editor_layout[1], buf),
AppComponents::AppExplorer(explorer) => { AppComponents::AppExplorer(explorer) => {
// TODO: What to do about errors during rendering?
// Once there is a debug console, maybe log it and discard? Panic isn't great.
explorer explorer
.render(horizontal[0], buf) .render(horizontal[0], buf)
.expect("Failed to render Explorer"); .context("Failed to render Explorer")
.unwrap_or_else(|e| error!(target:id.as_str(), "{}", e));
} }
AppComponents::AppLogger(logger) => logger.render(vertical[2], buf), AppComponents::AppLogger(logger) => logger.render(vertical[2], buf),
AppComponents::AppComponent(_) => {} AppComponents::AppComponent(_) => {}