elm nightmare

This commit is contained in:
ldlda committed 2025-07-03 16:18:42 +07:00
1 parent b34f32310c
commit c165b3fa94
3 files changed
+337 -127

No files matched your search

+304 -127
View File
@@ -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))
}
+32
View File
@@ -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")
}
}
+1
View File
@@ -5,6 +5,7 @@ use ratatui_explorer::FileExplorer;
mod app;
// mod defer;
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;