[tui] Remove most usage of expect().
Still not quite sure what to do about some pieces in QML bindings for the GUI.
This commit is contained in:
parent
ce2949159c
commit
42a40fe7f3
15
src/main.rs
15
src/main.rs
@ -1,6 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use std::io::stdout;
|
||||
use std::process::{Command, Stdio};
|
||||
use crate::tui::Tui;
|
||||
|
||||
pub mod gui;
|
||||
pub mod tui;
|
||||
@ -30,16 +34,17 @@ fn main() -> Result<()> {
|
||||
// If the CLI was provided a directory, convert it to absolute.
|
||||
Some(path) => std::path::absolute(path)?,
|
||||
// If no path was provided, use the current directory.
|
||||
None => std::env::current_dir().unwrap_or_else(|_|
|
||||
// If we can't find the CWD, attempt to open the home directory.
|
||||
dirs::home_dir().expect("Failed to access filesystem.")),
|
||||
None => std::env::current_dir().unwrap_or(
|
||||
// If we can't find the CWD, attempt to open the home directory.
|
||||
dirs::home_dir().context("Failed to obtain home directory")?,
|
||||
),
|
||||
};
|
||||
|
||||
match args.gui {
|
||||
true => gui::run(root_path),
|
||||
false => match args.tui {
|
||||
// Open the TUI editor if requested, otherwise use the QML GUI by default.
|
||||
true => Ok(tui::Tui::new(root_path).start()?),
|
||||
true => Ok(Tui::new(root_path)?.start()?),
|
||||
false => {
|
||||
// Relaunch the CLIDE GUI in a separate process.
|
||||
Command::new(std::env::current_exe()?)
|
||||
|
||||
11
src/tui.rs
11
src/tui.rs
@ -20,12 +20,11 @@ pub struct Tui {
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
pub fn new(root_path: std::path::PathBuf) -> Self {
|
||||
Self {
|
||||
terminal: Terminal::new(CrosstermBackend::new(stdout()))
|
||||
.expect("Failed to initialize terminal"),
|
||||
pub fn new(root_path: std::path::PathBuf) -> Result<Self> {
|
||||
Ok(Self {
|
||||
terminal: Terminal::new(CrosstermBackend::new(stdout()))?,
|
||||
root_path,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(self) -> Result<()> {
|
||||
@ -38,7 +37,7 @@ impl Tui {
|
||||
)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
let app_result = app::App::new(self.root_path)
|
||||
let app_result = app::App::new(self.root_path)?
|
||||
.run(self.terminal)
|
||||
.context("Failed to start the TUI editor.");
|
||||
Self::stop()?;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::tui::component::{Action, Component};
|
||||
use crate::tui::editor::Editor;
|
||||
use crate::tui::explorer::Explorer;
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::crossterm::event;
|
||||
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
@ -23,18 +23,21 @@ pub struct App<'a> {
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
pub(crate) fn new(root_path: PathBuf) -> Self {
|
||||
pub fn new(root_path: PathBuf) -> Result<Self> {
|
||||
let mut app = Self {
|
||||
components: vec![
|
||||
AppComponents::AppExplorer(Explorer::new(&root_path)),
|
||||
AppComponents::AppExplorer(Explorer::new(&root_path)?),
|
||||
AppComponents::AppEditor(Editor::new()),
|
||||
],
|
||||
};
|
||||
app.get_editor_mut()
|
||||
.unwrap()
|
||||
.set_contents(&root_path.join("src/tui/app.rs"))
|
||||
.expect("Failed to set editor contents.");
|
||||
app
|
||||
.context(format!(
|
||||
"Failed to initialize editor contents to path: {}",
|
||||
root_path.to_string_lossy()
|
||||
))?;
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
fn get_explorer(&self) -> Result<&Explorer<'a>> {
|
||||
@ -77,27 +80,22 @@ impl<'a> App<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_event(&mut self) -> Option<Event> {
|
||||
if !event::poll(Duration::from_millis(250)).expect("event poll failed") {
|
||||
return None;
|
||||
}
|
||||
|
||||
event::read().ok()
|
||||
}
|
||||
|
||||
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
loop {
|
||||
// TODO: Handle events based on which component is active.
|
||||
self.refresh_editor_contents()
|
||||
.context("Failed to refresh editor contents.")?;
|
||||
|
||||
terminal.draw(|f| {
|
||||
f.render_widget(&mut self, f.area());
|
||||
})?;
|
||||
|
||||
if let Some(event) = self.get_event() {
|
||||
match self.handle_event(event) {
|
||||
// TODO: Handle events based on which component is active.
|
||||
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
|
||||
match self.handle_event(event::read()?)? {
|
||||
Action::Quit => break,
|
||||
Action::Handled => {}
|
||||
_ => {
|
||||
// panic!("Unhandled event: {:?}", event);
|
||||
// anyhow::anyhow!("Unhandled event: {:?}", event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,7 +158,7 @@ impl<'a> App<'a> {
|
||||
};
|
||||
let editor = self
|
||||
.get_editor_mut()
|
||||
.expect("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 selected_pathbuf == current_file_path || !selected_pathbuf.is_file() {
|
||||
return Ok(());
|
||||
@ -208,8 +206,6 @@ impl<'a> Widget for &mut App<'a> {
|
||||
explorer.render(horizontal[0], buf);
|
||||
}
|
||||
self.draw_tabs(editor_layout[0], buf);
|
||||
self.refresh_editor_contents()
|
||||
.expect("Failed to refresh editor contents.");
|
||||
self.get_editor_mut().unwrap().render(editor_layout[1], buf);
|
||||
}
|
||||
}
|
||||
@ -227,12 +223,14 @@ impl<'a> Component for App<'a> {
|
||||
/// (such as editor tabs, file explorer, status bars, etc..)
|
||||
///
|
||||
/// Handles events for the App and delegates to attached Components.
|
||||
fn handle_event(&mut self, event: Event) -> Action {
|
||||
fn handle_event(&mut self, event: Event) -> Result<Action> {
|
||||
// Handle events in the primary application.
|
||||
if let Some(key_event) = event.as_key_event() {
|
||||
match self.handle_key_events(key_event) {
|
||||
Action::Quit => return Action::Quit,
|
||||
Action::Handled => return Action::Handled,
|
||||
let res = self
|
||||
.handle_key_events(key_event)
|
||||
.context("Failed to handle key events for primary App Component.");
|
||||
match res {
|
||||
Ok(Action::Quit) | Ok(Action::Handled) => return res,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -240,29 +238,29 @@ impl<'a> Component for App<'a> {
|
||||
// Handle events for all components.
|
||||
for component in &mut self.components {
|
||||
let action = match component {
|
||||
AppComponents::AppEditor(editor) => editor.handle_event(event.clone()),
|
||||
AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone()),
|
||||
AppComponents::AppComponent(comp) => comp.handle_event(event.clone()),
|
||||
AppComponents::AppEditor(editor) => editor.handle_event(event.clone())?,
|
||||
AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone())?,
|
||||
AppComponents::AppComponent(comp) => comp.handle_event(event.clone())?,
|
||||
};
|
||||
// Actions returned here abort the input handling iteration.
|
||||
match action {
|
||||
Action::Quit | Action::Handled => return action,
|
||||
Action::Quit | Action::Handled => return Ok(action),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Action::Noop
|
||||
Ok(Action::Noop)
|
||||
}
|
||||
|
||||
/// Handles key events for the App Component only.
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Action> {
|
||||
match key {
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: _state,
|
||||
} => Action::Quit,
|
||||
_ => Action::Noop,
|
||||
} => Ok(Action::Quit),
|
||||
_ => Ok(Action::Noop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
use anyhow::Result;
|
||||
use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent};
|
||||
|
||||
pub enum Action {
|
||||
@ -25,22 +26,22 @@ pub trait Component {
|
||||
/// This is used for lookup in a container of Components.
|
||||
fn id(&self) -> &str;
|
||||
|
||||
fn handle_event(&mut self, event: Event) -> Action {
|
||||
fn handle_event(&mut self, event: Event) -> Result<Action> {
|
||||
match event {
|
||||
Event::Key(key_event) => self.handle_key_events(key_event),
|
||||
_ => Action::Noop,
|
||||
_ => Ok(Action::Noop),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
|
||||
Action::Noop
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Action> {
|
||||
Ok(Action::Noop)
|
||||
}
|
||||
|
||||
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action {
|
||||
Action::Noop
|
||||
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result<Action> {
|
||||
Ok(Action::Noop)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Action {
|
||||
Action::Noop
|
||||
fn update(&mut self, action: Action) -> Result<Action> {
|
||||
Ok(Action::Noop)
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,32 +90,32 @@ impl Component for Editor {
|
||||
"editor"
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: Event) -> Action {
|
||||
fn handle_event(&mut self, event: Event) -> Result<Action> {
|
||||
if let Some(key_event) = event.as_key_event() {
|
||||
// Handle events here that should not be passed on to the vim emulation handler.
|
||||
match self.handle_key_events(key_event) {
|
||||
Action::Handled => return Action::Handled,
|
||||
match self.handle_key_events(key_event)? {
|
||||
Action::Handled => return Ok(Action::Handled),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.event_handler.on_event(event, &mut self.state);
|
||||
Action::Pass
|
||||
Ok(Action::Pass)
|
||||
}
|
||||
|
||||
/// The events for the vim emulation should be handled by EditorEventHandler::on_event.
|
||||
/// These events are custom to the clide application.
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Action> {
|
||||
match key {
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
} => {
|
||||
self.save().expect("Failed to save file.");
|
||||
Action::Handled
|
||||
self.save().context("Failed to save file.")?;
|
||||
Ok(Action::Handled)
|
||||
}
|
||||
// For other events not handled here, pass to the vim emulation handler.
|
||||
_ => Action::Noop,
|
||||
_ => Ok(Action::Noop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::tui::component::{Action, Component};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind};
|
||||
use ratatui::layout::{Alignment, Position, Rect};
|
||||
@ -17,33 +17,36 @@ pub struct Explorer<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Explorer<'a> {
|
||||
pub fn new(path: &std::path::PathBuf) -> Self {
|
||||
pub fn new(path: &std::path::PathBuf) -> Result<Self> {
|
||||
let explorer = Explorer {
|
||||
root_path: path.to_owned(),
|
||||
tree_items: Self::build_tree_from_path(path.to_owned()),
|
||||
tree_items: Self::build_tree_from_path(path.to_owned())?,
|
||||
tree_state: TreeState::default(),
|
||||
};
|
||||
explorer
|
||||
Ok(explorer)
|
||||
}
|
||||
|
||||
fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> {
|
||||
fn build_tree_from_path(path: std::path::PathBuf) -> Result<TreeItem<'static, String>> {
|
||||
let mut children = vec![];
|
||||
if let Ok(entries) = fs::read_dir(&path) {
|
||||
let mut paths = entries
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.collect::<Result<Vec<_>, std::io::Error>>()
|
||||
.expect("");
|
||||
.context(format!(
|
||||
"Failed to build vector of paths under directory: {:?}",
|
||||
path
|
||||
))?;
|
||||
paths.sort();
|
||||
for path in paths {
|
||||
if path.is_dir() {
|
||||
children.push(Self::build_tree_from_path(path));
|
||||
children.push(Self::build_tree_from_path(path)?);
|
||||
} else {
|
||||
if let Ok(path) = std::path::absolute(&path) {
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
children.push(TreeItem::new_leaf(
|
||||
path_str,
|
||||
path.file_name()
|
||||
.expect("Failed to get file name from path.")
|
||||
.context("Failed to get file name from path.")?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
));
|
||||
@ -53,13 +56,10 @@ impl<'a> Explorer<'a> {
|
||||
}
|
||||
|
||||
let abs = std::path::absolute(&path)
|
||||
.expect(
|
||||
format!(
|
||||
"Failed to find absolute path for TreeItem: {}",
|
||||
path.to_string_lossy().to_string()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.context(format!(
|
||||
"Failed to find absolute path for TreeItem: {:?}",
|
||||
path
|
||||
))?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
TreeItem::new(
|
||||
@ -70,13 +70,13 @@ impl<'a> Explorer<'a> {
|
||||
.to_string(),
|
||||
children,
|
||||
)
|
||||
.expect("Failed to build tree from path.")
|
||||
.context("Failed to build tree from path.")
|
||||
}
|
||||
|
||||
pub fn render(&mut self, area: Rect, buf: &mut Buffer) {
|
||||
pub fn render(&mut self, area: Rect, buf: &mut Buffer) -> Result<()> {
|
||||
StatefulWidget::render(
|
||||
Tree::new(&self.tree_items.children())
|
||||
.expect("Failed to build tree.")
|
||||
.context("Failed to build file Explorer Tree.")?
|
||||
.style(Style::default())
|
||||
.block(
|
||||
Block::default()
|
||||
@ -84,7 +84,7 @@ impl<'a> Explorer<'a> {
|
||||
.title(
|
||||
self.root_path
|
||||
.file_name()
|
||||
.expect("Failed to get file name from path.")
|
||||
.context("Failed to get file name from path.")?
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.title_style(Style::default().fg(Color::Green))
|
||||
@ -99,7 +99,8 @@ impl<'a> Explorer<'a> {
|
||||
area,
|
||||
buf,
|
||||
&mut self.tree_state,
|
||||
)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Result<String> {
|
||||
@ -114,24 +115,24 @@ impl<'a> Component for Explorer<'a> {
|
||||
fn id(&self) -> &str {
|
||||
"explorer"
|
||||
}
|
||||
fn handle_event(&mut self, event: Event) -> Action {
|
||||
fn handle_event(&mut self, event: Event) -> Result<Action> {
|
||||
if let Some(key_event) = event.as_key_event() {
|
||||
// Handle events here that should not be passed on to the vim emulation handler.
|
||||
match self.handle_key_events(key_event) {
|
||||
Action::Handled => return Action::Handled,
|
||||
match self.handle_key_events(key_event)? {
|
||||
Action::Handled => return Ok(Action::Handled),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(mouse_event) = event.as_mouse_event() {
|
||||
match self.handle_mouse_events(mouse_event) {
|
||||
Action::Handled => return Action::Handled,
|
||||
match self.handle_mouse_events(mouse_event)? {
|
||||
Action::Handled => return Ok(Action::Handled),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Action::Pass
|
||||
Ok(Action::Pass)
|
||||
}
|
||||
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
|
||||
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Action> {
|
||||
let changed = match key.code {
|
||||
KeyCode::Up => self.tree_state.key_up(),
|
||||
KeyCode::Char('k') => self.tree_state.key_up(),
|
||||
@ -145,12 +146,12 @@ impl<'a> Component for Explorer<'a> {
|
||||
_ => false,
|
||||
};
|
||||
if changed {
|
||||
return Action::Handled;
|
||||
return Ok(Action::Handled);
|
||||
}
|
||||
Action::Noop
|
||||
Ok(Action::Noop)
|
||||
}
|
||||
|
||||
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action {
|
||||
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result<Action> {
|
||||
let changed = match mouse.kind {
|
||||
MouseEventKind::ScrollDown => self.tree_state.scroll_down(1),
|
||||
MouseEventKind::ScrollUp => self.tree_state.scroll_up(1),
|
||||
@ -160,8 +161,8 @@ impl<'a> Component for Explorer<'a> {
|
||||
_ => false,
|
||||
};
|
||||
if changed {
|
||||
return Action::Handled;
|
||||
return Ok(Action::Handled);
|
||||
}
|
||||
Action::Noop
|
||||
Ok(Action::Noop)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user