mod engine;
use std::io::Read;
use std::net::{SocketAddrV4, TcpListener};
use std::sync::mpsc;
use std::time::Duration;
use engine::Engine;
mod art;
mod format;
mod help;
pub mod highlight;
mod history;
pub mod options;
mod parser;
mod ra;
mod script;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
use highlight::theme::Theme;
use history::History;
use irust_api::{Command, GlobalVariables};
use irust_repl::Repl;
use options::Options;
use printer::{buffer::Buffer, printer::Printer};
use ra::Completer;
use script::Script;
pub use format::format_err;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub struct IRust {
pub options: Options,
buffer: Buffer,
printer: Printer<std::io::Stdout>,
engine: Engine,
exit_flag: bool,
theme: Theme,
repl: Repl,
pub global_variables: GlobalVariables,
history: History,
completer: Option<Completer>,
script_mg: Option<Box<dyn Script>>,
pub bare_repl: bool,
}
impl IRust {
pub fn new(options: Options) -> Self {
let repl = Repl::new(
options.toolchain,
options.executor,
options.main_result,
options.edition,
(|| Some(crate::utils::irust_dirs::data_dir()?.join("irust")))(),
)
.expect("Could not create repl");
let mut global_variables = GlobalVariables::new();
let mut script_mg = Self::choose_script_mg(&options);
let prompt = script_mg
.as_mut()
.map(|script_mg| {
if let Some(prompt) = script_mg.input_prompt(&global_variables) {
prompt
} else {
options.input_prompt.clone()
}
})
.unwrap_or_else(|| options.input_prompt.clone());
global_variables.prompt_len = prompt.chars().count();
let printer = Printer::new(std::io::stdout(), prompt);
let completer = if options.enable_rust_analyzer {
Completer::start_ra(
&repl.cargo.paths.irust_dir,
&repl.cargo.paths.main_file,
repl.body(),
)
} else {
None
};
let buffer = Buffer::new();
let engine = Engine::default();
let exit_flag = false;
let theme = highlight::theme::theme_or_create_default(options.theme.clone());
let history = History::new(repl.cargo.paths.irust_dir.clone()).unwrap_or_default();
IRust {
options,
buffer,
printer,
engine,
exit_flag,
theme,
repl,
global_variables,
history,
completer,
script_mg,
bare_repl: false,
}
}
fn prepare(&mut self) -> Result<()> {
let title = if let Some(title) = self.trigger_set_title_hook() {
title
} else {
format!("IRust: {}", self.global_variables.get_cwd().display())
};
self.printer.writer.raw.set_title(&title)?;
self.welcome()?;
self.printer.print_prompt_if_set()?;
self.run_scripts_startup_cmds()?;
Ok(())
}
pub fn print_input(&mut self) -> Result<()> {
let theme = &self.theme;
self.printer
.print_input(&|buffer| highlight::highlight(buffer, theme), &self.buffer)?;
Ok(())
}
pub fn run(&mut self) -> Result<()> {
self.printer.enable_raw_mode();
self.prepare()?;
let mut server = if self.options.local_server {
start_server(self.options.local_server_adress).ok()
} else {
None
};
loop {
std::io::Write::flush(&mut self.printer.writer.raw)?;
let evs = if let Some(ref mut server) = server {
read_from_net_and_stdin(server)
} else {
vec![crossterm::event::read()]
};
for ev in evs {
match ev {
Ok(ev) => {
self.handle_input_event(ev)?;
if self.exit_flag {
return Ok(());
}
}
Err(e) => return Err(format!("failed to read input. error: {e}").into()),
}
}
}
}
fn handle_input_event(&mut self, ev: Event) -> Result<()> {
self.update_script_state();
if let Some(command) = self.input_event_hook(ev.clone()) {
self.execute(command)?;
return Ok(());
}
match ev {
Event::Mouse(_) => (),
Event::Resize(width, height) => {
self.printer.cursor.update_dimensions(width, height);
self.execute(Command::HandleCtrlC)?;
}
Event::Key(key_event) => match key_event {
KeyEvent {
kind: KeyEventKind::Release,
..
} => (),
KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
..
}
| KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::SHIFT,
..
} => self.execute(Command::HandleCharacter(c))?,
KeyEvent {
code: KeyCode::Char('e'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlE)?,
KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::ALT,
..
}
| KeyEvent {
code: KeyCode::Char('s'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleAltEnter)?,
KeyEvent {
code: KeyCode::Enter,
..
} => self.execute(Command::HandleEnter(false))?,
KeyEvent {
code: KeyCode::Tab, ..
} => self.execute(Command::HandleTab)?,
KeyEvent {
code: KeyCode::BackTab,
..
} => self.execute(Command::HandleBackTab)?,
KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::NONE,
..
} => self.execute(Command::HandleLeft)?,
KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::NONE,
..
} => self.execute(Command::HandleRight)?,
KeyEvent {
code: KeyCode::Up, ..
} => self.execute(Command::HandleUp)?,
KeyEvent {
code: KeyCode::Down,
..
} => self.execute(Command::HandleDown)?,
KeyEvent {
code: KeyCode::Backspace,
..
} => self.execute(Command::HandleBackSpace)?,
KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlC)?,
KeyEvent {
code: KeyCode::Char('d'),
modifiers: KeyModifiers::CONTROL,
..
} => {
self.execute(Command::HandleCtrlD)?;
}
KeyEvent {
code: KeyCode::Char('z'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlZ)?,
KeyEvent {
code: KeyCode::Char('l'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlL)?,
KeyEvent {
code: KeyCode::Char('r'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlR)?,
KeyEvent {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::MacroRecordToggle)?,
KeyEvent {
code: KeyCode::Char('p'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::MacroPlay)?,
KeyEvent {
code: KeyCode::Char('u'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::Undo)?,
KeyEvent {
code: KeyCode::Char('y'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::Redo)?,
KeyEvent {
code: KeyCode::Char('x'),
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::Multiple(vec![
Command::HandleHome,
Command::DeleteUntilChar('\n', true),
]))?,
KeyEvent {
code: KeyCode::Home,
..
} => self.execute(Command::HandleHome)?,
KeyEvent {
code: KeyCode::End, ..
} => self.execute(Command::HandleEnd)?,
KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlLeft)?,
KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::CONTROL,
..
} => self.execute(Command::HandleCtrlRight)?,
KeyEvent {
code: KeyCode::Delete,
..
} => {
self.execute(Command::HandleDelete)?;
self.execute(Command::PrintInput)?;
}
keyevent => {
if keyevent
.modifiers
.contains(KeyModifiers::CONTROL | KeyModifiers::ALT)
&& let KeyCode::Char(c) = keyevent.code
{
self.execute(Command::HandleCharacter(c))?;
}
}
},
Event::Paste(data) => data.chars().try_for_each(|c| {
self.handle_input_event(Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
}))
})?,
Event::FocusGained => (),
Event::FocusLost => (),
}
Ok(())
}
}
fn start_server(adress: SocketAddrV4) -> Result<mpsc::Receiver<String>> {
let server = TcpListener::bind(adress)?;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut buf = String::new();
loop {
(|| {
let mut c = server.accept().ok()?.0;
c.read_to_string(&mut buf).ok()?;
tx.send(buf.clone()).ok()?;
buf.clear();
Some(())
})();
}
});
Ok(rx)
}
fn read_from_net_and_stdin(server: &mut mpsc::Receiver<String>) -> Vec<std::io::Result<Event>> {
loop {
if let Ok(e) = server.try_recv() {
return e
.chars()
.map(|c| {
Ok(Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
}))
})
.chain(std::iter::once(Ok(Event::Key(KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
}))))
.collect();
}
if let Ok(true) = crossterm::event::poll(Duration::from_millis(100)) {
return vec![crossterm::event::read()];
}
}
}
impl Drop for IRust {
fn drop(&mut self) {
let _ = self.execute(Command::Exit);
if std::thread::panicking() {
let _ = self.printer.writer.raw.write("IRust panicked, to log the error you can redirect stderror to a file, example irust 2>log");
}
}
}