Split core and TUI crates

This commit is contained in:
Mikhail Kilin
2026-05-20 00:31:18 +03:00
parent 91a8700b8e
commit eefac431e5
238 changed files with 624 additions and 191 deletions

View File

@@ -0,0 +1,182 @@
use std::io;
use std::time::Duration;
use crossterm::{
event::{
self, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
Event, KeyCode, KeyEvent, KeyModifiers,
},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use tele_tui::{
app::{App, AppScreen},
input::handle_main_input,
test_support::{
app_builder::TestAppBuilder,
fake_tdclient::FakeTdClient,
test_data::{TestChatBuilder, TestMessageBuilder},
},
};
#[tokio::main]
async fn main() -> io::Result<()> {
let scenario = parse_scenario();
let mut app = build_app(&scenario);
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture, EnableBracketedPaste)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let result = run_fixture(&mut terminal, &mut app).await;
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture,
DisableBracketedPaste
)?;
terminal.show_cursor()?;
result
}
fn parse_scenario() -> String {
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
if arg == "--scenario" {
return args.next().unwrap_or_else(|| "inbox".to_string());
}
}
"inbox".to_string()
}
async fn run_fixture(
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
app: &mut App<FakeTdClient>,
) -> io::Result<()> {
loop {
if app.needs_redraw {
terminal.draw(|f| tele_tui::ui::render(f, app))?;
app.needs_redraw = false;
}
if event::poll(Duration::from_millis(16))? {
match event::read()? {
Event::Key(key) => {
if key.code == KeyCode::Char('c')
&& key.modifiers.contains(KeyModifiers::CONTROL)
{
return Ok(());
}
if key.code == KeyCode::F(10) {
return Ok(());
}
handle_main_input(app, normalize_fixture_key(key)).await;
app.needs_redraw = true;
}
Event::Resize(_, _) => {
app.needs_redraw = true;
}
Event::Paste(text) => {
for ch in text.chars() {
handle_main_input(
app,
KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE),
)
.await;
}
app.needs_redraw = true;
}
_ => {}
}
}
}
}
fn normalize_fixture_key(key: KeyEvent) -> KeyEvent {
match (key.code, key.modifiers) {
(KeyCode::Char('/'), KeyModifiers::NONE) => {
KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL)
}
(KeyCode::Char('j' | 'm'), modifiers) if modifiers.contains(KeyModifiers::CONTROL) => {
KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)
}
_ => key,
}
}
fn build_app(scenario: &str) -> App<FakeTdClient> {
match scenario {
"open-chat" => TestAppBuilder::new()
.screen(AppScreen::Main)
.with_chats(sample_chats())
.selected_chat(102)
.with_messages(102, sample_messages())
.build(),
"compose-draft" => TestAppBuilder::new()
.screen(AppScreen::Main)
.with_chats(sample_chats())
.selected_chat(102)
.message_input("hello from e2e")
.with_messages(102, sample_messages())
.build(),
"inbox" => TestAppBuilder::new()
.screen(AppScreen::Main)
.with_chats(sample_chats())
.with_messages(101, mom_messages())
.with_messages(102, sample_messages())
.with_messages(103, boss_messages())
.build(),
other => {
eprintln!("unknown scenario: {other}");
std::process::exit(2);
}
}
}
fn sample_chats() -> Vec<tele_tui::tdlib::ChatInfo> {
vec![
TestChatBuilder::new("Mom", 101)
.last_message("Dinner at 7?")
.unread_count(2)
.build(),
TestChatBuilder::new("Work Group", 102)
.last_message("Standup notes are ready")
.unread_mentions(1)
.build(),
TestChatBuilder::new("Boss", 103)
.last_message("Please review the deck")
.build(),
]
}
fn sample_messages() -> Vec<tele_tui::tdlib::MessageInfo> {
vec![
TestMessageBuilder::new("Morning, team", 201)
.sender("Alice")
.build(),
TestMessageBuilder::new("Standup notes are ready", 202)
.sender("Bob")
.build(),
TestMessageBuilder::new("Thanks, I will review them after lunch", 203)
.outgoing()
.build(),
]
}
fn mom_messages() -> Vec<tele_tui::tdlib::MessageInfo> {
vec![TestMessageBuilder::new("Dinner at 7?", 301)
.sender("Mom")
.build()]
}
fn boss_messages() -> Vec<tele_tui::tdlib::MessageInfo> {
vec![TestMessageBuilder::new("Please review the deck", 401)
.sender("Boss")
.build()]
}