189 lines
4.9 KiB
Rust
189 lines
4.9 KiB
Rust
use ratatui::{
|
|
buffer::Buffer,
|
|
layout::{Constraint, Direction, Layout, Rect},
|
|
style::{Color, Style},
|
|
text::Line,
|
|
widgets::{StatefulWidget, Widget},
|
|
};
|
|
|
|
use std::{
|
|
cell::RefCell,
|
|
env, fs, io, mem,
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
};
|
|
|
|
use crate::{config::Config, directory_operations};
|
|
|
|
pub enum ScrollingDirection {
|
|
Up,
|
|
Down,
|
|
}
|
|
|
|
pub struct FilesWidget;
|
|
pub struct FilesState {
|
|
selected: Option<usize>,
|
|
top: usize,
|
|
files: Vec<PathBuf>,
|
|
current_dir: PathBuf,
|
|
config: Rc<RefCell<Config>>,
|
|
}
|
|
|
|
impl FilesState {
|
|
pub fn get_current(&self) -> Option<&Path> {
|
|
Some(self.files[self.selected?].as_path())
|
|
}
|
|
|
|
pub fn scroll(&mut self, direction: ScrollingDirection) {
|
|
if self.files.is_empty() {
|
|
return;
|
|
}
|
|
|
|
match direction {
|
|
ScrollingDirection::Down => self.scroll_down(),
|
|
ScrollingDirection::Up => self.scroll_up(),
|
|
}
|
|
}
|
|
|
|
fn scroll_down(&mut self) {
|
|
match self.selected {
|
|
Some(v) => self.selected = Some((v + 1).min(self.files.len() - 1)),
|
|
None => self.selected = Some(0),
|
|
}
|
|
}
|
|
|
|
fn scroll_up(&mut self) {
|
|
self.selected = Some(self.selected.unwrap_or(0).saturating_sub(1));
|
|
}
|
|
|
|
pub fn go_up(&mut self) -> io::Result<()> {
|
|
let new_path = self.current_dir.parent().unwrap_or(Path::new("/"));
|
|
if !directory_operations::can_read(new_path) {
|
|
return Ok(());
|
|
}
|
|
self.current_dir.pop();
|
|
self.update_contents()
|
|
}
|
|
|
|
fn update_contents(&mut self) -> io::Result<()> {
|
|
self.files = fs::read_dir(&self.current_dir)?
|
|
.filter_map(|entry| entry.ok())
|
|
.map(|entry| entry.path())
|
|
.collect();
|
|
|
|
directory_operations::sort_dir(&self.config.borrow(), &mut self.files);
|
|
|
|
self.selected = if self.files.is_empty() { None } else { Some(0) };
|
|
self.top = 0;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn go_inner(&mut self) -> io::Result<()> {
|
|
let Some(selected) = self.selected else {
|
|
return Ok(());
|
|
};
|
|
|
|
if !self.files[selected].is_dir() {
|
|
return Ok(());
|
|
}
|
|
|
|
if !directory_operations::can_read(self.files[selected].as_path()) {
|
|
return Ok(());
|
|
}
|
|
|
|
self.current_dir = self.files.swap_remove(selected);
|
|
self.update_contents()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn toggle_hidden(&mut self) -> io::Result<()> {
|
|
let current_state = self.config.borrow().show_hidden;
|
|
self.set_hidden(!current_state)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn set_hidden(&mut self, state: bool) -> io::Result<()> {
|
|
{
|
|
let mut cfg = self.config.borrow_mut();
|
|
cfg.show_hidden = state;
|
|
}
|
|
|
|
self.update_contents()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn new(config: Rc<RefCell<Config>>) -> io::Result<Self> {
|
|
let current_dir = env::current_dir()?;
|
|
|
|
let mut output = FilesState {
|
|
selected: None,
|
|
files: vec![],
|
|
top: 0,
|
|
current_dir,
|
|
config,
|
|
};
|
|
|
|
output.update_contents()?;
|
|
|
|
Ok(output)
|
|
}
|
|
}
|
|
|
|
impl StatefulWidget for FilesWidget {
|
|
type State = FilesState;
|
|
|
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
|
let layout = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
.split(area);
|
|
|
|
let visible_rows = layout[0].height as usize;
|
|
|
|
if let Some(selected) = state.selected {
|
|
if selected < state.top {
|
|
state.top = selected;
|
|
} else if selected >= state.top + visible_rows {
|
|
state.top = selected - visible_rows + 1;
|
|
}
|
|
}
|
|
|
|
let mut items: Vec<Line> = state
|
|
.files
|
|
.iter()
|
|
.skip(state.top)
|
|
.take(visible_rows)
|
|
.map(|file| {
|
|
Line::default().spans(vec![
|
|
" ",
|
|
file.file_name()
|
|
.expect("Files should have a valid name")
|
|
.to_str()
|
|
.expect("Files should have a valid name"),
|
|
])
|
|
})
|
|
.collect();
|
|
|
|
let relative_selected = state.selected.map(|s| s - state.top);
|
|
|
|
if let Some(index) = relative_selected
|
|
&& let Some(item) = items.get_mut(index)
|
|
{
|
|
let mut old = mem::take(item);
|
|
|
|
old = old.patch_style(Style::default().fg(Color::Blue));
|
|
old.spans[0] = "> ".into();
|
|
|
|
*item = old;
|
|
}
|
|
|
|
let rows_layout =
|
|
Layout::vertical(vec![Constraint::Length(1); visible_rows]).split(layout[0]);
|
|
for (item, area) in items.into_iter().zip(rows_layout.iter()) {
|
|
item.render(*area, buf);
|
|
}
|
|
}
|
|
}
|