mod action;
mod app;
mod buffer_ref;
mod config;
mod editor;
mod effect;
mod event;
mod finder;
mod format;
mod grammar;
mod lsp;
mod mode;
mod prompt;
mod syntax;
mod ui;
mod vcs;
use std::io::{self, Stdout, Write};
use std::sync::mpsc;
use std::thread;
use anyhow::Result;
use crossterm::event::{self as crossterm_event, Event};
use crossterm::execute;
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use crate::app::App;
use crate::config::CursorShape;
fn main() -> Result<()> {
let argv: Vec<String> = std::env::args().collect();
if argv.get(1).map(String::as_str) == Some("grammar") {
return grammar::cli::run(&argv[2..]);
}
match argv.get(1).map(String::as_str) {
Some("-V" | "--version") => {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
Some("-h" | "--help") => {
print_usage();
return Ok(());
}
_ => {}
}
let path = argv.into_iter().nth(1);
let startup_cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let cfg = config::Config::load(config::default_path().as_deref())?;
let loader = syntax::Loader::new(cfg.grammar_dir.clone(), cfg.query_dir.clone());
let (event_tx, event_rx) = mpsc::channel::<event::AppEvent>();
let input_tx = event_tx.clone();
thread::spawn(move || {
loop {
match crossterm_event::read() {
Ok(ev) => {
if input_tx.send(event::AppEvent::Term(ev)).is_err() {
return;
}
}
Err(_) => return,
}
}
});
let mut app = App::new(cfg, loader, event_tx, startup_cwd);
if let Some(p) = path {
app.open_path(std::path::Path::new(&p))?;
}
let result = run(&mut terminal, &mut app, &event_rx);
disable_raw_mode()?;
let _ = io::stdout().write_all(b"\x1b[0 q");
let _ = io::stdout().flush();
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
result
}
fn run(
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
app: &mut App,
event_rx: &mpsc::Receiver<event::AppEvent>,
) -> Result<()> {
let mut last_shape: Option<CursorShape> = None;
while !app.should_quit {
app.buffer.refresh_highlights();
terminal.draw(|f| ui::draw(f, app))?;
let shape = app.config.cursor_shapes.for_mode(app.mode);
if last_shape != Some(shape) {
let mut out = io::stdout();
out.write_all(cursor_ansi(shape, app.config.cursor_shapes.blinking))?;
out.flush()?;
last_shape = Some(shape);
}
let first = match app.toast_remaining() {
Some(rem) => match event_rx.recv_timeout(rem) {
Ok(ev) => Some(ev),
Err(mpsc::RecvTimeoutError::Timeout) => None,
Err(mpsc::RecvTimeoutError::Disconnected) => return Ok(()),
},
None => match event_rx.recv() {
Ok(ev) => Some(ev),
Err(_) => return Ok(()),
},
};
if let Some(ev) = first {
dispatch(app, ev)?;
}
while let Ok(ev) = event_rx.try_recv() {
dispatch(app, ev)?;
}
app.sync_buffer_if_dirty();
}
Ok(())
}
fn dispatch(app: &mut App, ev: event::AppEvent) -> Result<()> {
match ev {
event::AppEvent::Term(Event::Key(key)) => app.handle_key(key)?,
event::AppEvent::Term(_) => {}
event::AppEvent::Lsp(lsp_ev) => app.handle_lsp_event(lsp_ev),
event::AppEvent::HighlighterReady { generation, result } => {
app.handle_highlighter_ready(generation, result);
}
event::AppEvent::LspReady {
generation,
client_key,
lang,
path,
result,
} => {
app.handle_lsp_ready(generation, client_key, lang, path, result);
}
event::AppEvent::PreviewReady(entry) => app.handle_preview_ready(entry),
}
Ok(())
}
fn print_usage() {
println!(
"{name} {version}
Usage:
vorto [FILE]
vorto grammar <list|install|remove> [args]
vorto -h | --help
vorto -V | --version",
name = env!("CARGO_PKG_NAME"),
version = env!("CARGO_PKG_VERSION"),
);
}
fn cursor_ansi(shape: CursorShape, blinking: bool) -> &'static [u8] {
match (shape, blinking) {
(CursorShape::Terminal, _) => b"\x1b[0 q",
(CursorShape::Block, true) => b"\x1b[1 q",
(CursorShape::Block, false) => b"\x1b[2 q",
(CursorShape::Underbar, true) => b"\x1b[3 q",
(CursorShape::Underbar, false) => b"\x1b[4 q",
(CursorShape::Bar, true) => b"\x1b[5 q",
(CursorShape::Bar, false) => b"\x1b[6 q",
}
}