elm nightmare
This commit is contained in:
1 parent
b34f32310c
commit
c165b3fa94
3 files changed
+337
-127
No files matched your search
+304
-127
@@ -1,6 +1,6 @@
|
||||
use crossterm::event::Event;
|
||||
use crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent};
|
||||
use ratatui::crossterm;
|
||||
use ratatui::crossterm::event::Event;
|
||||
use ratatui::text::{ToLine, ToSpan};
|
||||
use ratatui::widgets::Clear;
|
||||
use ratatui::{
|
||||
Frame, Terminal,
|
||||
@@ -11,24 +11,36 @@ use ratatui::{
|
||||
widgets::{Block, BorderType, Padding, Paragraph},
|
||||
};
|
||||
use ratatui_explorer::{FileExplorer, Input};
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
fs::{File, read_to_string},
|
||||
io::{self, Read},
|
||||
fs::File,
|
||||
io::{self},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub struct DocCache {
|
||||
doc: PathBuf,
|
||||
cache: String,
|
||||
content: String,
|
||||
offset: (u16, u16),
|
||||
length_hint: u16,
|
||||
}
|
||||
impl Debug for DocCache {
|
||||
pub struct Doc {
|
||||
doc: PathBuf,
|
||||
cache: Option<DocCache>,
|
||||
}
|
||||
impl Debug for Doc {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DocCache")
|
||||
.field("doc", &self.doc)
|
||||
.field("cache", &format!("{:.20}", &self.cache))
|
||||
.finish()
|
||||
let mut c = f.debug_struct("DocCache");
|
||||
c.field("doc", &self.doc);
|
||||
|
||||
if let Some(ref con) = self.cache {
|
||||
c.field("cache", &format!("Some({:.20})", &con.content));
|
||||
} else {
|
||||
c.field("cache", &"None");
|
||||
}
|
||||
|
||||
c.finish()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
@@ -38,11 +50,12 @@ enum AppState {
|
||||
Quit,
|
||||
DocSwitcher,
|
||||
FileError, // file open error. file read error. whatever error.
|
||||
// up, right.
|
||||
}
|
||||
/// model
|
||||
#[derive(Debug, Default)]
|
||||
pub struct App<'a> {
|
||||
doc: Option<DocCache>,
|
||||
doc: Option<Doc>,
|
||||
state: AppState,
|
||||
explorer: Option<&'a mut FileExplorer>,
|
||||
}
|
||||
@@ -51,16 +64,29 @@ impl<'a> App<'a> {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn with_doc(self, doc: impl Into<PathBuf>) -> io::Result<Self> {
|
||||
let doc = Some(DocCache::new(doc)?);
|
||||
Ok(Self { doc, ..self })
|
||||
pub fn with_doc(self, doc: impl AsRef<Path>) -> io::Result<Self> {
|
||||
let mut doc = Doc::new(doc);
|
||||
doc.reload_doc()?;
|
||||
Ok(Self {
|
||||
doc: Some(doc),
|
||||
..self
|
||||
})
|
||||
}
|
||||
pub fn with_explorer(self, explorer: &'a mut FileExplorer) -> Self {
|
||||
explorer.set_theme(
|
||||
explorer
|
||||
.theme()
|
||||
.clone()
|
||||
.with_title_bottom(|_| Line::from("press enter to select ig idk")),
|
||||
.with_title_bottom(|_| Line::from("press Space to select ig idk"))
|
||||
.with_title_top(|exp| {
|
||||
let thing = exp.cwd().display().to_string();
|
||||
Line::from_iter([
|
||||
"cur dir: <".to_span().yellow(),
|
||||
Span::from(thing).cyan(), // fuck is your probklem borrow checker
|
||||
">".to_span().yellow(),
|
||||
])
|
||||
.centered()
|
||||
}),
|
||||
);
|
||||
Self {
|
||||
explorer: Some(explorer),
|
||||
@@ -76,7 +102,10 @@ enum Message {
|
||||
Quit,
|
||||
ReloadDoc,
|
||||
OpenDocSwitcher,
|
||||
CloseDocSwitcher,
|
||||
DocSwitcherAction(Input),
|
||||
/// close doc switcher because there is a new file coming
|
||||
NewDoc,
|
||||
TextScroll(Option<bool>, Option<bool>),
|
||||
}
|
||||
impl App<'_> {
|
||||
pub fn run<B: Backend>(&mut self, term: &mut Terminal<B>) -> io::Result<()> {
|
||||
@@ -90,8 +119,14 @@ impl App<'_> {
|
||||
Ok(())
|
||||
}
|
||||
fn view(&self, frame: &mut Frame) {
|
||||
let [header, main] = Layout::vertical([Constraint::Length(3), Constraint::Min(2)])
|
||||
.split(frame.area())[..]
|
||||
frame.render_widget(Clear, frame.area()); // tf this about
|
||||
|
||||
let [header, main, foot] = Layout::vertical([
|
||||
Constraint::Length(3),
|
||||
Constraint::Min(2),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.split(frame.area())[..]
|
||||
.try_into()
|
||||
.expect("Layout::split / Rc problem");
|
||||
let header_box = Block::bordered()
|
||||
@@ -113,48 +148,67 @@ impl App<'_> {
|
||||
//wtf
|
||||
if let Some(doc) = &self.doc {
|
||||
file_name.push_span(doc.doc.display().to_string());
|
||||
content = Paragraph::new(doc.cache.as_str())
|
||||
.scroll(doc.offset)
|
||||
.block(content_block);
|
||||
if let Some(cache) = &doc.cache {
|
||||
let text = Text::from(cache.content.as_str());
|
||||
content = Paragraph::new(text).scroll(cache.offset);
|
||||
} else {
|
||||
content = Paragraph::new("errored buhhhhhh".to_line().centered())
|
||||
}
|
||||
} else {
|
||||
file_name.push_span("no file");
|
||||
content = Paragraph::new("no idea").block(content_block)
|
||||
content = Paragraph::new("no idea")
|
||||
}
|
||||
frame.render_widget(file_name, space);
|
||||
let content = content.block(content_block);
|
||||
frame.render_widget(header_box, header);
|
||||
frame.render_widget(content, main);
|
||||
frame.render_widget(
|
||||
Line::from_iter([
|
||||
Span::from("enter to choose file, "),
|
||||
Span::from("esc or q to quit"),
|
||||
]),
|
||||
foot,
|
||||
);
|
||||
{
|
||||
let bij = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Max(120),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(frame.area())[1];
|
||||
let real = Layout::vertical([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Max(30),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(bij)[1];
|
||||
|
||||
let bij = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Max(120),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(frame.area())[1];
|
||||
let real = Layout::vertical([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Max(30),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(bij)[1];
|
||||
if self.state == AppState::DocSwitcher {
|
||||
frame.render_widget(file_name, space);
|
||||
|
||||
if self.state == AppState::DocSwitcher {
|
||||
frame.render_widget(Clear, real);
|
||||
let Some(ref exp) = self.explorer else {
|
||||
let widget = Paragraph::new("you dont have shit on buh what fuck")
|
||||
.block(Block::bordered().title("shit cuh"));
|
||||
frame.render_widget(widget, real);
|
||||
return;
|
||||
};
|
||||
frame.render_widget_ref(exp.widget(), real);
|
||||
}
|
||||
if self.state == AppState::FileError {
|
||||
frame.render_widget(Clear, real);
|
||||
frame.render_widget(
|
||||
Paragraph::new("file error bihfhahf (if you select with the thing how could it be file error i wonder")
|
||||
frame.render_widget(Clear, real);
|
||||
let Some(ref exp) = self.explorer else {
|
||||
let widget = Paragraph::new("you dont have shit on buh what fuck")
|
||||
.block(Block::bordered().title("shit cuh"));
|
||||
frame.render_widget(widget, real);
|
||||
return;
|
||||
};
|
||||
frame.render_widget_ref(exp.widget(), real);
|
||||
} else if self.state == AppState::FileError {
|
||||
file_name.push_span(" (file errored)".red());
|
||||
frame.render_widget(file_name, space);
|
||||
|
||||
frame.render_widget(Clear, real);
|
||||
frame.render_widget(
|
||||
Paragraph::new(
|
||||
"file error bihfhahf (100% read error cant view esc choose another one",
|
||||
)
|
||||
.centered()
|
||||
.block(Block::bordered().padding(Padding::proportional(5))),
|
||||
real,
|
||||
);
|
||||
real,
|
||||
);
|
||||
} else {
|
||||
frame.render_widget(file_name, space);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,113 +233,236 @@ impl App<'_> {
|
||||
};
|
||||
}
|
||||
Message::OpenDocSwitcher => self.state = AppState::DocSwitcher,
|
||||
Message::CloseDocSwitcher => {
|
||||
Message::NewDoc => {
|
||||
let Some(ref mut expl) = self.explorer else {
|
||||
// no way bro doesnt have this and then close doc
|
||||
unreachable!()
|
||||
unreachable!("wym new doc without an explorer")
|
||||
};
|
||||
if let Some(file_name) = {
|
||||
let maybe = expl.current();
|
||||
(maybe.is_file()).then(|| maybe.path())
|
||||
} {
|
||||
if let Some(ref mut doc) = self.doc {
|
||||
// get cur file if
|
||||
{
|
||||
if file_name != &doc.doc {
|
||||
doc.doc.clear();
|
||||
doc.doc.push(file_name);
|
||||
return Some(Message::ReloadDoc); // ohoho i can do this
|
||||
if self
|
||||
.doc
|
||||
.as_ref()
|
||||
.filter(|doc| file_name == &doc.doc)
|
||||
.is_none()
|
||||
{
|
||||
self.doc = Some(Doc::new(file_name));
|
||||
return Some(Message::ReloadDoc);
|
||||
}
|
||||
}
|
||||
return Some(Message::Main);
|
||||
}
|
||||
|
||||
Message::DocSwitcherAction(action) => {
|
||||
let Some(ref mut exp) = self.explorer else {
|
||||
unreachable!()
|
||||
};
|
||||
if action == Input::Right {
|
||||
if exp.current().is_file() {
|
||||
return Some(Message::NewDoc);
|
||||
} else if exp.current().file_type().is_some_and(|c| c.is_symlink()) {
|
||||
match exp.current().path().read_link() {
|
||||
Ok(path) => exp.set_cwd(path).expect("i swear path is real"),
|
||||
Err(_not_real) => {
|
||||
// is shortcut != is symlink
|
||||
self.state = AppState::FileError
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match DocCache::new(file_name.clone()) {
|
||||
Ok(f) => {
|
||||
self.doc = Some(f);
|
||||
return Some(Message::Main);
|
||||
}
|
||||
Err(_e) => self.state = AppState::FileError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(_e) = exp.handle(action) {
|
||||
// reading dir Fucked Up
|
||||
self.state = AppState::FileError;
|
||||
return None;
|
||||
}; // noooo
|
||||
}
|
||||
|
||||
Message::TextScroll(up, right) => {
|
||||
if let Some(ref mut doc) = self.doc {
|
||||
if let Some(up) = up {
|
||||
let bij = if up { -1 } else { 1 }; // fuck is this about
|
||||
if let Some(ref mut cache) = doc.cache {
|
||||
cache.offset.0 = cache.offset.0.saturating_add_signed(bij);
|
||||
}
|
||||
};
|
||||
if let Some(right) = right {
|
||||
let bij = if right { 1 } else { -1 };
|
||||
if let Some(ref mut cache) = doc.cache {
|
||||
cache.offset.1 = cache.offset.1.saturating_add_signed(bij);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn event(&mut self) -> io::Result<Option<Message>> {
|
||||
let ev = ratatui::crossterm::event::read()?;
|
||||
if self.state == AppState::DocSwitcher {
|
||||
if let Some(ref mut exp) = self.explorer {
|
||||
use ratatui::crossterm::event; // 10/10 workaround
|
||||
let ev = event::read()?;
|
||||
{
|
||||
if matches!(
|
||||
ev,
|
||||
Event::Key(
|
||||
KeyEvent {
|
||||
code,..
|
||||
}
|
||||
) if code == KeyCode::Char('q') || code == KeyCode::Esc
|
||||
event::Event::Key(event::KeyEvent {
|
||||
code: _c @ (event::KeyCode::Char('q') | event::KeyCode::Esc),
|
||||
kind: _k @ (event::KeyEventKind::Press | event::KeyEventKind::Repeat),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return Ok(Some(Message::Main));
|
||||
}
|
||||
let cip = Input::from(&ev);
|
||||
Ok(Some(Message::DocSwitcherAction(cip)))
|
||||
}
|
||||
} else {
|
||||
let ev = crossterm::event::read()?;
|
||||
match self.state {
|
||||
AppState::DocSwitcher => unreachable!("jus handled that above"),
|
||||
AppState::FileError => {
|
||||
if matches!(
|
||||
ev,
|
||||
Event::Key(
|
||||
KeyEvent {
|
||||
code,..
|
||||
}
|
||||
) if code == KeyCode::Char('q') || code == KeyCode::Esc
|
||||
) {
|
||||
return Ok(Some(Message::OpenDocSwitcher));
|
||||
}
|
||||
|
||||
if matches!(
|
||||
ev,
|
||||
Ok(None)
|
||||
}
|
||||
_ => match ev {
|
||||
event::Event::Key(KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
..
|
||||
})
|
||||
) && exp.current().is_file()
|
||||
{
|
||||
return Ok(Some(Message::CloseDocSwitcher));
|
||||
};
|
||||
|
||||
exp.handle(Input::from(&ev))?;
|
||||
return Ok(None);
|
||||
code,
|
||||
modifiers,
|
||||
kind,
|
||||
state: _,
|
||||
}) => {
|
||||
if kind == KeyEventKind::Release {
|
||||
return Ok(None);
|
||||
}
|
||||
if code == KeyCode::Char('q') || code == KeyCode::Esc {
|
||||
return Ok(Some(Message::Quit));
|
||||
} else if code == KeyCode::Char('r')
|
||||
&& modifiers.contains(KeyModifiers::CONTROL)
|
||||
{
|
||||
return Ok(Some(Message::ReloadDoc));
|
||||
} else if code == KeyCode::Enter {
|
||||
return Ok(Some(Message::OpenDocSwitcher));
|
||||
}
|
||||
// no what is this code block
|
||||
if self.doc.is_some() {
|
||||
let updown = tri_state(
|
||||
code.is_up() || code.is_char('k'),
|
||||
code.is_down() || code.is_char('j'),
|
||||
);
|
||||
let leftright = tri_state(
|
||||
code.is_right() || code.is_char('l'),
|
||||
code.is_left() || code.is_char('h'),
|
||||
);
|
||||
return Ok(Some(Message::TextScroll(updown, leftright)));
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
event::Event::Mouse(MouseEvent {
|
||||
kind,
|
||||
column: _,
|
||||
row: _,
|
||||
modifiers,
|
||||
}) => {
|
||||
if self.doc.is_some() {
|
||||
eprintln!("{modifiers}");
|
||||
let updown = tri_state(
|
||||
kind.is_scroll_up() && !modifiers.contains(KeyModifiers::SHIFT),
|
||||
kind.is_scroll_down() && !modifiers.contains(KeyModifiers::SHIFT),
|
||||
);
|
||||
let leftright = tri_state(
|
||||
kind.is_scroll_right()
|
||||
|| (kind.is_scroll_down()
|
||||
&& modifiers.contains(KeyModifiers::SHIFT)),
|
||||
kind.is_scroll_left()
|
||||
|| (kind.is_scroll_up()
|
||||
&& modifiers.contains(KeyModifiers::SHIFT)),
|
||||
);
|
||||
return Ok(Some(Message::TextScroll(updown, leftright)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
_ => Ok(None),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match ev {
|
||||
event::Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers,
|
||||
kind,
|
||||
state: _,
|
||||
}) => {
|
||||
if kind == KeyEventKind::Release {
|
||||
return Ok(None);
|
||||
}
|
||||
if code == KeyCode::Char('q') || code == KeyCode::Esc {
|
||||
return Ok(Some(Message::Quit));
|
||||
} else if code == KeyCode::Char('r') && modifiers.contains(KeyModifiers::CONTROL) {
|
||||
return Ok(Some(Message::ReloadDoc));
|
||||
} else if code == KeyCode::Enter {
|
||||
return Ok(Some(Message::OpenDocSwitcher));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
event::Event::Mouse(MouseEvent {
|
||||
kind: _,
|
||||
column: _,
|
||||
row: _,
|
||||
modifiers: _,
|
||||
}) => Ok(None),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DocCache {
|
||||
pub fn new(doc: impl Into<PathBuf>) -> io::Result<Self> {
|
||||
let doc = doc.into();
|
||||
let cache = read_to_string(&doc)?;
|
||||
Ok(Self {
|
||||
doc,
|
||||
cache,
|
||||
offset: (0, 0),
|
||||
})
|
||||
impl Doc {
|
||||
pub fn new(doc: impl AsRef<Path>) -> Self {
|
||||
let doc = doc.as_ref().to_path_buf();
|
||||
let cache = None;
|
||||
// let length_hint = cache.lines().map(|_|1).sum();
|
||||
Self { doc, cache }
|
||||
}
|
||||
fn reload_doc(&mut self) -> io::Result<()> {
|
||||
self.cache.clear();
|
||||
File::open(&self.doc)?.read_to_string(&mut self.cache)?;
|
||||
if let Some(ref mut c) = self.cache {
|
||||
c.reload_doc(&self.doc).inspect_err(|_| {
|
||||
self.cache = None;
|
||||
})?;
|
||||
} else {
|
||||
// init the thing
|
||||
self.cache = Some(DocCache::new(&self.doc)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl DocCache {
|
||||
pub fn new(doc: impl AsRef<Path>) -> io::Result<Self> {
|
||||
read_lines(&doc).map(|(l, content)| Self {
|
||||
content,
|
||||
offset: (0, 0),
|
||||
length_hint: l as _,
|
||||
})
|
||||
}
|
||||
pub fn reload_doc(&mut self, doc: impl AsRef<Path>) -> io::Result<()> {
|
||||
let a = read_lines(doc)?; // bail as early as possible
|
||||
self.content.clear();
|
||||
let lh;
|
||||
(lh, self.content) = a;
|
||||
self.length_hint = lh as _;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// returns some true if real, some false if fake, none if neither,
|
||||
/// idk what happens if both
|
||||
fn tri_state(real: bool, fake: bool) -> Option<bool> {
|
||||
if real {
|
||||
Some(true)
|
||||
} else if fake {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// this works for now
|
||||
fn read_lines(doc: impl AsRef<Path>) -> io::Result<(usize, String)> {
|
||||
let file = File::open(doc)?;
|
||||
let lenf = file.metadata()?.len() as _;
|
||||
let mut strg = String::with_capacity(lenf);
|
||||
let mut fuek = BufReader::with_capacity(lenf, file);
|
||||
let mut c = 0;
|
||||
loop {
|
||||
let r = fuek.read_line(&mut strg)?;
|
||||
if r > 0 {
|
||||
c += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok((c, strg))
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
macro_rules! expr {
|
||||
($($does:tt)*) => {
|
||||
{$($does)*}
|
||||
};
|
||||
}
|
||||
macro_rules! defer_lda {
|
||||
{$($does:tt)*} => {
|
||||
let _realrealrealrealrealrealrealrealrealreal = $crate::defer::Defer(Some(|| expr!{$($does)*}));
|
||||
};
|
||||
}
|
||||
pub struct Defer<F: FnOnce ()>(pub Option<F>);
|
||||
|
||||
impl<F: FnOnce()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
self.0.take().unwrap()();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut string = String::new();
|
||||
{
|
||||
defer_lda!{
|
||||
string.push_str(" world"); // borrow checked
|
||||
}
|
||||
string.push_str("hello"); // why coudnt it just move the code to the end szjefhgsakjfghaskg``
|
||||
}
|
||||
assert_eq!(string, "hello world")
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use ratatui_explorer::FileExplorer;
|
||||
|
||||
|
||||
mod app;
|
||||
// mod defer;
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
Reference in new issue
Block a user