1use error::Error;
19use parser::{lexer::Lexer, parser::Parser};
20use ratatui::text::Text;
21use style::style::MdStyle;
22mod error;
23mod parser;
24pub mod style;
25
26
27pub trait MarkdownParsable {
29 fn parse_markdown(&self, style: Option<MdStyle>) -> Result<Text<'static>, Error>;
31}
32
33impl<T> MarkdownParsable for T where T: ToString {
34 fn parse_markdown(&self, style: Option<MdStyle>) -> Result<Text<'static>, Error> {
35 let mut lexer = Lexer::new();
36 let res = lexer.parse(self)?;
37
38 let mut parser = Parser::new(res, style);
39 let res = parser.parse()?;
40
41 Ok(Text::from(res))
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use std::{
49 error::Error,
50 io,
51 time::{Duration, Instant}, fs,
52};
53
54use crossterm::{
55 event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
56 execute,
57 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
58};
59use ratatui::{
60 backend::{Backend, CrosstermBackend},
61 style::{Color, Modifier, Style},
62 text::Span,
63 widgets::{Block, Borders, Paragraph, Wrap},
64 Frame, Terminal,
65};
66
67
68struct App {
69 scroll: u16,
70}
71
72impl App {
73 fn new() -> App {
74 App { scroll: 0 }
75 }
76
77 fn on_tick(&mut self) {
78 self.scroll += 1;
79 self.scroll %= 10;
80 }
81}
82#[test]
83#[ignore = "github action can't run tui"]
84fn ui_test() -> Result<(), Box<dyn Error>> {
85 enable_raw_mode()?;
87 let mut stdout = io::stdout();
88 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
89 let backend = CrosstermBackend::new(stdout);
90 let mut terminal = Terminal::new(backend)?;
91
92 let tick_rate = Duration::from_millis(250);
94 let app = App::new();
95 let res = run_app(&mut terminal, app, tick_rate);
96
97 disable_raw_mode()?;
99 execute!(
100 terminal.backend_mut(),
101 LeaveAlternateScreen,
102 DisableMouseCapture
103 )?;
104 terminal.show_cursor()?;
105
106 if let Err(err) = res {
107 println!("{err:?}");
108 }
109
110 assert_eq!(true, true);
111 Ok(())
112}
113
114fn run_app<B: Backend>(
115 terminal: &mut Terminal<B>,
116 mut app: App,
117 tick_rate: Duration,
118) -> io::Result<()> {
119 let mut last_tick = Instant::now();
120 loop {
121 terminal.draw(|f| ui(f, &app))?;
122
123 let timeout = tick_rate
124 .checked_sub(last_tick.elapsed())
125 .unwrap_or_else(|| Duration::from_secs(0));
126 if crossterm::event::poll(timeout)? {
127 if let Event::Key(key) = event::read()? {
128 if let KeyCode::Char('q') = key.code {
129 return Ok(());
130 }
131 }
132 }
133 if last_tick.elapsed() >= tick_rate {
134 app.on_tick();
135 last_tick = Instant::now();
136 }
137 }
138}
139
140#[allow(dead_code)]
141fn ui<B: Backend>(f: &mut Frame<B>, _app: &App) {
142 let size = f.size();
143
144 let file = fs::read("src/test/test.md").unwrap();
146 let s = "
147## TODO
1481. otehu
149
150> toehu
151>> aeouth
152___
153[lol](pog.com) lool
154- long_line
155- long_line
156* toue";
157 let mut long_line = s.repeat(usize::from(size.width) / s.len() + 4);
158 long_line.push('\n');
159
160 let block = Block::default().style(Style::default().fg(Color::Black));
161 f.render_widget(block, size);
162
163 let text = match String::from_utf8(file).unwrap().parse_markdown(None) {
164 Ok(text) => text,
165 Err(err) => Text::from(err.to_string())
166 };
167
168 let create_block = |title| {
169 Block::default()
170 .borders(Borders::ALL)
171 .style(Style::default().fg(Color::Gray))
172 .title(Span::styled(
173 title,
174 Style::default().add_modifier(Modifier::BOLD),
175 ))
176 };
177
178
179
180 let paragraph = Paragraph::new(text.clone())
181 .style(Style::default().fg(Color::Gray))
182 .block(create_block("Default alignment (Left), with wrap"))
183 .wrap(Wrap { trim: true });
184 f.render_widget(paragraph, f.size());
185}
186
187}