pub mod command_completion;
pub mod completion;
pub mod display_width;
pub mod edit_action;
pub mod fuzzy_search;
pub mod highlight;
pub mod history;
pub mod keymap;
pub mod kill_ring;
pub mod line_editor;
pub mod parse_status;
pub mod prompt;
pub mod terminal;
pub mod undo;
use std::io::{self, Write};
use crate::exec::Executor;
use crate::signal;
use completion::CompletionContext;
use command_completion::{CommandCompleter, CommandCompletionContext};
use highlight::{CheckerEnv, HighlightScanner};
use line_editor::LineEditor;
use parse_status::{ParseStatus, classify_parse};
use prompt::{PromptInfo, expand_prompt};
use terminal::CrosstermTerminal;
pub struct Repl {
executor: Executor,
line_editor: LineEditor,
terminal: CrosstermTerminal,
scanner: HighlightScanner,
command_completer: CommandCompleter,
}
impl Repl {
pub fn new(shell_name: String) -> Self {
signal::init_signal_handling();
let mut executor = Executor::new(shell_name, vec![]);
executor.env.mode.is_interactive = true;
executor.env.mode.options.monitor = true;
signal::init_job_control_signals();
crate::env::jobs::take_terminal(executor.env.process.shell_pgid).ok();
let home = executor.env.vars.get("HOME").unwrap_or("").to_string();
let histfile = format!("{}/.yosh_history", home);
let _ = executor.env.vars.set("HISTFILE", &histfile);
let _ = executor.env.vars.set("HISTSIZE", "500");
let _ = executor.env.vars.set("HISTFILESIZE", "500");
let _ = executor.env.vars.set("HISTCONTROL", "ignoreboth");
executor.env.history.load(std::path::Path::new(&histfile));
executor.load_plugins();
Self {
executor,
line_editor: LineEditor::new(),
terminal: CrosstermTerminal::new(),
scanner: HighlightScanner::new(),
command_completer: CommandCompleter::new(),
}
}
pub fn run(&mut self) -> i32 {
let mut input_buffer = String::new();
loop {
self.executor.reap_zombies();
self.executor.display_job_notifications();
if input_buffer.is_empty() {
self.executor.plugins.call_pre_prompt(&mut self.executor.env);
}
let prompt_var = if input_buffer.is_empty() { "PS1" } else { "PS2" };
let prompt = expand_prompt(&mut self.executor.env, prompt_var);
let prompt_info = PromptInfo::from_prompt(&prompt);
for line in &prompt_info.upper_lines {
eprint!("{}\r\n", line);
}
eprint!("{}", prompt_info.last_line);
io::stderr().flush().ok();
let cwd = std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| self.executor.env.vars.get("PWD").unwrap_or(".").to_string());
let home = self.executor.env.vars.get("HOME").unwrap_or("").to_string();
let show_dotfiles = self.executor.env.vars.get("KISH_SHOW_DOTFILES")
.map(|v| v == "1")
.unwrap_or(false);
let comp_ctx = CompletionContext { cwd, home, show_dotfiles };
let path_val = self.executor.env.vars.get("PATH").unwrap_or("").to_string();
let checker_env = CheckerEnv {
path: &path_val,
aliases: &self.executor.env.aliases,
};
let mut cmd_ctx = CommandCompletionContext {
completer: &mut self.command_completer,
path: &path_val,
builtins: crate::builtin::BUILTIN_NAMES,
aliases: &self.executor.env.aliases,
};
let line = match self.line_editor.read_line_with_completion(
&prompt_info.last_line,
&prompt_info.upper_lines,
&mut self.executor.env.history,
&mut self.terminal,
&comp_ctx,
&mut cmd_ctx,
&mut self.scanner,
&checker_env,
&input_buffer,
) {
Ok(Some(line)) => line,
Ok(None) => {
if self.executor.env.mode.options.ignoreeof {
eprintln!("\r\nyosh: Use \"exit\" to leave the shell.");
input_buffer.clear();
continue;
}
eprintln!();
break;
}
Err(_) => {
break;
}
};
if line.is_empty() && !input_buffer.is_empty() {
input_buffer.clear();
continue;
}
if line.is_empty() && input_buffer.is_empty() {
continue;
}
input_buffer.push_str(&line);
input_buffer.push('\n');
self.executor.verbose_print(&line);
match classify_parse(&input_buffer, &self.executor.env.aliases) {
ParseStatus::Complete(commands) => {
let histsize: usize = self.executor.env.vars.get("HISTSIZE")
.and_then(|s| s.parse().ok()).unwrap_or(500);
let histcontrol = self.executor.env.vars.get("HISTCONTROL")
.unwrap_or("ignoreboth").to_string();
let cmd_text = input_buffer.trim_end().to_string();
self.executor.env.history.add(&cmd_text, histsize, &histcontrol);
for cmd in &commands {
let status = self.executor.exec_complete_command(cmd);
self.executor.env.exec.last_exit_status = status;
if self.executor.exit_requested.is_some() {
break;
}
}
input_buffer.clear();
}
ParseStatus::Incomplete => {
continue;
}
ParseStatus::Empty => {
input_buffer.clear();
}
ParseStatus::Error(msg) => {
eprintln!("yosh: {}", msg);
input_buffer.clear();
}
}
self.executor.process_pending_signals();
if let Some(code) = self.executor.exit_requested {
self.executor.env.exec.last_exit_status = code;
break;
}
}
self.executor.process_pending_signals();
if self.executor.exit_requested.is_none() {
self.executor.execute_exit_trap();
}
let histfile = self.executor.env.vars.get("HISTFILE").unwrap_or("").to_string();
let histfilesize: usize = self.executor.env.vars.get("HISTFILESIZE")
.and_then(|s| s.parse().ok()).unwrap_or(500);
if !histfile.is_empty() {
self.executor.env.history.save(std::path::Path::new(&histfile), histfilesize);
}
self.executor.env.exec.last_exit_status
}
}